1.15.0 release notes
24th March 2022
🌸 Highlights
- gRPC health check protocol
- Multipart file uploads in annotated services
- Preliminary GraphQL support in DocService
- Refactored DNS resolver cache with higher hit ratio and reduced traffic
- New metrics that help you get alerts before your TLS certificates are expired
🌟 New features
- Servernow registers two gauges about TLS certificate expiry, so you can get alerts before the certificates are expired. #4075 #4082- armeria_server_certificate_validity- 1if certificate is in validity period.- 0otherwise.
 
- armeria_server_certificate_validity_days- The number of days before certificate expires, or -1if expired.
 
- The number of days before certificate expires, or 
 
- You can now specify a - Fileor- Pathparameter in your annotated service method to handle multipart file uploads. #3578 #4061- Server .builder() .annotatedService(new Object() { @Post("/upload") @Consumes("multipart/form-data") public String upload(@Param File file) { return "Successfully uploaded a file to " + file; } }) .build();
- You can now use the - ||(logical OR) operator in parameter and header predicate expressions. #4116 #4138- // "Hi!" for /greet?casual or /greet?casual=true // "Hello!" for /greet or /greet?casual=false Server .builder() .route() .get("/greet") .matchesParam("casual || casual=true") // 👈👈👈 .build((ctx, req) -> HttpResponse.of("Hi!")) .route() .get("/greet") .matchesParam("!casual || casual!=true") // 👈👈👈 .build(ctx, req) -> HttpResponse.of("Hello!"))
- You can now enable gRPC health check protocol with - GrpcHealthCheckService#3146 #3963- Server .builder() .service( GrpcService .builder() .addService(new MyGrpcService()) .enableHealthCheckService(true) // 👈👈👈 // or customize: // .addService(GrpcHealthCheckService.of(...)) .build() ) .build()
- DocServicenow supports- GraphqlServicewith CodeMirror auto-completion. #3706 #4023
- You can now configure the - EndpointGroupimplementations based on- DynamicEndpointGroupto allow or disallow empty endpoints. This can be useful when you want to avoid the situation where the endpoint group becomes empty due to misconfiguration. #3952 #3958- EndpointGroup endpointGroup = DnsAddressEndpointGroup .builder("example.com") .allowEmptyEndpoints(false) // 👈👈👈 .build();
- You can now configure the timeout of DNS sub-queries with - queryTimeoutMillisForEachAttempt(). Previously, you were only able to specify the timeout for the entire DNS resolution process, which can consist of more than one DNS query. #2940 #4133- DnsAddressEndpointGroup .builder("armeria.dev") .queryTimeoutMillisForEachAttempt(1000) // 👈👈👈 .queryTimeoutMillis(5000) .build();
- Armeria now has the global shared DNS cache by default for higher hit ratio and less DNS traffic. You can also build and share a custom DNS cache across multiple resolvers. #2940 #4133 - DnsCache dnsCache = DnsCache .builder() .ttl(60, 3600) .negativeTtl(600) .build(); EndpointGroup endpointGroup = DnsAddressEndpointGroup .builder("armeria.dev") .dnsCache(dnsCache) // 👈👈👈 .build(); ClientFactory factory = ClientFactory .builder() .domainNameResolverCustomizer(builder -> { builder.dnsCache(dnsCache); // 👈👈👈 }) .build(); WebClient client = WebClient .builder(SessionProtocol.HTTPS, endpointGroup) // 👈👈👈 .factory(factory) // 👈👈👈 .build();
- Failed search domain queries are now cached according to the negative TTL configuration. #2940 #4133 
- You can now specify a - SuccessFunctionwhen constructing a client or server, and let all decorators use it for determining whether a request was successful or not. #4101- Server .builder() .successFunction((ctx, log) -> { // 👈👈👈 // Treat only '200 OK' and '204 No Content' as success. switch (log.responseStatus().code()) { case 200: case 204: return true; default: return false; } }) .decorator(LoggingService.newDecorator()) .decorator(MetricCollectingService.newDecorator()) ... WebClient .builder() .successFunction(...) // 👈👈👈 .decorator(LoggingClient.newDecorator()) .decorator(MetricCollectingClient.newDecorator()) .build();
- You can now specify different sampling ratio (or - Sampler) for successful and failed requests when using- LoggingClientand- LoggingService#3666 #4101- LoggingService .builder() // Log at 10% sampling ratio if successful. .successSamplingRate(0.1) // Log all failure. .failureSamplingRate(1.0) .newDecorator()
- You can now customize how - LoggingClientand- LoggingServicedetermines log level more flexibly. #3972- LoggingService .builder() // Log at INFO if under /important. DEBUG otherwise. .requestLogLevelMapper(log -> { if (log.requestHeaders().path().startsWith("/important/")) { return LogLevel.INFO; } else { return LogLevel.DEBUG; } }) // Log at INFO if 200 OK. .responseLogLevel(HttpStatus.OK, LogLevel.INFO) // Log at WARN if 5xx. .responseLogLevel(HttpStatusClass.SERVER_ERROR, LogLevel.WARN) .newDecorator()
- You can now specify a customizer that customizes a - ClientRequestContextwhen building a client. #4075 #4082- WebClient client = WebClient .builder() .contextCustomizer(ctx -> { // 👈👈👈 String userId = MyUserContext.current().getId(); ctx.setAttr(USER_ID, userId); }) .build();- For instance, you could manually propagate a Brave thread-local - TraceContextusing it:- Tracing reactorTracing = ...; Tracing requestContextTracing = Tracing .newBuilder() .currentTraceContext(RequestContextCurrentTraceContext.ofDefault()) .build(); WebClient .builder() .contextCustomizer(TraceContextPropagation.inject(() -> { // Propagate Reactor's TraceContext to Armeria's TraceContext. return reactorTracing.currentTraceContext().get(); }) .decorator(BraveClient.newDecorator(requestContextTracing)) .build();
- You can now write the content of - StreamMessageinto a file using- StreamMessage.writeTo()#4048 #4130- SplitHttpResponse res = WebClient .of() .get("https://example.com/large_file") .split(); StreamMessage<HttpData> body = res.body(); body.writeTo( // 👈👈👈 Function.identity(), Path.of("/tmp", "large_file") );
- You can now convert a - StreamMessageinto an- InputStreamusing- StreamMessage.toInputStream()#4059- StreamMessage<HttpData> body = ...; BufferedReader in = new BufferedReader( new InputStreamReader( body.toInputStream(Function.identity()), // 👈👈👈 "UTF-8" ) ); for (;;) { String line = in.readLine(); if (line == null) break; System.out.println(line); }
- You can now use - StreamMessage.mapParallel()to modify a stream using an async operation with a configurable concurrency limit. #4031
- You can now decode a - StreamMessageinto another using- StreamMessage.decode()which was previously possible only for- HttpMessage. #4147 #4148 #4152
- You can now give Armeria a performance optimization hint by specifying if your service is unary, request-streaming, response-streaming or bidi-streaming by implementing - HttpService.exchangeType()#3956- class MyUnaryService implements HttpService { @Override public HttpResponse serve(ServiceRequestContext ctx, HttpRequest req) { return HttpResponse.from( req .aggregate() // Always aggregate .thenApply(aggregatedReq -> { HttpResponse.of( // Never streaming "You sent %d bytes.", aggregatedReq.content().length() ); }) ); } // Tells Armeria to assume non-streaming requests and responses. @Override public ExchangeType exchangeType() { return ExchangeType.UNARY; // 👈👈👈 } }
📈 Improvements
- Spring Boot WebServerExceptionmessage is now more user-friendly. #4146
🛠️ Bug fixes
- A response with status code 408doesn't trigger aNullPointerExceptionanymore, even if its reason phrase is not exactlyRequest Timeout. #4165
- Failed search domain queries are now cached properly according to the negative TTL configuration. #2940 #4133
- GrpcServicenow logs- RequestOnlyLog.requestContent()for invalid requests. #4128
- DocServicedoesn't crash the whole Armeria service anymore when- .protofiles have comments on service options or extensions. #4123 #4127
- GraphqlServicedoesn't throw a- ClassCastExceptionanymore when- queryor- operationNameare not a string. #4104 #4168
- FileServicedoesn't choose a- .brfile for generating decompressed content anymore when Brotli is not available. #4119
- You no longer see an HttpResponseExceptionorHttpStatusExceptionfrom built-in services. #4056 #4117- Thanks to this fix, you can now mutate a redirect response from FileServicein your decorator usingHttpResponse.mapHeaders()
 
- Thanks to this fix, you can now mutate a redirect response from 
- RampingUpEndpointWeightSelectordoesn't raise a- NoSuchElementExceptionanymore when endpoints are replaced completely. #3776 #4102
🏚️ Deprecations
- samplingRateproperty of- @LoggingDecoratorhas been deprecated in favor of- successSamplingRateand- failureSamplingRate.
- The successFunction()builder methods that require aBiPredicatehas been deprecated in favor ofSuccessFunctionthat's specified when constructing a client or server.
- dnsServerAddressStreamProvider()has been deprecated in favor of- serverAddressStreamProvider()in the DNS-related builders.
- disableDnsQueryMetrics()has been deprecated in favor of- enableDnsQueryMetrics()in the DNS-related builders.
☢️ Breaking changes
- The experimental HttpDecoderAPI has been replaced withStreamDecoder#4147 #4152
⛓ Dependencies
- Bucket4j 7.0.0 → 7.3.0
- Curator 5.2.0 → 5.2.1
- Dropwizard Metrics 4.2.7 → 4.2.9
- gRPC-Java 1.43.2 → 1.45.0
- Jackson 2.13.1 → 2.13.2
- java-jwt 3.18.3 → 3.19.0
- Jetty 9.4.44 → 9.4.45
- Graphql-Java 17.3 → 18.0
- Logback 1.2.10 → 1.2.11
- Micrometer 1.8.2 → 1.8.4
- Netty 4.1.73 → 4.1.75- io_uring 0.0.11 → 0.0.13
 
- Prometheus 0.14.1 → 0.15.0
- Reactor 3.4.14 → 3.4.16- Reactor Kotlin 1.1.5 → 1.1.6
 
- RxJava 3.1.3 → 3.1.4
- Sangria 2.1.6 → 3.0.0
- ScalaPB 0.11.8 → 0.11.10
- scala-collection-compat 2.6.0 → 2.7.0
- SLF4J 1.7.34 → 1.7.36
- Spring 5.3.15 → 5.3.17
- Spring Boot 2.6.3 → 2.6.5