1.6.0 release notes
7th April 2021
🌟 New features
You can now send and handle multipart requests and responses. #253 #3327
// Send a multipart message. WebClient client = WebClient.of("http://example.com/"); BodyPart part1 = BodyPart.of(ContentDisposition.of("form-data", "username"), "Armeria"); BodyPart part2 = BodyPart.of(ContentDisposition.of("form-data", "password"), "mypassword"); Multipart multipart = Multipart.of(part1, part2); client.execute(multipart.toHttpRequest("/login")); // Handle a multipart message. Server.builder() .service("/login", (ctx, req) -> { Multipart multipart = Multipart.from(req); multipart.aggregate().thenApply(aggregated -> { assert aggregated.field("username").contentUtf8().equals("Armeria"); assert aggregated.field("password").contentUtf8().equals("mypassword"); ... })); });
Armeria now provides various useful extensions and conversions for Scala. #3395
- See Scala integration for the full features.
You can now create a
StreamMessage
from aPath
or aFile
. #3344Path path = Paths.get("..."); StreamMessage<HttpData> publisher = StreamMessage.of(path);
You can now filter or transform values of a
StreamMessage
usingStreamMessage.filter()
orStreamMessage.map()
. #3351// Filter. StreamMessage<Integer> source = StreamMessage.of(1, 2, 3, 4, 5); StreamMessage<Integer> even = source.filter(x -> x % 2 == 0); // Transform. StreamMessage<Integer> source = StreamMessage.of(1, 2, 3, 4, 5); StreamMessage<Boolean> isEven = source.map(x -> x % 2 == 0);
You can now send a different response depending on the exception. #3209 #3413
Server.builder().exceptionHandler((ctx, cause) -> { if (cause instanceof RequestTimeoutException) { // The request timed out! return AggregatedHttpResponse.of(...); } // Return null to let ExceptionHandler.ofDefault() convert the exception. return null; })...
You can now convert an exception into an
RpcResponse
inTHttpService
. #3349 #3383THttpService.builder() .addService("hello", helloService) .exceptionHandler((ctx, cause) -> { if (cause instanceof IllegalArgumentException) { return RpcResponse.of(new CustomizedException("Bad Request!")); } ... })
You can now set a response timeout and attributes using
WebClientRequestPreparation
. #3347 #3357WebClient client = ...; client.prepare() .get("/my-service") .responseTimeout(Duration.ofSeconds(3)) .attr(USER_ID, userId) .attr(USER_SECRET, secret) .execute();
You can now specify additional error details to a gRPC response when an exception is raised in a gRPC service. #3307 #3329
GrpcService.builder() .exceptionMapping((cause, metadata) -> { if (throwable instanceof AuthError) { metadata.put(KEY, toMetadata(cause)) return Status.UNAUTHENTICATED.withCause(cause); } ... })
You can now customize the success condition of a metric. #3404 #3410
MetricCollectingService.builder(...) .successFunction((context, log) -> { final int statusCode = log.responseHeaders().status().code(); return (statusCode >= 200 && statusCode < 400) || statusCode == 404; });
You can now fluently build a
DecodingClient
using theDecodingClientBuilder
. #3348 #3372DecodingClient.builder() .autoFillAcceptEncoding(false) .strictContentEncoding(true) .newDecorator();
You can now fluently build an
HttpRequest
with aPublisher<HttpData>
. #3343StreamMessage<HttpData> publisher = StreamMessage.of(...); HttpRequest.builder() .method(HttpMethod.GET) .path("/") .content(MediaType.PLAIN_TEXT_UTF_8, publisher) .build();
You can now convert a stream of Protobuf Messages into JSON Text sequences using an annotated service. #3394
@Get("/items") @ProducesJsonSequences public Publisher<MyProtobufMessage> protobufJsonSeqPublisher() { return StreamMessage.of(MyProtobufMessage.newBuilder()...build(), MyProtobufMessage.newBuilder()...build()); } @Get("/items") @ProducesJsonSequences public Stream<MyProtobufMessage> protobufJsonSeqPublisher() { return Stream.of(MyProtobufMessage.newBuilder()...build(), MyProtobufMessage.newBuilder()...build()); }
You can now customize the default service name of a
RequestLog
. #3232 #3366Server.builder() .defaultServiceNaming(ctx -> { final ServiceConfig config = ctx.config(); return config.route().patternString(); }); // For a specific service. Server.builder() .route().path("/") .defaultServiceNaming(...) ...
You can now check if the current request is matched by any routes or not. #3365 #3378
ServerBuilder sb = ... sb.decorator((delegate, ctx, req) -> { if (ctx.config().route().isFallback()) { // This request is not matched any routes. } return delegate.serve(ctx, req); });
BraveService
does not trace requests forTransientService
such asHealthCheckService
anymore. #3382- You should specify
TransientServiceOption.WITH_TRACING
if you want to trace them.
HealthCheckService.builder() .transientServiceOptions(WITH_TRACING) ... .build();
- You should specify
You can now clean up resources by overriding
FilteredStreamMessage.onCancellation()
whenSubscription.cancel()
is called. #3375new MyFilteredHttpResponse(res) { ... @Override protected void onCancellation(Subscriber<? super U> subscriber) { // Clean up resources. } }
You can now easily set 'cookie' or 'set-cookie' headers. #3388 #3391
Cookie cookie = Cookie.of("cookie", "value"); RequestHeaders headers = RequestHeaders.builder(HttpMethod.GET, "/") .cookie(cookie) .build(); assert headers.cookies().equals(Cookies.of(cookie)); Cookie setCookie1 = ... Cookie setCookie2 = ... ResponseHeaders headers = ResponseHeaders.builder(HttpStatus.OK) .cookies(setCookie1, setCookie2) .build(); assert headers.cookies().equals(Cookies.of(setCookie1, setCookie2));
You can now use
req.root_id
BuiltInProperty
to log the ID ofRequestContext.root()
. #3429 #3433You can now use Thrift 0.14.0 with the new
armeria-thrift0.14
module. #3470 #3422You can now use OAuth 2.0 related features with the new
armeria-oauth2
module. #2268 #2840- OAuth 2.0 server-side token authorization using
OAuth2TokenIntrospectionAuthorizer
- Client credential grants using
OAuth2ClientCredentialsGrant
- Resource Owner Password Credentials Grant
using
OAuth2ResourceOwnerPasswordCredentialsGrant
- OAuth 2.0 Token Revocation using
TokenRevocation
- OAuth 2.0 server-side token authorization using
You can now use Jakarta RESTful Web Services on top of Armeria with the
armeria-resteasy
module. #3285 #3296
📈 Improvements
🛠️ Bug fixes
- You no longer see
NullPointerException
inHttpResponseDecoder
. #3036 #3407 - You no longer see
405 Method Not Allowed
when the exact and param path are defined with different HTTP methods. #3330 #3340 - You no longer see
Address family not supported by protocol
orConnection refused
error anymore on certain machines with IPv6 enabled. #3425 - The Unicode characters like emojis in a JSON response are now rendered correctly
in
DocService
. #3396 - You no longer see the wrong response body when the payload violates the protocol or is too large. #3419
- You can now use the Thrift client and service that is generated by
java:fullcamel
option of Thrift compiler. #3269 #3360 #3369 SmartLifecycle
for Armeria server graceful shutdown is only created when the Armeria server is created by Spring integration. #3300- You can now use Sealed oneofs message from ScalaPB with JSON for gRPC and annotated services. #3342 #3394
- You no longer see
CancellationException
when anHttpResponse
is fully consumed on server-side. #3387 - You no longer see
ClosedSteamException
in Jetty service when it fails to write to anHttpResponse
.EofException
is raised instead. #3412 - You no longer see
IllegalStateException
whenRequestContextHooks
is enabled. #3441 #3442 ArmeriaServerHttpResponse
of Spring Webflux integration now correctly propagates Reactor'sContext
. #3439 #3443
☢️ Breaking changes
ServiceRequestContext
is added to the parameter ofhandleMessage
inAbstractUnaryGrpcService
. #3403- The return type is also changed to
CompletionStage
. #3409
protected abstract CompletionStage<byte[]> handleMessage( ServiceRequestContext ctx, byte[] message); protected final CompletionStage<ByteBuf> handleMessage( ServiceRequestContext ctx, ByteBuf message) {...}
- The return type is also changed to
GrpcStatusFunction
now extendsBiFunction<Throwable, Metadata, Status>
. #3329The
numberSteps
method inWeightRampingUpStrategyBuilder
is now changed tototalSteps
. #3377
⛓ Dependencies
- Bucket4J 4.10.0 → 6.2.0
- Curator 4.3.0 → 5.1.0
- ZooKeeper 3.5.8 → 3.6.2
- Dropwizard 2.0.18 → 2.0.20
- Dropwizard Metrics 4.1.17 → 4.1.18
- gRPC 1.35.0 → 1.36.1
- Jackson 2.12.0 → 2.12.2
- java-jwt 3.12.1 → 3.14.0
- Jetty 9.4.36 → 9.4.39
- Micrometer 1.6.3 → 1.6.5
- Netty 4.1.58 → 4.1.63
- Netty TCNative BoringSSL 2.0.36 → 2.0.38
- Netty io_uring transport 0.0.3 → 0.0.5
- OpenSAML 3.4.5 → 3.4.6
- Shibboleth java-support 7.5.1 → 7.5.2
- Reactor 3.4.2 → 3.4.4
- Reactor Kotlin extensions 1.1.2 → 1.1.3
- RxJava 3.0.9 → 3.0.11, 2.2.20 → 2.2.21
- ScalaPb 0.10.10 → 0.11.0
- Spring Boot 2.4.2 → 2.4.4
- Tomcat 9.0.41 → 9.0.44, 8.5.61 → 8.5.64