Skip to main content
info

You're seeing the release note of an old version. Check out the latest release note.

v1.4.0

February 1, 2021

🌟 New features

  • You can now register/discover your servers to/from Consul using a new armeria-consul module. #194 #2192 #3002 #3281

    // Registration
    Server server = ...;
    server.addListener(
    ConsulUpdatingListener.builder(myConsulUri, "myService")
    .consulToken(myConsulAccessToken)
    .build());
    server.start().join();

    // Discovery
    ConsulEndpointGroup endpointGroup =
    ConsulEndpointGroup.builder(myConsulUri, "myService")
    .consulToken(myConsulAccessToken)
    .build();
  • You can now smoothly ramp up the weight of newly added Endpoint in an EndpointGroup using EndpointSelectionStrategy.rampingUp() to protect a fresh node from getting too much traffic suddenly. #1757 #3217

    EndpointGroup endpointGroup =
    DnsAddressEndpointGroup
    .builder("my.hostname.local")
    .selectionStrategy(EndpointSelectionStrategy.rampingUp())
    .build();

    // Customize as necessary.
    EndpointSelectionStrategy
    .builderForRampingUp()
    // Ramp up a fresh endpoint for 60 seconds,
    // increasing weight every second.
    .rampingUpInterval(Duration.ofSeconds(1))
    .numberSteps(60)
    .build();
  • You can now start your server as unhealthy with HealthCheckServiceBuilder.startUnhealthy(). #3260

    • This can be useful when you don't want to handle incoming requests immediately after startup but after some operational steps, such as warming up.
  • You can now add HealthCheckUpdateListeners to HealthCheckService to get notified when healthiness is updated externally, e.g., from a POST request handled by HealthCheckUpdateHandler. #3260

    • This can be useful when used in combination with startUnhealthy() above.
  • You can now specify a custom HTTP header naming rule for HTTP/1 connections, so that you can work around the interoperability issues with old HTTP/1 servers that don't recognize lower-cased headers such as content-length. #3196 #3259

    ClientFactory factory =
    ClientFactory.builder()
    // Send 'Content-Length', not 'content-length'.
    .http1HeaderNaming(http1HeaderNaming.traditional())
    .build();
    WebClient client =
    WebClient.builder()
    .factory(factory)
    .build();
  • You can now close a connection after handling a certain number of requests or after a certain period of time, rather than keeping it open indefinitely. #203 #3267

    • v0.99.7 introduced this feature partially for the server side. We expanded it into both client and server side and also added a way to disconnect after handling a certain number of requests.

      // For server
      Server.builder()
      // A connection will be closed after
      // sending the 10000th request's response.
      .maxNumRequestsPerConnection(10000);

      // For client
      ClientFactory.builder()
      // A connection will be closed after
      // receiving the 2000th request's response.
      .maxNumRequestsPerConnection(2000);
  • You can now turn any service into a transient service by decorating it with TransientHttpService or TransientRpcService. #3221

    • By making a service transient, you can disable metric collection by MetricCollectingService, service logging by LoggingService or access logging by AccessLogWriter.
    • The following example disables metric collection, service and access logs for HealthCheckService:
      Server.builder()
      .serviceUnder(
      "/internal/l7check",
      HealthCheckService.of()
      .decorate(TransientHttpService.newDecorator()))
      .build()
  • You can now disable the check that rejects potentially unsafe TLS cipher suites when configuring server-side TLS, at your own risk. #3292

    Server.builder()
    .tlsAllowUnsafeCiphers()
    .tlsCustomizer(sslCtxBuilder -> {
    sslCtxBuilder.ciphers(Set.of(
    "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA" // Bad cipher suite
    ))
    })
    ...
  • You can now get, set and parse a Content-Disposition header using ContentDisposition. #3253

    // Setting
    HttpHeaders headers = HttpHeaders.of(
    HttpHeaderNames.CONTENT_DISPOSITION,
    ContentDisposition.of("attachment", "fileName", "file.png"));

    // Getting
    ContentDisposition disposition = headers.contentDisposition();
    assert "attachment".equals(disposition.type());
    assert "fileName".equals(disposition.name());
    assert "file.png".equals(disposition.filename());

    // Parsing
    ContentDisposition parsed = ContentDisposition.parse(
    "attachment; name=\"fileName\"; filename=\"file.png\"");
    assert parsed.equals(disposition);
  • You can now serve a file by returning an HttpFile in an annotated service. #3258 #3287

    Server
    .builder()
    .annotatedService(new Object() {
    @Get
    @Head
    @Path("/files/{fileName}")
    HttpFile myFile(@Param String fileName) {
    return HttpFile.of(new File("/var/www/" + fileName));
    }
    })
    .build();
  • You can now invoke Runnable or Callable with RequestContext set in the thread-local storage using RequestContext.run() and RequestContext.run(). #3311

    Runnable task = () -> {
    logger.info("Current request: {}", ServiceRequestContext.current());
    };
    ctx.run(task);
  • You can now concat multiple Publishers into one using StreamMessage.concat(). #3254 #3299

    StreamMessage<String> stream =
    StreamMessage.concat(Mono.just("foo"),
    Mono.just("bar"));

    StepVerifier.create(stream)
    .expectNext("foo")
    .expectNext("bar")
    .expectComplete()
    .verify()

    // You can also concat a `Publisher` of `Publisher`s.
    StreamMessage<String> flat =
    StreamMessage.concat(Flux.just(Mono.just("foo"),
    Mono.just("bar")));
  • You can now create an HttpResponse from a ResponseHeaders and a stream of HttpDatas using HttpResponse.of(). #3089 #3237

    HttpResponse res = HttpResponse.of(
    ResponseHeaders.of(200),
    Flux.just("one\n", "two\n", "three\n")
    .map(HttpData::ofUtf8));
  • You can now check which native transports are available or not and why they are unavailable using TransportType.isAvailable() and TransportType.unavailabilityCause(). #3244

    if (!TransportType.EPOLL.isAvailable()) {
    logger.warn("/dev/epoll support not available:",
    TransportType.EPOLL.unavailabilityCause());
    }
    • We also added methods like serverChannelType(), socketChannelType() and datagramChannelType(), which may be useful when you need to deal with Netty channels.
  • You can now decode the body of HttpRequest or HttpResponse into a StreamMessage using HttpMessage.decode(). #3215

    • This feature can be useful when you need to reactively decode an HTTP body. For example, Armeria will use this feature to handle a multipart body.
  • Added StreamMessage.demand() that returns the remaining demand of the current subscription. #3215

  • Added withTags(Tag...) method to MeterIdPrefixFunction. #3241 #3242

  • You can now register a custom gRPC client stub factory via SPI or GrpcClientOptions.GRPC_CLIENT_STUB_FACTORY option. #3214 #3294

  • You can now keep using the old decoration ordering for route decorators by specifying the -Dcom.linecorp.armeria.useLegacyRouteDecoratorOrdering=true JVM option. #3279

📈 Improvements

  • You can now run your Armeria application even if netty-transport-native-epoll and netty-incubator-transport-native-io_uring are not in the class path. #3244
  • You can now use /dev/epoll transport on WSL 2. #3244
  • You can now use JettyService with Jetty 9.3. We previously supported Jetty 9.4 only. #3288
  • We improved how we shut down a Server in our Spring Boot integration so it's more future-proof. #3266

🛠️ Bug fixes

  • The future returned by StreamWriter.whenConsumed() is now completed immediately even when the current demand is 0. #3213
  • Fixed the violation of Reactive Streams specification when null is published. #3212 #3216
  • TomcatService does not raise a NullPointerException when it stops. #3136 #3218
  • Fixed stability and interoperability issues in Eureka service discovery module. #3235 #3275 #3238 #3301
  • The service name generated from a CGLIB-enhanced class does not have synthetic suffix anymore. #3103 #3240
  • HealthCheckedEndpointGroup now refreshes its Endpoints' status when their weights are changed. #3236
  • You'll no longer see a NullPointerException when retrying a gRPC request. #3251
  • Fixed the incorrect return type of some builder methods. #3220
  • Fixed a UI glitch in the go-to form of DocService client #3261
  • You'll no longer see a harmless Http2Exception: Stream N does not exist warning log message anymore. #3233
    • You'll see it if there was really a protocol violation, however.
  • Fixed an issue where a RequestLog is sometimes not completed immediately. #3291
  • TLS is now configured only once in Spring Boot WebFlux integration. #3266
  • Armeria now finds the gRPC ServiceDescriptor correctly from the code generated with Reactive-gRPC. #3294
  • A request refused with HTTP/2 REFUSED_STREAM error code now fails with an UnprocessedRequestException, so it can be safely retried. #3298
  • THttpService can now handle the Thrift code generated with the fullcamel option. #3272
  • ZooKeeperUpdatingListener does not raise an IllegalStateException anymore even if the given Curator client is started already. #3305

🏚️ Deprecations

☢️ Breaking changes

  • armeria-tomcat9 and armeria-tomcat8 don't depend on tomcat-embed-jasper anymore, which was pulled into runtime dependencies by mistake. #3256
  • You have to recompile your application because we changed the return type of some builder methods. #3220
  • Changes in the API annotated with @UnstableApi:

⛓ Dependencies

  • Bouncy Castle 1.67 → 1.68
  • Brave 5.13.1 → 5.13.3
  • Dropwizard 2.0.16 -> 2.0.18, 1.3.25 → 1.3.29
  • Dropwizard Metrics 4.1.15 → 4.1.17
  • gRPC-Java 1.33.1 → 1.35.0
  • gRPC-Kotlin 0.2.1 → 1.0.0
  • java-jwt 3.11.0 → 3.12.1
  • Jetty 9.4.35 → 9.4.36
  • Micrometer 1.6.1 → 1.6.3
  • Netty 4.1.54 → 4.1.58
    • tcnative 2.0.34 → 2.0.36
    • io_uring 0.0.1 → 0.0.3
      • io_uring is now an optional dependency.
  • Protobuf Jackson 1.1.0 → 1.2.0
  • Project Reactor 3.4.0 → 3.4.2
  • RxJava 3.0.7 → 3.0.9
  • ScalaPB runtime 0.10.8 → 0.10.10
  • ScalaPB json4s 0.10.1 → 0.10.3
  • Spring Boot 2.4.0 → 2.4.1
  • Tomcat 9.0.40 → 9.0.41, 8.5.58 → 8.5.61
    • We don't depend on tomcat-embed-jasper anymore. #3256

🙇 Thank you

This release was possible thanks to the following contributors who shared their brilliant ideas and awesome pull requests:

@delegacy@KarboniteKream@tobias-@codefromthecrypt@anuraaga@minwoox@okue@trustin@ghkim3221@ikhoon@max904-github@tumile@eisig@renaudb@arhont375@heowc@snaiper80@wickedev@syleeeee@ks-kim@jongmin92@eugene70@kojilin@richieyan@policeman-kh@imasahiro@progresivoJS@JunoJunho

Like Armeria?
Star us ⭐️

×