All Downloads are FREE. Search and download functionalities are using the official Maven repository.

templates.docs.requestContext.html Maven / Gradle / Ivy

There is a newer version: 2.2.0
Show newest version
{#==========================================
Docs : "Request Context"
==========================================#}

The Request Context

The Request Context is the object associated with the current request that Spincast passes to your matching Route Handlers. Its main purpose is to allow you to access information about the request, and to build the response to send.

Those functionalities are provided by simple methods, or by add-ons. What we call an "add-on" is an intermediate class containing a set of methods made available through the Request Context parameter. Here's an example of using the routing() add-on :

{% verbatim %}

public void myHandler(DefaultRequestContext context) {

    if(context.routing().isNotFoundRoute()) {
        //...
    }
}
{% endverbatim %}

This routing() add-on is available to any Route Handler, via its Request Context parameter, and provides a set of utility methods.

Here are some add-ons and some standalone methods available by default on a Request Context object : {% verbatim %}

public void myHandler(DefaultRequestContext context) {

    // Accesses the request information
    String name = context.request().getPathParam("name");

    // Sets the response
    context.response().sendPlainText("Hello world");

    // Gets information about the routing process and the current route
    boolean isNotFoundRoute = context.routing().isNotFoundRoute();

    // Gets/Sets request-scoped variables
    String someVariable = context.variables().getAsString("someVariable");

    // Direct access to the Json manager
    JsonObject jsonObj = context.json().create();

    // Direct access to the XML manager
    JsonObject jsonObj2 = context.xml().fromXml("<someObj></someObj>");

    // Direct access the Guice context
    SpincastUtils spincastUtils = context.guice().getInstance(SpincastUtils.class);

    // Direct access to the Templating Engine
    Map<String, Object> params = new HashMap<String, Object>();
    params.set("name", "Stromgol");
    context.templating().evaluate("Hello {{name}}", params);
    
    // Gets the best Locale to use for the current request
    Locale localeToUse = context.getLocaleToUse();
    
    // Gets the best TimeZone to use for the current request
    TimeZone timeZoneToUse = context.getTimeZoneToUse();
    
    // Sends cache headers
    context.cacheHeaders().cache(3600);
    
    // ...
}
{% endverbatim %}

Again, the main job of the Request Context is to allow the Route Handlers to deal with the request and the response. But it's also an extensible object on which various functionalities can be added to help the Route Handlers do their job! Take the "templating()" add-on, for example:

{% verbatim %}

public void myRouteHandler(DefaultRequestContext context) {

    Map<String, Object> params = new HashMap<String, Object>();
    params.set("name", "Stromgol");
    String content = context.templating().evaluate("Hi {{name}}!", params);

    // Do something with the evaluated content...
}
{% endverbatim %}

The templating() add-on does not directly manipulate the request or the response. But it still provides a useful set of methods for the Route Handlers.

If you have experience with Guice, or with dependency injection in general, you may be thinking that we could simply inject a TemplatingEngine instance in the controller and access it that way :

{% verbatim %}

public class AppController {

    private final TemplatingEngine templatingEngine;

    @Inject
    public AppController(TemplatingEngine templatingEngine) {
        this.templatingEngine = templatingEngine;
    }

    protected TemplatingEngine getTemplatingEngine() {
        return this.templatingEngine;
    }

    public void myRouteHandler(DefaultRequestContext context) {

        Map<String, Object> params = new HashMap<String, Object>();
        params.set("name", "Stromgol");
        String content = getTemplatingEngine().evaluate("Hi {{name}}!", params);

        // Do something with the evaluated content...
    }
}
{% endverbatim %}

The two versions indeed lead to the exact same result. But, for functionalities that are often used inside Route Handlers, or for functionalities that should be request scoped, extending the Request Context can be very useful.

Imagine a plugin which job is to manage authentification and autorization. Wouldn't it be nice if this plugin could add some extra functionalities to the Request Context object? For example :

public void myHandler(ICustomRequestContext context) {

    if(context.auth().isAuthenticated()) {
        String username = context.auth().user().getUsername();
        // ...
    }
}

There is some boilerplate code involved to get such custom Request Context type but, when it's in place, it's pretty easy to tweak and extend. In fact, we highly recommend that you use a custom Request Context as soon as possible in your application. That way, you will be able to easily add add-ons when you need them.

If you use the Quick Start as a start for your application, a custom Request Context type is already provided. But if you start from scratch, an upcoming section will show you how to extend the default Request Context type, by yourself.

{#========================================== Section "Request Context / default add-ons" ==========================================#}

The default add-ons

There are add-ons which are always available on a Request Context object, in any Spincast application. Let's have a quick look at them :

  • RequestRequestContextAddon<R> request()

    The request() add-on allows access to information about the current request: its body, its headers, its URL, etc. The default implementation, SpincastRequestRequestContextAddon, is provided by the Spincast Request plugin. Check this plugin's documentation for all the available API.

    Examples :

    // Gets the request full URL
    String fullUrl = context.request().getFullUrl();
    
    // Gets the request body as a JsonObject
    JsonObject body = context.request().getJsonBody();
    
    // Gets a HTTP header
    String authorization = context.request().getHeaderFirst(HttpHeaders.AUTHORIZATION);
    
    // Gets a cookie value
    String sesionId = context.request().getCookie("sess");
    
    // Gets a queryString parameter
    String page = context.request().getQueryStringParamFirst("page");
    
    // Gets the value of a dynamic path token.
    // For example for the route "/users/${userId}"
    String userId = context.request().getPathParam("userId");

  • ResponseRequestContextAddon<R> response()

    The response() add-on allows you to build the response : its content, its content-type, its HTTP status, its headers. The default implementation, SpincastResponseRequestContextAddon, is provided by the Spincast Response plugin. Check this plugin's documentation for all the available API.

    Examples :

    // Sets the status code
    context.response().setStatusCode(HttpStatus.SC_FORBIDDEN);
    
    // Sets a HTTP header value
    context.response().setHeader(HttpHeaders.CONTENT_LANGUAGE, "en");
    
    // Sets the content-type
    context.response().setContentType(ContentTypeDefaults.JSON.getMainVariation());
    
    // Sets a cookie
    context.response().setCookie("locale", "en-US");
    
    // Permanently redirects to a new url (the new url
    // can be absolute or relative). A Flash message
    // can be provided.
    context.response().redirect("/new-url", true, myFlashMessage);
    
    // Adds an element to the response model
    context.response().getModel().set("name", "Stromgol");
    
    // Sends the response model as Json
    context.response().sendJson();
    
    // Sends some bytes
    context.response().sendBytes("Hello World".getBytes("UTF-8"));
    
    // Sends a specific object as Json
    context.response().sendJson(user);
    
    // Sends HTML evaluated from a template, using the response 
    // model to provide the required variables
    context.response().sendHtmlTemplate("/templates/user.html");

  • RoutingRequestContextAddon<R> routing()

    The routing() add-on allows you to get information about the current routing process.

    Examples :

    // Gets all the matches returned by the Router.
    IRoutingResult<DefaultRequestContext> routingResult = context.routing().getRoutingResult();
    
    // Gets the current match : the Route Handler, its position
    // and its parsed path parameters.
    RouteHandlerMatch<DefaultRequestContext> currentMatch =
            context.routing().getCurrentRouteHandlerMatch();
    
    // Is the current Route a "Not found" one?
    boolean isNotFoundRoute = context.routing().isNotFoundRoute();
    
    // Are we currently on a Route to handle an exception?
    boolean isExceptionHandling = context.routing().isExceptionRoute();

  • TemplatingRequestContextAddon<R> templating()

    The templating() add-on gives access to the Templating Engine functionalities.

    Examples :

    {% verbatim %}

    Map<String, Object> params = new HashMap<>();
    params.set("name", "Stromgol");
    
    // Evaluation of inline content 
    String html = context.templating().evaluate("Hello {{name}}", params);
    
    // Evaluation of a template file
    String html = context.templating().fromTemplate("/templates/user.html", params);
    {% endverbatim %}

  • VariablesRequestContextAddon<R> variables()

    The variables() add-on allows you to add variables which are request scoped. They will only be available to the components accessing the current request. They are a good way to make a Route Handler communicate some informations to others.

    Examples :

    // Gets a request scoped variable as a JsonObject.
    JsonObject info = context.variables().getAsJsonObject("someObjectName");
    
    // Gets a request scoped variable as a String.
    String info = context.variables().getAsString("someKey");
    
    // Adds a new request scoped variable 
    context.variables().set("someKey", "someValue");
    

  • CacheHeadersRequestContextAddon<R> cacheHeaders()

    The cacheHeaders() add-on allows you to validate the HTTP cache headers sent by the client and to add such headers for the requested resource. Have a look at the HTTP Caching section for more information.

    Examples :

    // Tells the client to cache the resource for 3600 seconds
    context.cacheHeaders().cache(3600);
    
    // Tells the client to disable any cache for this resource
    context.cacheHeaders().noCache();
    
    // ETag and last modification date validation
    if(context.cacheHeaders().eTag(resourceEtag).lastModified(modifDate).validate(true)) {
        return;
    }

  • JsonManager json()

    Provides easy access to the JsonManager,
    for Json related methods.

  • XmlManager xml()

    Provides easy access to the XmlManager,
    for XML related methods.

  • Injector guice()

    Provides easy access to the Guice context of the application.

  • <T> T get(Class<T> clazz)

    Shortcut to get an instance from the Guice context. Will also cache the instance (as long as it is request scoped or is a singleton).

  • Locale getLocaleToUse()

    The best Locale to use for the current request, as found by the LocaleResolver.

  • TimeZone getTimeZoneToUse()

    The best TimeZone to use for the current request, as found by the TimeZoneResolver.

  • Object exchange()

    The underlying "exchange" object, as provided by the HTTP Server.
    If you know for sure what the implementation of this object is, you may cast it to access extra functionalities not provided by Spincast out of the box.

{#========================================== Section "Request Context / Extending the Request Context type" ==========================================#}

Extending the Request Context

Extending the Request Context is probably to most advanced thing to learn about Spincast. Once in place, a custom Request Context is quite easy to adjust and extend, but the required code to start may be somewhat challenging. This is why we recommend that you start your application with the Quick Start! This template already contains a custom Request Context type, so you don't have to write the bootstrapping code by yourself! But if you start from scratch or if you are curious about how a custom Request Context type is possible, keep reading.

First, let's quickly repeat why we could want to extend the default Request Context type... There may be a "translate(...)" method on some class and we frequently use it by our various Route Handlers. Let's say this is a method helping translate a sentence from one language to another.

Instead of injecting the class where this method is defined each time we need to use it, wouldn't it be nice if we would have access to it directly from a Request Context object? For example:

public class AppController {

    public void myRouteHandler(AppRequestContext context) {
        String translated = context.translate("Hello World!", Locale.ENGLISH, Locale.FRENCH);
        // ...
    }
}

Since this method doesn't exist on the default RequestContext interface, we'll have to create a custom type and add the method to it. In the previous snippet, this custom type is called "AppRequestContext".

Let's create this custom Request Context type...

public interface AppRequestContext extends RequestContext<AppRequestContext> {

    public void translate(String sentense, Locale from, Locale to);

    // Other custom methods and/or add-ons...
}

Note that we extend RequestContext, which is the base interface for any Request Context, but we parameterize it using our custom type. This is required because the base interface needs to know about it.

Then, the implementation:

public class AppRequestContextDefault extends RequestContextBase<AppRequestContext>
                                      implements AppRequestContext {

    @AssistedInject
    public AppRequestContextDefault(@Assisted Object exchange, 
                                    RequestContextBaseDeps<AppRequestContext> requestContextBaseDeps) {
        super(exchange, requestContextBaseDeps);
    }

    @Override
    public String translate(String sentense, Locale from, Locale to) {
        
        // More hardcoded than translated here!
        return "Salut, monde!";
    }
}

Explanation :

  • 1 : We extend RequestContextBase, to keep the default methods implementations and simply add our custom one. We also need to parameterize this base class with our custom AppRequestContext type.
  • 2 : We implement our custom interface.
  • 4-8 : The base class requires the server's exchange object and a RequestContextBaseDeps parameter, which are going to be injected using an assisted factory. Don't worry too much about this. Simply add this constructor, and things should be working.
  • 10-15 : We implement our new translate(...) method.

Last, but not the least, we need to tell Spincast about our new custom Request Context type! This is done by using the requestContextImplementationClass(...) of the Bootstrapper :

public static void main(String[] args) {

    Spincast.configure()
            .module(new AppModule())
            .requestContextImplementationClass(AppRequestContextDefault.class)
            .init(args);
    //....
}

Note that it is the implementation, "AppRequestContextDefault", that we have to specify, not the interface! This is to simplify your job : Spincast will automatically find the associated interface and will use it to parameterize the required components.

And that's it! From now on, when you are using a routing related component, which has to be parameterized with the Request Context type, you use your new custom type. For example:


Router<AppRequestContext, DefaultWebsocketContext> router = getRouter();

router.GET("/").handle(context -> {
    String translated = context.translate("Hello World!", Locale.ENGLISH, Locale.FRENCH);
    // do something with the translated sentence...
});

Or, using an inline Route Handler:


Router<AppRequestContext, DefaultWebsocketContext> router = getRouter();

router.GET("/").handle(new Handler<AppRequestContext>() {

    @Override
    public void handle(AppRequestContext context) {
        String translated = context.translate("Hello World!", Locale.ENGLISH, Locale.FRENCH);
        // do something with the translated sentence...
    }
});

(You may have motice that the parameterized version of the Router doesn't simply contain a Request Context type, but also a Websocket context type. This is because this type can also be extended.)

This may seem like a lot of boilerplate code! But it has to be done only one time and, once in place, it's easy to add new methods and add-ons to your Request Context objects! Also, using a unparameterized version of those generic components, it's way nicer. Let's see how to creat those unparameterized versions...

Using unparameterized components

You can do for your custom types what we already did for the default ones : to create an unparameterized version for each of them. For example, here's how the provided DefaultRouter is defined :

public interface DefaultRouter extends Router<DefaultRequestContext, DefaultWebsocketContext> {
    // nothing required
}

This interface has no other goal than to "hide" the parameterization, to be more visually appealing, more easy to use... Thanks to this definition, you can inject DefaultRouter instead of Router<DefaultRequestContext, DefaultWebsocketContext>, which is arguably nicer. Both types are interchangeable.

You can do the exact same thing with your custom Route Context type :

public interface AppRouter extends Router<AppRequestContext, DefaultWebsocketContext> {
    // nothing required
}

Now, you can inject AppRouter instead of Router<AppRequestContext, DefaultWebsocketContext> when you need an instance of your custom router! Here again, it's a matter of taste... Noth types are interchangeable.

For more details, have a look at the Quick Start application. It implements exactly this.





© 2015 - 2024 Weber Informatics LLC | Privacy Policy