- HTTP协议
- 请求报文
- 请求头部字段解析
- 响应报文
- 响应头部字段解析
- 响应状态码
- 请求报文
- HTTP服务器实现
http协议大概是我们接触的最多的协议了,每打开一个网页,浏览器和服务器之间,使用的就是HTTP协议。HTTP协议属于应用层协议,下一层是运输层。这段时间,学习了一些相关的知识,因为对C++的多线程和网络编程不是很熟悉,先用python实现了一遍,后续会用C++实现。
首先来介绍下http
协议。http
协议分为请求报文和相应报文两个部分组成。
请求报文
请求报文如下图所示:
请求报文由三个部分组成:
1. 请求行: 使用的请求方法,请求的地址,以及请求的协议
2. 请求头部,包含一些请求的描述信息,如是否保活connection
,来源哪一个地址Referer
,客户端信息User-Agent
等等,具体可以查相关协议
3. 请求数据,包含了请求的数据,主要是post
数据
注意:在请求行和请求头部之间没有空行,但在请求头部和请求数据之间是由一个空行的。
用代码来描述:
# 请求行
GET favicon.ico HTTP/1.1
# POST / HTTP/1.1
# 请求头部,这里没有空行【每一行结尾一个\r\n】
Host: 127.0.0.1:9999
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36
Accept: image/webp,image/apng,image*;q=0.8
Referer: http://127.0.0.1:9999/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
# 请求数据【body部分】,这里有一个空行,传送的是post的数据【2个\r\n】
use=123
name=ll
请求头部字段解析
Accept # 能够接受的响应类型
Cookie # 上传给服务器的cookie信息
Referer # 从哪一个URL过来的
Cache-Control # 控制缓存,如希望不要再客户端进行缓存
Host # 制定请求URL的主机和端口
Content-Length # POST请求时标记长度
Connection # 是否可以处理持久链接
Accept-Encoding # 编码类型,一般是gzip或者compass
Accept-Charset # 字符集
User-Agent # 请求的客户端信息
Authorization # 用于访问密码保护的网页时识别自己的身份
响应报文
响应报文于请求报文结构类似。分为三个部分:
1. 状态行: 对于请求响应的状态,成功失败或者其他
2. 响应头部:包括返回的报文的一些描述信息
3. 响应主体:返回的html
代码
用代码来描述:
# 状态行
HTTP/1.1 200 OK
# 响应头部【每一行结尾一个\r\n】
content-type: text/html; charset=UTF-8
# 返回的数据【body部分】,这里有一个空行,传送的是post的数据【2个\r\n】
<h1>Hello, World</h1>
响应头部字段解析
Allow # 服务器支持哪些请求方法(如GET、POST等)。
Content-Encoding # 文档的编码(Encode)方法。
Content-Length # 表示内容长度。只有当浏览器使用持久HTTP连接时才需要这个数据。
Content-Type # 表示文档的MIME类型。
Date # 当前的GMT时间。
Expires # 定义什么时候认为文档过期。
Last-Modified # 文档的最后改动时间。
Location # 表示客户应当到哪里去提取文档。
Server # 服务器名字。
Refresh # 表示浏览器应该在多少时间之后刷新文档,以秒计。
Set-Cookie # 设置和页面关联的Cookie。
WWW-Authenticate # 客户应该在Authorization头中提供什么类型的授权信息。
响应状态码
在响应报文中,第一行就包含了响应状态码,标志着整个请求的结果。分为以下几类:
1** # 服务器接受到信息,正在处理
2** # 成功
3** # 重定向,需要进一步操作
4** # 客户端错误,如无法找到请求文件
5** # 服务端错误,服务器处理请求时发生错误
实现一个http
服务器说白了就是实现一个监听程序,当客户端发来对应的http
请求时,能够解析请求,并且返回对应的资源给客户端,客户端解析显示到浏览器上。请求分为两种,一种是简单的静态请求,服务器只需要把对应的静态资源返回即可;另外一种是动态请求,服务器自身无法处理,需要将请求转给服务端其他的程序处理,比如python,php,C++
等,然后将处理结果返回给客户端,这就是CGI
这里使用python
进行实现,比较简单。实现的功能如下:
1. 可以响应静态请求get
,post
2. 可以响应图片请求
3. 可以实现简单的动态请求
实现主要在两个文件中,一个是主体文件,基于socket
的通信程序,一部分是对于http
协议的解析,封装和动态请求转发。代码不是很长,全部贴在这,具体可以看注释。
- TCP通信部分
# server.py
# -*- coding=utf-8 -*-
import socket
import threading
from HttpHead import HttpRequest
# 处理每一个请求
def tcp_link(sock, addr):
print('Accept new connection from %s:%s...' % addr)
request = sock.recv(1024)
http_req = HttpRequest()
http_req.pass_request(request)
# 发送数据
sock.send(http_req.get_response())
sock.close()
# 开启服务器
def start_server():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 监听端口:
s.bind(('127.0.0.1', 9999))
s.listen(5)
# print('Waiting for connection...')
while True:
# 接受一个新连接:
sock, addr = s.accept()
# 创建新线程来处理TCP连接:
t = threading.Thread(target=tcp_link, args=(sock, addr))
t.start()
if __name__ == '__main__':
start_server()
pass
http
协议解析和转发- 目前区分动态和静态请求仅根据是否是
.py
结尾 - 如果是根目录,会自动加上
index.html
- 目前的头部信息十分简陋,仅仅区别是否是图片
# -*- coding:utf-8 -*-
import os
# 返回码
class ErrorCode(object):
OK = "HTTP/1.1 200 OK\r\n"
NOT_FOUND = "HTTP/1.1 404 Not Found\r\n"
# Content类型
class ContentType(object):
HTML = 'Content-Type: text/html\r\n'
PNG = 'Content-Type: img/png\r\n'
class HttpRequest(object):
RootDir = 'root' # 根目录
NotFoundHtml = RootDir+'/'+'404.html' # 404页面
def __init__(self):
self.method = None
self.url = None
self.protocol = None
self.host = None
self.request_data = None
self.response_line = ErrorCode.OK # 响应码
self.response_head = ContentType.HTML # 响应头部
self.response_body = '' # 响应主题
# 解析请求,得到请求的信息
def pass_request(self, request):
request_line, body = request.split('\r\n', 1)
header_list = request_line.split(' ')
self.method = header_list[0].upper()
self.url = header_list[1]
# print self.url
self.protocol = header_list[2]
# 获得请求参数
if self.method == 'POST':
self.request_data = {}
request_body = body.split('\r\n\r\n', 1)[1]
parameters = request_body.split('\n') # 每一行是一个字段
for i in parameters:
key, val = i.split('=')
self.request_data[key] = val
self.handle_file_request(HttpRequest.RootDir + self.url)
if self.method == 'GET':
file_name = ''
# 获取get参数
if self.url.find('?') != -1:
self.request_data = {}
req = self.url.split('?', 1)[1]
file_name = self.url.split('?', 1)[0]
parameters = req.split('&')
for i in parameters:
key, val = i.split('=', 1)
self.request_data[key] = val
else:
file_name = self.url
# self.handle_keywords_request()
if len(self.url) == 1: # 如果是根目录
file_name = '/index.html'
file_path = HttpRequest.RootDir + file_name
self.handle_file_request(file_path)
# 处理请求
def handle_file_request(self, file_path):
# 如果找不到的话输出404
if not os.path.isfile(file_path):
f = open(HttpRequest.NotFoundHtml, 'r')
self.response_line = ErrorCode.NOT_FOUND
self.response_head = ContentType.HTML
self.response_body = f.read()
else:
f = None
self.response_line = ErrorCode.OK
extension_name = os.path.splitext(file_path)[1] # 扩展名
# 图片资源需要使用二进制读取
if extension_name == '.png':
f = open(file_path, 'rb')
self.response_head = ContentType.PNG
self.response_body = f.read()
# 执行CGI,将请求转发到本地的python脚本
elif extension_name == '.py':
file_path = file_path.split('.', 1)[0]
file_path = file_path.replace('/', '.')
m = __import__(file_path)
self.response_head = ContentType.HTML
self.response_body = m.main.app(self.request_data)
# 其他静态文件
else:
f = open(file_path, 'r')
self.response_head = ContentType.HTML
self.response_body = f.read()
def get_response(self):
return self.response_line+self.response_head+'\r\n'+self.response_body
目前只使用了python
简单实现,还有很多问题,后续将先完善。
完整代码见github
如有错误,欢迎指正~