Proteus is a blazing fast minimalist Java web server framework built atop Undertow.
Proteus is a blazing fast minimalist Java API server framework built atop Undertow for developing back end applications and microservices.
/bin/bash -e <(curl -fsSL https://raw.githubusercontent.com/noboomu/proteus-example/master/scripts/quickStart.sh)
<dependency>
<groupId>io.sinistral</groupId>
<artifactId>proteus-core</artifactId>
<version>0.6.0</version>
</dependency>
OpenAPI v3 Support
<dependency>
<groupId>io.sinistral</groupId>
<artifactId>proteus-openapi</artifactId>
<version>0.6.0</version>
</dependency>
Controller classes respect standard JAX-RS annotations:
@Path("/benchmarks")
@Produces((MediaType.APPLICATION_JSON))
@Consumes((MediaType.MEDIA_TYPE_WILDCARD))
public class DemoController
Controller class methods respect standard Swagger / JAX-RS annotations:
@GET
@Path("/plaintext")
@Produces((MediaType.TEXT_PLAIN))
public ServerResponse<ByteBuffer> plaintext(ServerRequest request)
{
return response("Hello, World!").textPlain();
}
OpenAPI v3 annotations are supported when using the
proteus-openapi
module.
Proteus has three built in annotations:
@Blocking
io.sinistral.proteus.annotations.Blocking
@Debug
io.sinistral.proteus.annotations.Debug
@Chain
io.sinistral.proteus.annotations.Chain
io.undertow.server.HttpHandler
classes.Controller methods arguments support the following JAX-RS annotations:
@PathParam
javax.ws.rs.PathParam
/dogs/{id}
, @PathParam(“id”) binds the path segment value to the method parameter.@QueryParam
javax.ws.rs.QueryParam
@FormParam
javax.ws.rs.FormParam
@HeaderParam
javax.ws.rs.HeaderParam
@CookieParam
javax.ws.rs.CookieParam
@BeanParam
javax.ws.rs.BeanParam
@DefaultParam
javax.ws.rs.DefaultParam
proteus-openapi
module.For total control and maximum performance the raw HttpServerExchange
can be passed to the controller method.
Methods that take an HttpServerExchange
as an argument should not return a value.
In this case the method takes on full responsibility for completing the exchange.
The static method io.sinistral.proteus.server.ServerResponse.response
helps create ServerResponse<T>
instances that are the preferred return type for controller methods.
If the response object’s contentType
is not explicitly set, the @Produces
annotation is used in combination with the Accept
headers to determine the Content-Type
.
For methods that should return a String
or ByteBuffer
to the client users can create responses like this:
...
import static io.sinistral.proteus.server.ServerResponse.response;
...
@GET
@Path("/hello-world")
@Produces((MediaType.TEXT_PLAIN))
@Operation(description = "Serve a plaintext message using a ServerResponse")
public ServerResponse<ByteBuffer> plaintext(ServerRequest request, @QueryParam("message") String message)
{
return response("Hello, World!").textPlain();
}
By default, passing a String
to the static ServerResponse.response
helper function will convert it into a ByteBuffer
.
For other types of responses the following demonstrates the preferred style:
...
import static io.sinistral.proteus.server.ServerResponse.response;
...
@GET
@Path("/world")
@Produces((MediaType.APPLICATION_JSON))
@Operation(description = "Return a world JSON object")
public ServerResponse<World> getWorld(ServerRequest request, @QueryParam("id") Integer id, @QueryParam("randomNumber") Integer randomNumber )
{
return response(new World(id,randomNumber)).applicationJson();
}
The entity can be set separately as well:
this disables static type checking!
...
import static io.sinistral.proteus.server.ServerResponse.response;
...
@GET
@Path("/world")
@Produces((MediaType.APPLICATION_JSON))
@Operation(description = "Return a world JSON object")
public ServerResponse getWorld(ServerRequest request, Integer id, Integer randomNumber )
{
return response().entity(new World(id,randomNumber));
}
CompletableFuture<ServerResponse<T>>
can also be used as a response type:
...
import static io.sinistral.proteus.server.ServerResponse.response;
...
@GET
@Path("/future/user")
@Operation(description = "Future user endpoint" )
public CompletableFuture<ServerResponse<User>> futureUser( ServerRequest request )
{
return CompletableFuture.completedFuture(response( new User(123L) ).applicationJson() );
}
In this case a handler will be generated with the following source code:
...
import static io.sinistral.proteus.server.ServerResponse.response;
...
public void handleRequest(final io.undertow.server.HttpServerExchange exchange) throws java.lang.Exception {
CompletableFuture<ServerResponse<User>> response = examplesController.futureUser();
response.thenAccept( r -> r.applicationJson().send(this,exchange) )
.exceptionally( ex -> {
throw new java.util.concurrent.CompletionException(ex);
} );
}
A io.sinistral.proteus.server.ServerRequest
can be added as an endpoint parameter if the user wishes to access request properties that are not included in the parameter list.
Proteus is capable of parsing most types of endpoint parameters automatically so long as the type has a fromString
, valueOf
, or can be deserialized from JSON.
Multipart/Form file uploads can be passed to the endpoint methods as a java.io.File
, a java.nio.Files.Path
, or a java.nio.ByteBuffer
.
Optional parameters are also supported, here is a more complex endpoint demonstrating several parameter types:
...
import static io.sinistral.proteus.server.ServerResponse.response;
...
@GET
@Path("/response/parameters/complex/{pathLong}")
@Operation(description = "Complex parameters")
public ServerResponse<Map<String,Object>> complexParameters(
ServerRequest serverRequest,
@PathParam("pathLong") final Long pathLong,
@QueryParam("optionalQueryString") Optional<String> optionalQueryString,
@QueryParam("optionalQueryLong") Optional<Long> optionalQueryLong,
@QueryParam("optionalQueryDate") Optional<OffsetDateTime> optionalQueryDate,
@QueryParam("optionalQueryUUID") Optional<UUID> optionalQueryUUID,
@HeaderParam("optionalHeaderUUID") Optional<UUID> optionalHeaderUUID,
@QueryParam("optionalQueryEnum") Optional<User.UserType> optionalQueryEnum,
@HeaderParam("optionalHeaderString") Optional<String> optionalHeaderString,
@QueryParam("queryUUID") UUID queryUUID,
@HeaderParam("headerString") String headerString,
@QueryParam("queryEnum") User.UserType queryEnum,
@QueryParam("queryIntegerList") List<Integer> queryIntegerList,
@QueryParam("queryLong") Long queryLong
)
{
Map<String,Object> responseMap = new HashMap<>();
responseMap.put("optionalQueryString", optionalQueryString.orElse(null));
responseMap.put("optionalQueryLong", optionalQueryLong.orElse(null));
responseMap.put("optionalQueryDate", optionalQueryDate.map(OffsetDateTime::toString).orElse(null));
responseMap.put("optionalQueryUUID", optionalQueryUUID.map(UUID::toString).orElse(null));
responseMap.put("optionalHeaderUUID", optionalHeaderUUID.map(UUID::toString).orElse(null));
responseMap.put("optionalHeaderString", optionalHeaderString.orElse(null));
responseMap.put("optionalQueryEnum", optionalQueryEnum.orElse(null));
responseMap.put("queryEnum", queryEnum);
responseMap.put("queryUUID", queryUUID.toString());
responseMap.put("queryLong", queryLong);
responseMap.put("pathLong", pathLong);
responseMap.put("headerString", headerString);
responseMap.put("queryIntegerList", queryIntegerList);
return response(responseMap).applicationJson();
}
Proteus has two standard services that extend the io.sinistral.proteus.services.DefaultService
class.
The io.sinistral.proteus.services.DefaultService
extends com.google.common.util.concurrent.AbstractIdleService
and implements the io.sinistral.proteus.services.BaseService
interface.
The ProteusApplication class expects services that implement io.sinistral.proteus.services.BaseService
.
AssetsService
Included in the proteus-core
module.
The AssetsService mounts an asset directory at a given path and is configured in your application.conf
file.
The default configuration:
assets {
# the base path assets will be server from
path = "/public"
# the directory to load the assets from
dir = "./assets"
cache {
# cache timeout for the assets
time = 500
}
}
OpenAPIService
Included in the proteus-openapi
module.
The OpenAPIService generates an openapi-spec file from your endpoints and serves a swagger-ui and spec.
The default configuration serves the spec at {application.path}/openapi.yaml
and the ui at ${application.path}/openapi
.
The service is configured in your application.conf
file.
The default configuration:
openapi {
basePath= ${application.path}"/openapi"
port = ${application.ports.http}
specFilename="openapi.yaml"
openapi="3.0.1"
# openapi info
info {
title = ${application.name}
version = ${application.version}
description="Proteus Server"
}
securitySchemes {
ApiKeyAuth = {
type="apiKey"
in="header"
name="X-API-KEY"
}
}
servers = [
{
url=${application.path}
description="Default Server"
}
]
}
Where are all of the plugins for everything?!?!
Proteus’s design philosophy is a minimal one, so from our perspective managing a long list of plug and play plugins does not make that much sense.
Our experience with other frameworks has been that plugins are swiftly out-dated or hide too much of the underlying implementation to be useful.
However, making your own “plugin” is simple and much more gratifying.
Here is an example AWSModule
that provides AWS S3 and SES support.
This example assumes you have defined the relevant aws properties in your config file:
public class AWSModule extends AbstractModule
{
private static Logger log = LoggerFactory.getLogger(AWSModule.class.getCanonicalName());
@Inject
@Named("aws.accessKey")
protected String accessKey;
@Inject
@Named("aws.secretKey")
protected String secretKey;
public void configure()
{
AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
AWSStaticCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(credentials);
bind(AWSStaticCredentialsProvider.class).toInstance(credentialsProvider);
AmazonS3Client s3Client = (AmazonS3Client) AmazonS3ClientBuilder.standard().withCredentials(credentialsProvider).withRegion("us-west-2").build();
TransferManager transferManager = TransferManagerBuilder.standard().withMultipartUploadThreshold(8000000L).withS3Client(s3Client).withExecutorFactory(new ExecutorFactory()
{
@Override
public ExecutorService newExecutor()
{
ThreadFactory threadFactory = new ThreadFactory()
{
private int threadCount = 1;
public Thread newThread(Runnable r)
{
Thread thread = new Thread(r);
thread.setName("s3-transfer-manager-worker-" + threadCount++);
return thread;
}
};
return (ThreadPoolExecutor) Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2, threadFactory);
}
}).build();
AmazonSimpleEmailServiceAsyncClient sesClient = (AmazonSimpleEmailServiceAsyncClient) AmazonSimpleEmailServiceAsyncClientBuilder.standard().withCredentials(credentialsProvider).withRegion("us-west-2").build();
bind(AmazonSimpleEmailServiceAsyncClient.class).toInstance(sesClient);
bind(AmazonS3Client.class).toInstance(s3Client);
bind(TransferManager.class).toInstance(transferManager);
}
}
Now you can simply inject these in any controller by adding the following lines to the top of the controller:
@Inject
protected TransferManager transferManager;
@Inject
protected AmazonS3Client s3Client;
@Inject
protected AmazonSimpleEmailServiceAsyncClient sesClient;
Also, please note that the implementation of ProteusApplication
you are using would also need to add the following line before starting:
app.addModule(AWSModule.class);
Proteus takes your MVC controller classes and methods decorated with Swagger / JAX-RS annotations and generates native Undertow handler classes at runtime.
By default, the configuration is loaded into a com.typesafe.config.Config
from a file at conf/application.conf
.
@Named
annotated properties of Module
, Service
and controller classes are bound to values found in the configuration.
Proteus applications generally have a main method that creates an instance of io.sinistral.proteus.ProteusApplication
.
Prior to calling start
on the ProteusApplication
instance:
Service
classes via registerService
Module
classes via registerModule
javax.ws.rs.Path
via registerController
Out of the box you get a Swagger UI at /openapi
.
A
Service
extendscom.google.common.util.concurrent.AbstractIdleService
orio.sinistral.proteus.services.DefaultService
.
A
Module
implementscom.google.inject.Module
.
Check out this example that also demonstrates pac4j integration.
Inspired by Play, Jooby, and light-4j.