1.17.0 release notes
6th July 2022
🌟 New features
You can now easily send and receive RESTful APIs using
RestClient. #4263- Java
RestClient restClient = RestClient.of("..."); CompletableFuture<ResponseEntity<Customer>> response = restClient.get("/api/v1/customers/{customerId}") .pathParam("customerId", "0000001") .execute(Customer.class); - Kotlin
val restClient: RestClient = RestClient.of("..."); val response: ResponseEntity<Customer> = restClient .get("/api/v1/customers/{customerId}") .pathParam("customerId", "0000001") .execute<Customer>() // a suspend function - Scala
val restClient: ScalaRestClient = ScalaRestClient("...") val response: Future[ResponseEntity[Result]] = restClient.post("/api/v1/customers") .contentJson(new Customer(...)) .execute[Result]()
- Java
You can now configure a timeout for an
Endpointselection of anDynamicEndpointGroup. #4246DnsAddressEndpointGroup delegate = DnsAddressEndpointGroup.of(...); HealthCheckedEndpointGroup healthGroup = HealthCheckedEndpointGroup .builder(delegate, "/health") .selectionTimeout(Duration.ofSeconds(10)) // 👈👈👈 .build();You can now inject dependencies in an annotation using
DependencyInjector. #4006 #4202// Inject authClient that is needed to create the AuthDecorator. WebClient authClient = ... DependencyInjector injector = DependencyInjector.ofSingletons(new AuthDecorator(authClient)); serverBuilder.dependencyInjector(dependencyInjector, true); // An annotated service that uses AuthDecorator. @Get("/foo") @Decorator(AuthDecorator.class) public FooResponse foo(FooRequest req) { // Authrorized request. ... } // authClient is injected. class AuthDecorator implements DecoratingHttpServiceFunction { AuthDecorator(WebClient authClient) { ... } @Override public HttpResponse serve(HttpService delegate, ServiceRequestContext ctx, HttpRequest req) throws Exception { // Authorize the request. ... } }- Set
armeria.enable-auto-injectiontotrueto applySpringDependencyInjectorautomatically when using Spring Boot integration.// in application.yml armeria: ports: ... enable-auto-injection: true // 👈👈👈
- Set
You can now customize the length of string fields or container fields of Thrift messages using
THttpServiceBuilderorThriftClientBuilder. #4024 #4226THttpService.builder() .addService(new MyThriftService()) .maxRequestStringLength(MAX_STRING_LENGTH) .maxRequestContainerLength(MAX_CONTAINER_LENGTH) .build(); ThriftClients.builder("https://my.server.com") .path("/thrift") .maxResponseStringLength(MAX_STRING_LENGTH) .maxResponseContainerLength(MAX_CONTAINER_LENGTH) .build(HelloService.AsyncIface.class);You can now set attributes to an
EndpointusingAttributesandAttributesBuilder. #4241AttributeKey<String> region = AttributeKey.valueOf(MyAttrs.class, "region"); Attributes attrs = Attributes .builder() .set(region, "us-west-1") .build(); Endpoint endpointA = ...; Endpoint endpointB = endpointA.withAttrs(attrs); assert endpointB.attr(region).equals("us-west-1"); assert !endpointA.equals(endpointB)You can now use
MultipartFileto get the actual filename of an uploaded file throughmultipart/form-data. #4262@Consumes(MediaTypeNames.MULTIPART_FORM_DATA) @Post("/upload") public HttpResponse upload(MultipartFile multipartFile) { // The name parameter of the "content-disposition" header String name = multipartFile.name(); // The filename parameter of the "content-disposition" header String filename = multipartFile.filename(); // The file that stores the multipart content. File file = multipartFile.file(); ... }You can now read a range of bytes using
ByteStreamMessage. #4058Path path = ...; // Fluently build a `StreamMessage` for a `Path`. ByteStreamMessage pathContent = StreamMessage.builder(path) .bufferSize(1024) .alloc(alloc) .executor(executor) .build(); // Slice a range of a file data ByteStreamMessage partialContent = pathContent.range(100, 200);You can now map a specific exception to a
LogLevelinLoggingServiceandLoggingClient. #3400 #4090LoggingService.builder() .responseLogLevel(IllegalStateException.class, LogLevel.WARN) .responseLogLevel(IllegalArgumentException.class, LogLevel.ERROR) ...You can now map a specific exception to an
HttpResponse. #4279 #4283HttpResponse recovered = response.recover(IllegalStateException.class, cause -> HttpResponse.of("Fallback"));You can now enable automatic compression for a gRPC response. #4258 #4266
GrpcService.builder() .addService(new MyGrpcService()) .autoCompression(true) .build();- This only works when the client sends a
grpc-accept-encodingheader and theCompressorRegistryhas the compressor.
- This only works when the client sends a
You can now add a hook to
ServerandClientFactorythat is called when the JVM shuts down. #4015 #4043server.closeOnJvmShutdown(() -> { System.err.println("Server is stopping soon."); }).thenRun(() -> { System.err.println("Server has stopped."); });You can now customize internal Netty
ChannelPipelineusingClientFactoryOptions.CHANNEL_PIPELINE_CUSTOMIZER. #3907 #4260- This is an advanced feature and not recommended to use if you are not familiar with Armeria and Netty internals.
Kotlin
- You can now use the Kotlin nullable type (
?) to indicate a nullable parameter in an annotated service. #4144 #4225serverBuilder.apply { annotatedService(object { // Both `/foo` and `/foo?a=bar` are allowed. @Get("/foo") fun foo(@Param a: String?) = // 👈👈👈 HttpResponse.of("a: $a") }) }
📈 Improvements
- The
EndpointSelectionTimeoutExceptionis thrown if timeout occurred when selecting anEndpointfrom anEndpointGroup. #4269 - You can now set multiple
AccessLogWriterstoServerBuilderandServiceBindingBuilder. #4280 - You can now give a performance optimization hint to a client by setting an
ExchangeTypeto aRequestOptions. TheExchangeTypeis configured automatically for:- A gRPC client
- A Thrift client
- A
WebClientwhen it's used withResponseAs. #4236
- Better performance for unary gRPC call. #4192
- Annotated services perform better on unary responses. #4177
🛠️ Bug fixes
- You can now run
ServerInterceptorsin a blocking task executor whenGrpcServiceBuilder.useBlockingTaskExecutor()is enabled. #4275 #4331 Multiparts.getBoundary()now throws anIllegalStateExceptioninstead ofNullPointerExceptionwhen a boundary is missing. #4193 #4325- Armeria retrofit streaming mode now requests data to upstream when it does not have one. #4319
- You no longer see deep recursive calls in
PublisherBasedStreamMessage. #4298 - You no longer see
IllegalArgumentExceptionindicating the prohibited character of a header name in an environment where Spring integration module and Netty client are used together. (e.g. Spring Cloud Gateway) #4293 ServerCacheControl.DISABLEDandServerCacheControl.REVALIDATEDnow havemax-ageset to0for better compatibility with old browsers. #4290 #4291- You no longer see a
NullPointerExceptionfromFlagswhenRequestContextExportingAppenderis used. #4285 - You no longer see the
InputStreamreturned byStreamMessage.toInputStream()blocks indefinitely in a certain situation. #4268 #4271 - A
WriteTimeoutExceptionis now thrown when a header only request times out. #4255 #4259 - The request now completes even when the content sanitizer throws an exception. #4248
- A proxy client doesn't resolve a hostname anymore. #4244 #4245
- The Spring Boot integration module doesn't create a new
WebClientanymore every time it's injected. #4240 - Armeria server sends now 400 Bad Request when an HTTP/2 upgrade request contains invalid headers. #4016 #4224
EurekaUpdatingListenernow registers itself with theinstanceIdwhich ishostname:appname:port, so it doesn't cause a conflict when more than one app with the same name are running in the same host. #4223- A ramping up
EndpointSelectionStrategynow works correctly in combination withHealthCheckedEndpointGroup. #4221 - The information of a thrown exception in gRPC service is now propagated to the client via the
grpc-status-details-binheader. #4203 #4204
🏚️ Deprecations
AbstractEndpointSelector.select(ClientRequestContext,ScheduledExecutorService,long)is deprecated in favor ofAbstractEndpointSelector.select()?full. #4246ThriftSerializationFormats.protocolFactory(SerializationFormat)is deprecated in favor ofThriftSerializationFormats.protocolFactory(SerializationFormat,int,int). #4226BINARY,COMPACT,JSON,TEXT, andTEXT_NAMED_ENUMinThriftProtocolFactoriesare deprecated in favor of corresponding factory methods. #4226StreamMessage.of(Path,int),StreamMessage.of(Path,ByteBufAllocator,int)andStreamMessage.of(Path,ExecutorService,ByteBufAllocator,int)are deprecated in favor ofStreamMessage.builder(Path). #4058ServiceConfig.shutdownBlockingTaskExecutorOnStop(),ServiceConfig.shutdownAccessLogWriterOnStop(),ServerConfig.shutdownBlockingTaskExecutorOnStop()andServerConfig.shutdownWorkerGroupOnStop()are not used anymore. #4280
☢️ Breaking changes
ByteStreamMessageis returned instead ofStreamMessage<HttpData>. #4058HttpService.exchangeType(RequestHeaders, Route)is nowHttpService.exchangeType(). #4177
⛓ Dependencies
- Brave 5.13.8 → 5.13.9
- Bucket4J 7.4.0 → 7.5.0
- Dropwizard metrics 4.2.9 → 4.2.10
- GraphQL 1.8.0 → 1.8.2
- gRPC-Java 1.45.1 → 1.47.0
- gRPC-Kotlin 1.2.1 → 1.3.0
- Jackson 2.13.2.1 → 2.13.3
- Kotlin 1.6.20 → 1.7.0
- Kotlinx 1.6.1 → 1.6.3
- Micrometer 1.8.5 → 1.9.1
- Netty 4.1.76.Final → 4.1.78.Final
- Netty incubator 0.0.13.Final → 0.0.14.Final
- Project Reactor 3.4.17 → 3.4.19
- Prometheus Simpleclient 0.15.0 → 0.16.0
- Reactive Streams 1.0.3 → 1.0.4
- RxJava 3.1.4 → 3.1.5
- ScalaPB 0.11.10 → 0.11.11
- Spring 5.3.19 → 5.3.21
- Spring Boot 2.6.6 → 2.7.1