# 路由(Routing)

到目前为止,我们已经接触了各式各样的装饰器,但是这些装饰器是干什么用的?我们该如何使用它?

@app.route("/stairway")
...
@app.get("/to")
...
@app.post("/heaven")
...

# 添加路由(Adding a route)

将响应函数进行挂载的最基本方式就是使用 app.add_route(),具体的细节请查看 API文档

async def handler(request):
    return text("OK")
app.add_route(handler, "/test")

默认的情况下,路由会绑定监听 HTTP GET 请求方式, 您可以通过修改 methods 参数,从而达到使用一个响应函数响应 HTTP 的多种请求方式。

app.add_route(
        handler,
        '/test',
        methods=["POST", "PUT"],
)

您也可以使用装饰器来进行路由绑定,下面是使用装饰器的方式进行路由绑定的例子,实现的效果和上一个例子相同。

@app.route('/test', methods=["POST", "PUT"])
async def handler(request):
    return text('OK')

# HTTP方法(HTTP methods)

每一个标准的 HTTP 请求方式都对应封装了一个简单易用的装饰器:

    # 路由参数(Path parameters)

    Sanic 允许模式匹配,并从 URL 中提取值。然后将这些参数作为关键字参数传递到响应函数中。

    @app.get("/tag/<tag>")
    async def tag_handler(request, tag):
        return text("Tag - {}".format(tag))
    

    您可以为路由参数指定类型,它将在匹配时进行强制类型转换。

    @app.get("/foo/<foo_id:uuid>")
    async def uuid_handler(request, foo_id: UUID):
        return text("UUID - {}".format(foo_id))
    

    # 匹配类型(Supported types)

      # 正则匹配(Regex Matching)

      更多时候,相对于复杂的路由,以上示例还是过于简单了,由我们使用了和以前完全不同的路由匹配模式,所以在这里我们要详细的说明一下正则的进阶用法。

      有时,您希望匹配路由中的某一部分:

      /image/123456789.jpg
      

      如果您想匹配文件模式,但只捕获数字部分,您需要做一些正则表达式的适配, 来体会编写正则表达式的乐趣 😄 :

      app.route(r"/image/<img_id:(?P<img_id>\d+)\.jpg>")
      

      更进一步,下面的这些匹配方式都是支持的:

      @app.get(r"/image/<foo:\d{9}.jpg>")                 # 完全匹配           
      @app.get(r"/image/<foo:(\d+).jpg>")                 # 定义单个的匹配组            
      @app.get(r"/image/<foo:(?P<foo>\d+).jpg>")          # 定义单个命名的匹配组       
      @app.get(r"/image/<foo:(?P<foo>\d+).(?:jpg|png)>)") # 定义一个命名的匹配组,以及一个或者多个不匹配的组 
      

      值得注意的是,如果您使用了命名的匹配组,它的名称必须与 label 相同

      @app.get(r"/<foo:(?P<foo>\d+).jpg>")  # 正确示例
      @app.get(r"/<foo:(?P<bar>\d+).jpg>")  # 错误示例
      

      更多的用方法请参考:正则表达式操作 (opens new window)

      # 动态访问(Generating a URL)

      Sanic 提供了一种基于处理程序方法名生成 url 的方法:app.url_for(),您只需要函数名称即可实现响应函数之间的处理权力的移交。在您不希望将 url 进行硬编码或希望响应函数之间具有层级关系的时候,这将非常有用。它的使用方法如下:

      @app.route('/')
      async def index(request):
          # generate a URL for the endpoint `post_handler`
          url = app.url_for('post_handler', post_id=5)
          # Redirect to `/posts/5`
          return redirect(url)
      @app.route('/posts/<post_id>')
      async def post_handler(request, post_id):
          ...
      

      您可以传递任意数量的关键字参数,任何非路由参数的部分都会被是做为查询字符串的一部分

      >> > app.url_for(
              "post_handler",
              post_id=5,
              arg_one="one",
              arg_two="two",
      )
      '/posts/5?arg_one=one&arg_two=two'
      

      该方法同样支持为一个键名传递多个值。

      >> > app.url_for(
              "post_handler",
              post_id=5,
              arg_one=["one", "two"],
      )
      '/posts/5?arg_one=one&arg_one=two'
      

      # 特殊关键字参数(Special keyword arguments)

      您可以在 API Docs (opens new window) 查看更多详细信息。

      >> > app.url_for("post_handler", post_id=5, arg_one="one", _anchor="anchor")
      '/posts/5?arg_one=one#anchor'
      # _external requires you to pass an argument _server or set SERVER_NAME in app.config if not url will be same as no _external
      >> > app.url_for("post_handler", post_id=5, arg_one="one", _external=True)
      '//server/posts/5?arg_one=one'
      # when specifying _scheme, _external must be True
      >> > app.url_for("post_handler", post_id=5, arg_one="one", _scheme="http", _external=True)
      'http://server/posts/5?arg_one=one'
      # you can pass all special arguments at once
      >> > app.url_for("post_handler", post_id=5, arg_one=["one", "two"], arg_two=2, _anchor="anchor", _scheme="http",
                       _external=True, _server="another_server:8888")
      'http://another_server:8888/posts/5?arg_one=one&arg_one=two&arg_two=2#anchor'
      

      # 自定义路由名称(Customizing a route name)

      在注册路由的时候,可以通过给定 name 参数来自定义路由名称

      @app.get("/get", name="get_handler")
      def handler(request):
          return text("OK")
      

      现在,您可以通过自定义的名称进行路由匹配。

      >> > app.url_for("get_handler", foo="bar")
      '/get?foo=bar'
      

      # Websocket

      Websocket 的工作方式和 HTTP 是类似的。

      async def handler(request, ws):
          messgage = "Start"
          while True:
              await ws.send(message)
              message = ws.recv()
      app.add_websocket_route(handler, "/test")
      

      它也具备有一个独立的装饰器。

      @app.websocket("/test")
      async def handler(request, ws):
          messgage = "Start"
          while True:
              await ws.send(message)
              message = ws.recv()
      

      具体的工作原理,我们会在之后的 websocket 进行更多描述。

      # 严格匹配分隔符(Strict slashes)

      Sanic 可以按需开启或关闭路由的严格匹配模式,开启后路由将会严格按照 / 作为分隔来进行路由匹配,您可以在以下几种方法中进行匹配,它们的优先级遵循:

      1. 路由(Route)
      2. 蓝图(Blueprint)
      3. 蓝图组(BlueprintGroup)
      4. 应用(Application)
      # 为应用程序下所有的路由都启用严格匹配模式
      app = Sanic(__file__, strict_slashes=True)
      
      # 为指定的路由启用严格匹配模式
      @app.get("/get", strict_slashes=False)
      def handler(request):
          return text("OK")
      
      # 为蓝图所属的路由启用严格匹配模式
      bp = Blueprint(__file__, strict_slashes=True)
      @bp.get("/bp/get", strict_slashes=False)
      def handler(request):
          return text("OK")
      
      bp1 = Blueprint(name="bp1", url_prefix="/bp1")
      bp2 = Blueprint(
          name="bp1",
          url_prefix="/bp2",
          strict_slashes=False,
      )
      # This will enforce strict slashes check on the routes
      # under bp1 but ignore bp2 as that has an explicitly
      # set the strict slashes check to false
      group = Blueprint.group([bp1, bp2], strict_slashes=True)
      

      # 静态文件(Static files)

      为了确保 Sanic 可以正确代理静态文件,请使用 app.static() 方法进行路由分配。

      在这里,参数的顺序十分重要

      第一个参数是静态文件所需要匹配的路由

      第二个参数是渲染文件所在的文件(夹)路径

      更多详细用法请参考 API docs

      app.static("/static", "/path/to/directory")
      

      您也可以提供单独的文件

      app.static("/", "/path/to/index.html")
      

      它同样支持自定义名称,来帮助您实现快速访问

      app.static(
              "/user/uploads",
              "/path/to/uploads",
              name="uploads",
      )
      

      检索 URL 的流程和响应函数类似,但是当您需要特定的文件的时候,可以通过添加 filename 参数来达到效果。

      >> > app.url_for(
              "static",
              name="static",
              filename="file.txt",
      )
      '/static/file.txt'
      ​```python
      >> > app.url_for(
              "static",
              name="uploads",
              filename="image.png",
      )
      '/user/uploads/image.png'
      

      提示

      如果您想要设置多个静态文件路由,我们强烈建议您手动为 static() 加上 name 参数。可以确定的是,这样做可以减少一些潜在且隐蔽的 bug。

      app.static("/user/uploads", "/path/to/uploads", name="uploads")
      app.static("/user/profile", "/path/to/profile", name="profile_pics")
      
      MIT Licensed
      Copyright © 2018-present Sanic Community Organization

      ~ Made with ❤️ and ☕️ ~