SkillAgentSearch skills...

Serverx

A Vert.x-powered asynchronous multithreaded web server with simple annotation-based configuration of routes

Install / Use

/learn @lukehutch/Serverx
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

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-GET requests)
  • SSL (and HTTP/2 + ALPN) is enabled by default, and http:// requests automatically redirect to https:// 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 MethodHandle to 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 Future is completed with an object of type String, which is also the parameter type of the RouteHandler.
  • If you do not specify a path parameter, the handler will match all request paths.
  • If you do not specify a method or additionalMethods parameter (e.g. POST), the handler will default to only handling GET requests.
  • requireLogin = true requires 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 specify requireLogin = 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

View on GitHub
GitHub Stars19
CategoryDevelopment
Updated4y ago
Forks1

Languages

Java

Security Score

75/100

Audited on Feb 28, 2022

No findings