捅开web应用的那层纱
疑问
这篇也是Django连接池试验引发来得,考虑到整个web请求流程的复杂和独立性,新起一篇单独讲解
前置
之前搞php,java时,经常提到CGI,FastCGI, 且当时听说FastCGI性能更高,但当时未求深入,不知细节原因。以及一个web请求所经历的生命历程,也是算明白,但不是很深入,此篇会细致讲解“网关接口(协议)”的发展历程,以及web流程的生命周期。
HTTP协议
HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网服务器传输超文本到本地浏览器的传送协议。
HTTP协议基于TCP/IP通信协议来传递数据(文本,图片,json串等)。但这里要注意,他不涉及传输包,只是定义客户端和服务器端的通信格式。
HTTP请求方法
HTTP/0.9 GET
HTTP/1.0 GET、POST、HEAD
HTTP/1.1 GET、POST、HEAD、PUT、PATCH、HEAD、OPTIONS、DELETE
HTTP请求报文
请求报文由以下四部分组成:
- 请求行:由 请求方法,请求URL(不包括域名),HTTP协议版本 组成
- 请求头(Request Header):由 key/vaue的形式组成
- 空行:请求头之下是一个空行,通知服务器不再有请求头(有请求体时才有)
- 请求体:一般post才有,但get也可以通过body传递
GET /books/?sex=man&name=Professional HTTP/1.1 // 请求行
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
Gecko/20050225 Firefox/1.0.1
Connection: Keep-Alive // 以上是请求头
POST / HTTP/1.1 // 请求行
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
Gecko/20050225 Firefox/1.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 40
Connection: Keep-Alive // 以上是请求头
(此处空行) // 代表请求头结束
sex=man&name=Professional // 请求体
注意:比较重要的Content-Type字段,GET方法里没有,是因为没设置请求体,如果要设置请求体则必须指定Content-Type,以指定请求或响应中的数据格式。
此处只介绍常用的三个
application/json:JSON数据格式 - 接口常用
application/x-www-form-urlencoded:表单提交时指定这个
multipart/form-data : 需要在表单中进行文件上传时,就需要使用该格式
HTTP响应报文
与请求报文类似,也是由四部分组成:
- 状态行:由 HTTP协议,状态码,状态描述 组成
- 响应头(Response Header):key/value的形式
- 空行:请求头之下是一个空行,通知服务器不再有请求头
- 响应正文:
HTTP/1.1 200 OK // 状态行
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=UTF-8
Content-Length: 624
Date: Mon, 03 Nov 2014 06:37:28 GMT // 以上为响应头
(此处为一空行) // 代表响应头的终结
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> // 以下为响应正文,此处指定为text/html数据格式
<html>
......
HTTP状态码
1xx:指示信息,表示请求已接收,继续处理
2xx:成功,表示请求已被成功接受,处理
3xx:重定向
4xx:客户端错误
5xx:服务器端错误
HTTP请求流程
即浏览器输入地址回车到显示返回的过程
简单来讲就是:域名解析 --> 发起TCP的3次握手 --> 建立TCP连接后发起http请求 --> 服务器响应http请求,浏览器得到html代码 --> 浏览器解析html代码,并请求html代码中的资源(如js、css、图片等) --> 浏览器对页面进行渲染呈现给用户 --> 四次挥手结束
画了个示意图,复杂细节暂不在此解释
网关接口(协议)
为什么需要这个,要从CGI的起源说起,很久很久以前.....
CGI
最开始的互联网只有静态内容,web server只需要实现HTTP协议解析与静态资源定位即可,但随着用户交互性的增强,服务端业务逻辑逐渐增加,这时纯静态内容已经满足不了了,但动态内容的东西都交给web server去做显然不合适,这时CGI应声出现,CGI 协议定义了 web server 与 cgi 程序之间通信的规范, web server 一收到动态资源的请求就 fork 一个子进程调用 cgi 程序处理这个请求, 同时将和此请求相关的 context 传给 cgi 程序, 像是 path_info, script path, request method, remote ip 等等...
但正因为每次都fork一个进程去处理,在并发比较多的时候对资源的消耗还是非常大的,同时响应速度也会变慢,所以CGI的升级版本FastCGI就出现了。
FastCGI
FastCGI致力于减少网页服务器与CGI程序之间交互的开销,从而使服务器可以同时处理更多的网页请求。
与CGI为每个请求创建一个新的进程不同,FastCGI使用持续的进程来处理一连串的请求。简单来说,其本质就是一个常驻内存的进程池技术,由调度器负责将传递过来的CGI请求发送给处理CGI的handler进程来处理。在一个请求处理完成之后,该处理进程不销毁,继续等待下一个请求的到来。
WSGI
事情继续发展,回到python上来,python也是作为“cgi application”一员出现,但是那时的Python应用程序通常是为CGI,FastCGI,mod_python中的一个而设计,甚至是为特定Web服务器的自定义的API接口而设计的。如何选择合适的Web应用程序框架成为困扰Python初学者的一个问题。
WSGI是作为Web服务器与Web应用程序或应用框架之间的一种低级别的接口,以提升可移植Web应用开发的共同点。WSGI是基于现存的CGI标准而设计的。
WSGI内容概要
WSGI协议主要包括server(服务器程序)和application(应用程序)两部分
application(应用程序)
WSGI规定
1. 应用程序应为可调用对象,需要接收2个参数
- environ,一个字典,该字典可以包含了客户端请求的信息以及其他信息,可以认为是请求上下文
- start_response,一个用于发送HTTP响应状态(HTTP status )、响应头(HTTP headers)的回调函数,第一个参数为HTTP响应状态,第二个参数为[(key, value),...]
2. 可调用对象要返回一个值,这个值是可迭代的。
通过回调函数将响应状态和响应头返回给server,同时返回响应正文(response body),响应正文是可迭代的、并包含了多个字符串。
看起来应用程序代码像下面这样:
# callable function
def application(environ, start_response):
status = "200 OK"
response_headers = [("Content-Type", "text/plain")]
start_response(status, response_headers)
return ["Hello world"]
or
# callable class
class Application:
def __init__(self, environ, start_response):
pass
def __iter__(self):
yield ["Hello world"]
or
# callable object
class ApplicationObj:
def __call__(self, environ, start_response):
return ["Hello world"]
server(服务器程序)
服务器程序要求监听HTTP请求,在每次客户端的请求传来时,调用我们写好的应用程序,并将处理好的结果返回给客户端。
3. 服务器程序需要调用应用程序
手撸一个server端
# server.py
# coding: utf-8
from __future__ import unicode_literals
import socket
import StringIO
import sys
import datetime
class WSGIServer(object):
socket_family = socket.AF_INET
socket_type = socket.SOCK_STREAM
request_queue_size = 10
def __init__(self, address):
self.socket = socket.socket(self.socket_family, self.socket_type)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.bind(address)
self.socket.listen(self.request_queue_size)
host, port = self.socket.getsockname()[:2]
self.host = host
self.port = port
def set_application(self, application):
self.application = application
def serve_forever(self):
while 1:
self.connection, client_address = self.socket.accept()
self.handle_request()
def handle_request(self):
self.request_data = self.connection.recv(1024)
self.request_lines = self.request_data.splitlines()
try:
self.get_url_parameter()
env = self.get_environ()
app_data = self.application(env, self.start_response)
self.finish_response(app_data)
print "[{0}] "{1}" {2}".format(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
self.request_lines[0], self.status)
except Exception, e:
pass
def get_url_parameter(self):
self.request_dict = {"Path": self.request_lines[0]}
for itm in self.request_lines[1:]:
if ":" in itm:
self.request_dict[itm.split(":")[0]] = itm.split(":")[1]
self.request_method, self.path, self.request_version = self.request_dict.get("Path").split()
def get_environ(self):
env = {
"wsgi.version": (1, 0),
"wsgi.url_scheme": "http",
"wsgi.input": StringIO.StringIO(self.request_data),
"wsgi.errors": sys.stderr,
"wsgi.multithread": False,
"wsgi.multiprocess": False,
"wsgi.run_once": False,
"REQUEST_METHOD": self.request_method,
"PATH_INFO": self.path,
"SERVER_NAME": self.host,
"SERVER_PORT": self.port,
"USER_AGENT": self.request_dict.get("User-Agent")
}
return env
def start_response(self, status, response_headers):
headers = [
("Date", datetime.datetime.now().strftime("%a, %d %b %Y %H:%M:%S GMT")),
("Server", "RAPOWSGI0.1"),
]
self.headers = response_headers + headers
self.status = status
def finish_response(self, app_data):
try:
response = "HTTP/1.1 {status}
".format(status=self.status)
for header in self.headers:
response += "{0}: {1}
".format(*header)
response += "
"
for data in app_data:
response += data
self.connection.sendall(response)
finally:
self.connection.close()
if __name__ == "__main__":
port = 8888
if len(sys.argv) < 2:
sys.exit("请提供可用的wsgi应用程序, 格式为: 模块名.应用名 端口号")
elif len(sys.argv) > 2:
port = sys.argv[2]
def generate_server(address, application):
server = WSGIServer(address)
server.set_application(TestMiddle(application))
return server
app_path = sys.argv[1]
module, application = app_path.split(".")
module = __import__(module)
application = getattr(module, application)
httpd = generate_server(("", int(port)), application)
print "RAPOWSGI Server Serving HTTP service on port {0}".format(port)
print "{0}".format(datetime.datetime.now().strftime("%a, %d %b %Y %H:%M:%S GMT"))
httpd.serve_forever()
这里可以看出服务器程序是如何与应用程序配合完成用户请求的。大概做了以下几件事:
初始化,建立套接字,绑定监听端口;
设置加载的 web app;
开始持续运行 server;
处理访问请求(self.handle_request());
获取请求信息及环境信息(self.get_environ());
用environ运行加载的 web app 得到返回信息(app_data = self.application(env, self.start_response));
构造返回信息头部;
返回信息;
总结
至此,整个HTTP请求过程,中间涉及到的协议等都一一讲解了下,希望各位明白
以上是 捅开web应用的那层纱 的全部内容, 来源链接: utcz.com/z/517702.html