Skip to content

gofabian/vertx-web-mount

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

34 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Vert.x Web Mounter

GitHub tag (latest SemVer)

This library enables you to define Vert.x web routes with JAX-RS annotations like this:

public class HelloWorldApi {
    @GET
    @Path("/hello")
    @Produces("text/plain")
    public String helloWorld() {
        return "Hello World!";
    }
}

Vert.x and Annotations? WTH!

A Vert.x application consists of explicit code without annotations. That's great! But Spring MVC and JAX-RS show that annotations can be quite useful to define web APIs.

This library is intended for developers who want...

  • Vert.x web routes defined with annotations
  • standardized JAX-RS annotations
  • support for all Vert.x web features
  • a reactive code style
  • a highly customizable library
  • to implement a parser for route definitions with or even without annotations

It is not...

  • not a full-featured implementation of JAX-RS
  • not using blocking handlers by default
  • not based on dependency injection (but support can be added easily)

Getting Started with Maven

Add vertx-web-mount to your pom.xml:

<dependency>
    <groupId>de.gofabian</groupId>
    <artifactId>vertx-web-mount</artifactId>
    <version>0.2.0</version>
</dependency>

Usage

Mount your API definition to a given router:

Vertx vertx = Vertx.vertx();
Router router = Router.router(vertx);

new VertxWebMounter()
    .addApiDefinition(new HelloWorldApi())
    .mount(router);

vertx.createHttpServer()
        .requestHandler(router)
        .listen(8080);

Minimal route definition

A minimal route definition consists of the following parts:

  • route path: @Path("/path/to/endpoint")
  • HTTP method: at least one of @GET, @POST, @PUT, @DELETE, @PATCH, @HEAD, @OPTIONS
@GET
@Path("/")
public void minimalDefinition() {
}

The @Path annotations from class and method are combined:

@Path("/user")
class MinmalApi {
    @GET
    public void pathFromClass() {
        // -> GET /user
    }

    @GET
    @Path("/picture")
    public void pathCombination() {
        // -> GET /user/picture
    }
}

Asynchronous route handlers

We advice you to use a Future as a return type:

@GET
@Path("/good")
public Future<String> nonBlocking() {
    Future<String> future = nonBlockingCallToDatabase();
    return future;
}

You should never block the route handler. DON'T do stuff like this!!!

@GET
@Path("/bad")
public String blocking() throws InterruptedException {
    String result = blockingCallToDatabase();   // bad!!!
    Thread.sleep(1000);                         // bad!!!
    return result;
}

Request body

The request body is provided as a method parameter without an annotation.

Define accepted content types with @Consumes:

@POST
@Path("/upload")
@Consumes("text/plain")
public void text(String requestBody) {
}

Supported request body readers ordered by priority:

  1. by content type application/json with Jackson
  2. by type Buffer for raw access
  3. by Jackson value conversion (fallback)

As a fallback we try to convert the request body according to the parameter type with Jackson value conversion like this:

JavaType javaType = Json.mapper.constructType(paramType);
T paramValue = Json.mapper.convertValue(string, javaType);

Response body

The method return value is written into the HTTP response body.

Use @Produces to define the content type:

@GET
@Path("/text")
@Produces("text/plain", "text/poetry")
public String text() {
    // -> Response body: 'some text'
    return "some text";
}

We use the official Vert.x ResponseContentTypeHandler to negotiate the content type according to the Accept header.

Supported response body writers ordered by priority:

  1. by return type Buffer for raw access
  2. by content type application/json with Jackson
  3. by content type text/* and type String
  4. fallback with toString() conversion

If the response body is empty the HTTP status will be 204 No Content.

JSON support

JSON content is converted with Jackson.

Example:

class User {
    String name = "george";
}

@POST
@Path("/json")
@Consumes("application/json")
@Produces("application/json")
public User text(User user) {
    // -> Response body: '{ "name": "tom" }'
    user.name = "tom";
    return user;
}

Request parameters

Access path parameters with @PathParam:

@GET
@Path("/users/:id")
public void getUser(@PathParam("id") int id) {
}

Access query parameters with @QueryParam. You can access the first occurrence of the query parameter or all occurrences as a List:

@GET
@Path("/query")
public void text(@QueryParam("name") String name, 
                 @QueryParam("name") List<String> names) {
    // example: GET /query?name=foo&name=bar
}

Request parameters are mandatory by default. Make them optional with @DefaultValue:

@GET
@Path("/query")
public void text(@QueryParam("name") @DefaultValue("smith") String name) {
}

The default value of @QueryParam may be a comma separated list.

We try to convert the request parameters according to the parameter type with Jackson value conversion like this:

JavaType javaType = Json.mapper.constructType(paramType);
T paramValue = Json.mapper.convertValue(string, javaType);

Access RoutingContext

You can access the RoutingContext directly. The @Context annotation is optional:

@GET
@Path("/context")
public void context(@Context RoutingContext context) {
}

Additionally you can access the following context parameters:

@GET
@Path("/context")
public void context(@Context Vertx vertx,
                    @Context HttpServerRequest request,
                    @Context HttpServerResponse response,
                    @Context User user) {
}

The parameter values are taken from the RoutingContext instance:

  • Vertx from RoutingContext::vertx()
  • HttpServerRequest from RoutingContext::request()
  • HttpServerResponse from RoutingContext::response()
  • User from RoutingContext.user()

Customization

Vert.x Web Mounter is fully customizable:

new VertxWebMounter()
    // support special request types
    .addRequestReader(new MyRequestReader())
    // support special response types
    .addResponseWriter(new MyResponseWriter())
    // support custom method parameters
    .addParamProviderFactory(new MyParamProviderFactory())

    // your own route definition parser
    .setRouteDefinitionFactory(new MyRouteDefinitionFactory())
    // support for Kotlin coroutines etc...
    .setRouteDefinitionInvoker(new MyRouteDefinitionInvoker())

    .addApiDefinition(new JaxRsApi())
    .mount(router);

Custom request body reader

You can add a custom RequestReader that reads the request and provides the result as a body parameter.

This example adds support for XML content with

  • content type application/xml and
  • method parameters of type String
@POST
@Path("/")
@Consumes("application/xml")
public void route(String body) {
}

public class XmlRequestReader implements RequestReader {
    @Override
    public boolean supports(RoutingContext context, Type type) {
        String contentType = context.request().getHeader("content-type");
        return "application/xml".equals(contentType) && type == String.class;
    }

    @Override
    public Object read(RoutingContext context, Type type, RequestReader delegate) {
        return context.getBodyAsString();
    }
}

The read method has a delegate RequestReader that is a composite of all available request readers. The delegate can be helpful if you want to support wrapper classes:

@POST
@Path("/")
@Consumes("application/json")
public void route(MyRequest<Person> request) {
}

public class MyRequestReader implements RequestReader {
    @Override
    public boolean supports(RoutingContext context, Type type) {
        if (type instanceof ParameterizedType) {
            return ((ParameterizedType) type).getRawType() == MyRequest.class;
        }
        return false;
    }

    @Override
    public Object read(RoutingContext context, Type type, RequestReader delegate) {
        // MyRequest<T>  with  T == genericType
        Type genericType = ((ParameterizedType) type).getActualTypeArguments()[0];
        Object value = delegate.read(context, genericType, delegate);
        return new MyRequest(value);
    }
}

Have a look at the built-in sub classes of RequestReader for more examples.

Custom response body writer

You can add a custom ResponseWriter that writes the route result to the response body:

@GET
@Path("/")
@Produces("text/awesome")
public String route() {
}

public class AwesomeResponseWriter implements ResponseWriter {
    @Override
    public boolean write(RoutingContext context, Object result, ResponseWriter delegate) {
        String contentType = context.getAcceptableContentType();
        if (!"text/awesome".equals(contentType)) {
            return false;
        }

        context.response()
                .putHeader("content-type", "text/awesome")
                .end("awesome: " + result);
        return true;
    }
}

Have a look at the built-in sub classes of ResponseWriter for more examples.

Custom parameter provider

With a custom ParamProviderFactory you can implement a provider for every type of method parameters e. g. body, path, query or context parameter. Or you can extend the behaviour of an existing parameter provider.

A simple provider for the Vertx instance:

public class VertxParamProviderFactory implements ParamProviderFactory {
    @Override
    public boolean supports(ParamDefinition paramDefinition) {
        return paramDefinition.getType() == Vertx.class;
    }

    @Override
    public ParamProvider createParamProvider(ParamDefinition paramDefinition) {
        return context -> context.vertx();
    }
}

Have a look at the built-in sub classes of ParamProviderFactory for more examples.

Implement your own parser

Interface: RouteDefinitionFactory

Built-in implementation: JaxRsRouteDefinitionFactory

Implement your own method invoker

Interface: RouteDefinitionInvoker

Built-in implementation: MethodRouteDefinitionInvoker

License

This project is licensed under the MIT License - see the LICENSE.md file for details

About

Mount your Vert.x web routes with JAX-RS annotations

Resources

License

Stars

Watchers

Forks

Packages

No packages published