0.97.0 release notes
8th December 2019
🌟 New features
We now have a new immutable cookie API with
SameSiteattribute support, which replaces Netty's cookie API. #1567 #2286Cookie c1 = Cookie.of("foo", "bar") Cookie c2 = Cookie.builder("alice", "bob") .domain("foo.com") .path("/bar") .maxAge(3600) .sameSite("Strict") .build(); // Encoding ResponseHeaders resHeaders = ResponseHeaders.builder(HttpStatus.OK) .add(HttpHeaderNames.SET_COOKIE, Cookie.toSetCookieHeaders(c1, c2)) .build(); // Decoding Cookies cookies = Cookie.fromSetCookieHeaders( resHeaders.getAll(HttpHeaderNames.SET_COOKIE));You can now replace the request headers of
HttpRequestmore conveniently usingHttpRequest.withHeaders(RequestHeaders). #2278 #2283HttpRequest req = HttpRequest.of(HttpMethod.POST, "/post", MediaType.PLAIN_TEXT_UTF_8, "Hello!"); RequestHeaders newHeaders = RequestHeaders.of(HttpMethod.PUT, "/put", MediaType.PLAIN_TEXT_UTF_8); HttpRequest newReq = req.withHeaders(newHeaders);ServiceRequestContext.blockingTaskExecutor()is now aScheduledExecutorServiceinstead ofExecutorService, which means you can schedule delayed or periodic tasks. #2269// A service that sends `PING!` every second. HttpService service = (ctx, req) -> { HttpResponse res = HttpResponse.streaming(); res.write(ResponseHeaders.of(200)); AtomicReference<ScheduledFuture<?>> futureHolder = new AtomicReference<>(); futureHolder.set(ctx.blockingTaskExecutor().scheduleWithFixedDelay(() -> { boolean success = res.tryWrite(HttpData.ofUtf8("PING!\r\n")); if (!success) { if (futureHolder.get() != null) { futureHolder.get().cancel(false); } } }, 1, TimeUnit.SECONDS)); return res; };You can now add converters and exception handlers to annotated services more conveniently using the new fluent builder methods. #2242
Server server = Server.builder() .annotatedService().requestConverters(converterA, converterB) .responseConverters(converterC, converterD) .exceptionHandlers(handlerA, handlerB) .build(myAnnotatedService) .build();You can now configure how Armeria decides
ServiceRequestContext.clientAddress()viaServerBuilder.clientAddressMapper(). #1631 #2294Server.builder() .clientAddressMapper(proxiedAddrs -> { InetSocketAddress srcAddr = proxiedAddrs.sourceAddress(); List<InetSocketAddress> destAddrs = proxiedAddrs.destinationAddresses(); if (destAddrs.isEmpty()) { // No proxy servers involved. return srcAddr; } else { // When there are more than one proxy server involved, // trust only the address given by the last proxy server. return destAddrs.get(destAddrs.size() - 1); } });You can now choose a different SLF4J
Loggerthan the default one when usingLoggingClientorLoggingService. #2220 #2237Logger myLogger = LoggerFactory.getLogger(MyService.class); Server server = Server.builder() .service("/", myService.decorate(LoggingService.builder() .logger(myLogger) .newDecorator())) .build();BraveClientnow adds connection timing information to a span. #2271 #2273connection-acquire.startandconnection-acquire.enddns-resolve.startanddns-resolve.endsocket-connect.startandsocket-connect.endconnection-reuse.startandconnection-reuse.end
withDeadlineAfteris now supported for gRPC client stubs. #2284GrpcServicenow allows controlling whether to respect the"grpc-timeout"header sent by a gRPC client. #2284- Consider disabling this feature via
GrpcServiceBuilder.useClientTimeoutHeader()in an insecure environment.
- Consider disabling this feature via
THttpServiceandThriftCallServicenow supports adding more than one Thrift service at the same endpoint. #2164 #2285class MyFooService implements FooService.Iface ... { public void foo() { ... } } class MyBarService implements BarService.Iface ... { public void bar() { ... } } // "foo" call goes to MyFooService and // "bar" call goes to MyBarService. Server server = Server.builder() .service("/thrift", THttpService.builder() .addService(new MyFooService()) .addService(new MyBarService()) .build()) .build();armeria-spring-boot-actuator-autoconfigurenow supports the official CORS settings. #2063# application.yml management.endpoints.web.cors.allowed-origins: https://example.com management.endpoints.web.cors.allowed-methods: GET,POST
📈 Improvements
- Improved routing performance. #2293 #2295
- The core JAR file is now smaller, by removing unnecessary resources such as web fonts and favicons. #2299 #2300
HttpRequest.of(RequestHeaders, Publisher)now handles the case where thePublisheris actually anHttpRequest. #2278 #2283
🔒 Security fixes
- (Moderate) CWE-113: Improper Neutralization of CRLF Sequences in HTTP Headers ('HTTP Response Splitting')
- (Low) Multiple timing attack vulnerabilities leading to the recovery of secrets based on the use of non-constant time compare function
🛠️ Bug fixes
- The event loop threads are properly cleaned up when
Serverfailed to start. #2288 Servernow sends a redirect response correctly for the requests without a trailing slash (/) even when you bound a fallback service atprefix:/. #2116 #2292- Fixed a bug where routing cache did not work as expected, leading suboptimal routing performance. #2293
- Annotated services now accept only the first
"Cookie"header when injectingCookies, because multiple"Cookie"headers must be prohibited according to RFC 6265. #2286 GrpcServicenow respects gRPC stub deadline. #2008 #2284
🏚️ Deprecations
- Deprecated
HttpRequest.of(HttpRequest, RequestHeaders)in favor ofHttpRequest.withHeaders()#2283 - Deprecated
HttpRequest.of(AggregatedHttpRequest)andHttpResponse.of(AggregatedHttpResponse)in favor ofAggregatedHttpRequest.toHttpRequest()andAggregatedHttpResponse.toHttpResponse(). #2283- These methods were removed from
AggregatedHttpMessagesome time ago due to ambiguity, but now we are reviving them because we have separate types for requests and responses.
- These methods were removed from
Cookies.copyOf()has been deprecated in favor ofCookies.of(). #2286AnnotatedServiceBuildingBuilder.requestConverter(),responseConverter()andexceptionHandler()have been deprecated in favor ofrequestConverters(),responseConverters()andexceptionHandlers()respectively. #2242Sampler.rateLimited()has been deprecated in favor ofrateLimiting(). #2122 #2289THttpService.of()andofFormats()factory methods that require aMap<String, ?>have been deprecated. UseTHttpService.builder()and callTHttpService.addService(String, Object)multiple times instead. #2285
☢️ Breaking changes
ServerBuilder.blockingTaskExecutor()now requires aScheduledExecutorServiceinstead of anExecutorService. #2269- The behavior of automatic redirection for the case of missing trailing slash (
/) has been changed (in a good way). #2116 #2292- For example, previously, when a client sent a request to
/docs, the following server routed the request toTomcatService:Server server = Server.builder() .serviceUnder("/docs", new DocService()) .serviceUnder("/", TomcatSerivce.forClassPath(...)) .build(); - However, from this release, the request will not be routed to
TomcatServicebut the server will issue a redirect response to/docs/.
- For example, previously, when a client sent a request to
ProxiedAddressesclass has been redesigned to handle the case of more than one intermediary proxy server. #1631 #2294Cookiesnow uses Armeria's ownCookie. #2286Cookiesnow may contain the cookies with the same name. #2286- Note that RFC does not prohibit duplicate names at all, so this is correct behavior.
- Annotated services now accept only the first
"Cookie"header when decoding cookies. Sending multiple"Cookie"headers was a violation of RFC 6265 anyway. #2286 - The
ThriftCallServicefactory method with signatureof(Map<String, ? extends Iterable<?>>)has been changed toof(Map<String, ? extends Iterable<?>>)#2285
⛓ Dependencies
- Brave 1.3.1
- Jetty 9.4.22 -> 9.4.24
- Micrometer 1.3.1 -> 1.3.2
- Netty TCNative BoringSSL 2.0.27 -> 2.0.26
- Project Reactor 3.3.0 -> 3.3.1
- RxJava2 2.2.14 -> 2.2.15
- Spring Boot 2.1.10 -> 2.2.1
- Tomcat 9.0.27 -> 9.0.29, 8.5.47 -> 8.5.49