Vert.x-web的讲解和使用(英文原版)
2015-07-16 10:46
375 查看
Vert.x-Web
Vert.x-Web is a set of building blocks for building web applications with Vert.x.Think of it as a Swiss Army Knife for buildingmodern, scalable, web apps.
Vert.x core provides a fairly low level set of functionality for handling HTTP, and for some applicationsthat will be sufficient.
VVert.x-Web builds on Vert.x core to provide a richer set of functionality for building real web applications, moreeasily.
It’s the successor to
Yoke in Vert.x 2.x, and takes inspiration from projects suchas
Express in the Node.js world and
Sinatra in the Ruby world.
Vert.x-Web is designed to be powerful, un-opionated and fully embeddable. You just use the parts you want and nothing more.Vert.x-Web is not a container.
You can use Vert.x-Web to create classic server-side web applications, RESTful web applications, 'real-time' (server push)web applications, or any other kind of web application you can think of. Vert.x-Web doesn’t care. It’s up to you to chosethe type of
app you prefer, not Vert.x-Web.
Vert.x-Web is a great fit for writing RESTful HTTP micro-services, but we don’tforce you to write apps like that.
Some of the key features of Vert.x-Web include:
Routing (based on method, path, etc)
Regular expression pattern matching for paths
Extraction of parameters from paths
Content negotiation
Request body handling
Body size limits
Cookie parsing and handling
Multipart forms
Multipart file uploads
Sub routers
Session support - both local (for sticky sessions) and clustered (for non sticky)
CORS (Cross Origin Resource Sharing) support
Error page handler
Basic Authentication
Redirect based authentication
Authorisation handlers
JWT based authorization
User/role/permission authorisation
Favicon handling
Template support for server side rendering, including support for the following template engines out of the box:
Handlebars
Jade,
MVEL
Thymeleaf
Response time handler
Static file serving, including caching logic and directory listing.
Request timeout support
SockJS support
Event-bus bridge
Most features in Vert.x-Web are implemented as handlers so you can always write your own. We envisage many more being writtenover time.
We’ll discuss all these features in this manual.
Using Vert.x Web
To use vert.x web, add the following dependency to the dependencies section of your build descriptor:Maven (in your
pom.xml):
[code]<dependency> <groupId>{maven-groupId}</groupId> <artifactId>{maven-artifactId}</artifactId> <version>{maven-version}</version> </dependency>
Gradle (in your
build.gradlefile):
[code]compile {maven-groupId}:{maven-artifactId}:{maven-version}
Re-cap on Vert.x core HTTP servers
Vert.x-Web uses and exposes the API from Vert.x core, so it’s well worth getting familiar with the basic concepts of writingHTTP servers using Vert.x core, if you’re not already.The Vert.x core HTTP documentation goes into a lot of detail on this.
Here’s a hello world web server written using Vert.x core. At this point there is no Vert.x-Web involved:
[code]HttpServer server = vertx.createHttpServer(); server.requestHandler(request -> { // This handler gets called for each request that arrives on the server HttpServerResponse response = request.response(); response.putHeader("content-type", "text/plain"); // Write to the response and end it response.end("Hello World!"); }); server.listen(8080);
We create an HTTP server instance, and we set a request handler on it. The request handler will be called whenevera request arrives on the server.
When that happens we are just going to set the content type to
text/plain, and write
Hello World!and end theresponse.
We then tell the server to listen at port
8080(default host is
localhost).
You can run this, and point your browser at
http://localhost:8080to verify that it works as expected.
Basic Vert.x-Web concepts
Here’s the 10000 foot view:A
Routeris one of the core concepts of Vert.x-Web. It’s an object which maintains zero or more
Routes.
A router takes an HTTP request and finds the first matching route for that request, and passes the request to that route.
The route can have a handler associated with it, which then receives the request. You thendo something with therequest, and then, either end it or pass it to the next matching handler.
Here’s a simple router example:
[code]HttpServer server = vertx.createHttpServer(); Router router = Router.router(vertx); router.route().handler(routingContext -> { // This handler will be called for every request HttpServerResponse response = routingContext.response(); response.putHeader("content-type", "text/plain"); // Write to the response and end it response.end("Hello World from Vert.x-Web!"); }); server.requestHandler(router::accept).listen(8080);
It basically does the same thing as the Vert.x Core HTTP server hello world example from the previous section,but this time using Vert.x-Web.
We create an HTTP server as before, then we create a router. Once we’ve done that we create a simple route withno matching criteria so it will matchall requests that arrive on the server.
We then specify a handler for that route. That handler will be called for all requests that arrive on the server.
The object that gets passed into the handler is a
RoutingContext- this containsthe standard Vert.x
HttpServerRequestand
HttpServerResponsebut also various other useful stuff that makes working with Vert.x-Web simpler.
For every request that is routed there is a unique routing context instance, and the same instance is passed toall handlers for that request.
Once we’ve set up the handler, we set the request handler of the HTTP server to pass all incoming requeststo
accept.
So, that’s the basics. Now we’ll look at things in more detail:
Handling requests and calling the next handler
When Vert.x-Web decides to route a request to a matching route, it calls the handler of the route passing in an instanceofRoutingContext.
If you don’t end the response in your handler, you should call
nextso anothermatching route can handle the request (if any).
You don’t have to call
nextbefore the handler has finished executing.You can do this some time later, if you want:
[code]Route route1 = router.route("/some/path/").handler(routingContext -> { HttpServerResponse response = routingContext.response(); // enable chunked responses because we will be adding data as // we execute over other handlers. This is only required once and // only if several handlers do output. response.setChunked(true); response.write("route1\n"); // Call the next matching route after a 5 second delay routingContext.vertx().setTimer(5000, tid -> routingContext.next()); }); Route route2 = router.route("/some/path/").handler(routingContext -> { HttpServerResponse response = routingContext.response(); response.write("route2\n"); // Call the next matching route after a 5 second delay routingContext.vertx().setTimer(5000, tid -> routingContext.next()); }); Route route3 = router.route("/some/path/").handler(routingContext -> { HttpServerResponse response = routingContext.response(); response.write("route3"); // Now end the response routingContext.response().end(); });
In the above example
route1is written to the response, then 5 seconds later
route2is written to the response,then 5 seconds later
route3is written to the response and the response is ended.
Note, all this happens without any thread blocking.
Using blocking handlers
Sometimes, you might have to do something in a handler that might block the event loop for some time, e.g. calla legacy blocking API or do some intensive calculation.You can’t do that in a normal handler, so we provide the ability to set blocking handlers on a route.
A blocking handler looks just like a normal handler but it’s called by Vert.x using a thread from the worker poolnot using an event loop.
You set a blocking handler on a route with
blockingHandler.Here’s an example:
[code]router.route().blockingHandler(routingContext -> { // Do something that might take some time synchronously service.doSomethingThatBlocks(); // Now call the next handler routingContext.next(); });
By default, any blocking handlers executed on the same context (e.g. the same verticle instance) areordered - thismeans the next one won’t be executed until the previous one has completed. If you don’t care about orderering anddon’t mind your blocking
handlers executing in parallel you can set the blocking handler specifying
orderedasfalse using
blockingHandler.
Routing by exact path
A route can be set-up to match the path from the request URI. In this case it will match any request which has a paththat’s the same as the specified path.In the following example the handler will be called for a request
/some/path/. We also ignore trailing slashesso it will be called for paths
/some/pathand
/some/path//too:
[code]Route route = router.route().path("/some/path/"); route.handler(routingContext -> { // This handler will be called for the following request paths: // `/some/path` // `/some/path/` // `/some/path//` // // but not: // `/some/path/subdir` });
Routing by paths that begin with something
Often you want to route all requests that begin with a certain path. You could use a regex to do this, but a simplyway is to use an asterisk*at the end of the path when declaring the route path.
In the following example the handler will be called for any request with a URI path that starts with
/some/path/.
For example
/some/path/foo.htmland
/some/path/otherdir/blah.csswould both match.
[code]Route route = router.route().path("/some/path/*"); route.handler(routingContext -> { // This handler will be called for any path that starts with // `/some/path/`, e.g. // `/some/path` // `/some/path/` // `/some/path/subdir` // `/some/path/subdir/blah.html` // // but not: // `/some/bath` });
With any path it can also be specified when creating the route:
[code]Route route = router.route("/some/path/*"); route.handler(routingContext -> { // This handler will be called same as previous example });
Capturing path parameters
It’s possible to match paths using placeholders for parameters which are then available in the requestparams.
Here’s an example
[code]Route route = router.route(HttpMethod.POST, "/catalogue/products/:productype/:productid/"); route.handler(routingContext -> { String productType = routingContext.request().getParam("producttype"); String productID = routingContext.request().getParam("productid"); // Do something with them... });
The placeholders consist of
:followed by the parameter name. Parameter names consist of any alphabetic character,numeric character or underscore.
In the above example, if a POST request is made to path:
/catalogue/products/tools/drill123/then the route will matchand
productTypewill receive the value
toolsand productID will receive the value
drill123.
Routing with regular expressions
Regular expressions can also be used to match URI paths in routes.[code]Route route = router.route().pathRegex(".*foo"); route.handler(routingContext -> { // This handler will be called for: // /some/path/foo // /foo // /foo/bar/wibble/foo // /foo/bar // But not: // /bar/wibble });
Alternatively the regex can be specified when creating the route:
[code]Route route = router.routeWithRegex(".*foo"); route.handler(routingContext -> { // This handler will be called same as previous example });
Capturing path parameters with regular expressions
You can also capture path parameters when using regular expressions, here’s an example:[code]Route route = router.routeWithRegex(".*foo"); // This regular expression matches paths that start with something like: // "/foo/bar" - where the "foo" is captured into param0 and the "bar" is captured into // param1 route.pathRegex("\\/([^\\/]+)\\/([^\\/]+)").handler(routingContext -> { String productType = routingContext.request().getParam("param0"); String productID = routingContext.request().getParam("param1"); // Do something with them... });
In the above example, if a request is made to path:
/tools/drill123/then the route will matchand
productTypewill receive the value
toolsand productID will receive the value
drill123.
Captures are denoted in regular expressions with capture groups (i.e. surrounding the capture with round brackets)
Routing by HTTP method
By default a route will match all HTTP methods.If you want a route to only match for a specific HTTP method you can use
method
[code]Route route = router.route().method(HttpMethod.POST); route.handler(routingContext -> { // This handler will be called for any POST request });
Or you can specify this with a path when creating the route:
[code]Route route = router.route(HttpMethod.POST, "/some/path/"); route.handler(routingContext -> { // This handler will be called for any POST request to a URI path starting with /some/path/ });
If you want to route for a specific HTTP method you can also use the methods such as
get,
postand
putnamed after the HTTPmethod name. For example:
[code]router.get().handler(routingContext -> { // Will be called for any GET request }); router.get("/some/path/").handler(routingContext -> { // Will be called for any GET request to a path // starting with /some/path }); router.getWithRegex(".*foo").handler(routingContext -> { // Will be called for any GET request to a path // ending with `foo` });
If you want to specify a route will match for more than HTTP method you can call
methodmultiple times:
[code]Route route = router.route().method(HttpMethod.POST).method(HttpMethod.PUT); route.handler(routingContext -> { // This handler will be called for any POST or PUT request });
Route order
By default routes are matched in the order they are added to the router.When a request arrives the router will step through each route and check if it matches, if it matches thenthe handler for that route will be called.
If the handler subsequently calls
nextthe handler for the nextmatching route (if any) will be called. And so on.
Here’s an example to illustrate this:
[code]Route route1 = router.route("/some/path/").handler(routingContext -> { HttpServerResponse response = routingContext.response(); // enable chunked responses because we will be adding data as // we execute over other handlers. This is only required once and // only if several handlers do output. response.setChunked(true); response.write("route1\n"); // Now call the next matching route routingContext.next(); }); Route route2 = router.route("/some/path/").handler(routingContext -> { HttpServerResponse response = routingContext.response(); response.write("route2\n"); // Now call the next matching route routingContext.next(); }); Route route3 = router.route("/some/path/").handler(routingContext -> { HttpServerResponse response = routingContext.response(); response.write("route3"); // Now end the response routingContext.response().end(); });
In the above example the response will contain:
route1 route2 route3
As the routes have been called in that order for any request that starts with
/some/path.
If you want to override the default ordering for routes, you can do so using
order,specifying an integer value.
Routes are assigned an order at creation time corresponding to the order in which they were added to the router, withthe first route numbered
0, the second route numbered
1, and so on.
By specifying an order for the route you can override the default ordering. Order can also be negative, e.g. if youwant to ensure a route is evaluated before route number
0.
Let’s change the ordering of route2 so it runs before route1:
[code]Route route1 = router.route("/some/path/").handler(routingContext -> { HttpServerResponse response = routingContext.response(); response.write("route1\n"); // Now call the next matching route routingContext.next(); }); Route route2 = router.route("/some/path/").handler(routingContext -> { HttpServerResponse response = routingContext.response(); // enable chunked responses because we will be adding data as // we execute over other handlers. This is only required once and // only if several handlers do output. response.setChunked(true); response.write("route2\n"); // Now call the next matching route routingContext.next(); }); Route route3 = router.route("/some/path/").handler(routingContext -> { HttpServerResponse response = routingContext.response(); response.write("route3"); // Now end the response routingContext.response().end(); }); // Change the order of route2 so it runs before route1 route2.order(-1);
then the response will now contain:
route2 route1 route3
If two matching routes have the same value of order, then they will be called in the order they were added.
You can also specify that a route is handled last, with
last
Routing based on MIME type of request
You can specify that a route will match against matching request MIME types usingconsumes.
In this case, the request will contain a
content-typeheader specifying the MIME type of the request body.This will be matched against the value specified in
consumes.
Basically,
consumesis describing which MIME types the handler canconsume.
Matching can be done on exact MIME type matches:
[code]router.route().consumes("text/html").handler(routingContext -> { // This handler will be called for any request with // content-type header set to `text/html` });
Multiple exact matches can also be specified:
[code]router.route().consumes("text/html").consumes("text/plain").handler(routingContext -> { // This handler will be called for any request with // content-type header set to `text/html` or `text/plain`. });
Matching on wildcards for the sub-type is supported:
[code]router.route().consumes("text/*").handler(routingContext -> { // This handler will be called for any request with top level type `text` // e.g. content-type header set to `text/html` or `text/plain` will both match });
And you can also match on the top level type
[code]router.route().consumes("*/json").handler(routingContext -> { // This handler will be called for any request with sub-type json // e.g. content-type header set to `text/json` or `application/json` will both match });
If you don’t specify a
/in the consumers, it will assume you meant the sub-type.
Routing based on MIME types acceptable by the client
The HTTPacceptheader is used to signify which MIME types of the response are acceptable to the client.
An
acceptheader can have multiple MIME types separated by ‘,’.
MIME types can also have a
qvalue appended to them* which signifies a weighting to apply if more than oneresponse MIME type is available matching the accept header. The q value is a number between 0 and 1.0.If omitted it defaults to 1.0.
For example, the following
acceptheader signifies the client will accept a MIME type of only
text/plain:
Accept: text/plain
With the following the client will accept
text/plainor
text/htmlwith no preference.
Accept: text/plain, text/html
With the following the client will accept
text/plainor
text/htmlbut prefers
text/htmlas it has a higher
qvalue (the default value is q=1.0)
Accept: text/plain; q=0.9, text/html
If the server can provide both text/plain and text/html it should provide the text/html in this case.
By using
producesyou define which MIME type(s) the route produces, e.g. thefollowing handler produces a response with MIME type
application/json.
[code]router.route().produces("application/json").handler(routingContext -> { HttpServerResponse response = routingContext.response(); response.putHeader("content-type", "application/json"); response.write(someJSON).end(); });
In this case the route will match with any request with an
acceptheader that matches
application/json.
Here are some examples of
acceptheaders that will match:
Accept: application/json Accept: application/* Accept: application/json, text/html Accept: application/json;q=0.7, text/html;q=0.8, text/plain
You can also mark your route as producing more than one MIME type. If this is the case, then you use
getAcceptableContentTypeto find out the actual MIME type thatwas accepted.
[code]router.route().produces("application/json").produces("text/html").handler(routingContext -> { HttpServerResponse response = routingContext.response(); // Get the actual MIME type acceptable String acceptableContentType = routingContext.getAcceptableContentType(); response.putHeader("content-type", acceptableContentType); response.write(whatever).end(); });
In the above example, if you sent a request with the following
acceptheader:
Accept: application/json; q=0.7, text/html
Then the route would match and
acceptableContentTypewould contain
text/htmlas both areacceptable but that has a higher
qvalue.
Combining routing criteria
You can combine all the above routing criteria in many different ways, for example:[code]Route route = router.route(HttpMethod.PUT, "myapi/orders") .consumes("application/json") .produces("application/json"); route.handler(routingContext -> { // This would be match for any PUT method to paths starting with "myapi/orders" with a // content-type of "application/json" // and an accept header matching "application/json" });
Enabling and disabling routes
You can disable a route withdisable. A disabled route will be ignored when matching.
You can re-enable a disabled route with
enable
Context data
You can use the context data in theRoutingContextto maintain any data that youwant to share between handlers for the lifetime of the request.
Here’s an example where one handler sets some data in the context data and a subsequent handler retrieves it:
You can use the
putto put any object, and
getto retrieve any object from the context data.
A request sent to path
/some/path/otherwill match both routes.
[code]router.get("/some/path").handler(routingContext -> { routingContext.put("foo", "bar"); routingContext.next(); }); router.get("/some/path/other").handler(routingContext -> { String bar = routingContext.get("foo"); // Do something with bar routingContext.response().end(); });
Alternatively you can access the entire context data map with
data.
Sub-routers
Sometimes if you have a lot of handlers it can make sense to split them up into multiple routers. This is also usefulif you want to reuse a set of handlers in a different application, rooted at a different path root.To do this you can mount a router at a mount point in another router. The router that is mounted is called asub-router. Sub routers can mount other sub routers so you can have several levels of sub-routers if you like.
Let’s look at a simple example of a sub-router mounted with another router.
This sub-router will maintain the set of handlers that corresponds to a simple fictional REST API. We will mount that on anotherrouter. The full implementation of the REST API is not shown.
Here’s the sub-router:
[code]Router restAPI = Router.router(vertx); restAPI.get("/products/:productID").handler(rc -> { // TODO Handle the lookup of the product.... rc.response().write(productJSON); }); restAPI.put("/products/:productID").handler(rc -> { // TODO Add a new product... rc.response().end(); }); restAPI.delete("/products/:productID").handler(rc -> { // TODO delete the product... rc.response().end(); });
If this router was used as a top level router, then GET/PUT/DELETE requests to urls like
/products/product1234would invoke the API.
However, let’s say we already have a web-site as described by another router:
[code]Router mainRouter = Router.router(vertx); // Handle static resources mainRouter.route("/static/*").handler(myStaticHandler); mainRouter.route(".*\\.templ").handler(myTemplateHandler);
We can now mount the sub router on the main router, against a mount point, in this case
/productsAPI
[code]mainRouter.mountSubRouter("/productsAPI", restAPI);
This means the REST API is now accessible via paths like:
/productsAPI/products/product1234
Default 404 Handling
If no routes match for any particular request, Vert.x-Web will signal a 404 error.This can then be handled by your own error handler, or perhaps the augmented error handler that we supply to use,or if no error handler is provided Vert.x-Web will send back a basic 404 (Not Found) response.
Error handling
As well as setting handlers to handle requests you can also set handlers to handle failures in routing.Failure handlers are used with the exact same route matching criteria that you use with normal handlers.
For example you can provide a failure handler that will only handle failures on certain paths, or for certain HTTP methods.
This allows you to set different failure handlers for different parts of your application.
Here’s an example failure handler that will only be called for failure that occur when routing to GET requeststo paths that start with
/somepath/:
[code]Route route = router.get("/somepath/*"); route.failureHandler(frc -> { // This will be called for failures that occur // when routing requests to paths starting with // '/somepath/' });
Failure routing will occur if a handler throws an exception, or if a handler calls
failspecifying an HTTP status code to deliberately signal a failure.
If an exception is caught from a handler this will result in a failure with status code
500being signalled.
When handling the failure, the failure handler is passed the routing context which also allows the failure or failure codeto be retrieved so the failure handler can use that to generate a failure response.
[code]Route route1 = router.get("/somepath/path1/"); route1.handler(routingContext -> { // Let's say this throws a RuntimeException throw new RuntimeException("something happened!"); }); Route route2 = router.get("/somepath/path2"); route2.handler(routingContext -> { // This one deliberately fails the request passing in the status code // E.g. 403 - Forbidden routingContext.fail(403); }); // Define a failure handler // This will get called for any failures in the above handlers Route route3 = router.get("/somepath/*"); route3.failureHandler(failureRoutingContext -> { int statusCode = failureRoutingContext.statusCode(); // Status code will be 500 for the RuntimeException or 403 for the other failure HttpServerResponse response = failureRoutingContext.response(); response.setStatusCode(statusCode).end("Sorry! Not today"); });
Request body handling
TheBodyHandlerallows you to retrieve request bodies, limit body sizes and handlefile uploads.
You should make sure a body handler is on a matching route for any requests that require this functionality.
[code]router.route().handler(BodyHandler.create());
Getting the request body
If you know the request body is JSON, then you can usegetBodyAsJson,if you know it’s a string you can use
getBodyAsString, or toretrieve it as a buffer use
getBody.
Limiting body size
To limit the size of a request body, create the body handler then usesetBodyLimitto specifying the maximum body size, in bytes. This is useful to avoid running out of memory with very large bodies.
If an attempt to send a body greater than the maximum size is made, an HTTP status code of 413 -
Request Entity Too Large,will be sent.
There is no body limit by default.
Merging form attributes
By default, the body handler will merge any form attributes into the request parameters. If you don’t want this behaviouryou can use disable it withsetMergeFormAttributes.
Handling file uploads
Body handler is also used to handle multi-part file uploads.If a body handler is on a matching route for the request, any file uploads will be automatically streamed to theuploads directory, which is
file-uploadsby default.
Each file will be given an automatically generated file name, and the file uploads will be available on the routingcontext with
fileUploads.
Here’s an example:
[code]router.route().handler(BodyHandler.create()); router.post("/some/path/uploads").handler(routingContext -> { Set<FileUpload> uploads = routingContext.fileUploads(); // Do something with uploads.... });
Each file upload is described by a
FileUploadinstance, which allows various propertiessuch as the name, file-name and size to be accessed.
Handling cookies
Vert.x-Web has cookies support using theCookieHandler.
You should make sure a cookie handler is on a matching route for any requests that require this functionality.
[code]router.route().handler(CookieHandler.create());
Manipulating cookies
You usegetCookieto retrievea cookie by name, or use
cookiesto retrieve the entire set.
To remove a cookie, use
removeCookie.
To add a cookie use
addCookie.
The set of cookies will be written back in the response automatically when the response headers are written so thebrowser can store them.
Cookies are described by instances of
Cookie. This allows you to retrieve the name,value, domain, path and other normal cookie properties.
Here’s an example of querying and adding cookies:
[code]router.route().handler(CookieHandler.create()); router.route("some/path/").handler(routingContext -> { Cookie someCookie = routingContext.getCookie("mycookie"); String cookieValue = someCookie.getValue(); // Do something with cookie... // Add a cookie - this will get written back in the response automatically routingContext.addCookie(Cookie.cookie("othercookie", "somevalue")); });
Handling sessions
Vert.x-Web provides out of the box support for sessions.Sessions last between HTTP requests for the length of a browser session and give you a place where you can addsession-scope information, such as a shopping basket.
Vert.x-Web uses session cookies to identify a session. The session cookie is temporary and will be deleted by your browserwhen it’s closed.
We don’t put the actual data of your session in the session cookie - the cookie simply uses an identifier to look-upthe actual session on the server. The identifier is a random UUID generated using a secure random, so it shouldbe effectively unguessable.
Cookies are passed across the wire in HTTP requests and responses so it’s always wise to make sure you are usingHTTPS when sessions are being used. Vert.x will warn you if you attempt to use sessions over straight HTTP.
To enable sessions in your application you must have a
SessionHandleron a matching route before your application logic.
The session handler handles the creation of session cookies and the lookup of the session so you don’t have to dothat yourself.
Session stores
To create a session handler you need to have a session store instance. The session store is the object thatholds the actual sessions for your application.Vert.x-Web comes with two session store implementations out of the box, and you can also write your own if you prefer.
Local session store
With this store, sessions are stored locally in memory and only available in this instance.This store is appropriate if you have just a single Vert.x instance of you are using sticky sessions in your applicationand have configured your load balancer to always route HTTP requests to the same Vert.x instance.
If you can’t ensure your requests will all terminate on the same server then don’t use this store as yourrequests might end up on a server which doesn’t know about your session.
Local session stores are implemented by using a shared local map, and have a reaper which clears out expired sessions.
The reaper interval can be configured with
LocalSessionStore.create.
Here are some examples of creating a
LocalSessionStore
[code]SessionStore store1 = LocalSessionStore.create(vertx); // Create a local session store specifying the local shared map name to use // This might be useful if you have more than one application in the same // Vert.x instance and want to use different maps for different applications SessionStore store2 = LocalSessionStore.create(vertx, "myapp3.sessionmap"); // Create a local session store specifying the local shared map name to use and // setting the reaper interval for expired sessions to 10 seconds SessionStore store3 = LocalSessionStore.create(vertx, "myapp3.sessionmap", 10000);
Clustered session store
With this store, sessions are stored in a distributed map which is accessible across the Vert.x cluster.This store is appropriate if you’re not using sticky sessions, i.e. your load balancer is distributing differentrequests from the same browser to different servers.
Your session is accessible from any node in the cluster using this store.
To you use a clustered session store you should make sure your Vert.x instance is clustered.
Here are some examples of creating a
ClusteredSessionStore
[code]Vertx.clusteredVertx(new VertxOptions().setClustered(true), res -> { Vertx vertx = res.result(); // Create a clustered session store using defaults SessionStore store1 = ClusteredSessionStore.create(vertx); // Create a clustered session store specifying the distributed map name to use // This might be useful if you have more than one application in the cluster // and want to use different maps for different applications SessionStore store2 = ClusteredSessionStore.create(vertx, "myclusteredapp3.sessionmap"); });
Creating the session handler
Once you’ve created a session store you can create a session handler, and add it to a route. You should make sureyour session handler is routed to before your application handlers.You’ll also need to include a
CookieHandleras the session handler uses cookies tolookup the session. The cookie handler should be before the session handler when routing.
Here’s an example:
[code]Router router = Router.router(vertx); // We need a cookie handler first router.route().handler(CookieHandler.create()); // Create a clustered session store using defaults SessionStore store = ClusteredSessionStore.create(vertx); SessionHandler sessionHandler = SessionHandler.create(store); // Make sure all requests are routed through the session handler too router.route().handler(sessionHandler); // Now your application handlers router.route("/somepath/blah/").handler(routingContext -> { Session session = routingContext.session(); session.put("foo", "bar"); // etc });
The session handler will ensure that your session is automatically looked up (or created if no session exists)from the session store and set on the routing context before it gets to your application handlers.
Using the session
In your handlers you an access the session instance withsession.
You put data into the session with
put,you get data from the session with
get, and you removedata from the session with
remove.
The keys for items in the session are always strings. The values can be any type for a local session store, and fora clustered session store they can be any basic type, or
Buffer,
JsonObject,
JsonArrayor a serializable
object, as the values have to serialized across the cluster.
Here’s an example of manipulating session data:
[code]router.route().handler(CookieHandler.create()); router.route().handler(sessionHandler); // Now your application handlers router.route("/somepath/blah").handler(routingContext -> { Session session = routingContext.session(); // Put some data from the session session.put("foo", "bar"); // Retrieve some data from a session int age = session.get("age"); // Remove some data from a session JsonObject obj = session.remove("myobj"); });
Sessions are automatically written back to the store after after responses are complete.
You can manually destroy a session using
destroy. This will remove the sessionfrom the context and the session store. Note that if there is no session a new one will be automatically createdfor the next request from the browser that’s routed
through the session handler.
Session timeout
Sessions will be automatically timed out if they are not accessed for a time greater than the timeout period. Whena session is timed out, it is removed from the store.Sessions are automatically marked as accessed when a request arrives and the session is looked up and and when theresponse is complete and the session is stored back in the store.
You can also use
setAccessedto manually mark a session as accessed.
The session timeout can be configured when creating the session handler. Default timeout is 30 minutes.
Authentication / authorisation
Vert.x comes with some out-of-the-box handlers for handling both authentication and authorisation.Creating an auth handler
To create an auth handler you need an instance ofAuthProvider. Auth provider isused for authentication and authorisation of users. Vert.x provides several auth provider instances out of the boxin the vertx-auth project. For full information
on auth providers and how to use and configure themplease consult the auth documentation.
Here’s a simple example of creating a basic auth handler given an auth provider.
[code]router.route().handler(CookieHandler.create()); router.route().handler(SessionHandler.create(LocalSessionStore.create(vertx))); AuthHandler basicAuthHandler = BasicAuthHandler.create(authProvider);
Handling auth in your application
Let’s say you want all requests to paths that start with/private/to be subject to auth. To do that you make sureyour auth handler is before your application handlers on those paths:
[code]router.route().handler(CookieHandler.create()); router.route().handler(SessionHandler.create(LocalSessionStore.create(vertx))); router.route().handler(UserSessionHandler.create(authProvider)); AuthHandler basicAuthHandler = BasicAuthHandler.create(authProvider); // All requests to paths starting with '/private/' will be protected router.route("/private/*").handler(basicAuthHandler); router.route("/someotherpath").handler(routingContext -> { // This will be public access - no login required }); router.route("/private/somepath").handler(routingContext -> { // This will require a login // This will have the value true boolean isAuthenticated = routingContext.user() != null; });
If the auth handler has successfully authenticated and authorised the user it will inject a
Userobject into the
RoutingContextso it’s available in your handlers with:
user.
If you want your User object to be stored in the session so it’s available between requests so you don’t have toauthenticate on each request, then you should make sure you have a session handler and a user session handler on matchingroutes before the auth
handler.
Once you have your user object you can also programmatically use the methods on it to authorise the user.
If you want to cause the user to be logged out you can call
clearUseron the routing context.
HTTP Basic Authentication
HTTP Basic Authentication is a simple means of authenticationthat can be appropriate for simple applications.With basic auth, credentials are sent unencrypted across the wire in HTTP headers so it’s essential that you serveyour application using HTTPS not HTTP.
With basic auth, if a user requests a resource that requires authorisation, the basic auth handler will send backa
401response with the header
WWW-Authenticateset. This prompts the browser to show a log-in dialogue andprompt the
user to enter their username and password.
The request is made to the resource again, this time with the
Authorizationheader set, containing the usernameand password encoded in Base64.
When the basic auth handler receives this information, it calls the configured
AuthProviderwith the username and password to authenticate the user. If the authentication is successful the handler attemptsto authorise the user. If that is successful
then the routing of the request is allowed to continue to the applicationhandlers, otherwise a
403response is returned to signify that access is denied.
The auth handler can be set-up with a set of authorities that are required for access to the resources tobe granted.
Redirect auth handler
With redirect auth handling the user is redirected to towards a login page in the case they are trying to accessa protected resource and they are not logged in.The user then fills in the login form and submits it. This is handled by the server which authenticatesthe user and, if authenticated redirects the user back to the original resource.
To use redirect auth you configure an instance of
RedirectAuthHandlerinstead of abasic auth handler.
You will also need to setup handlers to serve your actual login page, and a handler to handle the actual login itself.To handle the login we provide a prebuilt handler
FormLoginHandlerfor the purpose.
Here’s an example of a simple app, using a redirect auth handler on the default redirect url
/loginpage.
[code]router.route().handler(CookieHandler.create()); router.route().handler(SessionHandler.create(LocalSessionStore.create(vertx))); router.route().handler(UserSessionHandler.create(authProvider)); AuthHandler redirectAuthHandler = RedirectAuthHandler.create(authProvider); // All requests to paths starting with '/private/' will be protected router.route("/private/*").handler(redirectAuthHandler); // Handle the actual login router.route("/login").handler(FormLoginHandler.create(authProvider)); // Set a static server to serve static resources, e.g. the login page router.route().handler(StaticHandler.create()); router.route("/someotherpath").handler(routingContext -> { // This will be public access - no login required }); router.route("/private/somepath").handler(routingContext -> { // This will require a login // This will have the value true boolean isAuthenticated = routingContext.user() != null; });
JWT authorisation
With JWT authorisation resources can be protected by means of permissions and users without enough rights are deniedaccess.To use this handler there are 2 steps involved:
Setup an handler to issue tokens (or rely on a 3rd party)
Setup the handler to filter the requests
Please note that these 2 handlers should be only available on HTTPS, not doing so allows sniffing the tokens intransit which leads to session hijacking attacks.
Here’s an example on how to issue tokens:
[code]Router router = Router.router(vertx); JsonObject authConfig = new JsonObject().put("keyStore", new JsonObject() .put("type", "jceks") .put("path", "keystore.jceks") .put("password", "secret")); JWTAuth authProvider = JWTAuth.create(vertx, authConfig); router.route("/login").handler(ctx -> { // this is an example, authentication should be done with another provider... if ("paulo".equals(ctx.request().getParam("username")) && "secret".equals(ctx.request().getParam("password"))) { ctx.response().end(authProvider.generateToken(new JsonObject().put("sub", "paulo"), new JWTOptions())); } else { ctx.fail(401); } });
Now that your client has a token all it is required is that for all consequent request the HTTP header
Authorizationis filled with:
Bearer <token>e.g.:
[code]Router router = Router.router(vertx); JsonObject authConfig = new JsonObject().put("keyStore", new JsonObject() .put("type", "jceks") .put("path", "keystore.jceks") .put("password", "secret")); JWTAuth authProvider = JWTAuth.create(vertx, authConfig); router.route("/protected/*").handler(JWTAuthHandler.create(authProvider)); router.route("/protected/somepage").handler(ctx -> { // some handle code... });
JWT allows you to add any information you like to the token itself. By doing this there is no state in the serverwhich allows you to scale your applications without need for clustered session data. In order to add data to thetoken, during the creation of
the token just add data to the JsonObject parameter:
[code]JsonObject authConfig = new JsonObject().put("keyStore", new JsonObject() .put("type", "jceks") .put("path", "keystore.jceks") .put("password", "secret")); JWTAuth authProvider = JWTAuth.create(vertx, authConfig); authProvider.generateToken(new JsonObject().put("sub", "paulo").put("someKey", "some value"), new JWTOptions());
And the same when consuming:
[code]Handler<RoutingContext> handler = rc -> { String theSubject = rc.user().principal().getString("sub"); String someKey = rc.user().principal().getString("someKey"); };
Configuring required authorities
With any auth handler you can also configure required authorities to access the resource.By default, if no authorities are configured then it is sufficient to be logged in to access the resource, otherwisethe user must be both logged in (authenticated) and have the required authorities.
Here’s an example of configuring an app so that different authorities are required for different parts of theapp. Note that the meaning of the authorities is determined by the underlying auth provider that you use. E.g. somemay support a role/permission
based model but others might use another model.
[code]AuthHandler listProductsAuthHandler = RedirectAuthHandler.create(authProvider); listProductsAuthHandler.addAuthority("list_products"); // Need "list_products" authority to list products router.route("/listproducts/*").handler(listProductsAuthHandler); AuthHandler settingsAuthHandler = RedirectAuthHandler.create(authProvider); settingsAuthHandler.addAuthority("role:admin"); // Only "admin" has access to /private/settings router.route("/private/settings/*").handler(settingsAuthHandler);
Serving static resources
Vert.x-Web comes with an out of the box handler for serving static web resources so you can write static web serversvery easily.To serve static resources such as
.html,
.css,
.jsor any other static resource, you use an instance of
StaticHandler.
Any requests to paths handled by the static handler will result in files being served from a directory on the file systemor from the classpath. The default static file directory is
webrootbut this can be configured.
In the following example all requests to paths starting with
/static/will get served from the directory
webroot:
[code]router.route("/static/*").handler(StaticHandler.create());
For example, if there was a request with path
/static/css/mystyles.cssthe static serve will look for a file in thedirectory
webroot/static/css/mystyle.css.
It will also look for a file on the classpath called
webroot/static/css/mystyle.css. This means you can package up all yourstatic resources into a jar file (or fatjar) and distribute them like that.
When Vert.x finds a resource on the classpath for the first time it extracts it and caches it in a temporary directoryon disk so it doesn’t have to do this each time.
Configuring caching
By default the static handler will set cache headers to enable browsers to effectively cache files.Vert.x-Web sets the headers
cache-control,
last-modified, and
date.
cache-controlis set to
max-age=86400by default. This corresponds to one day. This can be configured with
setMaxAgeSecondsif required.
If a browser sends a GET or a HEAD request with an
if-modified-sinceheader and the resource has not been modifiedsince that date, a
304status is returned which tells the browser to use its locally cached resource.
If handling of cache headers is not required, it can be disabled with
setCachingEnabled.
When cache handling is enabled Vert.x-Web will cache the last modified date of resources in memory, this avoids a disk hitto check the actual last modified date every time.
Entries in the cache have an expiry time, and after that time, the file on disk will be checked again and the cacheentry updated.
If you know that your files never change on disk, then the cache entry will effectively never expire. This is thedefault.
If you know that your files might change on disk when the server is running then you can set files read only to false with
setFilesReadOnly.
To enable the maximum number of entries that can be cached in memory at any one time you can use
setMaxCacheSize.
To configure the expiry time of cache entries you can use
setCacheEntryTimeout.
Configuring the index page
Any requests to the root path/will cause the index page to be served. By default the index page is
index.html.This can be configured with
setIndexPage.
Changing the web root
By default static resources will be served from the directorywebroot. To configure this use
setWebRoot.
Serving hidden files
By default the serve will serve hidden files (files starting with.).
If you do not want hidden files to be served you can configure it with
setIncludeHidden.
Directory listing
The server can also perform directory listing. By default directory listing is disabled. To enabled it usesetDirectoryListing.
When directory listing is enabled the content returned depends on the content type in the
acceptheader.
For
text/htmldirectory listing, the template used to render the directory listing page can be configured with
setDirectoryTemplate.
Disabling file caching on disk
By default, Vert.x will cache files that are served from the classpath into a file on disk in a sub-directory of adirectory called.vertxin the current working directory. This is mainly useful when deploying services asfatjars in production
where serving a file from the classpath every time can be slow.
In development this can cause a problem, as if you update your static content while the server is running, thecached file will be served not the updated file.
To disable file caching you can provide the system property
vertx.disableFileCachingwith the value
true. E.g. youcould set up a run configuration in your IDE to set this when runnning your main class.
CORS handling
Cross Origin Resource Sharing is a safe mechanism forallowing resources to be requested from one domain and served from another.Vert.x-Web includes a handler
CorsHandlerthat handles the CORS protocol for you.
Here’s an example:
[code]router.route().handler(CorsHandler.create("vertx\\.io").allowedMethod(HttpMethod.GET)); router.route().handler(routingContext -> { // Your app handlers });
TODO more CORS docs
Templates
Vert.x-Web includes dynamic page generation capabilities by including out of the box support for several popular templateengines. You can also easily add your own.Template engines are described by
TemplateEngine. In order to render a template
renderis used.
The simplest way to use templates is not to call the template engine directly but to use the
TemplateHandler.This handler calls the template engine for you based on the path in the HTTP request.
By default the template handler will look for templates in a directory called
templates. This can be configured.
The handler will return the results of rendering with a content type of
text/htmlby default. This can also be configured.
When you create the template handler you pass in an instance of the template engine you want.
Here are some examples
[code]TemplateEngine engine = HandlebarsTemplateEngine.create(); TemplateHandler handler = TemplateHandler.create(engine); // This will route all GET requests starting with /dynamic/ to the template handler // E.g. /dynamic/graph.hbs will look for a template in /templates/dynamic/graph.hbs router.get("/dynamic/").handler(handler); // Route all GET requests for resource ending in .hbs to the template handler router.getWithRegex(".+\\.hbs").handler(handler);
MVEL template engine
When using theMVEL template engine, it will by default look fortemplates with the
.templextension if no extension is specified in the file name.
The routing context
RoutingContextis availablein the MVEL template as the
contextvariable, this means you can render the template based on anything in the contextincluding the request, response, session or context data.
Here are some examples:
The request path is @{context.request().path()} The variable 'foo' from the session is @{context.session().get('foo')} The value 'bar' from the context data is @{context.get('bar')}
Please consult the
MVEL templates documentation for how to writeMVEL templates.
Jade template engine
When using theJade template engine, it will by default look fortemplates with the
.jadeextension if no extension is specified in the file name.
The routing context
RoutingContextis availablein the Jade template as the
contextvariable, this means you can render the template based on anything in the contextincluding the request, response, session or context data.
Here are some examples:
!!! 5 html head title= context.get('foo') + context.request().path() body
Please consult the
Jade4j documentation for how to writeJade templates.
Handlebars template engine
When using theHandlebars template engine, it will by default look fortemplates with the
.hbsextension if no extension is specified in the file name.
Handlebars templates are not able to call arbitrary methods in objects so we can’t just pass the routing contextinto the template and let the template introspect it like we can with other template engines.
Instead, the context
datais available in the template.
If you want to have access to other data like the request path, request params or session data you shouldadd it the context data in a handler before the template handler. For example:
[code]TemplateEngine engine = HandlebarsTemplateEngine.create(); TemplateHandler handler = TemplateHandler.create(engine); router.get("/dynamic").handler(routingContext -> { routingContext.put("request_path", routingContext.request().path()); routingContext.put("session_data", routingContext.session().data()); routingContext.next(); }); router.get("/dynamic/").handler(handler);
Please consult the
Handlebars Java port documentation for how to writehandlebars templates.
Thymeleaf template engine
When using theThymeleaf template engine, it will by default look fortemplates with the
.htmlextension if no extension is specified in the file name.
The routing context
RoutingContextis availablein the Thymeleaf template as the
contextvariable, this means you can render the template based on anything in the contextincluding the request, response, session or context data.
Here are some examples:
[snip] <p th:text="${context.get('foo')}"></p> <p th:text="${context.get('bar')}"></p> <p th:text="${context.normalisedPath()}"></p> <p th:text="${context.request().params().get('param1')}"></p> <p th:text="${context.request().params().get('param2')}"></p> [snip]
Please consult the
Thymeleaf documentation for how to writeThymeleaf templates.
Error handler
You can render your own errors using a template handler or otherwise but Vert.x-Web also includes an out of the boxy"pretty" error handler that can render error pages for you.The handler is
ErrorHandler. To use the error handler just set it as afailure handler for any paths that you want covered.
Request logger
Vert.x-Web includes a handlerLoggerHandlerthat you can use to log HTTP requests.
By default requests are logged to the Vert.x logger which can be configured to use JUL logging, log4j or SLF4J.
Serving favicons
Vert.x-Web includes the handlerFaviconHandlerespecially for serving favicons.
Favicons can be specified using a path to the filesystem, or by default Vert.x-Web will look for a file on the classpathwith the name
favicon.ico. This means you bundle the favicon in the jar of your application.
Timeout handler
Vert.x-Web includes a timeout handler that you can use to timeout requests if they take too long to process.This is configured using an instance of
TimeoutHandler.
If a request times out before the response is written a
408response will be returned to the client.
Here’s an example of using a timeout handler which will timeout all requests to paths starting with
/fooafter 5seconds:
[code]router.route("/foo/").handler(TimeoutHandler.create(5000));
Response time handler
This handler sets the headerx-response-timeresponse header containing the time from when the request was receivedto when the response headers were written, in ms., e.g.:
x-response-time: 1456ms
SockJS
SockJS is a client side JavaScript library and protocol which provides a simple WebSocket-like interface allowing youto make connections to SockJS servers irrespective of whether the actual browser or network will allow real WebSockets.It does this by supporting various different transports between browser and server, and choosing one at run-timeaccording to browser and network capabilities.
All this is transparent to you - you are simply presented with the WebSocket-like interface whichjust works.
Please see the
SockJS website for more information on SockJS.
SockJS handler
Vert.x provides an out of the box handler calledSockJSHandlerforusing SockJS in your Vert.x-Web applications.
You should create one handler per SockJS application using
SockJSHandler.create.You can also specify configuration options when creating the instance. The configuration options are described withan instance of
SockJSHandlerOptions.
[code]Router router = Router.router(vertx); SockJSHandlerOptions options = new SockJSHandlerOptions().setHeartbeatInterval(2000); SockJSHandler sockJSHandler = SockJSHandler.create(vertx, options); router.route("/myapp/*").handler(sockJSHandler);
Handling SockJS sockets
On the server-side you set a handler on the SockJS handler, andthis will be called every time a SockJS connection is made from a client:The object passed into the handler is a
SockJSSocket. This has a familiarsocket-like interface which you can read and write to similarly to a
NetSocketora
WebSocket. It also implements
ReadStreamand
WriteStreamso you can pump it to and from other read and write streams.
Here’s an example of a simple SockJS handler that simply echoes back any back any data that it reads:
[code]Router router = Router.router(vertx); SockJSHandlerOptions options = new SockJSHandlerOptions().setHeartbeatInterval(2000); SockJSHandler sockJSHandler = SockJSHandler.create(vertx, options); sockJSHandler.socketHandler(sockJSSocket -> { // Just echo the data back sockJSSocket.handler(sockJSSocket::write); }); router.route("/myapp/*").handler(sockJSHandler);
The client side
In client side JavaScript you use the SockJS client side library to make connections.You can find that
here.The minified version is
here.
Full details for using the SockJS JavaScript client are on the
SockJS website,but in summary you use it something like this:
var sock = new SockJS('http://mydomain.com/myapp'); sock.onopen = function() { console.log('open'); }; sock.onmessage = function(e) { console.log('message', e.data); }; sock.onclose = function() { console.log('close'); }; sock.send('test'); sock.close();
Configuring the SockJS handler
The handler can be configured with various options usingSockJSHandlerOptions.
insertJSESSIONID
Insert a JSESSIONID cookie so load-balancers ensure requests for a specific SockJS sessionare always routed to the correct server. Default is
true.
sessionTimeout
The server sends a
closeevent when a client receiving connection have not been seen for a while.This delay is configured by this setting. By default the
closeevent will be emitted when a receivingconnection wasn’t seen for 5 seconds.
heartbeatInterval
In order to keep proxies and load balancers from closing long running httprequests we need to pretend that the connection is active and send a heartbeat packet once in a while.This setting controls how often this is done. By default a heartbeat packet is
sent every 25 seconds.
maxBytesStreaming
Most streaming transports save responses on the client side and don’t free memory usedby delivered messages. Such transports need to be garbage-collected once in a while.
max_bytes_streamingsets aminimum number of bytes that can be send over
a single http streaming request before it will be closed. After thatclient needs to open new request. Setting this value to one effectively disables streaming and will make streamingtransports to behave like polling transports. The default value is 128K.
libraryURL
Transports which don’t support cross-domain communication natively ('eventsource' to name one)use an iframe trick. A simple page is served from the SockJS server (using its foreign domain) and is placed in aninvisible iframe. Code run from this iframe doesn’t
need to worry about cross-domain issues, as it’s being run fromdomain local to the SockJS server. This iframe also does need to load SockJS javascript client library, and this optionlets you specify its url (if you’re unsure, point it to the latest minified
SockJS client release, this is the default).The default value is
http://cdn.sockjs.org/sockjs-0.3.4.min.js
disabledTransports
This is a list of transports that you want to disable. Possible values areWEBSOCKET, EVENT_SOURCE, HTML_FILE, JSON_P, XHR.
SockJS event bus bridge
Vert.x-Web comes with a built-in SockJS socket handler called the event bus bridge which effectively extends the server-sideVert.x event bus into client side JavaScript.This creates a distributed event bus which not only spans multiple Vert.x instances on the server side, but includesclient side JavaScript running in browsers.
We can therefore create a huge distributed bus encompassing many browsers and servers. The browsers don’t have tobe connected to the same server as long as the servers are connected.
This is done by providing a simple client side JavaScript library called
vertxbus.jswhich provides an APIvery similar to the server-side Vert.x event-bus API, which allows you to send and publish messages to the event busand register handlers to receive messages.
This JavaScript library uses the JavaScript SockJS client to tunnel the event bus traffic over SockJS connectionsterminating at at a
SockJSHandleron the server-side.
A special SockJS socket handler is then installed on the
SockJSHandlerwhichhandles the SockJS data and bridges it to and from the server side event bus.
To activate the bridge you simply call
bridgeon theSockJS handler.
[code]Router router = Router.router(vertx); SockJSHandler sockJSHandler = SockJSHandler.create(vertx); BridgeOptions options = new BridgeOptions(); sockJSHandler.bridge(options); router.route("/eventbus/*").handler(sockJSHandler);
In client side JavaScript you use the 'vertxbus.js` library to create connections to the event bus and to sendand receive messages:
<script src="http://cdn.sockjs.org/sockjs-0.3.4.min.js"></script> <script src='vertxbus.js'></script> <script> var eb = new vertx.EventBus('http://localhost:8080/eventbus'); eb.onopen = function() { // set a handler to receive a message eb.registerHandler('some-address', function(message) { console.log('received a message: ' + JSON.stringify(message); }); // send a message eb.send('some-address', {name: 'tim', age: 587}); } </script>
The first thing the example does is to create a instance of the event bus
var eb = new vertx.EventBus('http://localhost:8080/eventbus');
The parameter to the constructor is the URI where to connect to the event bus. Since we create our bridge withthe prefix
eventbuswe will connect there.
You can’t actually do anything with the connection until it is opened. When it is open the
onopenhandler will be called.
Securing the Bridge
If you started a bridge like in the above example without securing it, and attempted to send messages throughit you’d find that the messages mysteriously disappeared. What happened to them?For most applications you probably don’t want client side JavaScript being able to send just any message to anyhandlers on the server side or to all other browsers.
For example, you may have a service on the event bus which allows data to be accessed or deleted. We don’t wantbadly behaved or malicious clients being able to delete all the data in your database!
Also, we don’t necessarily want any client to be able to listen in on any event bus address.
To deal with this, a SockJS bridge will by default refuse to let through any messages. It’s up to you to tell thebridge what messages are ok for it to pass through. (There is an exception for reply messages which are always allowed through).
In other words the bridge acts like a kind of firewall which has a default
deny-all policy.
Configuring the bridge to tell it what messages it should pass through is easy.
You can specify which matches you want to allow for inbound and outbound traffic using the
BridgeOptionsthat you pass in when calling bridge.
Each match is a
PermittedOptionsobject:
setAddress
This represents the exact address the message is being sent to. If you want to allow messages based onan exact address you use this field.
setAddressRegex
This is a regular expression that will be matched against the address. If you want to allow messagesbased on a regular expression you use this field. If the
addressfield is specified this field will be ignored.
setMatch
This allows you to allow messages based on their structure. Any fields in the match must exist in themessage with the same values for them to be allowed. This currently only works with JSON messages.
If a message is in-bound (i.e. being sent from client side JavaScript to the server) when it is received Vert.x-Webwill look through any inbound permitted matches. If any match, it will be allowed through.
If a message is out-bound (i.e. being sent from the server to client side JavaScript) before it is sent to the clientVert.x-Web will look through any inbound permitted matches. If any match, it will be allowed through.
The actual matching works as follows:
If an
addressfield has been specified then the
addressmust matchexactly with the address of the messagefor it to be considered matched.
If an
addressfield has not been specified and an
addressRegexfield has been specified then the regular expressionin
address_remust match with the address of the message for it to be considered matched.
If a
matchfield has been specified, then also the structure of the message must match. Structuring matching worksby looking at all the fields and values in the match object and checking they all exist in the actual message body.
Here’s an example:
[code]Router router = Router.router(vertx); SockJSHandler sockJSHandler = SockJSHandler.create(vertx); // Let through any messages sent to 'demo.orderMgr' from the client PermittedOptions inboundPermitted1 = new PermittedOptions().setAddress("demo.orderMgr"); // Allow calls to the address 'demo.persistor' from the client as long as the messages // have an action field with value 'find' and a collection field with value // 'albums' PermittedOptions inboundPermitted2 = new PermittedOptions().setAddress("demo.persistor") .setMatch(new JsonObject().put("action", "find") .put("collection", "albums")); // Allow through any message with a field `wibble` with value `foo`. PermittedOptions inboundPermitted3 = new PermittedOptions().setMatch(new JsonObject().put("wibble", "foo")); // First let's define what we're going to allow from server -> client // Let through any messages coming from address 'ticker.mystock' PermittedOptions outboundPermitted1 = new PermittedOptions().setAddress("ticker.mystock"); // Let through any messages from addresses starting with "news." (e.g. news.europe, news.usa, etc) PermittedOptions outboundPermitted2 = new PermittedOptions().setAddressRegex("news\\..+"); // Let's define what we're going to allow from client -> server BridgeOptions options = new BridgeOptions(). addInboundPermitted(inboundPermitted1). addInboundPermitted(inboundPermitted1). addInboundPermitted(inboundPermitted3). addOutboundPermitted(outboundPermitted1). addOutboundPermitted(outboundPermitted2); sockJSHandler.bridge(options); router.route("/eventbus/*").handler(sockJSHandler);
Requiring authorisation for messages
The event bus bridge can also be configured to use the Vert.x-Web authorisation functionality to requireauthorisation for messages, either in-bound or out-bound on the bridge.To do this, you can add extra fields to the match described in the previous section that determine what authority isrequired for the match.
To declare that a specific authority for the logged-in user is required in order to access allow the messages you use the
setRequiredAuthorityfield.
Here’s an example:
[code]PermittedOptions inboundPermitted = new PermittedOptions().setAddress("demo.orderService"); // But only if the user is logged in and has the authority "place_orders" inboundPermitted.setRequiredAuthority("place_orders"); BridgeOptions options = new BridgeOptions().addInboundPermitted(inboundPermitted);
For the user to be authorised they must be first logged in and secondly have the required authority.
To handle the login and actually auth you can configure the normal Vert.x auth handlers. For example:
[code]Router router = Router.router(vertx); // Let through any messages sent to 'demo.orderService' from the client PermittedOptions inboundPermitted = new PermittedOptions().setAddress("demo.orderService"); // But only if the user is logged in and has the authority "place_orders" inboundPermitted.setRequiredAuthority("place_orders"); SockJSHandler sockJSHandler = SockJSHandler.create(vertx); sockJSHandler.bridge(new BridgeOptions(). addInboundPermitted(inboundPermitted)); // Now set up some basic auth handling: router.route().handler(CookieHandler.create()); router.route().handler(SessionHandler.create(LocalSessionStore.create(vertx))); AuthHandler basicAuthHandler = BasicAuthHandler.create(authProvider); router.route("/eventbus/*").handler(basicAuthHandler); router.route("/eventbus/*").handler(sockJSHandler);
Handling event bus bridge events
If you want to be notified when an event occurs on the bridge you can provide a handler when callingbridge.
Whenever an event occurs on the bridge it will be passed to the handler. The event is described by an instance of
BridgeEvent.
The event can be one of the following types:
SOCKET_CREATED
This event will occur when a new SockJS socket is created.
SOCKET_CLOSED
This event will occur when a SockJS socket is closed.
SEND
This event will occur when a message is attempted to be sent from the client to the server.
PUBLISH
This event will occur when a message is attempted to be published from the client to the server.
RECEIVE
This event will occur when a message is attempted to be delivered from the server to the client.REGISTER. This event will occur when a client attempts to register a handler.UNREGISTER. This event will occur when a client attempts to unregister a handler.
The event enables you to retrieve the type using
typeandinspect the raw message of the event using
rawMessage.
The raw message is a JSON object with the following structure:
{ "type": "send"|"publish"|"receive"|"register"|"unregister", "address": the event bus address being sent/published/registered/unregistered "body": the body of the message }
The event is also an instance of
Future. When you are finished handling the event you cancomplete the future with
trueto enable further processing.
If you don’t want the event to be processed you can complete the future with
false. This is a useful feature thatenables you to do your own filtering on messages passing through the bridge, or perhaps apply some fine grainedauthorisation or metrics.
Here’s an example where we reject all messages flowing through the bridge if they contain the word "Armadillos".
[code]Router router = Router.router(vertx); // Let through any messages sent to 'demo.orderMgr' from the client PermittedOptions inboundPermitted = new PermittedOptions().setAddress("demo.someService"); SockJSHandler sockJSHandler = SockJSHandler.create(vertx); BridgeOptions options = new BridgeOptions().addInboundPermitted(inboundPermitted); sockJSHandler.bridge(options, be -> { if (be.type() == BridgeEvent.Type.PUBLISH || be.type() == BridgeEvent.Type.RECEIVE) { if (be.rawMessage().getString("body").equals("armadillos")) { // Reject it be.complete(false); return; } } be.complete(true); }); router.route("/eventbus").handler(sockJSHandler);
You can also amend the raw message, e.g. change the body. For messages that are flowing in from the client you canalso add headers to the message, here’s an example:
[code]Router router = Router.router(vertx); // Let through any messages sent to 'demo.orderService' from the client PermittedOptions inboundPermitted = new PermittedOptions().setAddress("demo.orderService"); SockJSHandler sockJSHandler = SockJSHandler.create(vertx); BridgeOptions options = new BridgeOptions().addInboundPermitted(inboundPermitted); sockJSHandler.bridge(options, be -> { if (be.type() == BridgeEvent.Type.PUBLISH || be.type() == BridgeEvent.Type.SEND) { // Add some headers JsonObject headers = new JsonObject().put("header1", "val").put("header2", "val2"); be.rawMessage().put("headers", headers); } be.complete(true); }); router.route("/eventbus").handler(sockJSHandler);
相关文章推荐
- pip install 报错:Microsoft Visual C++ 9.0 is required Unable to find vcvarsall.bat
- C++实现简易log日志系统
- Spring学习笔记2——高级特性
- jenkins搭建与简单操作
- hdu 1421 经典dp
- SQL处理代码的顺序
- java格式显示及类型转换
- 苹果又推新iPod Touch 看值不值得买
- vim列块操作
- Spring学习笔记1——基础知识
- HTTPS与SSL
- Windows下mysql忘记root密码破解
- 自学基础_linux_5_环境变量配置
- 简单分析Swift语言的一些基本特征
- 将字符串从文件中读取出来
- POJ 2888 Magic Bracelet 有限制的Polya计数
- Core Image 和视频
- 点击空白处隐藏软键盘
- update fedora 20-21/22
- Eclipse 发布到网站的附加产品的形式 Update Site