因为python
代码的优雅美观且易于维护这一特点,越来越多的人选择使用Python
做Web开发。而Python
的Web
框架百花齐放,目前比较流行的框架有大包大揽的Django
,小巧灵活的Flask
、Bottle
,还有性能高效的异步框架Tornado
、sanic
。这么多框架只要选择一个,阅读他的文档,就可以很轻松的搭建一个web app
,完全不需要去管他实现的原理。
本篇文章意在对一个web开发做一个梳理。
前端网页三剑客
我们打开浏览器输入一个网址yukunweb.com
,然后就看到了浏览器给我们显示的页面,这个时候打开浏览器开发者工具,点击Network
,刷新页面,会看到下方的请求的url
,点击Response
,就可以看到服务器返回给浏览器的html
文件信息了。如果复制Response
响应的内容,保存为index.html
并且在浏览器打开,依然可以看到首页的内容,但是似乎缺少了一些页面的样式和功能。
这是因为当浏览器接收到首页的HTML
源码后,它会根据HTML
的规则去显示页面,然后再根据HTML
里的链接,自动发送HTTP请求给服务器,拿到相应的图片,和JavaScript
、CSS
等资源,最终显示出一个完整的页面。所以我们会在Network
下面能看到很多额外的以.js
,.css
等后缀的请求了。
其实我们看到的页面就是浏览器按照HTML
的规则,展示给我们的。HTML
告诉浏览器那里是导航,那里是主栏,那里是侧栏。而这些信息如何显示,或者是显示的样式,就是CSS
文件的功劳。至于比如导航的下拉隐藏上拉显示就是JavaScript
的作用。
如果想要做Web开发,就一定得熟悉HTML
、CSS
、JavaScript
三剑客的知识,这里推荐W3school的前端教程,也是我学习前端的地方:W3school
客户端和服务器通信
理解了前段三剑客,就知道如何去写一个网页。那么从我们在浏览器的地址栏输入URL
,到Web
页面呈现出来到底经历了什么。
如图,一般这种通过发送请求获取服务器资源的Web浏览器,都可以称为客户端(client)。首先发送一个请求(request)给服务器,大多是以GET请求方式访问,服务器接收到你的请求,然后取到请求的资源,返回给客户端。
服务器和客户端之间交流是怎么进行的呢,服务器是怎么理解客户端的请求的呢。这里就需要一种协议规范,就是HTTP(HyperText Transfer Protocol,超文本传输协议)。可以说,Web
是建立在HTTP
协议上通信的。
如图,仍然是之前的例子,打开浏览器访问yukunweb.com
,打开浏览器开发者工具,点击图中标记的选项卡(记得点view parsed),可以看到客户端发给服务器的请求头前两行。
GET / HTTP/1.1
Host: www.yukunweb.com
第一行开头的GET表示请求访问服务器的类型,称为方法(method)。随后的字符/
指明了请求访问的资源对象,即请求URI。最后的HTTP/1.1
,即HTTP的版本号,用来提示客户端使用的HTTP
协议功能。
综上所述,第一行请求内容的意思是:请求访问某台HTTP
服务器上的/
(首页)页面资源。所以第二行的Host
表示请求的域名也就是服务器所在地址。
如图,如果是POST
请求的话,不仅会有请求头部信息,还有一个Form Data
的请求实体内容。
接收到请求的服务器呢,他会将请求内容的处理结果以响应的形式返回,看图中的第一行:
HTTP/1.1 200 OK
开头的部分仍然是服务器对应的HTTP
版本,紧接着的200 OK
表示请求的处理结果的状态码 (status code) 和原因短语。200
状态码就表示响应成功,常见的404
表示访问错误,500
表示服务器响应错误。这里的OK
是没有固定的规则的,你也可以让他返回GOOD
啥的。
下一行是服务器信息,本站用的是Nginx
服务器,在下一行显示了创建响应的日期时间。在下一行的Content-Type
表示内容的类型,客户端会依赖他判断响应的内容是网页还是音频,图片等类型。
这里只是简单的介绍了HTTP
协议,即是客户端与服务器之间的通信协议。如果想要深入了解推荐阅读《HTTP权威指南》。
WSGI
如果你浏览一个地址http://www.yukunweb.com/search-result/?keywords=音乐
,你会访问到本站的音乐关键词的搜索结果。我们知道客户端发送请求给服务器,那么服务器是怎么拿到资源的呢。其实这是交给后端运行的应用返回的,好比你抓取一个页面到获取到信息,这些逻辑的处理肯定是我们的程序再跑。
但是,接收并且解析客户端的HTTP
请求在发送HTTP
响应这些底层操作,后端的程序肯定是不会去处理的。所以,要想只专注于Web业务逻辑,还需要一个服务器和web
应用之间的嫁接层————WSGI。
什么是WSGI(Web Server Gateway Interface)?
WSGI
翻译过来就是Web服务器网关接口。他只是一个规范,定义了Web
服务器如何与Python
应用程序进行交互,使得使用Python
写的Web
应用程序可以和Web服务器(nginx/apache)对接起来。
该规范的地址:PEP 333
WSGI
是Python
的Web开发的基石,有了它你就有了一切,它存在的目的有两个:
- 描述 Web 服务器如何与 Web 应用程序交互(将客户端请求传给应用程序),
- 描述 Web 应用程序如何处理请求和如何返回数据给服务器。
由于Python
内置的标准库里有一个WSGI
库wsgiref
,我们基于他来写一个体现WSGI
目的的例子:
from wsgiref.simple_server import make_server
def application(environ, start_response):
status = '200 OK'
response_headers = [('Content-type', 'text/html')]
start_response(status, response_headers)
body = '<h1>Hello, {name} !!!</h1>'.format(name=environ['PATH_INFO'][1:] or 'WSGI')
return [body.encode('utf-8')]
app = make_server('', 8000, application)
app.serve_forever()
运行程序,如果没有报错,此时打开浏览器输入地址127.0.0.1:8000
和127.0.0.1:8000/GuTianle
,就可以看到程序返回的页面了。如图:
我们可以看到一个请求,他的入口只需要一个WSGI
的处理函数。因为所有的请求信息都包含在environ
中,这样我们就可以根据这些信息去返回不同的数据。
参数:
- environ:字典类型,存放了所有和客户端相关的信息。如果想知道他里面有哪些参数,可以更改上面的代码在 return 行上面加一个
for k, v in environ.items()
的循环,打印出字典里的所有参数。 - start_response:一个可调用对象,接收两个必选参数和一个可选参数:
- status: 一个字符串,表示 HTTP 响应状态字符串,如 200,404
- response_headers: 一个列表,包含有如下形式的元组:(header_name, header_value),用来表示 HTTP 响应的 headers ,如(‘Content-type’, ’text/html’)
- exc_info(可选): 用于出错时,服务器需要返回给浏览器的信息
返回:一个可迭代对象, 服务器通过遍历这个可迭代对象可以获得body的全部内容,内容可以是html
也可以是json
。
这里简单的介绍了WSGI
是什么,干什么。如果理解了WSGI
,那么写一个Python
的Web框架就很简单了。这也是为什么Python
有成百上千web框架的原因。
实现基于WSGI的框架
上面我们理解了WSGI
是干什么的,那么我们基于它实现一个简单的web
框架可以说轻而易举了。
from wsgiref.simple_server import make_server
class Application(object):
def __init__(self, environ, start_response):
self.start_response = start_response
self.path = environ['PATH_INFO']
def __iter__(self):
if self.path == '/':
status = '200 OK'
response_headers = [('Content-type', 'text/html')]
self.start_response(status, esponse_headers)
yield '<h1>Hello,World!</h1>'.encode('utf-8')
elif self.path == '/wsgi':
status = '200 OK'
response_headers = [('Content-type', 'text/html')]
self.start_response(status, response_headers)
yield '<h1>Hello,WSGI!</h1>'.encode('utf-8')
else:
status = '404 NOT FOUND'
response_headers = [('Content-type', 'text/html')]
self.start_response(status, response_headers)
yield '<h1>404 NOT FOUND</h1>'.encode('utf-8')
if __name__ == "__main__":
app = make_server('127.0.0.1', 8000, Application)
print('Serving HTTP on port 8000...')
app.serve_forever()
这个Application
类只不过是对WSGI
又做了一层简单的封装而已,由于上面说过WSGI
函数返回的是一个可以迭代对象,所以需要实现一个__iter__方法,里面控制了客户端的请求路由并且返回不同的输出。
当然如果你想扩展成一个像样的框架还需要考虑很多,比如像flask
那样方便的路由系统,还有对于用户请求方式的处理等等。总之是个很需要折腾的过程,好比flask的0.1
版本去掉注释也就 200 多行,而如今最新版本。。。