下面列出了 io.netty.handler.codec.http2.HttpConversionUtil.ExtensionHeaderNames #com.linecorp.armeria.common.HttpHeaderNames 实例代码,或者点击链接到github查看源代码,也可以在右侧发表评论。
private static void addHttp2Scheme(io.netty.handler.codec.http.HttpHeaders in, URI uri,
RequestHeadersBuilder out) {
final String value = uri.getScheme();
if (value != null) {
out.add(HttpHeaderNames.SCHEME, value);
return;
}
// Consume the Scheme extension header if present
final CharSequence cValue = in.get(ExtensionHeaderNames.SCHEME.text());
if (cValue != null) {
out.add(HttpHeaderNames.SCHEME, cValue.toString());
} else {
out.add(HttpHeaderNames.SCHEME, "unknown");
}
}
@Test
public void testCorsDifferentPolicy() throws Exception {
final WebClient client = client();
final AggregatedHttpResponse response = request(client, HttpMethod.POST, "/cors", "http://example.com",
"POST");
final AggregatedHttpResponse response2 = request(client, HttpMethod.POST, "/cors",
"http://example2.com", "POST");
assertEquals(HttpStatus.OK, response.status());
assertEquals(HttpStatus.OK, response2.status());
assertEquals("http://example.com", response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN));
assertEquals("http://example2.com",
response2.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN));
assertEquals("allow_request_header",
response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_HEADERS));
assertEquals("allow_request_header2",
response2.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_HEADERS));
assertEquals("expose_header_1,expose_header_2",
response.headers().get(HttpHeaderNames.ACCESS_CONTROL_EXPOSE_HEADERS));
assertEquals("expose_header_3,expose_header_4",
response2.headers().get(HttpHeaderNames.ACCESS_CONTROL_EXPOSE_HEADERS));
}
@ParameterizedTest
@ArgumentsSource(ClientAndProtocolProvider.class)
void testStrings_acceptEncodingDeflate(WebClient client) throws Exception {
final RequestHeaders req = RequestHeaders.of(HttpMethod.GET, "/strings",
HttpHeaderNames.ACCEPT_ENCODING, "deflate");
final CompletableFuture<AggregatedHttpResponse> f = client.execute(req).aggregate();
final AggregatedHttpResponse res = f.get();
assertThat(res.status()).isEqualTo(HttpStatus.OK);
assertThat(res.headers().get(HttpHeaderNames.CONTENT_ENCODING)).isEqualTo("deflate");
assertThat(res.headers().get(HttpHeaderNames.VARY)).isEqualTo("accept-encoding");
final byte[] decoded;
try (InflaterInputStream unzipper =
new InflaterInputStream(new ByteArrayInputStream(res.content().array()))) {
decoded = ByteStreams.toByteArray(unzipper);
}
assertThat(new String(decoded, StandardCharsets.UTF_8)).isEqualTo("Armeria is awesome!");
}
@Test
public void echoPost() throws Exception {
try (CloseableHttpClient hc = HttpClients.createMinimal()) {
final HttpPost post = new HttpPost(server().httpUri() + "/jsp/echo_post.jsp");
post.setEntity(new StringEntity("test"));
try (CloseableHttpResponse res = hc.execute(post)) {
assertThat(res.getStatusLine().toString()).isEqualTo("HTTP/1.1 200 OK");
assertThat(res.getFirstHeader(HttpHeaderNames.CONTENT_TYPE.toString()).getValue())
.startsWith("text/html");
final String actualContent = CR_OR_LF.matcher(EntityUtils.toString(res.getEntity()))
.replaceAll("");
assertThat(actualContent).isEqualTo(
"<html><body>" +
"<p>Check request body</p>" +
"<p>test</p>" +
"</body></html>");
}
}
}
private void convertHeaders(HttpHeaders inHeaders, io.netty.handler.codec.http.HttpHeaders outHeaders,
boolean isTrailersEmpty) {
ArmeriaHttpUtil.toNettyHttp1ServerHeader(inHeaders, outHeaders);
if (!isTrailersEmpty && outHeaders.contains(HttpHeaderNames.CONTENT_LENGTH)) {
// We don't apply chunked encoding when the content-length header is set, which would
// prevent the trailers from being sent so we go ahead and remove content-length to
// force chunked encoding.
outHeaders.remove(HttpHeaderNames.CONTENT_LENGTH);
}
if (enableServerHeader && !outHeaders.contains(HttpHeaderNames.SERVER)) {
outHeaders.add(HttpHeaderNames.SERVER, ArmeriaHttpUtil.SERVER_HEADER);
}
if (enableDateHeader && !outHeaders.contains(HttpHeaderNames.DATE)) {
outHeaders.add(HttpHeaderNames.DATE, HttpTimestampSupplier.currentTime());
}
}
private static List<ClientAddressSource> toClientAddressSourceList(
@Nullable List<String> clientAddressSources,
boolean useDefaultSources, boolean specifiedProxyProtocol) {
if (clientAddressSources != null && !clientAddressSources.isEmpty()) {
return clientAddressSources.stream().map(
name -> "PROXY_PROTOCOL".equals(name) ? ofProxyProtocol() : ofHeader(name))
.collect(toImmutableList());
}
if (useDefaultSources) {
final Builder<ClientAddressSource> builder = new Builder<>();
builder.add(ofHeader(HttpHeaderNames.FORWARDED));
builder.add(ofHeader(HttpHeaderNames.X_FORWARDED_FOR));
if (specifiedProxyProtocol) {
builder.add(ofProxyProtocol());
}
return builder.build();
}
return ImmutableList.of();
}
@Test
public void throttle3() throws Exception {
final WebClient client = WebClient.of(serverRule.httpUri());
final AggregatedHttpResponse response1 = client.get("/http-throttle3").aggregate().get();
assertThat(response1.status()).isEqualTo(HttpStatus.OK);
assertThat(response1.headers().contains(HttpHeaderNames.RETRY_AFTER)).isFalse();
assertThat(response1.headers().contains("RateLimit-Remaining")).isFalse();
assertThat(response1.headers().contains("X-Rate-Limit-Remaining")).isFalse();
assertThat(response1.headers().contains("X-RateLimit-Remaining")).isFalse();
assertThat(response1.headers().contains("X-RateLimit-Reset")).isFalse();
final AggregatedHttpResponse response2 = client.get("/http-throttle3").aggregate().get();
assertThat(response2.status()).isEqualTo(HttpStatus.TOO_MANY_REQUESTS);
assertThat(response2.headers().contains(HttpHeaderNames.RETRY_AFTER)).isTrue();
final long retryAfter2 = Long.parseLong(response2.headers().get(HttpHeaderNames.RETRY_AFTER));
assertThat(retryAfter2).isBetween(0L, 10L);
assertThat(response2.headers().contains("RateLimit-Remaining")).isFalse();
assertThat(response2.headers().contains("X-Rate-Limit-Remaining")).isFalse();
assertThat(response2.headers().contains("X-RateLimit-Remaining")).isFalse();
assertThat(response2.headers().contains("X-RateLimit-Reset")).isFalse();
}
private static void createProject(CentralDogmaExtension dogma) {
final WebClient client = dogma.httpClient();
// the default project used for unit tests
RequestHeaders headers = RequestHeaders.of(HttpMethod.POST, "/api/v1/projects",
HttpHeaderNames.CONTENT_TYPE, MediaType.JSON);
String body = "{\"name\": \"myPro\"}";
client.execute(headers, body).aggregate().join();
// the default repository used for unit tests
headers = RequestHeaders.of(HttpMethod.POST, "/api/v1/projects/myPro/repos",
HttpHeaderNames.CONTENT_TYPE, MediaType.JSON);
body = "{\"name\": \"myRepo\"}";
client.execute(headers, body).aggregate().join();
// default files used for unit tests
addFooFile(client);
}
private ResponseHeaders addCommonHeaders(ResponseHeadersBuilder headers, HttpFileAttributes attrs,
@Nullable String etag) {
if (contentType != null) {
headers.set(HttpHeaderNames.CONTENT_TYPE, contentType.toString());
}
if (dateEnabled) {
headers.setTimeMillis(HttpHeaderNames.DATE, clock.millis());
}
if (lastModifiedEnabled) {
headers.setTimeMillis(HttpHeaderNames.LAST_MODIFIED, attrs.lastModifiedMillis());
}
if (etag != null) {
headers.set(HttpHeaderNames.ETAG, '\"' + etag + '\"');
}
headers.set(this.headers);
return headers.build();
}
@Test
void createRepositoryInAbsentProject() {
final WebClient client = dogma.httpClient();
final RequestHeaders headers = RequestHeaders.of(HttpMethod.POST,
PROJECTS_PREFIX + "/absentProject" + REPOS,
HttpHeaderNames.CONTENT_TYPE, MediaType.JSON);
final String body = "{\"name\": \"myRepo\"}";
final AggregatedHttpResponse aRes = client.execute(headers, body).aggregate().join();
assertThat(ResponseHeaders.of(aRes.headers()).status()).isEqualTo(HttpStatus.NOT_FOUND);
final String expectedJson =
'{' +
" \"exception\": \"" + ProjectNotFoundException.class.getName() + "\"," +
" \"message\": \"Project 'absentProject' does not exist.\"" +
'}';
assertThatJson(aRes.contentUtf8()).isEqualTo(expectedJson);
}
@Test
public void metaAnnotations() {
final AggregatedHttpResponse msg =
WebClient.of(rule.httpUri())
.execute(RequestHeaders.of(HttpMethod.POST, "/hello",
HttpHeaderNames.CONTENT_TYPE,
MediaType.PLAIN_TEXT_UTF_8,
HttpHeaderNames.ACCEPT, "text/*"),
HttpData.ofUtf8("Armeria"))
.aggregate().join();
assertThat(msg.status()).isEqualTo(HttpStatus.CREATED);
assertThat(msg.contentType()).isEqualTo(MediaType.PLAIN_TEXT_UTF_8);
assertThat(msg.headers().get(HttpHeaderNames.of("x-foo"))).isEqualTo("foo");
assertThat(msg.headers().get(HttpHeaderNames.of("x-bar"))).isEqualTo("bar");
assertThat(msg.contentUtf8())
.isEqualTo("Hello, Armeria (decorated-1) (decorated-2) (decorated-3)!");
assertThat(msg.trailers().get(HttpHeaderNames.of("x-baz"))).isEqualTo("baz");
assertThat(msg.trailers().get(HttpHeaderNames.of("x-qux"))).isEqualTo("qux");
}
@Test
public void contentLengthIsNotSetWhenTrailerExists() {
final WebClient client = WebClient.of(rule.httpUri());
AggregatedHttpResponse res = client.get("/trailersWithoutData").aggregate().join();
assertThat(res.headers().get(HttpHeaderNames.CONTENT_LENGTH)).isNull();
assertThat(res.trailers().get(HttpHeaderNames.of("foo"))).isEqualTo("bar");
assertThat(res.content()).isSameAs(HttpData.empty());
res = client.get("/dataAndTrailers").aggregate().join();
assertThat(res.headers().get(HttpHeaderNames.CONTENT_LENGTH)).isNull();
assertThat(res.trailers().get(HttpHeaderNames.of("foo"))).isEqualTo("bar");
assertThat(res.contentUtf8()).isEqualTo("trailer");
res = client.get("/additionalTrailers").aggregate().join();
assertThat(res.headers().get(HttpHeaderNames.CONTENT_LENGTH)).isNull();
assertThat(res.trailers().get(HttpHeaderNames.of("foo"))).isEqualTo("baz");
}
@Test
void streaming_HttpData() throws Exception {
final List<HttpData> contents = ImmutableList.of(HttpData.ofUtf8("foo"),
HttpData.ofUtf8("bar"),
HttpData.ofUtf8("baz"));
for (final Object result : ImmutableList.of(Flux.fromIterable(contents),
contents.stream())) {
StepVerifier.create(from(result))
.expectNext(OCTET_STREAM_HEADERS)
.expectNext(contents.get(0))
.expectNext(contents.get(1))
.expectNext(contents.get(2))
.expectComplete()
.verify();
}
StepVerifier.create(from(contents.get(0)))
.expectNext(OCTET_STREAM_HEADERS.toBuilder()
.addInt(HttpHeaderNames.CONTENT_LENGTH, 3)
.build())
.expectNext(contents.get(0))
.expectComplete()
.verify();
}
private Http2Headers convertHeaders(HttpHeaders inputHeaders, boolean isTrailersEmpty) {
final Http2Headers outHeaders = ArmeriaHttpUtil.toNettyHttp2ServerHeaders(inputHeaders);
if (!isTrailersEmpty && outHeaders.contains(HttpHeaderNames.CONTENT_LENGTH)) {
// We don't apply chunked encoding when the content-length header is set, which would
// prevent the trailers from being sent so we go ahead and remove content-length to force
// chunked encoding.
outHeaders.remove(HttpHeaderNames.CONTENT_LENGTH);
}
if (enableServerHeader && !outHeaders.contains(HttpHeaderNames.SERVER)) {
outHeaders.add(HttpHeaderNames.SERVER, ArmeriaHttpUtil.SERVER_HEADER);
}
if (enableDateHeader && !outHeaders.contains(HttpHeaderNames.DATE)) {
outHeaders.add(HttpHeaderNames.DATE, HttpTimestampSupplier.currentTime());
}
return outHeaders;
}
@Test
void testServerSentEvents() {
StepVerifier.create(Flux.from(client.get("/long")).log())
.expectNext(ResponseHeaders.of(HttpStatus.OK,
HttpHeaderNames.CONTENT_TYPE, MediaType.EVENT_STREAM))
.expectNext(HttpData.ofUtf8("data:0\n\n"))
.expectNext(HttpData.ofUtf8("data:1\n\n"))
.expectNext(HttpData.ofUtf8("data:2\n\n"))
.expectNext(HttpData.ofUtf8("data:3\n\n"))
.expectNext(HttpData.ofUtf8("data:4\n\n"))
.assertNext(o -> assertThat(o.isEndOfStream()).isTrue())
.expectComplete()
.verify();
StepVerifier.create(Flux.from(client.get("/short")).log())
.expectNext(ResponseHeaders.of(HttpStatus.OK,
HttpHeaderNames.CONTENT_TYPE, MediaType.EVENT_STREAM))
.expectNext(HttpData.ofUtf8("id:0\ndata:5\nretry:5000\n\n"))
.expectNext(HttpData.ofUtf8("id:1\ndata:6\nretry:5000\n\n"))
.expectNext(HttpData.ofUtf8("id:2\ndata:7\nretry:5000\n\n"))
.expectNext(HttpData.ofUtf8("id:3\ndata:8\nretry:5000\n\n"))
.expectNext(HttpData.ofUtf8("id:4\ndata:9\nretry:5000\n\n"))
.assertNext(o -> assertThat(o.isEndOfStream()).isTrue())
.expectComplete()
.verify();
}
@Test
void unremoveProject() {
final WebClient client = dogma.httpClient();
createProject(client, "bar");
final String projectPath = PROJECTS_PREFIX + "/bar";
client.delete(projectPath).aggregate().join();
final RequestHeaders headers = RequestHeaders.of(HttpMethod.PATCH, projectPath,
HttpHeaderNames.CONTENT_TYPE, MediaType.JSON_PATCH);
final String unremovePatch = "[{\"op\":\"replace\",\"path\":\"/status\",\"value\":\"active\"}]";
final AggregatedHttpResponse aRes = client.execute(headers, unremovePatch).aggregate().join();
assertThat(ResponseHeaders.of(aRes.headers()).status()).isEqualTo(HttpStatus.OK);
final String expectedJson =
'{' +
" \"name\": \"bar\"," +
" \"creator\": {" +
" \"name\": \"System\"," +
" \"email\": \"[email protected]\"" +
" }," +
" \"url\": \"/api/v1/projects/bar\"," +
" \"createdAt\": \"${json-unit.ignore}\"" +
'}';
assertThatJson(aRes.contentUtf8()).isEqualTo(expectedJson);
}
@Test
void splitTrailersIsIgnored() throws Exception {
final DecodedHttpResponse res = new DecodedHttpResponse(CommonPools.workerGroup().next());
final HttpResponseWrapper wrapper = httpResponseWrapper(res);
assertThat(wrapper.tryWrite(ResponseHeaders.of(200))).isTrue();
assertThat(wrapper.tryWrite(HttpHeaders.of(HttpHeaderNames.of("bar"), "baz"))).isTrue();
assertThat(wrapper.tryWrite(HttpHeaders.of(HttpHeaderNames.of("qux"), "quux"))).isFalse();
wrapper.close();
StepVerifier.create(res)
.expectNext(ResponseHeaders.of(200))
.expectNext(HttpHeaders.of(HttpHeaderNames.of("bar"), "baz"))
.expectComplete()
.verify();
}
@Test
void sanitizeRequestContent() throws Exception {
final HttpRequest req = HttpRequest.of(RequestHeaders.of(HttpMethod.POST, "/hello/trustin",
HttpHeaderNames.SCHEME, "http",
HttpHeaderNames.AUTHORITY, "test.com"));
final ClientRequestContext ctx = ClientRequestContext.of(req);
ctx.logBuilder().requestContent("Virginia 333-490-4499", "Virginia 333-490-4499");
// use default logger
final LoggingClient defaultLoggerClient =
LoggingClient.builder()
.requestLogLevel(LogLevel.INFO)
.successfulResponseLogLevel(LogLevel.INFO)
.requestContentSanitizer(RegexBasedSanitizer.of(
Pattern.compile("\\d{3}[-\\.\\s]\\d{3}[-\\.\\s]\\d{4}")))
.build(delegate);
// Before sanitize content
assertThat(ctx.logBuilder().toString()).contains("333-490-4499");
defaultLoggerClient.execute(ctx, req);
// Ensure sanitize the request content of the phone number 333-490-4499
assertThat(ctx.logBuilder().toString()).doesNotContain("333-490-4499");
}
private static void makeUnframedRequest(String name) throws Exception {
final WebClient client =
Clients.builder(server.httpUri())
.factory(clientFactory)
.addHttpHeader(HttpHeaderNames.CONTENT_TYPE, MediaType.PROTOBUF.toString())
.build(WebClient.class);
final SimpleRequest request =
SimpleRequest.newBuilder()
.setPayload(Payload.newBuilder()
.setBody(ByteString.copyFromUtf8(name)))
.build();
try {
client.post("/armeria.grpc.testing.TestService/UnaryCall2", request.toByteArray());
} catch (Throwable t) {
// Ignore, we will count these up
}
}
@Test
void ignoresAfterTrailersIsWritten() {
final HttpResponseWriter res = HttpResponse.streaming();
res.write(ResponseHeaders.of(100));
res.write(HttpHeaders.of(HttpHeaderNames.of("a"), "b"));
res.write(ResponseHeaders.of(200));
res.write(HttpHeaders.of(HttpHeaderNames.of("c"), "d")); // Split headers is trailers.
// Ignored after trailers is written.
res.write(HttpData.ofUtf8("foo"));
res.write(HttpHeaders.of(HttpHeaderNames.of("e"), "f"));
res.write(HttpHeaders.of(HttpHeaderNames.of("g"), "h"));
res.close();
final AggregatedHttpResponse aggregated = res.aggregate().join();
// Informational headers
assertThat(aggregated.informationals()).containsExactly(
ResponseHeaders.of(HttpStatus.CONTINUE, HttpHeaderNames.of("a"), "b"));
// Non-informational header
assertThat(aggregated.headers()).isEqualTo(ResponseHeaders.of(200));
assertThat(aggregated.contentUtf8()).isEmpty();
assertThat(aggregated.trailers()).isEqualTo(HttpHeaders.of(HttpHeaderNames.of("c"), "d"));
}
@Test
void unframed_serviceError() throws Exception {
final WebClient client = WebClient.of(server.httpUri());
final SimpleRequest request =
SimpleRequest.newBuilder()
.setResponseStatus(
EchoStatus.newBuilder()
.setCode(Status.DEADLINE_EXCEEDED.getCode().value()))
.build();
final AggregatedHttpResponse response = client.execute(
RequestHeaders.of(HttpMethod.POST,
UnitTestServiceGrpc.getStaticUnaryCallMethod().getFullMethodName(),
HttpHeaderNames.CONTENT_TYPE, "application/protobuf"),
request.toByteArray()).aggregate().get();
assertThat(response.status()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
checkRequestLog((rpcReq, rpcRes, grpcStatus) -> {
assertThat(rpcReq.method()).isEqualTo("armeria.grpc.testing.UnitTestService/StaticUnaryCall");
assertThat(rpcReq.params()).containsExactly(request);
assertThat(grpcStatus).isNotNull();
assertThat(grpcStatus.getCode()).isEqualTo(Code.UNKNOWN);
});
}
@Test
void renameDirectory() {
final WebClient client = dogma.httpClient();
addBarTxt(client);
final String body =
'{' +
" \"path\" : \"/a\"," +
" \"type\" : \"RENAME\"," +
" \"content\" : \"/b\"," +
" \"commitMessage\" : {" +
" \"summary\" : \"Rename /a\"," +
" \"detail\": \"Rename to /b\"," +
" \"markup\": \"PLAINTEXT\"" +
" }" +
'}';
final RequestHeaders headers = RequestHeaders.of(HttpMethod.POST, CONTENTS_PREFIX,
HttpHeaderNames.CONTENT_TYPE, MediaType.JSON);
final AggregatedHttpResponse aRes = client.execute(headers, body).aggregate().join();
final String expectedJson =
'{' +
" \"revision\": 3," +
" \"pushedAt\": \"${json-unit.ignore}\"" +
'}';
final String actualJson = aRes.contentUtf8();
assertThatJson(actualJson).isEqualTo(expectedJson);
}
@Test
void headersAndData() throws Exception {
final DecodedHttpResponse res = new DecodedHttpResponse(CommonPools.workerGroup().next());
final HttpResponseWrapper wrapper = httpResponseWrapper(res);
assertThat(wrapper.tryWrite(
ResponseHeaders.of(HttpStatus.OK, HttpHeaderNames.CONTENT_LENGTH, "foo".length()))).isTrue();
assertThat(wrapper.tryWrite(HttpData.ofUtf8("foo"))).isTrue();
wrapper.close();
StepVerifier.create(res)
.expectNext(ResponseHeaders.of(HttpStatus.OK, HttpHeaderNames.CONTENT_LENGTH, 3))
.expectNext(HttpData.ofUtf8("foo"))
.expectComplete()
.verify();
}
/**
* If CORS was configured as a route decorator and there's no binding for OPTIONS method,
* the server's fallback service decorated with the CORS decorator will be matched and thus
* must respond with a CORS response.
*/
@Test
public void testCorsWithPartialBindingAndRouteDecorator() {
final WebClient client = client();
AggregatedHttpResponse res;
res = preflightRequest(client, "/cors11/get", "http://example.com", "GET");
assertThat(res.status()).isEqualTo(HttpStatus.OK);
assertThat(res.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_METHODS)).isEqualTo("GET");
// GET must be allowed.
res = request(client, HttpMethod.GET, "/cors11/get", "http://example.com", "GET");
assertThat(res.status()).isSameAs(HttpStatus.OK);
assertThat(res.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN))
.isEqualTo("http://example.com");
// Other methods must be disallowed.
res = request(client, HttpMethod.GET, "/cors11/get", "http://notallowed.com", "GET");
assertThat(res.status()).isSameAs(HttpStatus.FORBIDDEN);
}
@Test
public void shouldRespondAuthnRequest_HttpRedirect() throws Exception {
final AggregatedHttpResponse resp = client.get("/redirect").aggregate().join();
assertThat(resp.status()).isEqualTo(HttpStatus.FOUND);
// Check the order of the parameters in the quest string.
final String location = resp.headers().get(HttpHeaderNames.LOCATION);
final Pattern p = Pattern.compile(
"http://idp\\.example\\.com/saml/sso/redirect\\?" +
"SAMLRequest=([^&]+)&RelayState=([^&]+)&SigAlg=([^&]+)&Signature=(.+)$");
assertThat(location).isNotNull();
assertThat(p.matcher(location).matches()).isTrue();
assertThat(QueryParams.fromQueryString(location)
.get(SIGNATURE_ALGORITHM)).isEqualTo(signatureAlgorithm);
}
@Test
void waitUntilUnhealthy() {
final CompletableFuture<AggregatedHttpResponse> f = sendLongPollingGet("healthy");
// Should not wake up until the server becomes unhealthy.
assertThatThrownBy(() -> f.get(1, TimeUnit.SECONDS))
.isInstanceOf(TimeoutException.class);
// Make the server unhealthy so the response comes in.
checker.setHealthy(false);
assertThat(f.join()).isEqualTo(AggregatedHttpResponse.of(
ImmutableList.of(ResponseHeaders.builder(HttpStatus.PROCESSING)
.set("armeria-lphc", "60, 5")
.build()),
ResponseHeaders.of(HttpStatus.SERVICE_UNAVAILABLE,
HttpHeaderNames.CONTENT_TYPE, MediaType.JSON_UTF_8,
"armeria-lphc", "60, 5"),
HttpData.ofUtf8("{\"healthy\":false}"),
HttpHeaders.of()));
}
@Test
void updateUsingPatch() {
final WebClient client = WebClient.of(server.httpUri());
// Make unhealthy.
final AggregatedHttpResponse res1 = client.execute(
RequestHeaders.of(HttpMethod.PATCH, "/hc_updatable"),
"[{\"op\":\"replace\",\"path\":\"/healthy\",\"value\":false}]").aggregate().join();
assertThat(res1).isEqualTo(AggregatedHttpResponse.of(
ResponseHeaders.of(HttpStatus.SERVICE_UNAVAILABLE,
HttpHeaderNames.CONTENT_TYPE, MediaType.JSON_UTF_8,
"armeria-lphc", "60, 5"),
HttpData.ofUtf8("{\"healthy\":false}")));
// Make healthy.
final AggregatedHttpResponse res2 = client.execute(
RequestHeaders.of(HttpMethod.PATCH, "/hc_updatable"),
"[{\"op\":\"replace\",\"path\":\"/healthy\",\"value\":true}]").aggregate().join();
assertThat(res2).isEqualTo(AggregatedHttpResponse.of(
ResponseHeaders.of(HttpStatus.OK,
HttpHeaderNames.CONTENT_TYPE, MediaType.JSON_UTF_8,
"armeria-lphc", "60, 5"),
HttpData.ofUtf8("{\"healthy\":true}")));
}
@Test
void blacklistedHeadersAndTrailersMustBeFiltered() {
final WebClient client = WebClient.of(server.httpUri());
final AggregatedHttpResponse res = client.get("/headers_and_trailers").aggregate().join();
assertThat(res.status()).isEqualTo(HttpStatus.OK);
assertThat(res.headers().names()).doesNotContain(HttpHeaderNames.SCHEME,
HttpHeaderNames.METHOD,
HttpHeaderNames.PATH,
HttpHeaderNames.TRANSFER_ENCODING);
assertThat(res.trailers().names()).doesNotContain(HttpHeaderNames.SCHEME,
HttpHeaderNames.STATUS,
HttpHeaderNames.METHOD,
HttpHeaderNames.PATH,
HttpHeaderNames.TRANSFER_ENCODING);
}
@Test
public void throttle1() throws Exception {
final WebClient client = WebClient.of(serverRule.httpUri());
final AggregatedHttpResponse response1 = client.get("/http-throttle1").aggregate().get();
assertThat(response1.status()).isEqualTo(HttpStatus.OK);
assertThat(response1.headers().contains(HttpHeaderNames.RETRY_AFTER)).isFalse();
assertThat(response1.headers().contains("RateLimit-Remaining")).isFalse();
assertThat(response1.headers().contains("X-Rate-Limit-Remaining")).isFalse();
assertThat(response1.headers().contains("X-RateLimit-Remaining", "0")).isTrue();
assertThat(response1.headers().contains("X-RateLimit-Reset")).isTrue();
final long reset1 = Long.parseLong(response1.headers().get("X-RateLimit-Reset"));
assertThat(reset1).isBetween(0L, 10L);
assertThat(response1.headers().contains("X-RateLimit-Limit")).isFalse();
final AggregatedHttpResponse response2 = client.get("/http-throttle1").aggregate().get();
assertThat(response2.status()).isEqualTo(HttpStatus.TOO_MANY_REQUESTS);
assertThat(response2.headers().contains(HttpHeaderNames.RETRY_AFTER)).isTrue();
final long retryAfter2 = Long.parseLong(response2.headers().get(HttpHeaderNames.RETRY_AFTER));
assertThat(retryAfter2).isBetween(0L, 10L);
assertThat(response2.headers().contains("RateLimit-Remaining")).isFalse();
assertThat(response2.headers().contains("X-Rate-Limit-Remaining")).isFalse();
assertThat(response2.headers().contains("X-RateLimit-Remaining", "0")).isTrue();
assertThat(response2.headers().contains("X-RateLimit-Reset")).isTrue();
final long reset = Long.parseLong(response2.headers().get("X-RateLimit-Reset"));
assertThat(reset).isEqualTo(retryAfter2);
assertThat(response2.headers().contains("X-RateLimit-Limit")).isFalse();
}
@Override
public HttpResponse execute(ClientRequestContext ctx, HttpRequest req) throws Exception {
if (req.headers().contains(HttpHeaderNames.ACCEPT_ENCODING)) {
// Client specified encoding, so we don't do anything automatically.
return unwrap().execute(ctx, req);
}
req = req.withHeaders(req.headers().toBuilder()
.set(HttpHeaderNames.ACCEPT_ENCODING, acceptEncodingHeader));
ctx.updateRequest(req);
final HttpResponse res = unwrap().execute(ctx, req);
return new HttpDecodedResponse(res, decoderFactories, ctx.alloc());
}