1.23.0 release notes
7th April 2023
🌸 Highlights
DocService
now supports auto-completion for gRPC and Thrift services. #4516- Check out this awesome demo video by @Dogacel to learn more.
🌟 New features
- You can now write a circuit breaker or retry rule that makes a decision based on gRPC trailers. #4496 #4535
final RetryRule rule =
RetryRule.builder()
.onGrpcTrailers((ctx, trailers) -> trailers.containsInt(
"grpc-status", 13)) // Retry for internal error.
.thenBackoff(backoff);
Unhandled exceptions are logged periodically out of the box. #4324 #4687
- You can change the interval of the periodic logging
using
ServerBuilder.unhandledExceptionsReportIntervalMillis()
.
ServerBuilder sb = ... // Log every 10 seconds. sb.unhandledExceptionsReportIntervalMillis(10000L);
- You can change the interval of the periodic logging
using
You can now use Spring Boot 3 with Armeria. #4574
- Use
com.linecorp.armeria:armeria-spring-boot3-autoconfigure:1.23.0
dependency for the integration.
- Use
You can now disconnect connections gracefully using
ClientRequestContext.initiateConnectionShutdown()
. #4555 #4708ClientRequestContext ctx = ... // The connection will be gracefully shut down after a request is finished. ctx.initiateConnectionShutdown();
You can now use
ServiceErrorHandler
to configure the per-service or per-virtual host error handlers. #3421 #4716ServiceErrorHandler handler = (ctx, cause) -> { if (cause instanceof IllegalArgumentException) { return HttpResponse.of(HttpStatus.BAD_REQUEST); } // Return null to let the default error handler handle the exception. return null; }; ServerBuilder sb = ... sb.route() .get("/foo") .errorHandler(handler) ... .build();
You can now handle an HTTP/1 request with an absolute URI using
ServerBuilder.absoluteUriTransformer()
. #4681 #4726ServerBuilder sb = ... sb.absoluteUriTransformer(absoluteUri -> { // https://foo.com/bar -> /bar return absoluteUri.replaceFirst("^https://\\.foo\\.com/", "/"); // Or store the original URI in a query and use it in the proxy service. // return "/proxy?uri=" + URLEncoder.encode(absoluteUri); });
You can now listen to the result of DNS queries using
DnsQueryListener
. #4690 #4715DnsAddressEndpointGroup .builder("foo.com") .addDnsQueryListeners(new DnsQueryListener() { @Override void onSuccess(...) {...} @Override public void onFailure(...) { logger.warn("DNS query failed"); } }) .build();
You can now create a
BlockingTaskExecutor
by wrapping aScheduledExecutorService
. #4760- Additionally, you can create context-aware or context-propagating
Executor
andBlockingTaskExecutor
.
ScheduledExecutorService executorService = ... // Wrap a ScheduledExecutorService. BlockingTaskExecutor blockingTaskExecutor = BlockingTaskExecutor.of(executorService); RequestContext ctx = ... BlockingTaskExecutor blockingTaskExecutor = ... // context-aware ContextAwareBlockingTaskExecutor.of(context, executor); // context-propagating RequestContext.makeContextPropagating(blockingTaskExecutor);
- Additionally, you can create context-aware or context-propagating
You can now use
StreamMessage.of()
to convert anInputStream
toStreamMessage
. #3937 #4703InputStream in = ... // The input stream is divided into chunks of 8192 bytes by default. ByteStreamMessage stream = StreamMessage.of(in);
You can now use
StreamMessage.streaming()
to createStreamWriter
for writing streaming messages. #4253 #4696StreamWriter<String> writer = StreamMessage.streaming(); writer.write("foo"); writer.write("bar"); writer.close(); // Subscribe to the writer. writer.subscribe(...);
You can now generate a
RequestId
using the information ofRoutingContext
. #4362 #4691- Additionally, the request ID generator can be configured per-service or per-virtual host. #4730 #4752
ServerBuilder sb = ... sb.requestIdGenerator((routingCtx, req) -> { // Create a request ID from the trace ID of an OpenTelemetry headers. return RequestId.of( requestId(routingCtx.headers().get("traceparent"))); });
You can now collect metrics of open and closed client connections using
ConnectionPoolListener.metricCollecting()
. #4685 #4686MeterRegistry registry = ... ConnectionPoolListener listener = ConnectionPoolListener.metricCollecting(registry); ClientFactory factory = ClientFactory.builder() .connectionPoolListener(listener) .build();
ClientRequestContext.authority()
can now be used to get the authority that will be sent to the server. #4697ClientRequestContext ctx = ... String authority = ctx.authority();
You can now use
ManagementServerProperties
to configure the management server when using Spring integration. #4560 #4574armeria: ports: - port: 8080 # Use a different port with a custom base path: management: server: port: 8443 base-path: /foo endpoints: web: exposure: include: health, loggers, prometheus
You can now execute gRPC
ServerInterceptor
asynchronously in Kotlin usingCoroutineServerInterceptor
. #4669 #4724class AuthInterceptor : CoroutineServerInterceptor { private val authorizer = ... override suspend fun <ReqT, RespT> suspendedInterceptCall( call: ServerCall<ReqT, RespT>, headers: Metadata, next: ServerCallHandler<ReqT, RespT> ): ServerCall.Listener<ReqT> { val result = authorizer.authorize(ServiceRequestContext.current(), headers).await() if (result) { return next.startCall(call, headers) } else { throw AnticipatedException("Invalid access") } } }
📈 Improvements
- The debug page of
DocService
is now a pop-up for easier access. #4599 - You can now use
Connection: close
header to close a connection after sending or receiving a response. #4131 #4454 #4471 #4531 - Armeria now exports the gauge metrics of
CommonPools.workerGroup()
. #4675 #4750- The metric names:
armeria.netty.common.event.loop.workers
armeria.netty.common.event.loop.pending.tasks
- The metric names:
- If the mapping for a HEAD request is not found, the request is rerouted to the service bound to the GET method on the same path. #4038 #4706
🛠️ Bug fixes
- Armeria client now handles the fragment part of URI consistently. #4789
- The
RetryConfig
fromRetryConfigMapping
is retrieved only once for a retry session and reused. #4753 #4778 - Fixed a memory leak of the response body for HEAD requests. #4771
- A
#
character in a request path is now always percent-encoded on the server side. #4751 #4765 Self-suppression
exception is not raised anymore in HTTP/1.1 encoder. #4749 #4758ConcurrentModificationException
is not raised anymore when usingAsyncServerInterceptor
. #4729RequestHeaders.path()
now returns percent-encoded value likeRequestContext.path()
does when used in an Armeria server. #4694 #4721ByteStreamMessage.range()
of a path stream message now handles the overflow of the range correctly. #4705- The authority header is now correctly set for retried requests. #4697
- You no longer see
DnsTimeoutException
when an IPv6 network interface is enabled. #4695 - No more deadlocks occur in Thrift 0.14 or older when loading
DocService
. #4688 HttpStatusException
is not raised anymore when there is no matching route for a request. #4548 #4552- Normal 404 Not Found response is sent instead.
- A gRPC call is now correctly terminated with
Status.INTERLAL
when an invalid response is received. #4808 - A server-side
RequestLog
is completed correctly when an empty response is sent. #4807
🏚️ Deprecations
ClientFactory.setMeterRegistry()
is now deprecated in favor ofFlagsProvider.meterRegistry()
. #4785
☢️ Breaking changes
Spring Boot 1 integration is no longer supported. #4651 #4787
The type of
blockingTaskExecutor
property such asServiceConfig.blockingTaskExecutor()
is changed fromScheduledExecutorService
toBlockingTaskExecutor
. #4760- Simply recompiling your code should be enough in most cases because
BlockingTaskExecutor
is aScheduledExecutorService
.
- Simply recompiling your code should be enough in most cases because
The return types of
makeContextAware
methods onRequestContext
are changed to the context-aware types. #4760makeContextAware(Executor)
returnsContextAwareExecutor
instead ofExecutor
.makeContextAware(ScheduledExecutorService)
returnsContextAwareScheduledExecutorService
instead ofScheduledExecutorService
.
The signatures of
UserClient.execute()
have been changed. #4789- It now contains the newly added
RequestTarget
as a parameter instead of thepath
,query
, andfragment
.
- It now contains the newly added
The names of path cache meters have been changed. #4789
- The old meter name:
armeria.server.parsed.path.cache
- New meter names:
armeria.path.cache{type=client}
armeria.path.cache{type=server}
- The old meter name:
Armeria client doesn't normalize consecutive slashes (e.g.
foo//bar
) in a client request path anymore. #4789
⛓ Dependencies
- Brotli4j 1.9.0 → 1.11.0
- Dropwizard 2.1.4 → 2.1.5
- Dropwizard Metrics 4.2.15 → 4.2.18
- fastutil 8.5.11 → 8.5.12
- GraphQL Kotlin 6.3.5 → 6.4.0
- java-jwt 4.2.2 → 4.3.0
- Micrometer 1.10.3 → 1.10.5
- Netty 4.1.87.Final → 4.1.91.Final
- Reactor Kotlin 1.2.1 → 1.2.2
- Sangria 3.5.0 → 3.5.3
- Spring 5.3.25 → 6.0.6
- Spring Boot
- 3.0.5
- 2.7.8 → 2.7.10