建立自己的 python web 开发知识模型

东方鹗

本人使用 python flask 框架进行 web 开发已经有段时间了,而且也将自己的博客 —— 《藕丝空间》 源码开源到了github —— https://eastossifrage.github.io/pyblog/。但是,随着开发的项目的增多,总是感觉力不从心,感觉 web 的基础知识还有所不足。本文的主要目的就是为了总结知识,建立自己的知识模型。

1 HTTP 协议

1.1 HTTP 介绍

HTTP 协议(HyperText Transfer Protocol,超文本传输协议)是用于从 WWW 服务器传输超文本到本地浏览器的传送协议。它可以使浏览器更加高效,使网络传输减少。它不仅保证计算机正确快速地传输超文本文档,还确定传输文档中的哪一部分,以及哪部分内容首先显示(如文本先于图形)等。

HTTP是一个应用层协议,由请求和响应构成,是一个标准的客户端服务器模型。HTTP是一个无状态的协议。

提示: 说到底,Web应用本质上也是一个socket服务器,用户的浏览器就是一个socket客户端。 只是使用 HTTP 协议对传输的行为进行了限制。

1.2 HTTP 的请求响应模型

HTTP 协议永远都是客户端发起请求(request),服务器回送响应(response)。见下图:

这样的结果,就对传输的行为进行了限制,无法实现在客户端没有发起请求的时候,服务器将消息推送给客户。 在 HTML 5 中,websocket 协议突破了该限制。

HTTP协议是一个无状态的协议,同一个客户端的这次请求和上次请求是没有对应关系

1.3 工作流程

一次 HTTP 操作称为一个事务,其工作过程可分为四步:

  1. 首先客户端与服务器需要建立联系,只要单击某个超链接,HTTP 开始工作。
  2. 建立连接后,客户端发送一个请求给服务器,请求方式的格式为:统一资源标示(URL)、协议版本号,后边是 MIME 信息,包括请求修饰符、客户机信息和可能的内容。
  3. 服务器接到请求后,给予相应的响应信息,其格式为一个状态行,包括信息的协议版本号、一个成功或错误的代码,后边是一个 MIME 信息,包括服务器信息、实体信息和可能的内容。
  4. 客户端接收服务器所返回的信息,通过浏览器显示在用户的显示屏上,然后客户机与服务器断开连接。

如果在以上的过程中的某一步出现错误,那么产生错误的信息将返回到客户端,由显示屏输出。对于用户来说,这些过程是由 HTTP 自己完成的,用户只要用鼠标点击,等待信息显示就可以了。

1.4 模拟 web 服务器

正如开篇的 HTTP 介绍中的提示所示,Web应用本质上也是一个socket服务器,用户的浏览器就是一个socket客户端。我们可以使用 python 的 socket 编程来实现一个简单的 web 服务器。

1.4.1 代码

# -*- coding:utf-8 -*-
__author__ = '东方鹗'
__blog__ = 'www.os373.cn'


import socket

def handle_request(client):
    buf = client.recv(1024)
    print("2、客户端发过来的请求内容 %s \r\n" % buf)
    res_header = "HTTP/1.1 200 OK\r\n\r\n"  # 返回客户端HTTP头信息
    client.send(('%s' % res_header).encode())
    msg = "服务器返回的响应内容 >>> %s" % "Hello, World!\r\n"
    client.send(('%s' % msg).encode())

def main():
    ip_port = ("localhost", 64055)
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(ip_port)
    sock.listen(5)
    print("1、web 服务器正在 {} 端口运行......".format(ip_port))

    while True:
        conn, addr = sock.accept()
        handle_request(conn)
        conn.close()

if __name__ == "__main__":
    main()

上述代码中,main()函数就是服务器函数,handle_request()就是用来处理客户端信息的应用程序。

1.4.2 测试代码

代码运行环境是 Ubuntu 17.10。

从上图所示并结合代码,我们分析一下这个用 python 代码模拟的 http 请求过程。

  1. 客户端发送请求,发送的内容为 b'GET / HTTP/1.1\r\nHost: localhost:64055\r\nUser-Agent: curl/7.55.1\r\nAccept: /\r\n\r\n'。此处使用 curl 来代替浏览器。
  2. 服务器收到客户端的信息,先返回固定格式的头信息 "HTTP/1.1 200 OK\r\n\r\n",再返回自定义的内容。

个人理解:

  1. 客户端发送固定格式的内容至服务器,服务器首先进行判定客户端是否是浏览器。
  2. 如果是浏览器,服务器则返回固定格式的响应头内容,浏览器会判定返回响应的服务器是否是 web 服务器,如果是,则继续接收服务器返回的响应内容。

本代码实例在服务器不返回固定格式的响应头内容的情况下,curl 客户端将检测到头信息有问题。

服务器:注释掉 client.send(('%s' % res_header).encode()) 代码。 客户端:通过 curl -I http://localhost:64055 命令,用于显示服务器端响应的头信息,返回的结果是 curl: (8) Weird server reply ,明显检测到头信息有问题。

如果是真正的浏览器,将会判断服务器端并不是真正的 web 服务器,进而不在显示服务器端返回的响应内容。

1.5 http 协议的局限

在现实需求中,往往需要一种交互式的方案,也就是说,客户端裹挟着特殊的请求,这些请求需要应用程序来甄别处理,然后将响应返回给 WEB 服务器,再由 WEB 服务器返回给客户端。

但是,http 协议仅仅定义了客户端发起请求,服务器端返回响应。WEB 服务器并不会处理客户端中裹挟的特殊请求,这需要由应用程序来实现,至于应用程序如何实现,如何编写,并没有明确的定义,实现起来也是千差万别,兼容性很成问题。

为了定义 Web 服务器和应用程序之间的交互过程,就形成了很多不同的规范。这种规范里最早的一个是 CGI,1993 年开发的。后来又出现了很多这样的规范。比如改进 CGI 性能的 FasgCGI,Java 专用的 Servlet 规范,还有 Python 专用的 WSGI 规范等。提出这些规范的目的就是为了定义统一的标准,提升程序的可移植性。在 WSGI 规范的最开始的 PEP-333 中一开始就描述了为什么需要WSGI规范。

2 CGI 协议

2.1 CGI 介绍

CGI(Common Gateway Interface) 规范允许 web 服务器执行外部程序,并将它们的输出发送给 web 浏览器, CGI 将 web 的一组简单的静态超文本文档编程一个完整的新的交互式媒体。

提示: http 协议仅仅是简单的实现了静态页面的展示功能。

2.2 CGI 功能

绝大多数 CGI 程序被用来解释处理来自表单的输入信息,并在服务器产生相应的处理,或将相应的信息反馈贵浏览器。

CGI 程序使网页具有了交互功能。

对于一个 CGI 程序,做的工作其实只有: 从环境变量(environment variables)标准输入(standard input)中读取数据,处理数据,向标准输出(standard output)输出数据。

环境变量中存储的叫做(Request Meta-Variables),也就是诸如 QUERY_STRING、PATH_INFO 之类的变量,这些由 Web Server 通过环境变量传递给 CGI 程序,CGI 程序也是从环境变量读取的。

标准输入中存放的往往是用户通过 PUTS 或 POST 提交的数据。

一句话总结,CGI 是一个标准,定义了客户端与服务器之间如何传递数据。

3 WSGI 协议

3.1 WSGI 介绍

WSGI 是基于现存的 CGI 标准而设计的。 Web服务器网关接口(Python Web Server Gateway Interface,缩写为WSGI)是为 Python 语言定义的 Web 服务器和 Web 应用程序或框架之间的一种简单而通用的接口。具体的来说,WSGI 是一个规范,定义了 Web 服务器如何与 Python 应用程序进行交互,使得使用 Python 写的 Web 应用程序可以和 Web 服务器对接起来。

3.2 WSGI 如何工作

WSGI 相当于是 Web 服务器和 Python 应用程序之间的桥梁。那么这个桥梁是如何工作的呢?首先,我们明确桥梁的作用,WSGI 存在的目的有两个:

  1. 让 Web 服务器知道如何调用 Python 应用程序,并且把用户的请求告诉应用程序。
  2. 让 Python 应用程序知道用户的具体请求是什么,以及如何返回结果给 Web 服务器。

3.3 实现 WSGI 服务

编写一个新脚本,此处是用 python 已封装好的 wsgiref 模块来写 server 端。我们在此偷个懒,不再使用 socket 模块来重写 server 端, 如有兴趣,请查看 wsgiref 的源码。

# -*- coding:utf-8 -*-
__author__ = '东方鹗'
__blog__ = 'www.os373.cn'

# 首先实现 web 应用程序的 WSGI 处理函数
def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    body = '<h1>Hello, %s!</h1>' % (environ['PATH_INFO'][1:] or 'web')
    return[body.encode('utf-8')]

# 实现 WSGI 服务器
# 从wsgiref模块导入:
from wsgiref.simple_server import make_server

# 创建一个服务器,IP地址为空,端口是64055,处理函数是application:
httpd = make_server('0.0.0.0', 64055, application)
print("Serving HTTP on port 64055...")
# 开始监听HTTP请求:
httpd.serve_forever()

4 uWSGI

4.1 uWSGI 介绍

uWSGI 是一个 Web 服务器,它实现了 WSGI 协议、uwsgi、http 等协议。Nginx 中 HttpUwsgiModule 的作用是与 uWSGI 服务器进行交换,也就是说可以用 Nginx + uWSGI 的模式来部署 web 服务。

要注意 WSGI / uwsgi / uWSGI 这三个概念的区分。

  • WSGI 是一种通信协议。
  • uwsgi 也是一种通信协议,在此常用于在uWSGI服务器与其他网络服务器的数据通信。
  • 而 uWSGI 是实现了 uwsgi 和 WSGI 两种协议的 Web 服务器。

uwsgi 协议是一个 uWSGI 服务器自有的协议,它用于定义传输信息的类型(type of information),每一个 uwsgi packet 前 4byte 为传输信息类型描述,它与 WSGI 相比是两样东西。

5 reference

  1. http协议详解(超详细)
  2. HTTP 协议入门
  3. WSGI简介
  4. Web服务器网关接口
  5. Python/WSGI 应用快速入门

  • 打赏