0.97.0 release notes

8th December 2019

🌟 New features

  • We now have a new immutable cookie API with SameSite attribute support, which replaces Netty's cookie API. #1567 #2286

    Cookie 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 HttpRequest more conveniently using HttpRequest.withHeaders(RequestHeaders). #2278 #2283

    HttpRequest 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 a ScheduledExecutorService instead of ExecutorService, 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() via ServerBuilder.clientAddressMapper(). #1631 #2294

    Server.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 Logger than the default one when using LoggingClient or LoggingService. #2220 #2237

    Logger myLogger = LoggerFactory.getLogger(MyService.class);
    Server server =
        Server.builder()
              .service("/",
                       myService.decorate(LoggingService.builder()
                                                        .logger(myLogger)
                                                        .newDecorator()))
              .build();
  • BraveClient now adds connection timing information to a span. #2271 #2273

    • connection-acquire.start and connection-acquire.end
    • dns-resolve.start and dns-resolve.end
    • socket-connect.start and socket-connect.end
    • connection-reuse.start and connection-reuse.end
  • withDeadlineAfter is now supported for gRPC client stubs. #2284

  • GrpcService now 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.
  • THttpService and ThriftCallService now supports adding more than one Thrift service at the same endpoint. #2164 #2285

    class 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-autoconfigure now 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 the Publisher is actually an HttpRequest. #2278 #2283

🔒 Security fixes

🛠️ Bug fixes

  • The event loop threads are properly cleaned up when Server failed to start. #2288
  • Server now sends a redirect response correctly for the requests without a trailing slash (/) even when you bound a fallback service at prefix:/. #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 injecting Cookies, because multiple "Cookie" headers must be prohibited according to RFC 6265. #2286
  • GrpcService now respects gRPC stub deadline. #2008 #2284

🏚️ Deprecations

  • Deprecated HttpRequest.of(HttpRequest, RequestHeaders) in favor of HttpRequest.withHeaders() #2283
  • Deprecated HttpRequest.of(AggregatedHttpRequest) and HttpResponse.of(AggregatedHttpResponse) in favor of AggregatedHttpRequest.toHttpRequest() and AggregatedHttpResponse.toHttpResponse(). #2283
    • These methods were removed from AggregatedHttpMessage some time ago due to ambiguity, but now we are reviving them because we have separate types for requests and responses.
  • Cookies.copyOf() has been deprecated in favor of Cookies.of(). #2286
  • AnnotatedServiceBuildingBuilder.requestConverter(), responseConverter() and exceptionHandler() have been deprecated in favor of requestConverters(), responseConverters() and exceptionHandlers() respectively. #2242
  • Sampler.rateLimited() has been deprecated in favor of rateLimiting(). #2122 #2289
  • THttpService.of() and ofFormats() factory methods that require a Map<String, ?> have been deprecated. Use THttpService.builder() and call THttpService.addService(String, Object) multiple times instead. #2285

☢️ Breaking changes

  • ServerBuilder.blockingTaskExecutor() now requires a ScheduledExecutorService instead of an ExecutorService. #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 to TomcatService:
      Server server =
          Server.builder()
                .serviceUnder("/docs", new DocService())
                .serviceUnder("/", TomcatSerivce.forClassPath(...))
                .build();
    • However, from this release, the request will not be routed to TomcatService but the server will issue a redirect response to /docs/.
  • ProxiedAddresses class has been redesigned to handle the case of more than one intermediary proxy server. #1631 #2294
  • Cookies now uses Armeria's own Cookie. #2286
  • Cookies now 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 ThriftCallService factory method with signature of(Map<String, ? extends Iterable<?>>) has been changed to of(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

🙇 Thank you