0.98.0 release notes
12th February 2020
🎬 Before we begin
This release contains more breaking changes and deprecations than usual, which were necessary for the preparation of 1.0 release and the long term evolution of our API after 1.0. We'd like to apologize for any inconveniences caused by the breaking changes. Please don't forget we're always here for you and do let us know if you have any trouble upgrading, so we can help you!
🌟 New features
- Since this release, in preparation of 1.0 release, we annotated classes and packages which may have a chance of breaking change with the
@UnstableApi
annotation. #2445 - The attribute access API of
RequestContext
has been revamped for simplicity. #2322AttributeKey<String> MY_ATTR = AttributeKey.valueOf("MY_ATTR"); RequestContext ctx = ...; ctx.setAttr(MY_ATTR, "foo"); assert "foo".equals(ctx.attr(MY_ATTR));
- When a
RequestContext
is derived or inherited from anotherRequestContext
, the parent context's attributes are now visible from the derived or inherited context: #2322AttributeKey<String> MY_ATTR = AttributeKey.valueOf("MY_ATTR"); ServiceRequestContext parentCtx = ...; ClientRequestContext childCtx = ...; assert childCtx.root() == parentCtx; parentCtx.setAttr(MY_ATTR, "foo"); // Root context's attributes are visible from its children. assert "foo".equals(childCtx.attr(MY_ATTR)); // Not visible anymore if the attribute is overwritten. childCtx.setAttr(MY_ATTR, "bar"); assert "foo".equals(parentCtx.attr(MY_ATTR)); assert "bar".equals(childCtx.attr(MY_ATTR));
{Client,Service}RequestContext
now provides more useful ways to schedule request or response timeout: #2343Server.builder() .service("/svc", myService.decorate((delegate, ctx, req) -> { if (req.headers().contains("x-extend-timeout")) { // Extend the timeout by 10 seconds. ctx.extendRequestTimeout(Duration.ofSeconds(10)); } return delegate.serve(ctx, req); }));
RequestLog
API has been revamped for safety and usability. #2342RequestContext ctx = ...; // Asynchronous retrieval: ctx.log().whenRequestComplete().thenAccept(log -> { // Can't access response properties at compilation level. assert log instanceof RequestOnlyLog; System.err.println(log.toStringRequestOnly()); }) ctx.log().whenComplete().thenAccept(log -> { // Can access all properties. assert log instanceof RequestLog; System.err.println(log.toStringResponseOnly()); }); ctx.log().whenAvailable(RequestLogProperty.SESSION).thenAccept(log -> { // Advanced use case: // Raises an exception if accessing an unavailable property. System.err.println(log.channel()); }); // Accessing a property ensuring availability: ctx.log().ensureRequestComplete().requestEndTimeNanos(); ctx.log().ensureComplete().responseEndTimeNanos(); ctx.log().ensureAvailable(RequestLogProperty.RESPONSE_CONTENT) .responseContent();
RequestLog
also has a new propertyNAME
, which can be used as a method name in an RPC call, a span name in distributed tracing or any other human-readable string that can be used for identifying a request. #2413- Added a new immutable API for encoding and decoding HTTP query strings: #2307
QueryParams params = QueryParams.builder() .add("foo", "1") .add("bar", "2") .build(); // Encoding String queryString = params.toQueryString(); assert "foo=1&bar=2".equals(queryString); // Decoding QueryParams decodedParams = QueryParams.fromQueryString("foo=1&bar=2"); assert decodedParams.equals(params); // Mutation QueryParams newParams = params.toBuilder() .add("baz", "3") .build(); assert "foo=1&bar=2&baz=3".equals(newParams.toQueryString());
- Added various convenient boolean getter methods to
HttpStatus
: #2435assert HttpStatus.CONTINUE.isInformational(); assert HttpStatus.OK.isSuccess(); assert HttpStatus.FOUND.isRedirect(); assert HttpStatus.BAD_REQUEST.isClientError(); assert HttpStatus.SERVICE_UNAVAILABLE.isServerError(); // No need to write like this anymore assert HttpStatus.OK.codeClass() == HttpStatusClass.SUCCESS;
- Added
MediaTypeNames
which providesString
version of well knownMediaType
s, which is useful when writing an annotated service: #2438class MyAnnotatedService { @Get("/download/zip") @Produces(MediaTypeNames.ZIP) HttpResponse downloadArchive() { ... } }
- You can now add
{Request,Response}ConverterFunction
andExceptionHandlerFunction
to all annotated services in yourServer
easily. #2316Server.builder() .annotatedService("/users", userService) .annotatedService("/posts", postService) .annotatedService("/files", fileService) // Applies all extensions to all 3 annotated services. .annotatedServiceExtensions( commonRequestConverters, commonResponseConverters, commonExceptionHandlers) .build();
- You can now require a route to have HTTP headers and/or query parameters: #2102
Server.builder() // Route to 'myService' only when: // - 'x-must-exist' header exists, // - and 'bar' query parameter exists. .route().get("/foo") .matchesHeaders("x-must-exist") .matchesParams("bar") .build(myService)
- You can now customize the
SslContext
created fromServerBuilder.tlsSelfSigned()
orVirtualHost.tlsSelfSigned()
: #2340Server.builder() .tlsSelfSigned() .tlsCustomizer(sslCtxBuilder -> { sslCtxBuilder.ciphers(...); })
- You can now close an
EndpointGroup
asynchronously: #2430DnsAddressEndpointGroup group = DnsAddressEndpointGroup.of("cluster.com", 8080); group.whenClosed().thenRun(() -> { System.err.println("Closed!"); }); group.closeAsync();
- You do not need to register your
EndpointGroup
toEndpointGroupRegistry
for client-side load balancing. Just specify it when you build a client: #2381EndpointGroup group = EndpointGroup.of( Endpoint.of("node1.cluster.com"), Endpoint.of("node2.cluster.com")); // Thrift HelloService.Iface client = Clients.builder("tbinary+http", group) .path("/api/thrift/hello") .build(HelloService.Iface.class); // gRPC HelloServiceBlockingStub client = Clients.builder("gproto+http", group) .build(HelloServiceBlockingStub.class); // Web WebClient client = WebClient.of(SessionProtocol.HTTP, group);
- You can now limit the number of endpoints in
HealthCheckedEndpointGroup
, which is very useful when there are many candidate endpoints in the group but you want to send requests to only a few of them, to avoid unnecessarily large number of outbound connections: #2177HealthCheckedEndpointGroup group = HealthCheckedEndpointGroup.builder(delegateGroup, "/health") .maxEndpointCount(3) .build();
- You can now capture the
ClientRequestContext
of your client call withClientRequestContextCaptor
: #2344WebClient client = WebClient.of("http://foo.com/"); try (ClientRequestContextCaptor ctxCaptor = Clients.newContextCaptor()) { HttpResponse res = client.get("/"); ClientRequestContext ctx = ctxCaptor.get(); ... }
- Added
ClientFactory.insecure()
andClientFactoryBuilder.tlsNoVerify()
to simplify testing SSL/TLS connections with self-signed certificates: #2340// Using the default insecure factory WebClient.builder("https://127.0.0.1:8443") .factory(ClientFactory.insecure()) .build(); // Using a custom insecure factory WebClient.builder("https://127.0.0.1:8443") .factory(ClientFactory.builder() .tlsNoVerify() ... .build()) .build();
ClientFactory
is now part ofClientOptions
for easier creation of derived clients. #2384ClientFactory factory = ...; WebClient client = WebClient.builder(...) .factory(factory) .build(); WebClient clientCopy = WebClient.builder(...) .options(client.options()) .build(); // Note that ClientFactory is copied as well. assert client.factory() == clientCopy.factory();
RetrofitMeterIdPrefixFunction
is now capable of adding HTTP method and request path pattern if you specify a Retrofit service class: #2356- New module
armeria-dropwizard
provides the integration with Dropwizard, which allows you to leverage the best of the both worlds. #2236- See Using Armeria with Dropwizard for more information.
- Special thanks to @cricket007 who volunteered for this.
- You can now customize
DocService
when integrating with Spring framework by injectingDocServiceConfigurator
: #2327@Bean public DocServiceConfigurator docServiceConfigurator() { // Exclude all Thrift services from DocService. return docServiceBuilder -> { docServiceBuilder.exclude(DocServiceFilter.ofThrift()); }; }
ServerRule
(JUnit 4) andServerExtension
(JUnit 5) now have more getters: #2449Endpoint
getters:endpoint(SessionProtocol)
,httpEndpoint()
andhttpsEndpoint()
URI
getters:uri(SessionProtocol)
,uri(SessionProtocol, SerializationFormat)
,httpUri()
,httpUri(SerializationFormat)
,httpsUri()
andhttpsUri(SerializationFormat)
- The old deprecated getters return
String
instead ofURI
.
InetSocketAddress
getters:socketAddress(SessionProtocol)
- The
CompletableFuture
s returned by our API will leave a warning log like the following when you perform a blocking operation in an event loop thread: #2275You can disable this functionality by specifying theCalling a blocking method on CompletableFuture from an event loop or non-blocking thread. You should never do this ... java.lang.IllegalStateException: Blocking event loop, don't do this.
-Dcom.linecorp.armeria.reportBlockedEventLoop=false
JVM option. - You can now serialize and deserialize
ThriftCall
,ThriftReply
,TMessage
andTBase
into TTEXT JSON usingThriftJacksonModule
. #2439ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new ThriftJacksonModule());
- You can now make any SLF4J
Logger
context-aware withRequestContext.makeContextAware(Logger)
: #2341// Prints 'Hello!' Logger logger = ...; logger.info("Hello!"); // Prints '[<current context>] Hello!' Logger ctxAwareLogger = ctx.makeContextAware(logger); ctxAwareLogger("Hello!");
RequestContextExporter
is now part of the core API, allowing you to integrate with other logging frameworks than Logback, such as Log4J2. #2314- You can now disable HTTP header validation the the
-Dcom.linecorp.armeria.validateHeaders=false
JVM option.
📈 Improvements
- Slightly reduced memory footprint of
Logging{Client,Service}
#2341 UnknownHostException
raised by Armeria now explains what DNS query has failed. #2332WebClient
now accepts a URI that starts withnone+
as well. #2361HealthCheckedEndpointGroup
now logs a helpful warning message when it receives a 4XX response. #2401- Our builder API Javadoc does not show mysterious return type parameter such as
B
andSELF
anymore. #2454 - A client now gets
SSLException
instead ofClosedSessionException
if a connection attempt fails with an SSL/TLS handshake error. #2338
🛠️ Bug fixes
WebClient
does not omit a request query string when sending a request to an absolute URI. #2309- A user cannot start a
Server
with a misconfiguredSslContext
anymore. #2325 - A user now always gets the correct
RequestContext
even if the contexts are nested. #1083 - Fixed a bug where thread-local context customizers were called for derived contexts unintentionally. #2344
Clients.withHttpHeaders()
andwithContextCustomizer()
now work with gRPC calls. #2344ClientRequestContext.path()
now returns a correct path for gRPC client calls. #2344- You can now send a POST request with an empty body with
DocService
client. #2357 - Server-side route cache hit ratio was not as good as we intended. #2358
- Fixed various potential memory leak in
HttpResponseWriter
. #2359 - Long-polling health check mechanism now detects a stall server which accepts a connection but does not send any response. #2392
ClientFactoryOptions
does not raise aNullPointerException
anymore. #2387- An
AsyncMethodCallback
specified when making an async Thrift call now has thread-localClientRequestContext
set properly. #2383 - gRPC client and server now works well with non-official gRPC stub generators such as reactive-grpc. #2376
- Fixed a bug where a
Server
can be started back afterclose()
is called. #2406 - Fixed a regression where Reactor does not treat Armeria's event loop threads as non-blocking. #2404
- Armeria does not fail to initialize anymore even if it failed to load the
com.linecorp.armeria.versions.properties
file. #2398 - You'll not see the
cannot start a new stream with a DATA frame
errors anymore. #2429 RequestLog.requestStartTime
property now includes the time taken for making a connection attempt and the time taken by decorators. #2436- The
-Dcom.linecorp.armeria.dumpOpenSslInfo=true
JVM option does not trigger aStackOverflowError
anymore. #2418 - Fixed cosmetic issues in
DocService
client sidebar. #2470 - Made sure IPv6 DNS queries are not sent to some IPv4 only machines with a link-local IPv6 interface. #2464
🏚️ Deprecations
HttpParameters
has been deprecated in favor ofQueryParams
#2307ServerBuilder.tls()
methods that require aConsumer
have been deprecated. UsetlsCustomizer(Consumer)
instead. #2340// Before: Server.builder() .tls(..., sslCtxBuilder -> { ... }); // After: Server.builder() .tls(...) .tlsCustomizer(sslCtxBuilder -> { ... });
- Many classes which have
Http
in their names have been deprecated in favor of those withoutHttp
, e.g. #2323RetryingHttpClient
->RetryingClient
HttpFileService
->FileService
- Many builder classes' constructors have been deprecated in favor of
builder()
static methods, e.g. #1719new ClientBuilder()
->Clients.builder()
new ArmeriaRetrofitBuilder()
->ArmeriaRetrofit.builder()
new ServerCacheControlBuilder()
->ServerCacheControl.builder()
- Many public static final fields that are not truly constants have been deprecated in favor of static factory methods, e.g.
EndpointSelectionStrategy.ROUND_ROBIN
->roundRogin()
NodeValueCodec.DEFAULT
->ofDefault()
AuthTokenExtractors.BASIC
->basic()
MoreNamingConventions
has been deprecated because we follow Micrometer's recommended naming convention. #2367Version.identify()
has been deprecated in favor ofgetAll()
#2398ServiceRequestContext.setRequestTimeout*()
has been deprecated in favor ofextendRequestTimeout*()
,setRequestTimeoutAfter*()
,setRequestTimeoutAt*()
andclearRequestTimeout()
. #2343ClientRequestContext.setResponseTimeout*()
has been deprecated in favor ofextendResponseTimeout*()
,setResponseTimeoutAfter*()
,setResponseTimeoutAt*()
andclearResponseTimeout()
. #2343- Many methods that return a
CompletableFuture
have been renamed from*Future()
towhen*()
, e.g. #2427HttpRequest.completionFuture()
->whenComplete()
HttpResponseWriter.onDemand()
->whenConsumed()
EndpointGroup.initialEndpointsFuture()
->whenReady()
- Many URI-returning methods in
ServerRule
andServerExtension
have been deprecated in favor of the new methods that do not require a path parameter: #2449ServerExtension server = ...; // Before server.httpUri("/"); server.httpUri("/foo"); // After server.httpUri(); server.httpUri().resolve("/foo");
THttpService.allowedSerializationFormats()
has been deprecated in favor ofsupportedSerializationFormats()
for consistency withGrpcService
. #2453Service.decorate(Class)
has been deprecated in favor of otherdecorate()
methods that require a function.ClosedPublisherException
has been deprecated in favor ofClosedStreamException
. #2468
☢️ Breaking changes
- Most revamped APIs in this release were changed in a backward-incompatible way:
RequestLog
- Attribute API in
RequestContext
- Content previewing mechanism has been revamped into decorators. Use
ContentPreviewingClient
andContentPreviewingService
. - You cannot add callbacks to
RequestContext
anymore because we found this feature results in poor performance and confusing behavior in many cases. We may want to revisit this feature if there is a valid use case for it. {Server,VirtualHost}Builder.tls()
methods do not throw anSSLException
anymore.build()
will throw anIllegalStateException
instead. As a result, any SSL configuration failure will be known when a user callsbuild()
, rather thantls()
.- We were not able to keep some classes or method signatures while we remove
Http
from class names. ServiceRequestContext.logger()
has been removed due to performance issues with Log4J2.RequestContext.isTimedOut()
has been removed.- We do not support Tomcat 8.0 anymore, which was obsoleted by Tomcat 8.5 anyway.
- The classes in
armeria-grpc-protocol
have been reorganized into multiple packages. - Our Micrometer meter IDs have been changed, which means you might need to update your monitoring configuration. If you wish to switch back to the legacy naming style, specify the
-Dcom.linecorp.armeria.useLegacyMeterNamed=true
JVM option. However, please keep in mind this option will eventually go away, because the new naming convention is recommended by Micrometer. - All our methods do not return
Optional
anymore. They are all@Nullable
now. If you wish to continue usingOptional
, just wrap the return value withOptional.ofNullable()
. EndpointGroupRegistry
has been removed, because you can now just specify anEndpointGroup
directly when creating a client.- As a result, you need to specify an
EndpointSelectionStrategy
when building anEndpointGroup
. If unspecified,EndpointSelectionStrategy.weightedRoundRobin()
is used.
- As a result, you need to specify an
MeterIdPrefixFunction
is not a functional interface anymore. You must implement two methods explicitly:activeRequestPrefix()
andcompleteRequestPrefix()
.- Now that
ClientFactory
is a part ofClientOption
, the following code will not work as expected, becauseoptions(options)
will overwrite the factory.To fix this, you must callClientOptions options = ClientOptions.builder().maxResponseLength(...).build(); ClientFactory factory = ClientFactory.builder().useHttp2Preface(false).build(); WebClient client = WebClient.builder(...) .factory(factory) .options(options) .build(); // This will fail! assert client.options().factory() == factory;
options()
first, and then override the individual properties:WebClient client = WebClient.builder(...) .options(options) .factory(factory) .build();
StreamMessage.subscribe(..., boolean withPooledObjects)
has been removed. Usesubscribe(..., SubscriptionOption.WITH_POOLED_OBJECTS)
.StreamMessage.drainAll(..., boolean withPooledObjects)
has been removed. UsedrainAll(..., SubscriptionOption.WITH_POOLED_OBJECTS)
.HttpRequestDuplicator
andHttpResponseDuplicator
are now interfaces. UseHttpRequest.toDuplicator()
andHttpResponse.toDuplicator()
to create a duplicator.StructuredLog
andStructuredLoggingService
have been removed. UseAccessLogWriter
.ThriftStructuredLogJsonFormat
has been removed. RegisterThriftJacksonModule
toObjectMapper
to serialize or deserialize Thrift objects.
⛓ Dependencies
- Brave 5.9.1 -> 5.9.3
- Dropwizard 1.3.17 -> 1.3.18
- Dropwizard Metrics 4.1.1 -> 4.1.2
- gRPC 1.25 -> 1.27
- Jackson 2.10.1 -> 2.10.2.20200130
- java-jwt 3.8.3 -> 3.9.0
- Jetty 9.4.24 -> 9.4.26
- Micrometer 1.3.2 -> 1.3.3
- Netty 4.1.43 -> 4.1.45
- TCNative BoringSSL 2.0.26 -> 2.0.28
- Prometheus Java client 0.8.0 -> 0.8.1
- Reactor 3.3.1 -> 3.3.2
- Retrofit 2.6.2 -> 2.7.1
- RxJava 2.2.15 -> 2.2.17
- SLF4J 1.7.29 -> 1.7.30
- Spring Boot 2.2.1 -> 2.2.4, 2.1.10 -> 2.1.12
- Thrift 0.12.0 -> 0.13.0
- Tomcat 9.0.29 -> 9.0.30, 8.5.49 -> 8.5.50