Flask 的全部内容是「一个网址对应一个函数」:有人访问这个地址,就调那个函数,
把返回值当网页正文发回去。
找得到叫 200,找不到叫 404,服务器自己坏了叫 500。
把内部错误报成 404,排查的人会以为是地址写错,白找半天。
绑 127.0.0.1 只有本机能访问,绑 0.0.0.0 才轮得到别人。
「我起了服务,同事打不开」八成就是这个原因。
前八章的程序都是「跑一下、出结果、结束」。从这一章开始,程序要一直挂着等别人访问。
教材第 9 章开头三节是【认识 HTTP 通信协议】和【Flask 网站应用程序设计】,
还有一节【设置 Flask 服务器的 IP 地址和端口】。
本篇的演示是真跑的:用 make_server 把服务挂在后台线程里,端口让系统分配,
然后用 requests 真发请求、真拿响应。不是「示意代码」。
Flask 是这一章唯一需要装的第三方包,前面几篇 pip install flask 那一步已经做了。
一、三行起一个网站
from flask import Flask,app = Flask(__name__),然后@app.route('/') 下面挂一个函数。
那个 @app.route('/') 叫装饰器(附录 A 会讲它的原理),意思是「有人访问根路径,
就调下面这个函数」。
函数返回什么,浏览器就看到什么。返回一个字符串,那就是网页正文。
这三行就是 Flask 的全部核心。 剩下的一切——参数、模板、表单、
数据库——都是在这套「地址对函数」的骨架上长出来的。
启动要写 app.run()。
但这一句会阻塞,一直占着不放,所以演示里用的是 make_server,它把服务挂在后台线程里,
主线程还能继续发请求。
二、一次请求到底交换了什么
演示第二段打出来六项:请求地址、状态码 200、内容类型 text/html; charset=utf-8、正文 15 字节、
正文内容、服务器标识。
这一来一回就是一个完整的 HTTP 交换。 客户端说「我要 GET /」,服务器回一个状态码、几个头、
一段正文。
状态码是三个数字,第一位决定大类:2 开头是成功、3 开头是重定向、4 开头是客户端错了、
5 开头是服务器错了。
Content-Type 里那个 charset=utf-8 很关键。 没有它,浏览器可能按别的编码解释,
中文就成乱码。这是 Flask 默认帮你加上的——它知道你返回的是字符串。
正文 15 字节这个数也对得上:「你好,世界」是 5 个字符,其中逗号是全角的,
UTF-8 里每个汉字和全角标点都占 3 字节,5 × 3 = 15。
不过别在正文里硬算字节数。
同一个汉字在 UTF-8 里 3 字节、在 GBK 里 2 字节,半角标点只占 1 字节,混在一起心算必错。
程序里用 len(s.encode('utf-8')) 算,正文里说「十几个字节」就够了。
三、网址里带参数
@app.route('/hello/<name>') 里的 <name> 是占位符,函数多收一个参数 name。
默认是字符串。想要整数,写 <int:no>。演示里 /item/7 返回 200,/item/abc 返回 404。
注意 404 不是「报错」,是「这个地址根本不存在」。 因为 abc 不符合 int,
Flask 认为没有哪条规则能匹配这个网址。
这种设计叫 REST 风格的路径参数:把变化的那个东西放进路径,而不是放进问号后面。
/item/7 比 /item?id=7 好在两处:一是好读,二是被搜索引擎当成不同的页面。
商品页、用户页、文章页都这么写。
四、404 和 500 是两个不同的道歉
演示第四段三个地址各试了一次:/ok 是 200、/missing 是 404、/boom 是 500。
404 的意思是「你要的东西我这没有」。可能是网址打错了,可能是内容被删了。
500 的意思是「我这边坏了」。代码抛了没接住的异常,服务端自己出问题。
这两个千万别混。 把内部错误报成 404,排查的人会去查网址、查路由,查半天才发现是数据库连不上。 反过来也一样:本该 404 的地方返回 200 加一句「找不到」,监控系统就永远发现不了坏链。 Flask 默认的 404 页和 500 页都很朴素,正文一两百字节。 真实项目会自己写错误页,但状态码必须对——页面好不好看是次要的。
五、host 和 port 到底在说什么
| 写法 | 谁能访问 | 什么时候用 |
|---|---|---|
127.0.0.1:5000 |
只有本机 | 自己调试 |
0.0.0.0:5000 |
同一局域网的机器都能访问 | 给同事看、手机测试 |
0.0.0.0:80 |
同上,但端口是默认端口 | 网址不用带端口号 |
127.0.0.1 是本机回环地址,数据包根本不出网卡。
所以别人访问不到,不是防火墙的问题,是压根没往外发。
0.0.0.0 表示「所有网卡都听」。绑这个,同一个局域网里的机器才能连上。
很多新手说「我起了服务,同事打不开」,八成就是绑在 127.0.0.1 上。
但反过来也要小心:绑 0.0.0.0 等于把服务暴露给整个网络。
没有密码保护的东西别这么干,尤其在公司的办公网里。
端口也有讲究。1024 以下是系统保留端口,绑定要管理员权限;
80 是 HTTP 的默认端口,用了它网址就不用带 :端口号。开发阶段用 5000 或 8000 就行。
六、动手 Demo:三行代码起一个网站
用 make_server 把 Flask 挂在后台线程,端口让系统分配,然后真发请求。
先看最小应用,再看路径参数、404 与 500 的差别。
最后把 host 的三种写法列出来。
import logging, threading
from flask import Flask, abort
from werkzeug.serving import make_server
import requests
logging.getLogger('werkzeug').setLevel(logging.ERROR)
app = Flask(__name__)
@app.route('/')
def home():
return '你好,世界'
@app.route('/hello/<name>')
def hello(name):
return '你好,%s' % name
@app.route('/item/<int:no>')
def item(no):
return '第 %d 号商品,总价 %.2f' % (no, no * 1.5)
@app.route('/ok')
def ok():
return '一切正常'
@app.route('/boom')
def boom():
return 1 / 0
srv = make_server('127.0.0.1', 0, app)
port = srv.server_port
threading.Thread(target=srv.serve_forever, daemon=True).start()
B = 'http://127.0.0.1:%d' % port
print('① 最小的应用')
print(" from flask import Flask")
print(" app = Flask(__name__)")
print(" @app.route('/')")
print(' def home():')
return '你好,世界'
print(" 装饰器 @app.route('/') 的意思是:有人访问根路径,就调下面这个函数,")
print(' 把它的返回值当网页正文发回去。')
print('② 真起服务,真发请求')
r = requests.get(B + '/', timeout=5)
print(' 请求 %s/' % B)
print(' 状态码 %d' % r.status_code)
print(' 内容类型 %s' % r.headers.get('Content-Type'))
print(' 正文长度 %d 字节' % len(r.content))
print(' 正文 %s' % r.text)
print(' 服务器 %s' % r.headers.get('Server'))
print(' 这一来一回就是一个完整的 HTTP 交换。')
print('③ 网址里带参数')
for path in ('/hello/小王', '/item/7', '/item/abc'):
r = requests.get(B + path, timeout=5)
body = r.text.strip().splitlines()[0][:24] if r.status_code == 200 else '(Flask 自带的 404 页)'
print(' %-14s 状态 %d %s' % (path, r.status_code, body))
print(' <name> 默认是字符串,<int:no> 限定只能是整数。')
print(' 写 abc 进去直接 404——不是报错,是「这个地址根本不存在」。')
print('④ 找不到页面、出错页面')
for path in ('/ok', '/missing', '/boom'):
r = requests.get(B + path, timeout=5)
print(' %-10s 状态 %d 正文 %d 字节' % (path, r.status_code, len(r.content)))
print(' 404 是「没这个东西」,500 是「我这边坏了」。')
print(' 这两个状态码别混:把内部错误报成 404,排查的人会以为是地址写错,白找半天。')
print('⑤ host 和 port 到底在说什么')
print('| 写法 | 谁能访问 | 什么时候用 |')
print('|---|---|---|')
print('| 127.0.0.1:5000 | 只有本机 | 自己调试 |')
print('| 0.0.0.0:5000 | 同一局域网的机器都能访问 | 给同事看、手机测试 |')
print('| 0.0.0.0:80 | 同上,但端口是默认端口 | 网址不用带端口号 |')
print(' 本次实际用的是 127.0.0.1:%d(端口由系统分配)' % port)
srv.shutdown()
实跑输出:
--------------------------------------------------------------------
三行代码起一个网站
--------------------------------------------------------------------
① 最小的应用
--------------------------------------------------------------------
from flask import Flask
app = Flask(__name__)
@app.route('/')
def home():
return '你好,世界'
装饰器 @app.route('/') 的意思是:有人访问根路径,就调下面这个函数,
把它的返回值当网页正文发回去。
② 真起服务,真发请求
--------------------------------------------------------------------
请求 http://127.0.0.1:62795/
状态码 200
内容类型 text/html; charset=utf-8
正文长度 15 字节
正文 你好,世界
服务器 Werkzeug/3.1.9 Python/3.13.14
这一来一回就是一个完整的 HTTP 交换:客户端说「GET /」,服务器回一个状态码、几个头、一段正文。
Content-Type 里那个 charset=utf-8 很关键。
没有它,浏览器可能按别的编码解释,中文就成乱码——这是 Flask 默认帮你加上的。
③ 网址里带参数
--------------------------------------------------------------------
/hello/小王 状态 200 你好,小王
/item/7 状态 200 第 7 号商品,总价 10.50
/item/abc 状态 404 (Flask 自带的 404 页)
<name> 默认是字符串,<int:no> 限定只能是整数。
写 abc 进去直接 404——不是报错,是「这个地址根本不存在」。
这种设计叫 REST 风格的路径参数:把变化的那个东西放进路径,而不是放进问号后面。
商品页、用户页、文章页都这么写。
④ 找不到页面、出错页面
--------------------------------------------------------------------
/ok 状态 200 正文 6 字节
/missing 状态 404 正文 207 字节
/boom 状态 500 正文 265 字节
404 是「没这个东西」,500 是「我这边坏了」。
这两个状态码别混:把内部错误报成 404,排查的人会以为是地址写错,白找半天。
⑤ host 和 port 到底在说什么
--------------------------------------------------------------------
| 写法 | 谁能访问 | 什么时候用 |
|---|---|---|
| 127.0.0.1:5000 | 只有本机 | 自己调试 |
| 0.0.0.0:5000 | 同一局域网的机器都能访问 | 给同事看、手机测试 |
| 0.0.0.0:80 | 同上,但端口是默认端口 | 网址不用带端口号 |
127.0.0.1 是本机回环地址,数据包根本不出网卡。0.0.0.0 表示「所有网卡都听」,这才轮得到别人访问。
很多新手说「我起了服务,同事打不开」,八成就是绑在 127.0.0.1 上。
但反过来,绑 0.0.0.0 就等于把服务暴露给整个网络,没有密码保护的东西别这么干。
第一段那三行是 Flask 的全部核心。剩下的一切都是在这套「地址对函数」的骨架上长出来的。 第二段六个数里,状态码和 Content-Type 最该记。charset=utf-8 没有的话,中文可能变乱码。 第三段那个 /item/abc 返回 404 而不是报错,原因在 int 限定:abc 不符合类型, Flask 认为没有规则能匹配这个网址。 第四段 404 和 500 的正文长度差得不多(207 和 265),光看大小分不出来,得看状态码。 这就是为什么状态码必须对。 演示最后那句 srv.shutdown() 别漏。不写的话服务一直挂着,脚本退不出去。
30 秒实验
- 把演示里的
@app.route('/item/<int:no>')改成<no>(去掉 int),再请求/item/abc, 看状态码变成几。 - 加一个路由
/sum/<int:a>/<int:b>,返回两数之和,访问/sum/3/4看结果。 - 把
make_server('127.0.0.1', 0, app)的地址改成'0.0.0.0', 再在手机浏览器里输你这台电脑的局域网 IP 加端口,看能不能打开。
常见误区
- 「404 和 500 都是出错,差不多。」404 是「你要的东西没有」,500 是「我这边坏了」。 混起来会让排查的人找错方向。
- 「服务起了,同事打不开,是防火墙问题。」先看绑的是不是
127.0.0.1。 绑回环地址的数据包根本不出网卡。 - 「前端已经校验过了,服务端不用再校验。」前端校验挡得住手滑,挡不住直接发请求的人。 服务端校验是不能省的那一道。
自测三问(点开对答案)
1. `/item/7` 返回 200,`/item/abc` 返回 404,为什么是 404 而不是 500? —— 因为路由写的是 `下一篇让页面长得像样一点:静态文件怎么发,模板怎么把数据填进骨架。
- ☐ (本篇完,下一篇 9.2 静态网页与模板引擎)