先下结论

先说结论, 一般来说, 在项目初始就定好规矩, 在restful中, 将所有资源都看成文件, 那么统一成不加/的好, 然后访问加了斜杠的路径会报404, 加不加看心情但是一个项目中最好统一.

因为在flask中如果路由是/haha/, 那么访问/haha会被重定向到/haha/

1
2
3
4
5
6
7
qq@zz:~/test$ curl -I  http://127.0.0.1:3000/users/kaka
HTTP/1.0 308 PERMANENT REDIRECT
Content-Type: text/html; charset=utf-8
Content-Length: 273
Location: http://127.0.0.1:3000/users/kaka/
Server: Werkzeug/0.15.4 Python/3.6.6
Date: Mon, 20 May 2019 07:39:15 GMT

但是定义一个/haha的路径, 然后访问/haha/会报404:

1
2
3
4
5
6
qq@zz:~/test$ curl -I  http://127.0.0.1:3000/users/kaka/
HTTP/1.0 404 NOT FOUND
Content-Type: text/html
Content-Length: 232
Server: Werkzeug/0.15.4 Python/3.6.6
Date: Mon, 20 May 2019 07:53:04 GMT

举例子

在flask路由的时候发现一个很好玩的事情, 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/users/kaka')
def update_user():
return jsonify(a=1)

@app.route('/users/kaka/')
def update_userss():
return jsonify(a=11)

if __name__ == '__main__':
app.run(debug=True, port=3000)

然后调用:

1
2
3
4
5
6
7
8
9
qq@zz:~$ curl http://127.0.0.1:3000/users/kaka/
{
"a": 11
}
qq@zz:~$ curl http://127.0.0.1:3000/users/kaka
{
"a": 1
}
# 这是因为werkzug中所有的路由都存在一个列表中, 所以涉及到匹配的先后顺序

然后将路径/users/kaka最后的斜杠顺序换过来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/users/kaka/')
def update_user():
return jsonify(a=1)

@app.route('/users/kaka')
def update_userss():
return jsonify(a=11)

if __name__ == '__main__':
app.run(debug=True, port=3000)

返回结果:

1
2
3
4
5
6
7
8
9
qq@zz:~$ curl http://127.0.0.1:3000/users/kaka/
{
"a": 1
}
qq@zz:~$ curl http://127.0.0.1:3000/users/kaka
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to target URL: <a href="http://127.0.0.1:3000/users/kaka/">http://127.0.0.1:3000/users/kaka/</a>. If not click the link.

找解决

werkzeug/routing.py中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    - you receive a `RequestRedirect` exception with a `new_url`
attribute. This exception is used to notify you about a request
Werkzeug requests from your WSGI application. This is for example the
case if you request ``/foo`` although the correct URL is ``/foo/``
You can use the `RequestRedirect` instance as response-like object
similar to all other subclasses of `HTTPException`.

`string`
Rule strings basically are just normal URL paths with placeholders in
the format ``<converter(arguments):name>`` where the converter and the
arguments are optional. If no converter is defined the `default`
converter is used which means `string` in the normal configuration.

URL rules that end with a slash are branch URLs, others are leaves.
If you have `strict_slashes` enabled (which is the default), all
branch URLs that are matched without a trailing slash will trigger a
redirect to the same URL with the missing slash appended.

追溯到第一使用地点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Rule:

def match(self, **kwargs):
if (
self.strict_slashes
and not self.is_leaf
and not groups.pop("__suffix__")
and (
method is None or self.methods is None or method in self.methods
)
):
raise RequestSlash()


class MapAdapter(object):
def match(self, path_info=None, method=None, return_rule=False, query_args=None):
for rule in self.map._rules:
try:
rv = rule.match(path, method)
except RequestSlash:
raise RequestRedirect(
self.make_redirect_url(
url_quote(path_info, self.map.charset, safe="/:|+") +"/",
query_args,
)
)
except RequestAliasRedirect as e:
raise RequestRedirect(
self.make_alias_redirect_url(
path, rule.endpoint, e.matched_values, method, query_args
)
)

class Map:

def __init__(strict_slashes=True,
redirect_defaults=True,):
pass

小总结

最后为什么一个匹配的上一个匹配不上, 这涉及到werkzeug的路由建立顺序和匹配顺序, 在另一篇路由的实现和查找中有提到.

番外–linux的普通文件和目录文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
qq@zz:~/test$ touch haha
qq@zz:~/test$ mkdir kaka
qq@zz:~/test$ stat haha
文件:'haha'
大小:0 块:0 IO 块:4096 普通空文件
设备:806h/2054d Inode:1058541 硬链接:1
权限:(0664/-rw-rw-r--) Uid:( 1000/ qq) Gid:( 1000/ qq)
最近访问:2019-05-20 15:34:08.849485139 +0800
最近更改:2019-05-20 15:34:08.849485139 +0800
最近改动:2019-05-20 15:34:08.849485139 +0800
创建时间:-
qq@zz:~/test$ stat kaka
文件:'kaka'
大小:4096 块:8 IO 块:4096 目录
设备:806h/2054d Inode:1061448 硬链接:2
权限:(0775/drwxrwxr-x) Uid:( 1000/ qq) Gid:( 1000/ qq)
最近访问:2019-05-20 15:34:12.837483383 +0800
最近更改:2019-05-20 15:34:12.837483383 +0800
最近改动:2019-05-20 15:34:12.837483383 +0800
创建时间:-
qq@zz:~/test$ ll
总用量 12
drwxrwxr-x 3 qq qq 4096 5月 20 15:34 ./
drwxr-xr-x 80 qq qq 4096 5月 20 15:02 ../
-rw-rw-r-- 1 qq qq 0 5月 20 15:34 haha
drwxrwxr-x 2 qq qq 4096 5月 20 15:34 kaka/

在linux下面, 普通文件结尾为filename, 目录文件结尾是dirname/