HTTP(一):实现一个最简单的基于http协议的web服务器(基于python)

这篇文章的最初来源是《计算机网络:自顶向下》的第7版的第2章(应用层)末尾,套接字编程作业第一题实验题。通过这个实验作业,学习 Python 中 TCP 连接的套接字编程基础知识:如何创建一个套接字,将其绑定到特定的地址和端口,以及发送和接收 HTTP 数据包。同时也学习一些 HTTP 报文格式的基础知识。

目标:开发一个一次处理一个 HTTP 请求的 Web 服务器。 这个 Web 服务器应该接受并解析 HTTP 请求,从服务器的文件系统中获取请求的文件,创建一个 HTTP 响应消息,该消息由请求的文件和标题行组成,然后将响应直接发送到客户端。 如果服务器中不存在请求的文件,则服务器应将 HTTP “404 Not Found”消息发送回客户端。

一、回顾http协议

HTTP(超文本传输协议)是在TCP/IP协议族中的一种应用层协议,除了HTTP以外,预存在TCP/IP协议族中的应用层协议还包括知名的FTP(用于文件传输服务),SMTP(简单邮件传输协议,其他的邮件协议还有POP3),DNS(域名解析系统)等等。

预备知识:URI 统一资源定位符

需要认识URI这个东西,uniform resource identification(和URL差不多,但是比URL更加具体,因为它定义了每一个网络资源单位的绝对地址路径)。HTTP 协议使用 URI 定位互联网上的资源。

HTTP(一):实现一个最简单的基于http协议的web服务器(基于python)_第1张图片
这就是一个URI的格式示意图(图自《图解HTTP》)【注意:URI不区分大小写】,下面是几个不同协议的URI的示例:

ftp://ftp.is.co.za/rfc/rfc1808.txt
http://www.ietf.org/rfc/rfc23...
ldap://[2001:db8::7]/c=GB?objectClass?one
mailto:John.D[email protected]
news:comp.infosystems.www.servers.unix
tel:+1-816-555-1212
telnet://192.0.2.16:80/
urn:oasis:names:specification:docbook:dtd:xml:4.1.2

接下来讲述 HTTP的报文结构

HTTP用于客户端与服务端的通信,通过请求和响应的交换达成通信。具体过程为:由客户端发起请求,服务器接收到,然后回复响应报文。

请求报文

书上的两个http请求报文的例子:
HTTP(一):实现一个最简单的基于http协议的web服务器(基于python)_第2张图片

HTTP(一):实现一个最简单的基于http协议的web服务器(基于python)_第3张图片

第1部分(第1行),请求行(request line):包括字段:方法、URI和协议版本

  • 方法取值:基础的有5个,POST GET HEAD PUT DELETE 【注意区分大小写!需要用大写字母
    HTTP(一):实现一个最简单的基于http协议的web服务器(基于python)_第4张图片
    【辨析GET与POST】简单讲讲GET和POST的区别:GET和POST 都能够获取网络资源,GET是直接请求获取页面,POST是提交表单让服务器返回结果资源。概念上,大概区分下面几点即可(不做web开发的话)。
    (1)GET把参数包含在URL中,POST通过request body传递参数。相对而言,GET比POST更不安全,因为参数直接暴露在URL上,但是由于都是http没加密,所以本质上都不安全。
    (2)GET在浏览器回退时是无害的,而POST会再次提交请求。
    (3)GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
    (4)GET产生一个TCP数据包;POST产生两个TCP数据包。对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据)。(并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次)
    (5)它们其实有时候可以混用,本质上都是通过TCP封装
    【PUT和DELETE方法】HTTP/1.1 的 PUT和DELETE 方法自身不带验证机制, 任何人都可以上传或删除文件 , 存在安全性问题, 因此一般的 Web 网站不使用该方法。
  • URI:上文已经给出了定义
    1) 如果不是访问特定资源而是对服务器本身发起请求, 可以用一个 * 来代替请求 URI
    2) 可以使用完整的域名+服务器路径作为URI,也可以把域名填在首部行的 Host 字段

* 协议版本
例子中都是HTTP/1.1
【说明:历史上的HTTP版本:(1)HTTP/0.9: HTTP 于 1990 年问世。 那时的 HTTP 并没有作为正式的标准被建立。含有 HTTP1.0 之前版本的意思, 因此被称为 HTTP/0.9。(2)HTTP/1.0:HTTP 正式作为标准被公布是在 1996年5月,版本被命名为 HTTP/1.0,并记载于 RFC1945 1。虽说是初期标准,但该协议标准至今仍被广泛使用在服务器端。(3)HTTP/1.1:1997年1月公布的 HTTP/1.1 是目前主流的 HTTP 协议版本。当初的标准是 RFC2068,之后发布的修订版 RFC2616 就是当前的最新版本 2 (4)HTTP/2.0 协议的主要目的是提高网页性能,目前尚未广泛使用】

第2部分(第2~K行),首部行(header line) 包含字段:各种首部字段(分为三类请求首部,通用首部和实体首部),每个首部字段分别占一行,格式为: 首部字段名+空格+值+CRLF(换行符)。下面是首部格式的示意图:
HTTP(一):实现一个最简单的基于http协议的web服务器(基于python)_第5张图片

第3部分 空行 直接一个CRLF

第4部分 实体体(entity body)
GET方法时,实体体为空。POST方法时候才使用(将这些内容提交上去)。HEAD方法也为空,因为这个一般用于调试追踪(响应不返回请求的内容,只返回首部)。

总结
以上4部分合起来,构成一个请求报文,格式如下:
HTTP(一):实现一个最简单的基于http协议的web服务器(基于python)_第6张图片

响应报文

依旧是从一个例子开始,
HTTP(一):实现一个最简单的基于http协议的web服务器(基于python)_第7张图片

可以看到,响应报文由状态行,首部行,空行和实体体构成

  • 状态行:包括 协议版本 状态码 状态码原因短语
    协议版本:例如 HTTP/1.0
    状态码:仅记录在 RFC2616 上的 HTTP 状态码就达 40 种,加上其他的就更多。
    HTTP(一):实现一个最简单的基于http协议的web服务器(基于python)_第8张图片
    但是只要记住14种就可以:
    200 OK
    204 no content 没有实际内容,请求已成功处理, 但在返回的响应报文中不含实体的主体部分
    206 Partial Content 客户端发出的是获取资源的部分范围请求,Content-Range指定
    301 Moved Permanently 请求的资源已被分配了新的 URI
    302 Found 该状态码表示请求的资源已被分配了新的 URI, 希望用户(本次) 能使用新的 URI 访问
    303 See Other 由于请求对应的资源存在着另一个 URI, 应使用 GET方法定向获取请求的资源
    304 Not Modified 客户端发送的是附带条件的请求,服务器端允许请求访问资源, 但没有满足条件的情况。附带条件的请求是指采用 GET方法的请求报文中包含 If-Match, If-ModifiedSince, If-None-Match, If-Range, If-Unmodified-Since 中任一首部。
    307 Temporary Redirect 临时重定向 和302差不多
    400 Bad Request 请求报文有语法错误
    401 Unauthorized 发送的请求需要有通过 HTTP 认证(BASIC 认证、DIGEST 认证) 的认证信息
    403 Forbidden 服务器拒绝访问资源,服务器可以不说明原因,如要说明原因则填写在响应报文的实体体,一般可能是例如文件权限,从未授权的发送源IP地址试图访问等原因
    404 Not Found 找不到或者服务器不想告诉你他有这个资源
    500 Internal Server Error 服务器内部故障
    503 Service Unavailable 服务器超负荷或者停机维护,现在无法处理请求。
  • 首部行:这里主要研究的是符合HTTP/1.1的规范的首部字段
    HTTP(一):实现一个最简单的基于http协议的web服务器(基于python)_第9张图片
    响应报文的首部行和请求报文的差不多,只是字段 不再有请求报文字段而是改为响应报文字段
  • 空行和实体体
  • 总结
    HTTP(一):实现一个最简单的基于http协议的web服务器(基于python)_第10张图片

请求报文和响应报文的首部行

上面提到了,这两种报文的首部行有很多字段,可以分为四类 通用首部字段,实体首部字段,请求首部字段(请求报文),响应首部字段(响应报文)
接下来给出详细定义:

通用首部字段
HTTP(一):实现一个最简单的基于http协议的web服务器(基于python)_第11张图片

请求首部字段
HTTP(一):实现一个最简单的基于http协议的web服务器(基于python)_第12张图片

响应首部字段
HTTP(一):实现一个最简单的基于http协议的web服务器(基于python)_第13张图片

实体首部字段
HTTP(一):实现一个最简单的基于http协议的web服务器(基于python)_第14张图片

更详细的解释以及它们的用法需要再去查看RFC文档或者相关书籍

二、完成一个搭建web服务器的简单实例(python)

http是基于tcp连接的,因此首先要掌握一下 python中 tcp通信的基本接口用法。

服务端socket通信

  1. 导包
    from socket import *
  2. 创建一个socket:
    serverSocket = socket(AF_INET, SOCK_STREAM)
  3. 绑定本地端口
    serverSocket.bind("127.0.0.1", 80)
  4. 设置监听,操作系统可以挂起的最大连接数量设置为5
    serverSocket.listen(5)
  5. 监听等待一个外部进来的连接,返回一个新的socket表示这个连接,以及提取出客户端的地址
    connectionSocket, addr = serverSocket.accept()
  6. 一旦连接成功,则从客户端处获取数据
    message = connectionSocket.recv(1024)

准备一个简单的html文件
例如我准备的 hello.html 如下






Insert title here



    你好啊, 天才

放到程序同目录下

然后写web server的主程序
web_server.py:

from socket import *
serverSocket = socket(AF_INET, SOCK_STREAM)
serverSocket.bind(("127.0.0.1", 80))
serverSocket.listen(5)

while True:
    print("ready to serve")
    connectionSocket, addr = serverSocket.accept()
    try:
        # establish connection
        message = connectionSocket.recv(20000)
        # print(message)
        filename = message.split()[1]
        f = open(filename[1:], 'rb')
        outputdata = f.readlines()
        # send on HTTP Header line into socket
        header = bytearray('HTTP/1.1 200 OK\r\n Date: Sat, 28 May 2022 18:10:01 GMT \r\n Content-Type: text/html \r\n \r\n ', 'utf-8')
        connectionSocket.send(header)

        # send to content of the requested file to the client
        for i in range(0, len(outputdata)):
            connectionSocket.send(outputdata[i])
        connectionSocket.close()

    except IOError:
        header = bytearray('HTTP/1.1 404 Not Found\r\n Date: Tue, 03 Jul 2012 04:40:59 GMT \r\n Content-Type: text/html \r\n \r\n ', 'utf-8')
        connectionSocket.send(header)       
        connectionSocket.close()

serverSocket.close()

程序里明确了让它运行在 本地 127.0.0.1的 80 端口上,因此运行程序以后,在浏览器打开如下网址即可获取到这个html的内容

http://127.0.0.1:80/hello.html

HTTP(一):实现一个最简单的基于http协议的web服务器(基于python)_第15张图片

如果出错了,获取不到那个文件,会返回404

HTTP(一):实现一个最简单的基于http协议的web服务器(基于python)_第16张图片

后记:遗留下的一些问题

1)现代的web服务,除了本文最基础的HTTP协议外,还有很多重要的实现和特性,包括:持久连接,pipeline作业, cookie, SSL加密,压缩编码传输,分块传输 这些是什么?
2)web服务如何准确响应返回多个文件内容?例如加了CSS和各种图片文件元素的网页
3)如何处理高并发的访问请求?

上述问题会在后续的系列里继续详细说。

你可能感兴趣的:(python服务器http)