- Basic Vert.x-Web concepts
- Handling requests and calling the next handler
- Using blocking handlers
- Routing by exact path
- Routing by paths that begin with something
- Capturing path parameters
- Capturing path parameters with regular expressions
- Routing with regular expressions
- Routing by HTTP method
- Route order
- Routing based on MIME type of request
- Routing based on MIME types acceptable by the client
- Combining routing criteria
- Enabling and disabling routes
- Context data
- Sub-routers
- Default 404 Handling
- Error handling
- Request body handling
Basic Vert.x-Web concepts
Here’s the 10000 foot view:
Router
是Vert.x-Web
最核心的概念. Router
是一个持有零到多个Routes
的对象
Router
会将HTTP request
发送到第一个匹配该请求的route
身上.
route
持有一个与HTTP request
相匹配的handler
, 然后该handler
接受该请求. 然后执行具体任务, 当执行完任务之后你可以选择结束该请求或者将它传递给下一个匹配的handler
.
下面是一个简单的示例:
1 | HttpServer server = vertx.createHttpServer(); |
我们创建了一个HTTP Server
服务器, 接着创建了一个router
. 我们没有对这个route
指定匹配规则,因此它会匹配所有的HTTP request
.
然后我们在该route
上设置了一个handler
, 这个handler
会处理该服务器上所有的HTTP request
.
传递给handler
的是一个RoutingContext
对象, 该对象包含一个一个标准的Vert.x HttpServerRequest
和Vert.x HttpServerResponse
,但是还包含了很多其他的Vert.x-Web
里的特性.
对于每一个HTTP request
都会生成一个唯一的RoutingContext
实例, 但是给实例会传递给所有匹配该请求的handler
.
Handling requests and calling the next handler
当Vert.x-Web``route
一个HTTP Request
到一个与之匹配的route
,它会向该route
的handler
传递一个RoutingContext
实例.
如果在当前handler
里,你不想结束response
, 那么你应该调用下一个相匹配的route
继续处理该请求.
你没有必要在当前handler
执行完之前调用下一个route
, 你可以稍后再做这件事.
1 | Route route1 = router.route("/some/path/").handler(routingContext -> { |
在上面的例子中, route1
被写到response
的5秒钟之后,route2
被写到response
,在过了5秒钟之后,route3
被写到response
,然后结束掉response
.
注意这一切的发生都是非阻塞的,
Using blocking handlers
在某些环境下,你也许想要在handler执行时,将event loop
阻塞住, 例如调用一个阻塞API或者执行一些密集型计算. 这种情况下,你不能在普通handler
中进行操作,我们为route
提供了一个阻塞的handler
.
阻塞式和非阻塞式handler
非常像,只不过阻塞式是由Vert.x
从worker pool
中借出一个线程进行任务执行,而非是从event loop
中.
下例进行了说明:
1 | router.route().blockingHandler(routingContext -> { |
在默认情况下, Vert.x中任何的阻塞handler
都是在相同的上下文中(例如verticle
实例中)顺序执行的,这意味着当前一个handler未完成之前,下一个handler是不会执行的. 如果你不关心任务的执行顺序,而且不介意阻塞handler
并行执行,你可以在调用blockingHandler
方法时传递一个false
的参数,让其不按照任务的指定顺序进行执行.
Routing by exact path
route
可以被设定为匹配特定的URI
. 在这种情况下,它就可以指定路径的请求了.
在下面的例子中,handler
会被路径为/some/path/
的请求调用. 但是我们会忽略末尾斜杠,因此当路径为/some/path
和/some/path//
该handler都会被调用.
1 | Route route = router.route().path("/some/path/"); |
Routing by paths that begin with something
通常情况下,你会想设置一个通用的前置路径. 如果是这种情况你可以使用正则表达式, 但是一个比较简单的实现方式是在route path
的末尾加上一个*
.
在下面的例子中,当请求路径前缀为/some/path/
的时候,我们设置的handler
都会被执行. (例如/some/path/foo.html
和/some/path/otherdir/blah.css
都是匹配的)
1 | Route route = router.route().path("/some/path/*"); |
你还可以将路径参数放在route()
方法里
1 | Route route = router.route("/some/path/*"); |
Capturing path parameters
我们可以在请求参数上使用通配符进行匹配路径
1 | Route route = router.route(HttpMethod.POST, "/catalogue/products/:productype/:productid/"); |
通配符由:
组成,将其放在参数名后面. 参数名由字母和数字下划线组成.
在上面的例子中,如果一个POST
请求地址是/catalogue/products/tools/drill123/
, 那么上面的route
会被匹配到, productType
接收到tools
值, productID
会接收到drill123
值.
Capturing path parameters with regular expressions
当使用正则表达式的时候,你还可以捕获路径参数:
1 | Route route = router.routeWithRegex(".*foo"); |
在上面的例子中,如果请求路径是/tools/drill123/
, 那么我们设置的route
会被匹配到, 然后productType
会接收到参数值tools
, productID
会接收到参数值drill123
.
Captures are denoted in regular expressions with capture groups (i.e. surrounding the capture with round brackets)
Routing with regular expressions
正则还可以被使用在URI
路径的匹配上:
1 | Route route = router.route().pathRegex(".*foo"); |
还有一种做法是,正则可以在创建route
时进行指定.
1 | Route route = router.routeWithRegex(".*foo"); |
Routing by HTTP method
在默认的情况下route
会匹配所有的HTTP methods
.
如果你想要某个route
只匹配特定的HTTP method
,你可以像下面这样做:
1 | Route route = router.route().method(HttpMethod.POST); |
或者你在创建route
时直接指定:
1 | Route route = router.route(HttpMethod.POST, "/some/path/"); |
当然还有其他方式可用,你可以直接调用get()
, post
, put
等方法调用
1 | router.get().handler(routingContext -> { |
如果你想要对某个route
指定多个HTTP method
,你可以像下面这样做:
1 | Route route = router.route().method(HttpMethod.POST).method(HttpMethod.PUT); |
Route order
在默认情况下routes
的排序是按照添加添加进router
的顺序进行排序的.
当router
接受到一个请求时, router
会遍历自身的每一个route
查看是否与请求匹配,如果匹配的话,该route的handler
就会被调用.
如果handler
随后调用了下一个handler
, 那么下一个与之匹配的route
也会被调用.
1 | Route route1 = router.route("/some/path/").handler(routingContext -> { |
在上面的例子中,输出结果为:
1 | route1 |
/some/path
开头的请求都是按照刚才的那个顺序进行route
调用的.
如果你想要改变route
的调用顺序,你可以使用order()
方法,向其指定一个指定值.
Routes
在router
中的位置是按照他们添加进去的时间顺序进行排序的, 而且他们的位置是从0开始的.
当然像上文所说的,你还可以调用order()
方法改变这个排序. 需要注意的是序号可以是负数, 例如你想要某个route
在序号0的route
之前执行,你就可以将某个route
序号指定为-1.
下例中我们改变了route2
的序号,确保他在route1
之前执行.
1 | Route route1 = router.route("/some/path/").handler(routingContext -> { |
接下来我们就看到了我们所期望的结果
1 | route2 |
如果俩个route
有相同的序号,那么他们会按照添加的顺序进行执行. 你还可以通过调用last()
方法将某个route
放到最后一个.
Routing based on MIME type of request
你可以通过consumes
方法指定route
需要匹配请求的MIME
类型.
这下面的例子中, 请求包含了一个content-type
请求头,该值指定了请求体的MINE
类型. 这个值会和consumes
方法里的值进行匹配.
MINE
类型的匹配可以做到精确匹配.
1 | router.route().consumes("text/html").handler(routingContext -> { |
同样我们还可以进行多个MINE
类型的匹配
1 | router.route().consumes("text/html").consumes("text/plain").handler(routingContext -> { |
我们还可以通过通配符对子类型进行匹配
1 | router.route().consumes("text/*").handler(routingContext -> { |
我们还可以通过通配符对父类型进行匹配
1 | router.route().consumes("*/json").handler(routingContext -> { |
如果你在consumers
不指定/
, 它会假定你指的是子类型.
Routing based on MIME types acceptable by the client
HTTP accept header
常常用于表示客户端接收到的服务器响应的MIME
类型.
accept header
可以带有多个MIME
类型,他们之间通过','
分割.
MIME
类型还可以有一个q
值,
MIME types can also have a q value appended to them* which signifies a weighting to apply if more than one response 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.
例如,下面的accept header
表示只会接受text/plain
的MIME
类型.
1 | Accept: text/plain |
下面的accept header
会接受text/plain
和text/html
的MIME
类型,这俩者直接并没有优先级.
1 | Accept: text/plain, text/html |
但是下面的客户端会会接受text/plain
和text/html
的MIME
类型,但是text/html
的优先级高于text/plain
, 因为text/html
有一个更高的q
值. (默认情况下q=1
)
1 | Accept: text/plain; q=0.9, text/html |
如果服务器能够同时提供text/plain
和text/html
, 那么在这个例子中,他就应该提供text/html
.
通过使用produces
方法设置了route
产生的MIME
类型, 例如下面的handler
设置了一个MIME
类型为application/json
的响应
1 | router.route().produces("application/json").handler(routingContext -> { |
在这个例子中,route
会匹配到所有的 accept header
为application/json
的请求.
下面是该accept headers
的匹配值:
1 | Accept: application/json |
你还可以设置你的route
produce多个MIME
类型. 在这种情况中, 你可以使用getAcceptableContentType()
方法找到实际接受到的MIME
类型
1 | router.route().produces("application/json").produces("text/html").handler(routingContext -> { |
在上面的例子中,如果你发送下面的accept header
1 | Accept: application/json; q=0.7, text/html |
Then the route would match and acceptableContentType would contain text/html as both are acceptable but that has a higher q value.
Combining routing criteria
你可以将很多route
规则组合到一起, 例如:
1 | Route route = router.route(HttpMethod.PUT, "myapi/orders") |
Enabling and disabling routes
你可以通过调用disable()
方法手动的关闭某个route
, 被关闭的route
会忽略所有的匹配.
你还可以通过调用enable
重新打开被关闭的route
Context data
在一个请求的生命周期之内, 你可以将想要在handler
之间共享的数据放在RoutingContext
中进行传递.
下面的例子中,使用put
添加数据, 使用get
检索数据.
1 | router.get("/some/path").handler(routingContext -> { |
Sub-routers
有时候你也许会有很多handler
来处理请求, 在这种情况下将这些handler
分配到多个route
中是非常有好处的. 另外如果你想在不同的应用程序中通过不同的root
路径来复用这些handler
, 这种做法同样是很有帮助的.
为了能达到这种效果, 你可以
Sometimes if you have a lot of handlers it can make sense to split them up into multiple routers. This is also useful if 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 a sub-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 another router. The full implementation of the REST API is not shown.
Here’s the sub-router:
1 | Router restAPI = Router.router(vertx); |
If this router was used as a top level router, then GET/PUT/DELETE requests to urls like /products/product1234 would invoke the API.
However, let’s say we already have a web-site as described by another router:
1 | Router mainRouter = Router.router(vertx); |
We can now mount the sub router on the main router, against a mount point, in this case /productsAPI
mainRouter.mountSubRouter(“/productsAPI”, restAPI);
This means the REST API is now accessible via paths like: /productsAPI/products/product1234
Default 404 Handling
如果没有route
匹配到客户端的请求,那么Vert.x-Web
会发送一个404的信号错误.
你可以自行手动设置error handler
, 或者选择我们提供的error handler
, 如果没有设置error handler
,Vert.x-Web
会返回一个基本的404回应
Error handling
我们可以设置handler
来处理请求,同样我们还可以设置handler
处理route
中的失败情况.
Failure handlers
通常是和处理普通handler
的route
一起工作.
例如,你可以提供一个failure handler
只是用来处理某种特定路径下的或者某种特定HTTP method
的失败.
这种机制就为你向应用程序的不同部分设置不同的failure handler
了.
下面的例子演示了我们向/somepath/
开始的路径和GET
请求才会被调用的failure handler
.
1 | Route route = router.get("/somepath/*"); |
如果handler
中抛出异常Failure routing
就会发生作用, 或者handler
调用失败,向客户端发送一个失败的HTTP
状态码信号.
如果我们从handler
中捕获一个异常, 我们将会向客户端返回500
的状态码.
在处理失败的时候, failure handler
会被传递给routing context
, 我们可以在该context
中检索出当前的错误, 因此failure handler
可以用于生成failure response
。
1 | Route route1 = router.get("/somepath/path1/"); |
Request body handling
The BodyHandler allows you to retrieve request bodies, limit body sizes and handle file uploads.
You should make sure a body handler is on a matching route for any requests that require this functionality.
1 | router.route().handler(BodyHandler.create()); |
Getting the request body
If you know the request body is JSON, then you can use getBodyAsJson, if you know it’s a string you can use getBodyAsString, or to retrieve it as a buffer use getBody.
Limiting body size
To limit the size of a request body, create the body handler then use setBodyLimit to 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 behaviour you can use disable it with setMergeFormAttributes.
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 the uploads directory, which is file-uploads by default.
Each file will be given an automatically generated file name, and the file uploads will be available on the routing context with fileUploads.
Here’s an example:
1 | router.route().handler(BodyHandler.create()); |
Each file upload is described by a FileUpload instance, which allows various properties such as the name, file-name and size to be accessed.
Handling cookies
Vert.x-Web
使用CookieHandler
来支持cookies
.
你必须确保当请求需要cookies
支持的时候,你已经设置上了cookie handler
。
1 | router.route().handler(CookieHandler.create()); |
Manipulating cookies
你可以向getCookie()
方法, 或者通过cookies()
方法检索出cookie
集合.
getCookie()
, 传递一个cookie name
的参数来检索出一个cookie
cookies()
, 检索出cookie
集合removeCookie
, 删除一个cookie
addCookie
, 添加一个cookie
当response headers
被写回的时候, cookies
集合会自动的被写入到response
中.
Cookies
是通过Cookie
实例进行描述的. 你可以通过该实例检索出cookie
中的name
, value
, domain
, path
或者其他的cookie
属性.
下面的例子演示了如何检索和添加cookie
1 | router.route().handler(CookieHandler.create()); |
Handling sessions
Vert.x-Web
同样提供了对于session
的支持.
Sessions last between HTTP requests for the length of a browser session and give you a place where you can add session-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 browser when 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-up the actual session on the server. The identifier is a random UUID generated using a secure random, so it should be effectively unguessable.
Cookies are passed across the wire in HTTP requests and responses so it’s always wise to make sure you are using HTTPS 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 SessionHandler on 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 do that yourself.
Session stores
To create a session handler you need to have a session store instance. The session store is the object that holds 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 application and 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 your requests 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
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 different requests 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
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 sure your session handler is routed to before your application handlers.
You’ll also need to include a CookieHandler as the session handler uses cookies to lookup the session. The cookie handler should be before the session handler when routing.
Here’s an example:
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 with session.
You put data into the session with put, you get data from the session with get, and you remove data 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 for a clustered session store they can be any basic type, or Buffer, JsonObject, JsonArray or a serializable object, as the values have to serialized across the cluster.
Here’s an example of manipulating session data:
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 session from the context and the session store. Note that if there is no session a new one will be automatically created for 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. When a 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 the response is complete and the session is stored back in the store.
You can also use setAccessed to manually mark a session as accessed.
The session timeout can be configured when creating the session handler. Default timeout is 30 minutes.
Authentication
Serving static resources
CORS handling
Cross Origin Resource Sharing
是一个安全的资源请求途径(AJAX跨域问题的解决方案)
Vert.x-Web
包含了一个CorsHandler
, 用于处理CORS
协议. 例如
1 | router.route().handler(CorsHandler.create("vertx\\.io").allowedMethod(HttpMethod.GET)); |
TODO more CORS docs
Templates
Error handler
你可以自己提供一个ErrorHandler
用于处理error
异常,否则的话Vert.x-Web
会包含一个包装好的pretty error handler
用于响应错误页面.
如果你自己想要设置一个ErrorHandler
, 那么你只需要将其设置成failure handler
就可以了.
Request logger
Vert.x-Web
内嵌了LoggerHandler
,使用它你可以将HTTP requests
通过日志形式记录下来.
默认情况下,请求是Vert.x logger
进行记录的,我们可以将其配置成JUL logging, log4j or SLF4J
Serving favicons
Vert.x-Web
包含一个FaviconHandler
来响应favicons
.
Favicons
可以被指定为文件系统里的一个路径,或者Vert.x-Web
会默认的在classpath
搜索名为favicon.ico
的文件.这意味着你需要在你的应用程序的jar包绑定该favicon
Timeout handler
Vert.x-Web
包含一个超时handler
,你可以使用它应付某些操作执行时间太长的请求.
我们通过一个TimeoutHandler
实例来配置它.
如果一个请求在写回之前超时了,那么4.8响应将会写回个给客户端.
下面的例子使用了一个超时handler
:
1 | router.route("/foo/").handler(TimeoutHandler.create(5000)); |
Response time handler
这个handler
设置了x-response-time
响应头, 该响应头包含了请求从接受到响应所耗费的时间,单位是ms
:
1 | x-response-time: 1456ms |