Server basics
Table of contents
- Ports
- Services
- Path patterns
- Per service configuration
- SSL/TLS
- PROXY protocol
- Serving HTTP and HTTPS on the same port
- Virtual hosts
- Getting an IP address of a client who initiated a request
- See also
To start a server, you need to build it first. Use ServerBuilder
:
import com.linecorp.armeria.server.Server;
import com.linecorp.armeria.server.ServerBuilder;
ServerBuilder sb = Server.builder();
// TODO: Configure your server here.
Server server = sb.build();
CompletableFuture<Void> future = server.start();
// Wait until the server is ready.
future.join();
Ports
To serve anything, you need to specify which TCP/IP port you want to bind onto:
ServerBuilder sb = Server.builder();
// Configure an HTTP port.
sb.http(8080);
// TODO: Add your services here.
Server server = sb.build();
CompletableFuture<Void> future = server.start();
future.join();
Services
Even if we opened a port, it's of no use if we didn't bind any services to them. Let's add some:
import com.linecorp.armeria.common.HttpRequest;
import com.linecorp.armeria.common.HttpResponse;
import com.linecorp.armeria.common.HttpStatus;
import com.linecorp.armeria.common.MediaType;
import com.linecorp.armeria.common.MediaTypeNames;
import com.linecorp.armeria.common.QueryParams;
import com.linecorp.armeria.server.AbstractHttpService;
import com.linecorp.armeria.server.Server;
import com.linecorp.armeria.server.ServerBuilder;
import com.linecorp.armeria.server.ServiceRequestContext;
import com.linecorp.armeria.server.annotation.Consumes;
import com.linecorp.armeria.server.annotation.Default;
import com.linecorp.armeria.server.annotation.Get;
import com.linecorp.armeria.server.annotation.Param;
import com.linecorp.armeria.server.annotation.Post;
import com.linecorp.armeria.server.annotation.Produces;
import com.linecorp.armeria.server.logging.LoggingService;
ServerBuilder sb = Server.builder();
sb.http(8080);
// Add a simple 'Hello, world!' service.
sb.service("/", (ctx, req) -> HttpResponse.of("Hello, world!"));
// Using path variables:
sb.service("/greet/{name}", new AbstractHttpService() {
@Override
protected HttpResponse doGet(ServiceRequestContext ctx, HttpRequest req) {
String name = ctx.pathParam("name");
return HttpResponse.of("Hello, %s!", name);
}
}.decorate(LoggingService.newDecorator())); // Enable logging
// Using an annotated service object:
sb.annotatedService(new Object() {
@Get("/greet2/:name") // `:name` style is also available
public HttpResponse greet(@Param("name") String name) {
return HttpResponse.of("Hello, %s!", name);
}
});
// Just in case your name contains a slash:
sb.serviceUnder("/greet3", (ctx, req) -> {
String path = ctx.mappedPath(); // Get the path without the prefix ('/greet3')
String name = path.substring(1); // Strip the leading slash ('/')
return HttpResponse.of("Hello, %s!", name);
});
// Using an annotated service object:
sb.annotatedService(new Object() {
@Get("regex:^/greet4/(?<name>.*)$")
public HttpResponse greet(@Param("name") String name) {
return HttpResponse.of("Hello, %s!", name);
}
});
// Using a query parameter (e.g. /greet5?name=alice) on an annotated service object:
sb.annotatedService(new Object() {
@Get("/greet5")
public HttpResponse greet(@Param("name") String name,
@Param("title") @Default("Mr.") String title) {
// "Mr." is used by default if there is no title parameter in the request.
return HttpResponse.of("Hello, %s %s!", title, name);
}
});
// Getting a map of query parameters on an annotated service object:
sb.annotatedService(new Object() {
@Get("/greet6")
public HttpResponse greet(QueryParams params) {
return HttpResponse.of("Hello, %s!", params.get("name"));
}
});
// Using media type negotiation:
sb.annotatedService(new Object() {
@Get("/greet7")
@Produces(MediaTypeNames.JSON_UTF_8)
public HttpResponse greetGet(@Param("name") String name) {
return HttpResponse.of(HttpStatus.OK, MediaType.JSON_UTF_8,
"{\"name\":\"%s\"}", name);
}
@Post("/greet7")
@Consumes(MediaTypeNames.FORM_DATA)
public HttpResponse greetPost(@Param("name") String name) {
return HttpResponse.of(HttpStatus.OK);
}
});
Server server = sb.build();
CompletableFuture<Void> future = server.start();
future.join();
As described in the example, service()
and serviceUnder()
perform an exact match and a prefix match
on a request path respectively. ServerBuilder
also provides advanced path mapping such as regex and
glob pattern matching.
Also, we decorated the second service using LoggingService
, which logs all requests and responses.
You might be interested in decorating a service using other decorators, for example to gather metrics.
You can also use an arbitrary object that's annotated by the @Path
annotation
using annotatedService()
.
Path patterns
You can use the following path patterns to map an HTTP request path to a service or a decorator.
Pattern | Example |
---|---|
Exact match |
|
Curly-brace style path variables |
|
Colon style path variables |
|
Prefix match |
|
Glob pattern |
|
Regular expression |
|
Per service configuration
The examples above are just mapping the path of an HTTP request on a service. If you want to set configuration for a specific service, you can use fluent API:
ServerBuilder sb = Server.builder();
sb.route() // Configure the service.
.post("/foo/bar") // Matched when the path is "/foo/bar" and the method is POST.
.consumes(MediaType.JSON) // Matched when the "content-type" header is "application/json".
.produces(MediaType.JSON) // Matched when the "accept" headers is "application/json".
.matchesHeaders("baz=qux") // Matched when the "baz" header is "qux".
.matchesParams("quux=quuz") // Matched when the "quux" parameter is "quuz".
.requestTimeoutMillis(5000)
.maxRequestLength(8192)
.verboseResponses(true)
.build((ctx, req) -> HttpResponse.of(OK)); // Should call to finish and return to the ServerBuilder.
Or use a Consumer
:
import com.linecorp.armeria.common.HttpMethod;
ServerBuilder sb = Server.builder();
sb.withRoute(builder -> builder.path("/baz") // Matched when the path is "/baz".
// Matched when the method is GET or POST.
.methods(HttpMethod.GET, HttpMethod.POST)
...
.build((ctx, req) -> HttpResponse.of(OK)));
SSL/TLS
You can also add an HTTPS port with your certificate and its private key files:
ServerBuilder sb = Server.builder();
sb.https(8443)
.tls(new File("certificate.crt"), new File("private.key"), "myPassphrase");
...
PROXY protocol
Armeria supports both text (v1) and binary (v2) versions of PROXY protocol. If your server is behind a load balancer such as HAProxy and AWS ELB, you could consider enabling the PROXY protocol:
import static com.linecorp.armeria.common.SessionProtocol.HTTP;
import static com.linecorp.armeria.common.SessionProtocol.HTTPS;
import static com.linecorp.armeria.common.SessionProtocol.PROXY;
ServerBuilder sb = Server.builder();
sb.port(8080, PROXY, HTTP);
sb.port(8443, PROXY, HTTPS);
...
Serving HTTP and HTTPS on the same port
For whatever reason, you may have to serve both HTTP and HTTPS on the same port. Armeria is one of the few implementations that supports port unification:
ServerBuilder sb = Server.builder();
sb.port(8888, HTTP, HTTPS);
// Enable PROXY protocol, too.
sb.port(9999, PROXY, HTTP, HTTPS);
...
Virtual hosts
Use ServerBuilder.virtualHost()
to configure a name-based virtual host:
ServerBuilder sb = Server.builder();
// Configure foo.com.
sb.virtualHost("foo.com")
.service(...)
.tls(...)
.and() // Configure *.bar.com.
.virtualHost("*.bar.com")
.service(...)
.tls(...)
.and() // Configure the default virtual host.
.service(...)
.tls(...);
...
Getting an IP address of a client who initiated a request
You may want to get an IP address of a client who initiated a request in your service. In that case,
you can use ServiceRequestContext.clientAddress()
. But you need to configure
your ServerBuilder
before doing that:
import com.linecorp.armeria.common.util.InetAddressPredicates;
import com.linecorp.armeria.server.ClientAddressSource;
ServerBuilder sb = Server.builder();
// Configure a filter which evaluates whether an address of a remote endpoint is
// trusted. If unspecified, no remote endpoint is trusted.
// e.g. servers who have an IP address in 10.0.0.0/8.
sb.clientAddressTrustedProxyFilter(InetAddressPredicates.ofCidr("10.0.0.0/8"));
// Configure a filter which evaluates whether an address can be used as
// a client address. If unspecified, any address would be accepted.
// e.g. public addresses
sb.clientAddressFilter(address -> !address.isSiteLocalAddress());
// Configure a list of sources which are used to determine where to look for
// the client address, in the order of preference. If unspecified, 'Forwarded',
// 'X-Forwarded-For' and the source address of a PROXY protocol header would be used.
sb.clientAddressSources(ClientAddressSource.ofHeader(HttpHeaderNames.FORWARDED),
ClientAddressSource.ofHeader(HttpHeaderNames.X_FORWARDED_FOR),
ClientAddressSource.ofProxyProtocol());
// Get an IP address of a client who initiated a request.
sb.service("/", (ctx, res) ->
HttpResponse.of("A request was initiated by %s!",
ctx.clientAddress().getHostAddress()));