Serverx
A Vert.x-powered asynchronous multithreaded web server with simple annotation-based configuration of routes
Install / Use
/learn @lukehutch/ServerxREADME
Serverx
Serverx is a Vert.x-powered asynchronous multithreaded web server with simple annotation-based configuration of routes.
Serverx strives to enable the setup of a web server with a minimum amount of code, by encapsulating a large amount
of Vert.x boilerplate into a reusable library, and by allowing for flexible
and simple access to a wide array of features of the vertx-core and vertx-web projects.
Serverx is designed to be secure by default:
- all reasonable security headers are enabled by default
- the built-in template engine follows all OWASP guidelines for escaping parameters
- XSS protection measures are put in place automatically
- CSRF protection is put in place automatically (the built-in template engine automatically inserts CSRF tokens into HTML forms, and the server will check the token on non-
GETrequests) - SSL (and HTTP/2 + ALPN) is enabled by default, and
http://requests automatically redirect tohttps://requests with the same path - routes require a user to be authenticated by default
- cookies are HTTPS-only and disallowed for Javascript requests
- the static resource handler disables directory listing and access to hidden files/dirs
- CORS origin and allowed request methods can be configured
Serverx also strives to be as fast and scalable as possible by:
- building on the asynchronous multithreaded core of Vert.x
- using internal compilation and caching of HTML templates
- using
MethodHandleto perform required reflective access checks (for template models) at server startup time rather than at tempate rendering time
Serverx is a new project, and is functional, but has not yet been tested widely.
Setting up routes
When you start the Serverx server, it will scan the classes in your project (using ClassGraph) to find route handlers, start a Vertx server, and automatically add the discovered routes.
Route handlers should be annotated with
serverx.route.Route,
and should implement RouteHandler<T>
for some response object type T. The second parameter of the handle method will have type Future<T>. The value that
this Future is completed with will be referred to below as the response object.
Serving text/plain responses
You can serve the string Hello world with MIME type text/plain from the URL path /hello/world as follows:
@Route(path = "/hello/world", requireLogin = false, responseType = ResponseType.STRING)
public class HelloWorld implements RouteHandler<String> {
@Override
public void handle(final RoutingContext ctx, final Future<String> response) {
response.complete("Hello world");
}
}
Things to observe:
- The
Futureis completed with an object of typeString, which is also the parameter type of theRouteHandler. - If you do not specify a
pathparameter, the handler will match all request paths. - If you do not specify a
methodoradditionalMethodsparameter (e.g.POST), the handler will default to only handlingGETrequests. requireLogin = truerequires the user to authenticate to view the route. This is the default, for security reasons, so if you do not need the user to be authenticated, you need to specifyrequireLogin = false.
If responseType = ResponseType.STRING is set in the annotation, then the toString() method is called on the response object. In other words, the response object type does not have to be String. For example, the following handler will respond with the text [x, y, z], since that is the value returned by List::toString for the returned list:
@Route(path = "/xyz", requireLogin = false, responseType = ResponseType.STRING)
public class XYZ implements RouteHandler<List<String>> {
@Override
public void handle(final RoutingContext ctx, final Future<List<String>> response) {
response.complete(Arrays.asList("x", "y", "z"));
}
}
If the value of the response object is null, the empty string will be returned in the HTTP response.
Serving application/json responses
Instead of responding with a string and MIME type text/plain, you can serve any Java object as JSON by simply changing
the responseType parameter of the annotation to ResponseType.JSON:
@Route(path = "/widget", requireLogin = false, responseType = ResponseType.JSON)
public class WidgetHandler implements RouteHandler<Widget> {
@Override
public void handle(final RoutingContext ctx, final Future<Widget> response) {
response.complete(new Widget("Fancy Widget", 100, Currency.DOLLARS));
}
}
Serving text/html responses
Rendering HTML templates
You can render any Java object into HTML by simply changing the responseType parameter of the annotation to
ResponseType.HTML, and then ensuring that the response object type is an implementation of TemplateModel:
@Route(path = "/datetoday.html", requireLogin = false, responseType = ResponseType.HTML)
public class DateHandler implements RouteHandler<DateToday> {
@Override
public void handle(final RoutingContext ctx, final Future<DateToday> response) {
var date = new SimpleDateFormat("yyyy-MM-dd").format(Calendar.getInstance().getTime());
response.complete(new DateToday(date));
}
}
Note that responseType = ResponseType.HTML is the default, so this can be omitted.
The response object must implement TemplateModel, so that its templates can be found by classpath scanning
on startup. A TemplateModel implementation that contains a field of a given name (e.g. date) will be rendered
into HTML by substituting the field into a parameter with the same name as the field, surrounded by double
curly braces {{...}} (e.g. {{date}}):
package datetoday;
import serverx.template.TemplateModel;
public class DateToday implements TemplateModel {
public String date;
public static final String _template = "<b>Today's date is:</b> {{date}}";
public DateToday(String date) {
this.date = date;
}
}
Fields of a TemplateModel are generally converted to strings and then HTML-escaped
(using OWASP-compliant escaping, and using appropriate escaping for both text and HTML attributes),
before being inserted into the HTML template. (The exception is if a field's type is itself TemplateModel,
which causes that field to itself be rendered as a template.)
A TemplateModel class should generally have a default HTML template, which can be specified either by including
a field public static final String _template in the TemplateModel implementation itself, or by adding a file
with the extension .html and the same name as the TemplateModel implementation in the same package
(i.e. datetoday/DateToday.html for the class datetoday.DateToday).
If the value of any field (e.g. x) is null, any use of the corresponding template parameter (e.g. {{x}})
will be replaced with the empty string (i.e. to not include a template parameter, set the corresponding field to
null).
You can override the default HTML template for any TemplateModel by specifying htmlTemplatePath in the Route
annotation:
@Route(path = "/mobile/weatherforecast.html", requireLogin = false,
// Override HTML template with mobile-friendly template for mobile route path:
htmlTemplatePath = "/templates/mobile/weather.html")
public class WeatherHandler implements RouteHandler<Weather> {
@Override
public void handle(final RoutingContext ctx, final Future<Weather> response) {
// Get weather asynchonously, then complete the response
WeatherForecast.getWeather(response);
}
}
If a TemplateModel has no default HTML template, you will need to manually specify an htmlTemplatePath
to use for each Route.
Rendering nested HTML templates
When using responseType = ResponseType.HTML, if the response object, which must be a TemplateModel, has fields
of type TemplateModel, they will be recursively rendered into HTML fragments, and inserted at the corresponding
template parameter position as normal.
public class OuterWidget implements TemplateModel {
public String name;
public InnerWidget innerWidget;
public static final String _template = "<b>{{name}}</b>: {{innerWidget}}";
}
public class InnerWidget implements TemplateModel {
public int count;
public static final String _template = "current count: {{count}}";
}
(The rendering of nested TemplateModel instances can currently only use the default template for the TemplateModel
implementing class. This may be changed in future by extending the syntax of the template parameters, e.g.
using {{paramName template="/path/to/override-template.html"}}, but this is not currently supported.)
Rendering complete HTML pages
Note that all the HTML template examples given so far render only an HTML fragment, not a complete page.
An HTML template can contain a complete HTML page, but usually all or most pages on a site need to use the
same surrounding page content, and only the <title> and <body> elements need to change on a page-by-page
basis.
Consequently, if a TemplateModel implementation contains a field public String _title, after the TemplateModel
has been rendered into an HTML fragment, the value of the _title field is inserted into the {{_title}} parameter
of a page template, and the value of the rendered HTML fragment is inserted into the {{_body}} parameter of
the page template.
The [default page template](https://git
