乐趣区

关于python: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 定位互联网上的资源。


这就是一个 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.Doe@example.com
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 申请报文的例子:

第 1 局部(第 1 行),申请行(request line):包含字段:办法、URI 和协定版本

  • 办法取值:根底的有 5 个,POST GET HEAD PUT DELETE【留神辨别大小写!须要用大写字母

    【辨析 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(换行符)。上面是首部格局的示意图:

第 3 局部 空行 间接一个 CRLF

第 4 局部 实体体(entity body)
GET 办法时,实体体为空。POST 办法时候才应用(将这些内容提交下来)。HEAD 办法也为空,因为这个个别用于调试追踪(响应不返回申请的内容,只返回首部)。

总结
以上 4 局部合起来,形成一个申请报文,格局如下:

响应报文

仍旧是从一个例子开始,

能够看到,响应报文由状态行,首部行,空行和实体体形成

  • 状态行:包含 协定版本 状态码 状态码起因短语
    协定版本:例如 HTTP/1.0
    状态码:仅记录在 RFC2616 上的 HTTP 状态码就达 40 种,加上其余的就更多。

    然而只有记住 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 的标准的首部字段

    响应报文的首部行和申请报文的差不多,只是字段 不再有申请报文字段而是改为响应报文字段
  • 空行和实体体
  • 总结

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

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

通用首部字段

申请首部字段

响应首部字段

实体首部字段

更具体的解释以及它们的用法须要再去查看 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 如下

<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>

<body>
    你好啊, 蠢才
</body>
</html>

放到程序同目录下

而后写 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

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

后记:遗留下的一些问题

1)古代的 web 服务,除了本文最根底的 HTTP 协定外,还有很多重要的实现和个性,包含:长久连贯,pipeline 作业,cookie,SSL 加密,压缩编码传输,分块传输 这些是什么?
2)web 服务如何精确响应返回多个文件内容?例如加了 CSS 和各种图片文件元素的网页
3)如何解决高并发的拜访申请?

上述问题会在后续的系列里持续具体说。


  1. https://www.ietf.org/rfc/rfc1… ↩
  2. https://www.ietf.org/rfc/rfc2… ↩
退出移动版