这篇文章的最后起源是《计算机网络:自顶向下》的第 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 通信
- 导包
from socket import *
- 创立一个 socket:
serverSocket = socket(AF_INET, SOCK_STREAM)
- 绑定本地端口
serverSocket.bind("127.0.0.1", 80)
- 设置监听,操作系统能够挂起的最大连贯数量设置为 5
serverSocket.listen(5)
- 监听期待一个内部进来的连贯,返回一个新的 socket 示意这个连贯,以及提取出客户端的地址
connectionSocket, addr = serverSocket.accept()
- 一旦连贯胜利,则从客户端处获取数据
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)如何解决高并发的拜访申请?
上述问题会在后续的系列里持续具体说。
- https://www.ietf.org/rfc/rfc1… ↩
- https://www.ietf.org/rfc/rfc2… ↩