疑问
这篇也是Django链接池试验引起来得,考虑到整个web请求流程的复杂和独立性,新起一篇单独讲解php
前置
以前搞php,java时,常常提到CGI,FastCGI, 且当时据说FastCGI性能更高,但当时未求深刻,不知细节缘由。以及一个web请求所经历的生命历程,也是算明白,但不是很深刻,此篇会细致讲解“网关接口(协议)”的发展历程,以及web流程的生命周期。css
HTTP协议
HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网服务器传输超文本到本地浏览器的传送协议。html
HTTP协议基于TCP/IP通讯协议来传递数据(文本,图片,json串等)。但这里要注意,他不涉及传输包,只是定义客户端和服务器端的通讯格式。java
HTTP请求方法
HTTP/0.9 GET HTTP/1.0 GET、POST、HEAD HTTP/1.1 GET、POST、HEAD、PUT、PATCH、HEAD、OPTIONS、DELETE
HTTP请求报文
请求报文由如下四部分组成:python
- 请求行:由 请求方法,请求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,以指定请求或响应中的数据格式。web
此处只介绍经常使用的三个json
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}\r\n'.format(status=self.status) for header in self.headers: response += '{0}: {1}\r\n'.format(*header) response += '\r\n' 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请求过程,中间涉及到的协议等都一一讲解了下,但愿各位明白