跟着Flask文档,编写一个基于Flask的Web 服务器
启动Flask 1 2 3 4 5 6 from flask import Flask app = Flask(__name__)@app.route('/' ) def hello_world (): return "Hello,World"
执行
1 2 3 export FLASK_APP=hello.py python -m flask run 使 flask 服务器开始运行
这里主要定义了一个路由“/” 和 调用方法的关系。启动服务器后对对应的路径进行访问
通过指定 —host=0.0.0.0 允许操作系统监听所有公开IP的请求
路由中的变量定义
string
(缺省值) 接受任何不包含斜杠的文本
int
接受正整数
float
接受正浮点数
path
类似 string
,但可以包含斜杠
uuid
接受 UUID 字符串
url_for() url_for 可以通过具体的函数名返回其所对应的资源路径
1 2 3 4 5 @app.route('/user/<username>' ) def profile (username ): return '{}\'s profile' .format (escape(username))with app.test_request_context(): print (url_for('profile' ,username='Codfish' ))
定义http请求方法对应的处理 1 2 3 4 5 6 7 8 from flask import request@app.route('/login' , methods=['GET' , 'POST' ] ) def login (): if request.method == 'POST' : return do_the_login() else : return show_the_login_form()
静态文件 url_for('static', filename='style.css')
静态文件和路径的映射
渲染模板 1 2 3 4 5 6 7 from flask import render_template@app.route('/hello/' ) @app.route('/hello/name' ) def hello (name=None ) return render_template('hello.html' ,name=name)
模板可以用来直接向其中填充调用方法中的变量信息,模板会帮助进行后期的替换
文件上传 1 2 3 4 5 6 7 from flaskimport request@app.route('/upload' , methods=['GET' , 'POST' ] ) def upload_file ():if request.method == 'POST' : f = request.files['the_file' ] f.save('/var/www/uploads/uploaded_file.txt' )
这里说的是接收文件上传请求将文件保存到服务器。 f = request.files[‘the_file’] 从请求中获取文件信息
f.save(‘/var/www/uploads/uploaded_file.txt’) 将文件保存到目标路径
获取Cookies request中存在一个对象用来存储所有的cookies
这里是 获取名为username的cookies信息
1 2 3 4 5 6 7 from flask import request@app.route('/' ) def index (): username = request.cookies.get('username' )
对应的向客户端设置cookie
1 2 3 4 5 6 7 from flask import make_response@app.route('/' ) def index (): resp = make_response(render_template(...)) resp.set_cookie('username' , 'the username' ) return resp
使用重定向 ,转移到另一个路径 1 2 3 4 5 from flask import abort, redirect, url_for@app.route('/' ) def index (): return redirect(url_for('login' ))
将用户跳转到错误页面 1 2 3 4 5 6 7 abort(401 ) from flask import render_template@app.errorhandler(404 ) def page_not_found (error ): return render_template('page_not_found.html' ), 404
返回 支持多种返回类型自动转换
响应对象 → 直接返回
字符串 → 包装字符串和缺省参数 返回
字典 → 使用jsonfy创建一个返回对象
元组 → 接收类型为(response,status) (response,headers) 以及(response,status,headers)类型的项目 会将信息组装成新的响应
或者使用make_response() 构建响应返回
或者 直接 返回json 字段
会话 session 对象用于储存同用户的多个请求的信息
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 from flask import Flask, session, redirect, url_for, escape, request app = Flask(__name__) app.secret_key = b'_5#y2L"F4Q8z\n\xec]/' @app.route('/' ) def index (): if 'username' in session: return 'Logged in as %s' % escape(session['username' ]) return 'You are not logged in' @app.route('/login' , methods=['GET' , 'POST' ] ) def login (): if request.method == 'POST' : session['username' ] = request.form['username' ] return redirect(url_for('index' )) return ''' <form method="post"> <p><input type=text name=username> <p><input type=submit value=Login> </form> ''' @app.route('/logout' ) def logout (): session.pop('username' , None ) return redirect(url_for('index' ))
日志打印 1 2 3 app.logger.debug('A value for debugging' ) app.logger.warning('A warning occurred (%d apples)' , 42 ) app.logger.error('An error occurred' )
Web后端框架的核心功能在于
HTTP请求处理 - 将请求文本处理为请求对象
路由资源 - 在框架中定义路由资源添加的方法,以及如何根据请求查找到对应的路径
响应构建 - 请求结束后的响应是如何组装的,以什么形式返回响应
扩展功能 - 一些协助构建整个服务的扩展功能,比如模板渲染,动态资源路径
WSGI (Web Server Gateway Interface) wsgi 是定义 web server(可以理解为端口监听,线程管理,以及socket的部分) 如何与实际应用(http请求处理,报文头分析等具体的函数处理部分)交互的接口
1 2 3 4 5 6 7 8 9 10 11 def application (environ, start_response ): ... start_response("200 OK" , [("Content-Type" , "text/plain" )]) return [b"Hello, WSGI World!" ]
这里定义了 web server 需要将请求内容以及一个用于向其写入状态码信息和响应头的回调函数传入。在接口内部需要进行请求处理,处理完毕后,调用回调函数写入状态码和响应头
最终将响应体通过return 返回。
Flask 的wsgi实现 flask 对于wsgi的实现主要有3部分
定义了一个请求对象RequestContext
,将请求体中的内容封装到对象中。
1 2 3 4 5 6 class RequestContext : def __init__ (self, app, environ ): self .request = app.request_class(environ) self .session = app.open_session(self .request) self .app = app ...
定义了一个上下文栈,将请求对象压入栈中
1 2 3 4 from werkzeug.local import LocalStack _request_ctx_stack = LocalStack()
定义了一个代理对象request,使flask不必真正的将RequestContext 对象传入函数,而是通过代理获取外部的作用域来查询RequestContext中的属性
构建了app 这样一个可调用对象来实现wsgi接口,底层是通过wsgi_app 函数来完成整个过程的执行,app对象本身是为了存储一些中间状态
1 2 3 class Flask : def __call__ (self, environ, start_response ): return self .wsgi_app(environ, start_response)
实现一个轻量框架 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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 import socketfrom wsgiref.simple_server import make_serverfrom werkzeug.local import LocalStack, LocalProxy _request_ctx_stack = LocalStack()def _lookup_request (): top = _request_ctx_stack.top if top is None : raise RuntimeError("No request context" ) return top['request' ] request = LocalProxy(_lookup_request)class Request : def __init__ (self, environ ): self .method = environ['REQUEST_METHOD' ] self .path = environ['PATH_INFO' ] self .environ = environclass Response : def __init__ (self, body, status="200 OK" , headers=None ): self .body = [body.encode('utf-8' )] self .status = status self .headers = headers or [("Content-Type" , "text/html" )] def __call__ (self, environ, start_response ): start_response(self .status, self .headers) return self .bodyclass MiniFlask : def __init__ (self ): self .routes = {} def route (self, path ): def decorator (f ): self .routes[path] = f return f return decorator def wsgi_app (self, environ, start_response ): req = Request(environ) _request_ctx_stack.push({'request' : req}) try : handler = self .routes.get(req.path) if handler is None : return Response("404 Not Found" , "404 NOT FOUND" )( environ, start_response ) result = handler() if isinstance (result, Response): return result(environ, start_response) else : return Response(str (result))(environ, start_response) finally : _request_ctx_stack.pop() def __call__ (self, environ, start_response ): return self .wsgi_app(environ, start_response) def run (self, host="127.0.0.1" , port=5000 ): print (f"Running on http://{host} :{port} " ) with make_server(host, port, self ) as httpd: httpd.serve_forever() app = MiniFlask()@app.route("/" ) def index (): return f"Hello, your method is {request.method} " @app.route("/hi" ) def hi (): return Response("<h1>Hi there!</h1>" )if __name__ == '__main__' : app.run()
这里面实现了我们上述的几个关键点
定义了 对http请求的封装
1 2 3 4 5 class Request : def __init__ (self, environ ): self .method = environ['REQUEST_METHOD' ] self .path = environ['PATH_INFO' ] self .environ = environ
定义了一个上下文栈,在消息到达时,将请求存储上下文栈,响应后将请求弹出
1 2 3 4 5 6 7 8 _request_ctx_stack = LocalStack()def _lookup_request (): top = _request_ctx_stack.top if top is None : raise RuntimeError("No request context" ) return top['request' ]
一个对于上下文栈中请求的代理
1 request = LocalProxy(_lookup_request)
实现 wsgi接口,接收请求环境信息和回调函数,返回最终的响应体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def wsgi_app (self, environ, start_response ): req = Request(environ) _request_ctx_stack.push({'request' : req}) try : handler = self .routes.get(req.path) if handler is None : return Response("404 Not Found" , "404 NOT FOUND" )( environ, start_response ) result = handler() if isinstance (result, Response): return result(environ, start_response) else : return Response(str (result))(environ, start_response) finally : _request_ctx_stack.pop()