Your go-to microservice framework for any situation, from the creator of Netty et al. You can build any type of microservice leveraging your favorite technologies, including gRPC, Thrift, Kotlin, Retrofit, Reactive Streams, Spring Boot and Dropwizard.
APACHE-2.0 License
Bot releases are visible (Hide)
ServerBuilder sb = new ServerBuilder();
// Enables previewing the content with the maximum length of 100 for textual contents.
sb.contentPreview(100);
// A user can use their customized previewer factory.
sb.contentPreviewerFactory((ctx, headers) -> {
return ContentPreviewer.ofBinary(100, byteBuf -> {
byte[] contents = new byte[Math.min(byteBuf.readableBytes(), 100)];
byteBuf.readBytes(contents);
return BaseEncoding.base16().encode(contents);
});
});
ClientRequestContextBuilder
and ServiceRequestContextBuilder
. #1548
RequestContext
.@Test
public void testService() throws Exception {
// Given
HttpRequest req = HttpRequest.of(HttpMethod.POST, "/greet",
MediaType.JSON_UTF_8,
"{ \"name\": \"foo\" }");
ServiceRequestContext sctx = ServiceRequestContext.of(req);
// When
HttpResponse res = service.serve(sctx, req);
// Then
AggregatedHttpMessage aggregatedRes = res.aggregate().get();
assertEquals(200, aggregatedRes.status().code());
}
@CorsDecorator
in annotated services. #1547
sb.annotatedService("/cors6", new Object() {
@Get("/any/get")
@CorsDecorator(origins = "*", exposedHeaders = { "expose_header_1", "expose_header_2" },
allowedRequestHeaders = { "allow_request_1", "allow_request_2" },
allowedRequestMethods = HttpMethod.GET, maxAge = 3600,
preflightRequestHeaders = {
@AdditionalHeader(name = "x-preflight-cors", value = "Hello CORS")
})
public HttpResponse anyoneGet() {
return HttpResponse.of(HttpStatus.OK);
}
}
ClientFactoryBuilder.domainNameResolverCustomizer()
so that a user can customize the resolver easily. #1553
ClientFactory f = new ClientFactoryBuilder()
.domainNameResolverCustomizer(resolverBuilder -> {
resolverBuilder.maxQueriesPerResolve(10);
resolverBuilder.traceEnabled(false);
})
.build();
// Define a custom annotation:
@ProducesJson
@LoggingDecorator
@interface MyApiSpecification {}
// Apply it to the annotated HTTP service:
@Get("/api")
@MyApiSpecification // You can use one annotation which holds other other annotations.
public Something getSomething() {}
@AdditionalHeader
and @AdditionalTrailer
to insert headers easily in annotated services. #1555// Before
GrpcService s = new GrpcServiceBuilder().addService(a)
.addService(b)
.build();
// After
GrpcService s = new GrpcServiceBuilder().addServices(a, b).build();
HttpRequest req = ...;
AggregatedHttpMessage aggregated = ...;
MediaType contentType;
String content;
// Before
contentType = req.headers().contentType();
contentType = aggregated.headers().contentType();
content = aggregated.content().toStringUtf8();
// After
contentType = req.contentType();
contentType = aggregated.contentType();
content = aggregated.contentUtf8();
RequestObject
is shown in DocService
. #1557verboseSocketExceptions
flag so that a user can ignore harmless socket-related error message. #1577HttpFileService
. #1573armeria-spring-boot-actuator
dependency. #1578
responseCauseSanitizer
to LoggingDecoratorBuilder
. #1594
RequestLog.responseCause()
or avoid logging the stack trace completely.ServerBuilder sb = ...
final Function<Throwable, Throwable> responseCauseSanitizer = cause -> {
if (cause instanceOf AnticipatedException) {
return null; // Do not log when AnticipatedException is raised.
}
return cause;
};
sb.decorator(new LoggingServiceBuilder().requestLogLevel(LogLevel.INFO)
.successfulResponseLogLevel(LogLevel.INFO)
.responseCauseSanitizer(responseCauseSanitizer)
.newDecorator());
public class MyAnnotatedService {
@Get("/publisher")
@ProducesEventStream
public Publisher<ServerSentEvent<?>> publisher() {
return Flux.just(ServerSentEvent.ofData("foo"),
ServerSentEvent.ofData("bar"),
ServerSentEvent.ofData("baz"),
ServerSentEvent.ofData("qux"));
}
}
400 Bad Request
happens. #1575armeria-retry-count
header when RetryingClient
is used. #1593httpStatus
tag is not set properly. #1559Content-length
header when HTTP trailers exist. #1566GrpcService
when the value is 0
or Long.MAX_VALUE
. #1549HttpHeaders
in AggregatedHttpMessage
is immutable.
headers.toMutable()
to set or remove a header.RequestContext.isTimedOut()
has been deprecated. #1589
ServiceRequestContext.isTimedOut()
instead.Bouncy Castle 1.60 -> 1.61
Brave 5.6.0 -> 5.6.1
java-jwt 3.5.0 -> 3.7.0
Micrometer 1.1.2 -> 1.1.3
Netty 4.1.32 -> 4.1.33
Project Reactor 3.2.5 -> 3.2.6
protobuf-jackson 0.3.0 -> 0.3.1
RxJava 2.2.5 -> 2.2.6
Thrift 0.11.0 -> 0.12.0
Tomcat 9.0.14 -> 9.0.16, 8.5.37 -> 8.5.38
spring-boot-starter-actuator has benn removed from the transitive dependencies.
ServerBuilder sb = new ServerBuilder();
// Auto-generates ETag, Last-Modified.
// Handles 'If-None-Match' and 'If-Modified-Since'.
sb.service("/favicon.ico", HttpFile.of(new File("/var/www/favicon.ico")).asService());
sb.annotatedService(new Object() {
@Get("/files/{fileName}")
public HttpResponse getFile(ServiceRequestContext ctx, HttpRequest req,
@Param String fileName) {
return HttpFile.of(new File("/var/www/files", fileName)).asService(ctx, req);
}
});
ServerBuilder sb = new ServerBuilder().service("/message", myService.decorate(
CorsServiceBuilder.forOrigins("http://example.com")
.allowCredentials()
.allowNullOrigin() // 'Origin: null' will be accepted.
...
.andForOrigins("http://example2.com")
...
.and() // Should call to return to CorsServiceBuilder.
.newDecorator()));
Publisher
and Stream
return type is handled by the default response converters in AnnotatedHttpService
. #1530
public class MyAnnotatedService {
@Get("/stream")
@ProducesJsonSequences
public Stream<String> stream() {
return Stream.of("foo", "bar", "baz", "qux");
}
@Get("/publisher")
@ProducesJsonSequences
public Publisher<String> publisher() {
return Flux.just("foo", "bar", "baz", "qux");
}
}
Maybe
, Single
, Completable
and Observable
are supported once you add armeria-rxjava
to the dependencies as well.
public class MyAnnotatedService {
@Get("/observable")
@ProducesJsonSequences
public Observable<String> observable() {
return Observable.just("foo", "bar", "baz", "qux");
}
}
@StatusCode
. #1509
public class MyAnnotatedService {
@StatusCode(201)
@Post("/users/{name}")
public User createUser(@Param String name) { ... }
// @StatusCode(200) would be applied by default.
@Get("/users/{name}")
public User getUser(@Param String name) { ... }
// @StatusCode(204) would be applied by default.
@Delete("/users/{name}")
public void deleteUser(@Param String name) { ... }
}
ServerBuilder sb = new ServerBuilder();
sb.service(new GrpcServiceBuilder().useBlockingTaskExecutor(true)
.addService(...)
.build());
Status.cause
between server and client for more structured error handling in gRPC. #1504verboseResponses
property to ServerBuilder
and ServerConfig
. #1507 #1508
ServerBuilder sb = new ServerBuilder();
sb.verboseExceptions(true);
DocService
UI are now collapsible. #1506HttpHeaderNames.of()
now accepts CharSequence
. #1516
// Before:
HttpClientBuilder b = new HttpClientBuilder(...);
b.addHttpHeader(AsciiString.of("my-header"), value);
// After:
b.addHttpHeader(HttpHeaderNames.of("my-header"), value);
// or
b.addHttpHeader("my-header", value);
Armeria(Client|Server)Configurator
extends org.springframework.core.Ordered
. #1535
CountingSampler
in logging is not syncronized anymore. #1527HttpStatus
and HttpStatusException
lookup performance is improved. #1545verboseExceptions
is not working correctly when Exceptions.isExpected(Throwable)
is used. #1529initCause
and addSuppressed
can be called on singleton exceptions. #1545content-length: 0
204
, 205
and 304
. #1505AnnotatedHttpService
. #1538CorsService
. #1537CorsConfig
and CorsServiceBuilder
except for anyOriginSupported
and shortCircuit
to CorsPolicy
and AbstractCorsPolicyBuilder
. #1526HttpVfs.Entry
with HttpFile
. #1505
HttpVfs.get()
returns HttpFile
instead HttpVfs.Entry
.ResponseConverterFunction.convert()
method takes more parameters which are headers
and trailingHeaders
. #1509null
object is returned is changed in AnnotatedHttpService
. #1509CircuitBreakerListener
is changed. #1509
String circuitBreakerName
instead of CircuitBreaker circuitBreaker
.ArmeriaClientConfigurator.customize()
to configure()
. #1535IllegalStateException
is raised instead of ClosedClientFactoryException
when a request in progress is cancelled due to the termination of ClientFactory
. #1532ClosedClientFactoryException
has been deprectated. #1532DocService
. #1479com.linecorp.armeria.common.util.InetAddressPredicates
for easier IP address filtering. #1494 #1498
Predicate<InetAddress> ipFilter =
InetAddressPredicates.ofCidr("192.168.0.0/24").or(
InetAddressPredicates.ofCidr("10.1.0.0/16"));
See Getting an IP address of a client who initiated a request for more examples.content-length: 0
header when a user sends a PUT/POST/PATCH request without content. #1476 #1499AnnotatedServiceRegistrationBean
are not applied correctly. #1497ClientBuilder b = new ClientBuilder(...);
// Before
b.decorator(HttpRequest.class, HttpResponse.class, httpLevelDecorator);
b.decorator(RpcRequest.class, RpcResponse.class, rpcLevelDecorator);
// After
b.decorator(httpLevelDecorator); // No need to specify req/res types.
b.rpcDecorator(rpcLevelDecorator); // Added a dedicated method for RPC-level decorators.
ServiceRequestContext.clientAddress()
which returns the IP address of the client which initiated the request. #1467
Forwarded
, X-Forwarded-For
and PROXY protocol headers in the configurable order of preferences.%a
.clientAddressSources()
, clientAddressTrustedProxyFilter()
and clientAddressFilter()
in ServerBuilder
for more information.ServiceRequestContext.additionalResponseTrailers
which allows you to set additional HTTP trailers easily. #1488ClientFactory.disableShutdownHook()
. #1082 #1472DocService
debug form does not show all example HTTP headers. #1466EndpointGroup
health check meters sometimes have missing ip
tag. #1474MAX_CONCURRENT_STREAMS
settings. #1206 #1481ClientBuilder.decorator(Class, Class, ...)
has been deprecated in favor of the new decorator()
and rpcDecorator()
methods introduced in the 'New features' section above. #1482 #1484armeria-spring-boot-webflux-autoconfigure
which allows you to use Armeria as the web server and client implementation of Spring WebFlux. #1326
HttpHealthCheckedEndpointGroup
customizable. #1431EventLoopGroups
. #1438 #1445CircuitBreakerClientBuilder
which helps you apply CircuitBreaker
pattern: #1418
final CircuitBreakerStrategy strategy = CircuitBreakerStrategy.onServerErrorStatus();
final HttpClient client = new HttpClientBuilder(...)
.decorator(new CircuitBreakerHttpClientBuilder(strategy)
.circuitBreakerMapping(CircuitBreakerMapping.ofDefault()
.newDecorator())
.build();
final AggregatedHttpMessage res = client.execute(...).aggregate().join();
armeria-bom
.
armeria-bom
will now define the versions of Armeria artifacts only. For example, previously, armeria-bom
defined the versions of Armeria's transitive dependencies such as Guava and Netty, but we do not from this release.:authority
header is not set properly when RetryingClient
is used. #1433Span.remoteServiceName
. #1434KeyStoreCredentialResolverBuilder
does not raise FileNotFoundException
when it failed to find the specified resource. #1439NullPointerException
raised by AbstractStreamMessageDuplicator
and its subtypes. #1446 #1449-shaded
suffix from all Armeria artifact IDs. You should not see any differences besides that because all Armeria artifacts are now distributed as shaded JARs by default. #1435armeria-bom
, your build might fail to resolve some of your dependencies if you relied on armeria-bom
for the version numbers of non-Armeria dependencies.KeyedChannelPoolHandler
and its subtypes have been replaced with ConnectionPoolListener
and its respective subtypes. #818 #1441(Retry|CircuitBreaker)Strategy
are split into (Retry|CircuitBreaker)Strategy
and (Retry|CircuitBreaker)StrategyWithContent
. #1418
(Retry|CircuitBreaker)Stategy
unless you need to retry by looking into the response content, because it reduces performance.RequestConverterFunction
s for service method parameters. #1412
public class MyService {
@Get("/info")
public String getInfo(@RequestConverter(FirstConverter.class)
@RequestConverter(SecondConverter.class)
MyInfoRequest request) { ... }
// In old Armeria:
// public String getInfo(@RequestObject(FirstConverter.class)
// MyInfoRequest request) { ... }
}
RequestLog.requestStartTimeMicros()
and responseStartTimeMicros()
which provide timestamps with microsecond precision. This feature is available for Java 9 or above only. #1427DocService
. Previously, you were allowed to specify only one script for all requests. #1400@RequestObject
annotation for service method parameters anymore. #1412
@RequestConverter(MyInfoRequestConverter.class)
public class MyService {
@Get("/info")
public String getInfo(MyInfoRequest request) { ... }
// In old Armeria:
// public String getInfo(@RequestObject MyInfoRequest request) { ... }
}
HttpFileService
does not invalidate cache when a file is modified, deleted or created. #1404ClosedSessionException
is raised when UnprocessedRequestException
is expected. #1406RequestLog
does not reach COMPLETE
availability in some cases. #1415HttpClient
does not normalize the request path unnecessarily anymore. #1416 #1417@RequestObject
does not have a value anymore. Use @RequestConverter
annotation instead. #1412RequestConverterFunction
. #1402Sent a GOAWAY frame
message is logged unnecessarily at WARN level when it was not actually sent. #1401 #1403UnprocessedRequestException
which is raised by an Armeria HTTP client when a user sent a request with the stream ID higher than the lastStreamId
of the GOAWAY frame sent by the server. #1392
UnprocessedRequestException
, it is safe to retry the request even if the request was not idempotent, as explained in the GOAWAY section of RFC 7540. Consider using RetryingHttpClient
with RetryStrategy.onUnprocessed()
, which auto-retries the request on an UnprocessedRequestException
:
// HTTP
final HttpClient client = new HttpClientBuilder("https://example.com")
.decorator(RetryingHttpClient.newDecorator(RetryStrategy.onUnprocessed()))
.build();
// RPC
final MyGrpcServiceStub client = new ClientBuilder("gproto+https://example.com")
.decorator(HttpRequest.class, HttpResponse.class,
RetryingHttpClient.newDecorator(RetryStrategy.onUnprocessed()))
.build(MyGrpcServiceStub.class);
ServerBuilder
#1389 #1393
final Server server = new ServerBuilder()
.http2InitialConnectionWindowSize(...) // Default: 1 MiB
.http2InitialStreamWindowSize(...) // Default: 1 MiB
.http2MaxFrameSize(...) // Default: 16384
.http2MaxStreamsPerConnection(...) // Default: 2^31-1
.http2MaxHeaderListSize(...) // Default: 8192
...
http2MaxHeaderListSize
property to ClientFactoryBuilder
#1393
final ClientFactory clientFactory = new ClientFactoryBuilder()
.http2MaxHeaderListSize(...) // Default: 8192
...
com.linecorp.armeria.defaultHttp2InitialConnectionWindowSize
com.linecorp.armeria.defaultHttp2InitialStreamWindowSize
com.linecorp.armeria.defaultHttp2MaxFrameSize
com.linecorp.armeria.defaultHttp2MaxStreamsPerConnection
com.linecorp.armeria.defaultHttp2MaxHeaderListSize
MediaType.WASM_APPLICATION
#1394HttpRequest.completionFuture()
never completes when the request has no content. #1387http1MaxInitialLineLength
(BAD) maxHttp1InitialLineLength
http2InitialConnectionWindowSize
(BAD) initialHttp2ConnectionWindowSize
ServerBuilder.startStopExecutor(Executor)
so that a user can specify an alternative Executor
other than GlobalEventExecutor
which is used when Server
invokes start/stop tasks. #1379AccessLogWriter
which replaces the usage of Consumer<RequestLog>
#1381KafkaAccessLogWriter
which produces Kafka records from RequestLog
s. #1381
KafkaStructuredLoggingService
, it can even produce the records for unmapped (404) or broken requests (400).com.linecorp.armeria.annotatedServiceExceptionVerbosity
that allows a user adjust the verbosity of the exceptions raised by annotated services. #1382
all
- logs all exceptions.unhandled
- (default) logs the exceptions that are not:
IllegalArgumentException
HttpStatusException
or HttpResponseException
ExceptionHandler
snone
- does not log any exceptions.ServiceRequestContext
. #1383HealthCheckedEndpointGroup
does not work correctly when there are multiple endpoints with different IP addresses and same authority. #1375armeria-saml
does not decode some SAML messages correctly. #1377AccessLogWriters
has been deprecated. Use the factory methods in AccessLogWriter
. #1381StructuredLogJsonKafkaSerialize
has been deprecated. Use @daniel-shuy's kafka-jackson-serializer instead. #1381KafkaStructuredLoggingService
has been deprecated. Use KafkaAccessLogWriter
. #1381ServerBuilder.accessLogWriter()
does not accept Consumer<? super RequestLog>
but AccessLogWriter
. #1381AccessLogWriters
do not return Consumer<RequestLog>
but AccessLogWriter
. #1381ExceptionHandlerFunction.DEFAULT
has been removed because it was not intended for use by a user. #1383HttpClient
used by HttpHealthCheckedEndpointGroup
. #1363
final HttpHealthCheckedEndpointGroup group =
new HttpHealthCheckedEndpointGroupBuilder(delegateGroup, "/monitor/l7check")
.withClientOptions(options -> options.setHttpHeader(HttpHeaderNames.USER_AGENT, "my-agent"))
.build();
Publisher
. #1366armeria-rxjava
adds a built-in response converter for Observable
. #1342 #1366NullPointerException
in HttpRequestSubscriber.onSubscribe()
#1369ByteBuf
leaks in gRPC client and server #1370 #1371RequestContext.decodedPath()
and ServiceRequestContext.decodedMappedPath()
which decodes the percent-encoded UTF-8 path. #756 #1356
RequestContext.path()
and ServiceRequestContext.mappedPath()
remain unchanged.
ServiceRequestContext ctx = ...;
assert ctx.path().equals("/unicode/%F0%9F%91%8D");
assert ctx.decodedPath().equals("/unicode/👍");
AsyncMethodCallbacks
which bridges the gap between CompletionStage
and AsyncMethodCallback
. #508 #1357
// Before:
public void myThriftServiceMethod(AsyncMethodCallback<String> cb) {
CompletableFuture<String> f = someAsyncOp();
f.whenComplete((res, cause) -> {
if (cause != null) {
if (cause instanceof Exception) {
cb.onError(cause);
} else {
cb.onError(new CompletionException(cause));
}
} else {
cb.onComplete(res);
}
});
}
// After:
public void myThriftServiceMethod(AsyncMethodCallback<String> cb) {
AsyncMethodCallbacks.transfer(someAsyncOp(), cb);
}
AssertionError
in StartStopSupport
. #1351 #1358%26
, %3B
and %3D
are not handled correctly in a query string. #1354ServiceRequestContext.pathParam*()
and PathMappingResult.pathParams()
@Param
annotationURLDecoder.decode()
to avoid double decode.Added RequestContextCurrentTraceContext.newBuilder()
which allows a user to customize the Zipkin context, such as decorating a trace scope #1328
Armeria now provides Spring configuration metadata for better IDE integration. #1340
Added a new utility class StartStopSupport
which simplifies the implementation of asynchronous start-stop style life cycle #1344
public class MyService implements AutoCloseable {
private final StartStopSupport<Void, MyServiceListener> startStop = new StartStopSupport<>(ForkJoinPool.commonPool()) {
@Override
protected CompletableFuture<Void> doStart() { /* .. Implement startup .. */ }
@Override
protected CompletableFuture<Void> doStop() { /* .. Implement shutdown .. */ }
@Override
protected void notifyStarting(MyServiceListener listener) {
listener.myServiceStarted(this);
}
...
};
public CompletableFuture<Void> start() { return startStop.start(true /* fail if started */); }
public CompletableFuture<Void> stop() { return startStop.stop(); }
public void addListener(MyServiceListener listener) { startStop.addListener(listener); }
@Override
public void close() { startStop.close(); }
}
MyService myService = new MyService();
CompletableFuture<Void> startFuture = myService.start();
CompletableFuture<Void> stopFuture = myService.stop();
hostname
command should be lowered from WARN
to DEBUG
, because some distributions do not have hostname
command. #1333 #1348RequestContext
is not pushed for some gRPC callbacks such as onReady()
. #1338RequestContextCurrentTraceContext.INSTANCE
has been deprecated in favor of RequestContextCurrentTraceContext.DEFAULT
. #1328Added SAML module so that a user can enable single sign-on on his or her services easily. #1292
RequestContextCurrentTraceContext
is introduced which stores/loads the trace context into/from a RequestContext
s attribute so that a user does not need to use thread local variable. #1322
Tracing.Builder.currentTraceContext(RequestContextCurrentTraceContext.INSTANCE)
Added a new flag: com.linecorp.armeria.verboseResponses
which is specified when a user wants to send verbose response, which is false
by default. #1327
Added Copy and Clear buttons to DocService
client. #1321
endOfStream
for headers based on HTTP/2 frame EOS information. #1325RequestContextCurrentTraceContext
when building a Tracing
. #1322-Dcom.linecorp.armeria.verboseResponses=true
JVM option. #1327