跳转至

路由

什么是路由?

每个HTTP请求都有一个URL。一个URL标识着一个资源。在互联网的早期,资源就是一个文件。例如,URL http://www.geocities.com/my_page/image.jpg将从位于www.geocities.com的网络服务器上的文件夹my_page中返回文件image.jpg。在今天的Web应用中,资源来自于许多其他的数据来源,如数据库或连接的设备。Web应用程序的工作就是为URL提供、创建或修改资源,无论这个资源来自哪里。

一个URL是由许多部分组成的,其中有些部分是可选的。我们作为人类所看到的典型的URL看起来是这样的。http://stablekernel.com/about。大多数人认识到,在浏览器中输入这个URL,就会进入我们公司的 "关于 "页面。换句话说,我们的 "关于 "页面就是一个资源,而这个URL就是它的标识。

一般来说,"关于 "页面的URL有三个必要的组成部分:scheme (http)、host (stablekernel.com)和path (/about)。主机指定了负责提供资源的计算机,路径标识了资源,协议让请求者和主机知道他们应该如何交换信息。

当方案为“ http”(或“ https”)且主机引用运行该应用程序的计算机时,Aqueduct 应用程序会接收请求。 因此,一旦应用程序获得请求,它就只关心其余部分:路径。

在Aqueduct 中,Router根据请求路径将 Request路由到Controller。 此过程称为路由。 当应用程序启动时,路由会在ApplicationChannel的子类中注册。 每个注册的路由都会创建一个新的Controller的channel来处理请求。

路由规格匹配HTTP请求路径

通过调用Router.route.route来注册一个路由。这个方法需要一个路由规范\,一个带有一些语法规则的 String,它将与请求的路径匹配。这个注册是在应用程序第一次启动时通过覆盖ApplicationChannel.entryPoint来进行的。比如说

class MyApplicationChannel extends ApplicationChannel {
  @override
  Controller get entryPoint {
    final router = new Router();

    router
      .route("/users")
      .linkFunction((req) async => new Response.ok(await getAllUsers());

    return router;
  }
}

The argument to route is the route specification string. This particular route matches the path /users. That is, a request for the URL http://myserver.com/users will be handled by the linkFunction closure. (Leading and trailing slashes are stripped out when routes are compiled, so including them has no effect, but it is good style to show a leading slash.)

A path can have multiple segments (the characters between slashes). For example, the path /users/foo has two path segments: users and foo. A route specification matches each segment of a path against each of its segments. The path and the route must also have the same number of segments. Thus, the route specification /users/foo would match the path /users/foo, but it would not match the paths /users, /users/7 or /users/foo/1.

路径变量

A route specification may have path variables. A path variable captures the value from a path segment, so that your code can use it. A path variable is most often used to uniquely identify a resource by some identifier, like /users/1 and /users/2.

In a route specification, a path variable starts with a colon (:). The name of the variable follows this colon. For example, consider the following route that declares a path variable named userID:

router.route("/users/:userID")

This route specification will match /users/1, /users/2, /users/foo, etc. The value of userID is 1, 2 and foo, respectively. This route won't match /users or /users/1/2.

可选路径段

Routes may have optional path segments. This allows a group of routes that all refer to a resource collection and its individual resources to go to the same controller. For example, the requests /users and /users/1 can both be covered by a single route specification.

An optional path segment has square brackets ([]) around it. The brackets can go before or after slashes. For example, the following two syntaxes register a route that accepts both /users and /users/:userID:

route("/users/[:userID]")
route("/users[/:userID]")

Conceptually, a request with a path of /users/1 identifies a single user, where /users identifies all users. Optional segments are used to create better code structure by forwarding requests that deal with a specific type of resource to the same controller. Therefore, the code to handle one user or multiple users is written in the same place.

You may have any number of optional segments in a route specification. Each optional segment must be nested. The following route would match /a, /a/b and /a/b/c. It would not match /a/c.

route("/a/[b/[c]]")

It's pretty rare to have more than one optional segment in a route. For example, consider the route:

route("/users/[:id/[:subresource/[:subresourceid]]]");

The code to handle one or more users is likely very different than the code to handle one of its subresources - different database tables will be queried and different authorization may be needed. Thus, a better approach is to split subresources into their own routes to keep controller logic modular:

// Matches /users and /users/:id
route("/users/[:id]")...;

// Matches /users/:userId/posts and /users/:userId/posts/:postId
route("/users/:userId/posts/[:postId]")...;

// Matches /users/:userId/notes and /users/:userId/notes/:noteId
route("/users/:userId/notes/[:noteId]")...;

限制路径变量值

Path variables may restrict their possible values with a regular expression. The expression comes in parentheses following the path variable name. For example, the following route specification limits userID to numbers only:

route("/users/:userID([0-9]+)")

This regular expression would only apply to the :userID segment. Note that capture groups and parentheses in general can't be included in a route's regular expression.

Everything in between the parentheses is evaluated as the regular expression. Therefore, any additional spaces or characters will be a part of the regular expression. Since spaces aren't valid in a URL, you don't want spaces in a regular expression.

匹配剩余路径

Finally, a route specification may have a special 'match-all' token, the asterisk (*). This token allows for any remaining request path to be matched, regardless of its contents or length. For example, the route specification /users/* would match the following paths:

/users
/users/1
/users/foo
/users/foo/bar
/users/foo/bar/something/else/and/this/goes/on/forever

This token is used when another medium is going to interpret the URL. For example, FileController - which reads a file from the filesystem - might have a route /file/*. It uses everything after /file to figure out the path on the filesystem.

访问路径变量

Information that a router parses from a request path - like path variables - are stored in Request.path. When a Request is handled by a router, its path is set to an instance of this type. Controllers deeper in the channel access Request.path to help determine which resource the request is identifying. The path is an instance of RequestPath.

A RequestPath contains an map of variables, where the key is path variable name and the value is the value of that variable in the request. For example, consider a route specification /users/:id. When a request with path /users/1 is routed, the value 1 is stored in this map for the key id:

final identifier = request.path.variables["id"];
// identifier = "1"

The values in variables are always Strings, since a request path is a String. Controllers may parse path variables into types like int.

ResourceController uses path variables to select a operation method to handle a request.

匹配失败返回404

A Router will return a 404 Not Found if there is no matching route for the request. No linked controllers will handle the request. This behavior may be overridden by providing a closure to Router's constructor.