Giter VIP home page Giter VIP logo

molecule's Introduction

Build Status Coverage Status Maven Central License

Quick Start

public class HelloWorld {
    public static void main(String[] args) throws IOException {
        WebServer server = WebServer.create();
        server.start(request -> Response.ok().done("Hello, World"));
    }
}

Access your application at:

http://localhost:8080

About

Molecule is an HTTP toolkit for Java, with no dependencies. It is

  • fast and small,
  • easy to use and extend,
  • fully tested.

Molecule is built around the simple concept of Application as a Function:

  • An Application is an asynchronous function of (Request) -> Response, typically representing some remote endpoint or service.
  • A Middleware is a simple function of (Application) -> Application that implements application-independent functionality. A middleware is composed with an application to modify application behavior.

Molecule is great for building microservices or regular web applications. It is designed around simplicity, testability and freedom of choice. Built entirely using TDD it provides super-easy ways to test your application and individual endpoints, both in and out of container.

Molecule is small - it weights less than 150k - and will stay as lean as possible. It is pluggable through the concept of middlewares (a.k.a filters). It offers various abstractions for a number of functionalities (such as routing, templating, security, etc.). You're free to use the built-in options or provide your own implementations.

Molecule requires Java 8. It runs an embedded web-server powered by your choice of Undertow or Simple. Both are fully asynchronous and non-blocking, which means they can scale to very high loads.

Have fun!

Download

You can get the latest release version from Maven Central:

<dependency>
      <groupId>com.vtence.molecule</groupId>
      <artifactId>molecule</artifactId>
      <version>0.14.0</version>
</dependency>

To choose Undertow as your web server, add Undertow to your dependencies:

<dependency>
      <groupId>io.undertow</groupId>
      <artifactId>undertow-core</artifactId>
      <version>2.2.16.Final</version>
      <scope>runtime</scope>
</dependency>

To use Simple as your web server, add Simple instead as a dependency:

<dependency>
      <groupId>org.simpleframework</groupId>
      <artifactId>simple-http</artifactId>
      <version>6.0.1</version>
      <scope>runtime</scope>
</dependency>

Want to start with some code?

Try out the following examples:

Getting Started

First thing first, you need a server to run your app:

WebServer server = WebServer.create();

This will set the web server to run locally on port 8080.

To start the server, give it an app:

server.start(request -> Response.ok().done("Hello, World!"));

To stop the server, call the stop method:

server.stop()

You can optionally specify the interface and port to bound to when creating the server, e.g. if you want to make your server globally available:

WebServer server = WebServer.create("0.0.0.0", 8088);

Asynchronous Processing

Molecule uses Undertow as a default web server. You have the choice to run using Simple instead. Both are fully asynchronous and non-blocking. This allows the server to scale to very high loads and handle as many concurrent connections as possible, even when depending on a high latency external resource.

What this means is you can serve your response content from a thread separate to the original servicing thread. For instance your application might need to wait for some remote process that takes some time to complete, such as an HTTP or SOAP request to an external server. You can simply call this external resource from a different thread, and complete the response when you get the result.

To tell the server that you're ready to serve the response, call the done method on the response.

Look at the Asynchronous example to see how to serve content from a separate thread.

HTTP/2

Undertow supports HTTP/2 (unfortunately Simple does not support it). Enabling HTTP/2 must be done before starting the WebServer :

WebServer server = WebServer.create();
server.enableHTTP2();

Routing

Most modern webapps have nice URLs. Simple URLs are also easier to remember and more user friendly.

Molecule comes with a routing middleware that let you define your URL routes.

Routes let you map incoming requests to different applications based on the request verb and path. A route is composed of a path pattern, an optional set of verbs to match, and an application endpoint:

server.route(new Routes() {{
    get("/posts/:id").to(request -> {
        // retrieve a given post
    });
    
    post("/posts").to(request -> {
        // create a new post
    }); 
    
    put("/posts/:id").to(request -> {
        // update an existing post
    });
    
    delete("/posts/:id").to(request -> {
        // delete a post
    }); 
    
    map("/").to(request -> {
        // show the home page
    })
}});

To start the server with the router use Server#route. For an example have a look at Dynamic Routes.

Matching

Routes are matched in the order they are defined. If not defined route matches, the default behaviour is to render a 404 Not Found. This can be configured to pass the control to any default application.

By default, a route matches a single verb, specified by the method you use, i.e. get, post, put, delete. That can be changed by providing the verbs as arguments to the via method:

map("/").via(GET, HEAD).to(request -> {
    // show the home page
});

If you don't provide any verbs, map will match on all verbs.

You can also match based on the HTTP Accept header, such as:

get("/api/posts").accept("application/json").to(request -> {...});

Dynamic Parameters

Route patterns can be matched exactly - they are said to be static - or can include named parameters, which are then accessible as regular request parameters on the request object:

// matches "GET /photos/18" and "GET /photos/25"
// request.parameter("id") is either '18' or '25'
get("/photos/:id").to(request -> {
    Response.ok().done("Photo #" + request.parameter("id"));
});

Custom Matching

You are not limited to the provided match patterns. You can provide your own predicate and decide exactly how to match an incoming url to an application.

To do this, use the route definition methods that accept a Predicate rather than a String.

Working with the Request

Applications receive a Request that provide information about the request coming in from the client.

The request represents the environment for the client request, including the headers, the request body as well as parameters and other common things. The Request object is built from information provided by the HTTP server.

Any middleware can modify the content of the request during processing before passing control to the next stage of the middleware pipeline. This of course has no effect on the original HTTP request. See Middlewares for more information on middlewares and the middleware pipeline.

Request

request.uri();                          // the full uri, e.g. http://localhost:8080/foo?bar
request.url();                          // the full url, e.g. http://www.example.com/foo?bar
request.path();                         // the path info, e.g. /foo
request.query();                        // the query string, e.g. bar
request.remoteIp();                     // ip of the client
request.remoteHost();                   // hostname of the client
request.remotePort();                   // port of the client
request.protocol();                     // protocol, e.g. HTTP/1.1
request.scheme();                       // the scheme
request.hostname();                     // the hostname part of the HOST header
request.port();                         // the port part of the HOST header
request.timestamp();                    // time the request came in
request.secure();                       // whether the request was made over a secure connection
request.method();                       // HTTP method (e.g.  GET, POST, PUT, etc.)
request.body();                         // the body as a string, decoded using the request charset
request.bodyContent();                  // the raw body content as bytes
request.bodyStream();                   // the body as a stream of bytes
request.parts();                        // list of parts of a multipart/form-data request
request.part("name");                   // named part of a multipart/form-data request
request.charset();                      // charset of the body, read from the content type
request.hasHeader("name");              // checks presence of a named header
request.header("name");                 // value of a given HTTP header
request.headers("name");                // list of values of a given HTTP header
request.headerNames();                  // the set of HTTP header names received
request.contentLength();                // length of the body
request.contentType();                  // content type of the body
request.hasParameter("name")            // checks for the presence of a request parameter
request.parameter("name");              // value of a specific request parameter
request.paremeters("name");             // list of values for a specific request parameter
request.parameterNames();               // set of all request parameter names
request.allParameters();                // map of all request parameters
request.attribute("key");               // value of a keyed attribute
request.attribute("key", "value");      // sets the value of a keyed attribute
request.attributeKeys();                // set of all attibute keys
request.removeAttribute("key");         // removes a keyed attribute
request.attributes();                   // map of all request attributes

For the complete documentation, see the Javadoc of the Request class.

Attributes

Request attributes are not sent by the client - as opposed to request parameters. They are used for server-side processing only.

Attributes are a local server storage mechanism, scoped within the request. Whereas request parameters are string literals, request attributes can be any type of Objects.

Working with the Response

Applications respond to client requests by sending data back to the client using the Response.

The response includes a status code and text, headers and an optional body.

Any middleware can modify the content of the response during processing before returning control to the previous stage of the middleware pipeline. See Middlewares for more information on middlewares and the middleware pipeline.

Response

response.status(HttpStatus.OK);         // sets the status
response.header("name", "value");       // sets the single value of a named header
response.addHeader("name", "value");    // adds another value to a named header
response.contentType("text/html");      // sets the Content-Type header of the response
response.contentLength(16384);          // sets the Content-Length header of the response
response.charset("utf-8");              // sets the charset of the response body
response.body("text");                  // sets the response body as text
response.done();                        // sends the response to the client
response.done("text");                  // sends the specified response text to the client

For the complete documentation, see the Javadoc of the Response class.

Note that no response will actually be sent back to the client until the done method is called. Calling done signals the end of the request processing and triggers sending back the status, headers and body.

Rendering

There are a variety of ways to send back a response to the client. You can render text, binary content, the content of a file, XML, JSON, use a view template or render nothing at all. You can specify the content type or HTTP status of the rendered response as well.

Molecule uses the concept of response Body to represent data to send back to the client.

Molecule comes with a few body implementations ready to use for sending text, binary content, the content of a file or a view template.

Rendering an empty response

You can render an empty response by not specifying a body at all. In which case, an empty body is used.

So this is perfectly valid:

response.done();

Rendering text

You can send plain text - with no markup at all - back to the browser like this:

response.done("All good");

Rendering pure text is sometimes useful if you're client is expecting something other than proper HTML.

Rendering HTML

You can send an HTML string back to the browser by using a text body to render:

response.done("<html><body><h1>It Works</h1></body></html>");

This can be useful when you're rendering a small snippet of HTML code. However, you might want to consider moving it to a template file if the markup is complex. See View Templating for more on using templates.

Rendering JSON

You can send back JSON to the browser by using a text body. Here's an example using google Gson library:

Gson gson = new Gson();
response.done(gson.toJSON("ok"));

Another option would be to create a your own body implementation - let's call it JSONBody - and use your preferred JSON serializer:

response.done(new JSONBody("ok"));

or with a static factory method:

response.done(json("ok"));

Rendering XML can be done the same way.

Rendering binary content

You can send back a raw body as binary like this:

byte[] content = ... //
response.body(content).done();

Rendering the content of a file

You can use a FileBody to stream the content of a file:

response.render(new FileBody(new File("/path/to/file")))).done();

This will use a default chunk size of 8K, although you can specify a different chunk size.

Redirection

You can trigger a browser redirect using a See Other (303) status code :

Response.redirect("/url").done();

If you need to use a different status code, simply change the status:

Response.redirect("/url", 301).done(); // moved permanently

Cookies

To enable cookies support first add the Cookies middleware to your middleware pipeline (see Middlewares for more information on using middlewares).

This will create a CookieJar and populate that cookie jar with cookies sent by the client. The cookie jar is available as a request attribute:

CookieJar cookies = CookieJar.get(request);

To read cookies sent by the client:

Cookie customer = cookies.get("customer");

To send back cookies to the client, all you have to do is add cookies to the jar:

cookies.add("weapon", "rocket launcher")
       .path("/ammo")
       .maxAge(30)

To expire a cookie, discard it from the jar:

cookies.discard("weapon").path("/ammo");

For more on cookies, see the Cookies example.

Sessions

Enabling sessions support

To use sessions you need to start your server with session support:

server.add(new Cookies())
      .add(new CookieSessionTracker(CookieSessionStore.secure("your secret");))
      .route(new Routes() {{
      // your routing here
      }});

Since HTTP is a stateless protocol and Molecule uses a minimalist approach, sessions are not part of the request. Usage of sessions requires the use of a middleware. This means you have full control over the way sessions are tracked and stored.

Molecule comes with a CookieSessionTracker middleware that uses cookies to track sessions across HTTP requests and two session storage mechanisms:

  • An in memory session store, the SessionPool
  • A client side storage using encrypted cookies, the CookieSessionStore

Using sessions

Once session support is enabled, the current session is bound to the request:

Session session = Session.get(request);

One thing to understand is that as long as your server is started with session support, there will always be a session bound to the request. There's no need to check against null. There's no need to ask for session creation either.

The session attached to the request can be a fresh and empty session or the session opened in a previous request. This does not mean a new session is automatically opened for each request though. A fresh session is only persisted if it is modified before the end of the request cycle. This means you can safely read from a new session.

If you write data to the session then it is automatically persisted to the session store you've selected - created in case of a new session or updated in case of an existing session. If you invalidate the session, it will be discarded from the session store automatically.

Some of the things you can do with sessions include:

session.fresh()             // checks if session is new
session.id()                // gets the session id
session.createdAt()         // the session creation time
session.updatedAd()         // the last session update time
session.expires()           // whether this session expires
session.expirationTime()    // this session expiration time, if it expires
session.maxAge(30)          // sets the session to expire after 30s of inactivity
session.contains("key")     // checks if this session contains a keyed attribute
session.get("key")          // returns the keyed attribute
session.remove("key")       // removes a keyed attribute
session.put("key", "value") // writes a key value pair to the session
session.clear()             // clears the session content
session.invalidate()        // invalidates the session

For more on using sessions, see the Session Example.

View Templates

When your markup becomes complex, you might want to consider moving it to a template file.

Rendering a template requires a RenderingEngine. To use the built-in JMustacheRenderer, first add JMustache to your dependencies:

<dependency>
    <groupId>com.samskivert</groupId>
    <artifactId>jmustache</artifactId>
    <version>1.13</version>
</dependency>

Rendering a template takes a view model and returns a body object to use with the response. Assuming a Mustache template file named profile.mustache, here's how we would render the template using an hypothetical Employee object:

// Declare the template engine
Templates templates = new Templates(JMustacheRenderer.fromDir(new File("/path/to/template/files")));
// Load the 'profile.mustache' template
Template<Employee> profile = templates.named("profile");
// Render the template using an Employee instance 
response.done(profile.render(new Employee("Bob", "...")));

For further information on using view templates, take a look at the View Templates and Layout example.

View Layouts

Sometimes you want to share a common look and feel for a set of pages. This is when view layouts can help.

Layouts are just templates that are used to decorate existing pages. A layout defines the surroundings of your HTML page. It's where you define a common look and feel of your final output. This reverses the common pattern of including shared headers and footers in many templates to isolate changes in repeated setups.

Think of the layout rendering process in two steps.

The first step processes the generated HTML view to extract its content. This extraction process makes individual pieces of the original page content available to the layout template:

  • the head content, excluding the title
  • the title
  • the body content
  • all the meta parameters

The second step merges the content pieces with the layout template to produce the final result.

Here's a contrived example of a mustache layout template that simply recreates the original page:

<html>
<head>
{{head}}
<title>{{title}}</title>
</head>
<body>
{{body}}
</body>
</html>

The Layout middleware intercepts generated HTML responses and merges them with layout decorator(s) to build the final result:

// Declare the template engine
Templates layouts = new Templates(JMustacheRenderer.fromClasspath("/path/to/layout/templates"));
// Load the main layout template
Template<Map<String, String>> mainLayout = layouts.named("main");

// Apply the main site layout to requests under the / path, in other words to all rendered pages
server.filter("/", Layout.html(mainLayout))
      .route(new Routes() {{
          // Your routes definitions here
          // ...
      }});

For more on using layouts, take a look at the View Templates and Layout example.

SSL

To use HTTPS connections, enable SSL on the WebServer. Enabling HTTPS/SSL requires you to have a keystore file, which you can generate using the Java keytool. You specify the location of your keystore, the keystore password and the keys password like this:

WebServer server = WebServer.create();
server.enableSSL(new File("/path/to/keystore/file"), "keyStorePassword", "keyPassword"))
      .start(...);

This will start the server with TLS enabled, trusting all clients.

If you need more control over the type of keystore and algorithms used, you can alternatively pass an javax.net.ssl.SSLContext to use.

See the SSL example for more details.

Testing

Molecule provides fluent assertion helpers for unit testing your application endpoints and middlewares. It also provides a test HTTP client for integration testing your server.

The test helpers are located in the com.vtence.molecule.testing package. To use them, you need to include Hamcrest to your list of dependencies:

<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest</artifactId>
    <version>2.2</version>
    <scope>test</scope>
</dependency>

If you want to use charset detection, you will also need JUniversalChardet:

<dependency>
    <groupId>com.googlecode.juniversalchardet</groupId>
    <artifactId>juniversalchardet</artifactId>
    <version>1.0.3</version>
    <scope>test</scope>
</dependency>

Unit Testing Middlewares and Application Endpoints

The Request and Response are plain Java objects that you can use directly in your unit tests. They are simple representations of the environment for the client request and the content of the response your application wants to send back. They are not wrappers around the actual HTTP request and response, so there's no need to mock them:

// Call your application endpoint
Response response = application.handle(Request.get("/")
                                              .header("Authorization", "Basic " + mime.encode("joe:bad secret")));
 
// Make assertions on the response
// ...

Fluent Assertions

ResponseAssert provides a fluent interface for asserting the generated response. Based on the previous example:

// ...
assertThat(response).hasStatus(UNAUTHORIZED)
                    .hasHeader("WWW-Authenticate", "Basic realm=\"WallyWorld\"")
                    .hasContentType("text/plain")
                    .isEmpty()
                    .isDone();

RequestAssert helps make assertions on the request:

assertThat(request).hasAttribute("username", "joe");

CookieJarAssert and CookieAssert both provide fluent assertions for Cookies and the CookieJar:

assertThat(cookieJar).hasCookie("session")
                     .hasValue("sJaAMRYetEJhQcS9s283pMrlGoXr88LkFf0NCOTZb8A")
                     .isHttpOnly();

Integration Testing

You can use the test HTTP client to write integration tests for your API or test how various parts of your web application interact:

HttpRequest request = new HttpRequest(8080).followRedirects(false);
HttpResponse response = request.content(Form.urlEncoded().addField("username", "bob")
                                                         .addField("password", "secret"))
                               .post("/login");

Fluent Assertions

HttpResponseAssert and HttpCookieAssert provides fluent interfaces for making assertions on the HTTP response and cookies:

assertThat(response).hasStatusCode(303)
                    .hasHeader("Location", "/users/bob")
                    .hasCookie("session").hasMaxAge(300);

Middlewares

Middlewares are a way to enhance your application with optional building blocks, using a pipeline design. A middleware component sits between the client and the server, processing inbound requests and outbound responses.

Middlewares implement functionality you tend to need across all your applications, but you don't want to build everytime. Things like access logging, authentication, compression, static files, routing, etc.

Being able to separate the processing of the request (and post-processing of the response) in different stages has several benefits:

  • It separate concerns, which helps keep your design clean and application well-structured
  • It let you only include the functionality you need, so your server is as small and fast as possible
  • It let you plug in your own processing stages, to customize the behavior of your application
  • It let you reuse and share middlewares, as elemental building blocks of application behavior

For example you could have the following separate stages of the pipeline doing:

  1. Capturing internal server errors to render a nice 500 page
  2. Monitoring, logging accesses to the server
  3. Authentication and authorisation, to control access to your applicatoin
  4. Caching, returning a cached result if request has already been processed recently
  5. Compression, to reduce bandwith usage
  6. Security, to prevent attacks such as CSRF
  7. Processing, the actual request processing by your application

The Middleware Stack

You can think of the Middleware pipeline as a stack, at the bottom of which is your application. When a request comes in, it starts at the top of the stack and goes down until it is processed by your application. The response then goes up the stack backwards through the middleware chain, before it is sent back to the client.

We call it a middleware stack because each part will call the next part in sequence. Or it might not. In fact there are two types of middlewares. Those that modify the request or response and call the next step in the chain, and those that short circuit the stack and return their own response without ever calling anything further down the stack.

Building the Middleware Stack

The simplest way to build your middleware stack is to add middlewares to your WebServer.

For example, suppose we'd like to enhance performance of our application by adding caching and compression:

WebServer server = WebServer.create();
server.add(new ContentLengthHeader())
      .add(new ConditionalGet())
      .add(new ETag())
      .add(new Compressor())
      .route(new Routes() {{
          // ...
      }});

Here's what a more complete, real-life middleware stack might look like:

server.failureReporter(failureReporter)
      .add(new ServerHeader("Simple/6.0.1"))
      .add(new DateHeader())
      .add(new ApacheCommonLogger(logger))
      // a custom middleware to redirect non secure requests to HTTPS 
      .add(new ForceSSL())
      .add(new ContentLengthHeader())
      .mount("/api", new MiddlewareStack() {{
          use(new Failsafe());
          use(new FailureMonitor(failureReporter));
          use(new ConnectionScope(database));
          // runs the api application
          run(api());
      }})
      .add(new ConditionalGet())
      .add(new ETag())
      .add(new Compressor().compressibleTypes(CSS, HTML))
      // configures the StaticAssets middleware
      .add(staticAssets())
      .add(new HttpMethodOverride())
      .add(new Cookies())
      // a custom middleware to redirect based on the preferred user locale
      .add(selectLocale())
      // a custom middleware to display static pages if case of errors
      .add(new PublicExceptions())
      .add(new Failsafe())
      .add(new FailureMonitor(failureReporter))
      .add(new ConnectionScope(dataSource))
      .add(new CookieSessionTracker(CookieSessionStore.secure("secret")))
      .add(new Flash())
      // starts the main application
      .start(webapp());

For more on using middlewares, take a look at the various code examples (see above), including the sample application.

The javadoc of the WebServer class is another good source of information.

Available Middlewares

Molecule comes with a bunch of handy middlewares that you can use to build your processing pipeline:

  • Router (See Routing)
  • Static Assets
  • File Server
  • Apache Common Logger
  • Apache Combined Logger
  • Cookies (See Cookies)
  • Locale Negotiation
  • Compressor
  • ETag
  • Conditional Get
  • Connection Scope
  • Server Header
  • Date Header
  • Content-Length Header
  • Filter Map
  • URL Map
  • Cookie Session Tracker
  • Failsafe
  • Failure Monitor
  • Not Found
  • Http Method Override
  • Layout
  • Flash

Living on the edge

If you want the latest development version, grab the latest snapshot from Sonatype snapshots repositories:

<dependency>
      <groupId>com.vtence.molecule</groupId>
      <artifactId>molecule</artifactId>
      <version>0.15.0-SNAPSHOT</version>
</dependency>

New snapshots are pushed to Sonatype on every commit. So you'll always be running the head version.

molecule's People

Contributors

ericminio avatar gbranchaudrubenovitch avatar mat128 avatar testinfected avatar waffle-iron avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

molecule's Issues

Ability to load assets from the classpath

For simple self-contained scenarios, we'd like the ability to load static assets (images, css, js, etc.) from the classpath - in situations where they're bundled in the jar.

Locale Selector Middleware

A more capable i18n middleware than our current locale negotiation middleware.

On an incoming request, it first tries to detect the locale value from the URI, then from a locale cookie, then from Accept-Language header, and finally falls back to the default platform locale.

It will redirect the user to the proper locale URI or, set the locale cookie to the detected locale and add a locale attribute to the request. It will also modify the path info to strip the locale prefix from the path. It should only redirect on head and get requests, not post, put or delete.

Move test helpers to main jar

Consider moving test helpers to main jar under a testing package to get rid of test jar. That'll make only one jar to use plus test jar isn't distributed with source code.

The downside is that it makes more optional dependencies (Hamcrest, JUniversalCharDet)

Optional bound parameters in dynamic routes

Make it possible to specify when a bound parameter is optional in a dynamic route, e.g.:

get("/products/:id(.:format)").to(products::show)

would specify that .:format is optional and match both /products/1 and /products/1.json

Sendfile Middleware

Provides a mean of intercepting a response whose body is being served from a file and replaces it with a server specific X-Sendfile header. The front-end web server (nginx, httpd, apache) is then responsible for writing the file contents to the client.

Support X-Sendfile and X-Accel-Redirect.

Provide request host name

The host name or ip from the host header, or if no header is found, resolve from the ip information.

Undertow Adapter

Provide a server adapter for Undertow. Undertow is a performant web server written in java based on NIO.

Async responses

Simple uses non blocking-io and is capable of serving content in a separate thread to the original servicing thread, but our middleware pipeline assumes synchronous processing.

See how we can leverage Java 8 promises (a.k.a CompletableFutures) and lambda expressions to rethink how the middleware pipeline is built to enable deferred response post-processing.

Support segment contraints in dynamic routes

Make it possible to enforce a format for a dynamic segment of the route, so we can write:

get("/products/:id", where("id", "[A-Z]{2}-\d{2}")).to(...)

This route would match paths such as /products/AB-123, but not /products/123456.

Anchors will not be supported in the RegExp since we want to match the entire segment.

Make cookie session tracker support session renewal

We'd like client applications to be able to replace the session bound to the request by a fresh new session to avoid session fixation attacks.

Our cookie tracker should process the session bound to the request at the end of the processing cycle.

URL Map Middleware

A middleware for dispatching requests to different apps based on the request URI.

Maps URL paths to applications. The match order is sorted by path length (longest strings first).

URL paths need to match from the beginning and should match completely until the path separator (or the end of the path). For example, if you register the path /foo, it will match with the request /foo, /foo/ or /foo/bar but it won't match with /foox.

CSRF Middleware

A middleware that makes a CSRF token available in the session. This token can then be added to requests which mutate state, as a parameter of header. The token is validated against the visitor's session.

Hash parameters

Provide a middleware that converts request parameters to (nested) hashes when appropriate.

So:

client[name]="Acme"
client[phone]="123-456-7890" 
client[address][postcode]="A1Z 9A9"
client[address][city]="City"

is available as:

request.parameter("client") -> { 
  "name" = "Acme", 
  "phone" = "123-456-7890", 
  "address" = { 
    "postcode" = "A1Z 9A9", 
    "city" = "City" 
  } 
}

Array parameters

Convert request parameters to arrays when appropriate.

So (actually this would be url encoded):

names=Alice&names=Bob&names=John

becomes:

request.params("names") -> [ "Alice", "Bob", "John"]

Locale middleware

A middleware for negotiating HTTP locale for incoming browser requests.

Based on a set of supported locales, it figures out the best one to use for each incoming request and makes it available as a request attribute.

This replaces the request/response api locales related methods

Auto-close stream buffers at the end of the request cycle

If the request body is too large to be allocated on the heap, Simple will allocate a file buffer instead of a memory buffer. Same for file uploads. We need to keep track of those buffers that are opened and close them at the end of the request cycle to avoid file descriptors leaks.

Cookies middleware

A middleware for reading request cookies and setting response cookies.

This would replace getting and setting cookies using the request/response api.

View Templating

Integrate view templating with a default implementation using Mustache.

ETag middleware

A middleware to automatically sets the ETag header on the response.

The ETag header is skipped if:

  • Status is neither 200 nor 201
  • ETag or Last-Modified headers are already present

When the ETag header is set, he Cache-Control directive is set by default to "max-age=0, private, no-cache" (could be configurable).

Conditional GET middleware

A middleware that enables conditional GET using If-None-Match and If-Modified-Since.

When either of the conditions is met, the response body is stripped off, the Content-Type and Content-Length headers are removed and and the response status is set to 304 Not Modified.

Beautiful documentation (yep!)

Documentation in a format that is easy to update and better looking that the actual README.

Options to consider include GitBook and Jekyll.

Support glob patterns in dynamic routes

Make dynamic paths understand glob patterns and get the variable part as a request parameter, so that we can write:

get("/products/*number").to(...)

which would match all GET requests starting with /products/ , and provide remaining path as request parameter number (could be 12345 as well as AAA/12345)

Option to renew session pool ids

Add a renew option to the session pool so that session id is regenerated at every save to prevent from session fixation attacks.

GZip Compression

Provide a middleware that compresses response output when client accepts gzip encoding

Cookie Store for Sessions

Provide simple cookie based session management. Include a secure digest of the content to prevent against session forgery.

Make Encoder/Decoder for cookie value configurable and provide default implementations:

  • no encoding (for debugging)
  • base64 encoding
  • base64 encoding of serialized content

Flash Hash

A simple flash hash implementation that provides a way to pass temporary primitive-types between requests. Anything in the flash will be exposed to the very next request and then cleared out.

This is a way of passing messages through redirection.

Provide request URL

Provide the full request URL, from the request line URI if absolute or reconstructed from the relative URI and scheme, hostname and port.

HexDecoder

Complement encoding method with its sister decoding method

Accept format middleware

A middleware for automatically adding a format extension at the end of the request path based on the Accept http header.

For instance:

GET /some/resource
Accept: application/json

would become:

GET /some/resource.json
Accept: application/json

Virtual Host middleware

A middleware to hand off processing to a different application when the host of the request matches a given hostname.

So we can write, e.g.:

Application serveAssets = new StaticAssets(new FileServer(new File("./public")), "/");
server.use(new VirtualHost("assets.example.com", serveAssets));
// add middlewares for main app
// ...
server.start(...); // the main app

Hostname to match can be specified as a string or a RegExp:

  • When hostname is a string, it can contain * to match 1 or more characters in that section of the hostname
  • When hostname is a RegExp, it will be forced to match based on the start and end of the hostname
  • Match is case insensitive, since hostnames are.

Rewrite/Redirect Middleware

A middleware for defining and applying rewrite and/or redirect rules. Rules can be static or pattern based.

Rewriting simply changes request path info and hand the request down the middleware stack, whereas redirecting short-circuits the middleware stack and sends back a redirect status code (support any of 301, 302, 303, 307).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.