You're seeing the release note of an old version. Check out the latest release note.
v1.17.0
July 6, 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
🙇 Thank you
This release was possible thanks to the following contributors who shared their brilliant ideas and awesome pull requests:




















