HttpServer 浏览器访问服务器的过程
用户通过浏览器浏览网站的过程:
用户浏览器(socket客户端)
客户端往服务端发消息
客户端接收消息
关闭
网站服务器(socket服务端)
启动,监听
等待客户端连接
服务端收消息
服务端回消息
关闭(一般都不会关闭)
下面,我们先写一个服务端程序,来模拟浏览器服务器访问过程。
得到以下结果,为什么?
这个时候就要引入Http协议了
HTTP协议
HTTP是一个客户端终端(用户)和服务器端(网站)请求和应答的标准(TCP)。
HTTP请求/响应步骤:
1. 客户端连接到Web服务器
一个HTTP客户端,通常是浏览器,与Web服务器的HTTP端口(默认为80)建立一个TCP套接字连接。
2. 发送HTTP请求
通过TCP套接字,客户端向Web服务器发送一个文本的请求报文,一个请求报文由请求行、请求头部、空行和请求数据4部分组成。
3. 服务器接受请求并返回HTTP响应
Web服务器解析请求,定位请求资源。服务器将资源复本写到TCP套接字,由客户端读取。一个响应由状态行、响应头部、空行和响应数据4部分组成。
4. 释放连接TCP连接
若connection 模式为close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection 模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求;
5. 客户端浏览器解析HTML内容
客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码。然后解析每一个响应头,响应头告知以下为若干字节的HTML文档和文档的字符集。客户端浏览器读取响应数据HTML,根据HTML的语法对其进行格式化,并在浏览器窗口中显示。
浏览器和服务端通信都要遵循一个HTTP协议(消息的格式要求)
关于HTTP协议:
1. 浏览器往服务端发的叫 请求(request)
请求的消息格式:
请求方法 路径 HTTP/1.1\r\n
k1:v1\r\n
k2:v2\r\n
\r\n
请求数据
2. 服务端往浏览器发的叫 响应(response)
响应的消息格式:HTTP/1.1 状态码 状态描述符\r\n
k1:v1\r\n
k2:v2\r\n
\r\n
响应正文 <-- html的内容
这时候,在浏览器上面就可以看到正确的页面了,并且可以调出Chrome的开发者工具查看到我们传过来的HTTP响应格式。
根据不同的路径返回不同的内容
细心的你可能会发现,现在无论我们输出什么样的路径,只要保持 IP 和端口号不变,浏览器页面显示的都是同样的内容,这不太符合我们日常的使用场景。
如果我想根据不同的路径返回不同的内容,应该怎么办呢?
这时候就需要我们把服务器收到的请求报文进行解析,读取到其中的访问路径。
观察收到的HTTP请求,会发现,它们的请求行、请求头部、请求数据是以 \r\n 进行分隔的,所以我们可以根据 \r\n 对收到的请求进行分隔,取出我们想要的访问路径。
这时候,我们访问不同的路径,例如 http://127.0.0.1:8001/xinan/ http://127.0.0.1:8001/hesheng/ 会在浏览器上显示不一样的内容
可以看到,我们现在的程序逻辑不是很清晰,我们可以改一下,url 用一个列表存起来,url 对应的响应分别写成一个个函数,通过函数调用进行 url 访问,你会发现,这跟某个框架的处理方式很像
返回具体的 HTML 页面
现在,你可能会在想,目前我们想要返回的内容是通过函数进行返回的,返回的都是一些简单地字节,如果我想要返回一个已经写好的精美的 HTML 页面应该怎么办呢?
我们可以把写好的 HTML 页面以二进制的形式读取进来,返回给浏览器,浏览器再进行解析,这就可以啦!
返回结果
返回动态 HTML 页面
这时候,你可能又会纳闷,现在返回的都是些静态的、固定的 HTML 页面,如果我想返回一个动态的 HTML 页面,应该怎么办?
动态的网页,本质上都是字符串的替换,字符串替换发生服务端,替换完再返回给浏览器。
这里,我们通过返回一个当前时间,来模拟动态 HTML 页面的返回过程。
动态时间效果
小结一下
1. web 框架的本质:
socket 服务端 与 浏览器的通信
2. socket 服务端功能划分:
a. 负责与浏览器收发消息( socket 通信) --> wsgiref/uWsgi/gunicorn...
b. 根据用户访问不同的路径执行不同的函数
c. 从 HTML 读取出内容,并且完成字符串的替换 --> jinja2 (模板语言)
3. Python 中 Web 框架的分类:
1. 按上面三个功能划分:
1. 框架自带 a,b,c --> Tornado
2. 框架自带 b 和 c,使用第三方的 a --> Django
3. 框架自带 b,使用第三方的 a 和 c --> Flask
2. 按另一个维度来划分:
1. Django --> 大而全(你做一个网站能用到的它都有)
2. 其他 --> Flask 轻量级
引入 wsgiref 模块实现 socket 通信
不知道你会不会觉得之前的程序中,socket 通信特别麻烦,而且还都是一样的套路,完完全全可以独立出来做成一个模块,要用的时候再直接引进来用就可以了。
没错,有你这种想法的人还不在少数(吃鲸......),特别是一些大牛们,就 socket 通信这一块,做出了一些特别好用的模块,例如我们下面要用的 wsgiref 模块。
你会发现,使用了 wsgiref 模块之后,程序封装更好了,代码逻辑也更加清晰了。
WSGI 协议
经过上面的 wsgiref 模块的示例,在使用通信模块的方便之余,你可能已经意识到一个问题,类似于 wsgiref 这样的模块肯定不止一个,我们自己写的 url 处理函数需要和这些模块进行通信,那么,我怎么知道这些模块传过来的信息是什么格式?如果各个模块传过来的信息结构都不一样的话,那岂不是说我得根据每一个模块去定制它专门的 url 处理函数?这不科学,这中间肯定需要一个协议进行约束,这个协议,就叫 WSGI 协议。