1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > 实现一个静态web服务器 http server

实现一个静态web服务器 http server

时间:2021-08-17 04:11:02

相关推荐

实现一个静态web服务器 http server

前言

不管是哪一种语言的web框架,其核心都是一致的,那就是以http协议为核心,围绕着http请求和http响应这两方面做文章。至于衍生的数据持久化(cookie session 数据库 等)只是存储手段罢了。

如果要理解好http协议,没有什么比实现一个自定义的http client客户端和http server服务器更好的方法了。

这篇博客博主就打算使用多种语言来做同一件事情:实现一个Http server,并且可以像apache那样实现静态资源的访问。

1、Http的请求过程

1.1 HTTP是一个基于TCP/IP通信协议来传递数据

这意味着可以使用建立socket的方式来监听某一端口,比如像tomcat一样监听8080端口,来实现这个web服务器。基于socket来实现client 和 server的交流。

1.2 HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,通过http请求可以访问服务器上的HTML 文件, 图片文件, 查询结果等。

可以放几个简单的静态网页作为访问使用

1.3 HTTP三点注意事项:

HTTP是无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。HTTP是媒体独立的:这意味着,只要客户端和服务器知道如何处理的数据内容,任何类型的数据都可以通过HTTP发送。客户端以及服务器指定使用适合的MIME-type内容类型。HTTP是无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

socket在处理完一个请求之后应该主动断开连接,然后方便处理下一请求

服务器端向客户端发送数据时,要通过MiME-type指定数据的类型

http不保存事务的处理状态,当然具体的业务逻辑可以使用session或者cookie来保存状态

1.4 HTTP请求图示

这是相对复杂的结构,HTTPServer、CGI我们可以合在一起,为了简单起见,就不需要访问database。数据可以用cache暂存。

大概就是这种结构:

Http server兼任 web server和CGI program的作用。

2、HTTP请求消息

客户端发送一个HTTP请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分组成,下图给出了请求报文的一般格式。

这个数据是很有格式的,我们可以通过对\r\n的切分就可以简单的在server端取出client端的数据

3、HTTP响应数据

HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。

2、3 引用自菜鸟教程

使用Python实现一个Http server

第一步,实现一个监听8080端口的socket server,这个server只会连接一次就断开

# -*- coding:utf-8 -*-import socketif __name__ == '__main__':# family: 套接字家族可以使AF_UNIX或者AF_INET# type: 套接字类型可以根据是面向连接的还是非连接分为SOCK_STREAM或SOCK_DGRAMs = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 绑定地址(host,port)到套接字, 在AF_INET下,以元组(host,port)的形式表示地址。s.bind(('localhost', 8080))while(True):# 开始TCP监听。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。# 该值至少为1,大部分应用程序设为5就可以了。s.listen(3)# 被动接受TCP客户端连接,(阻塞式)等待连接的到来conn, addr = s.accept()# 接收TCP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。# flag提供有关消息的其他信息,通常可以忽略。request = conn.recv(1024)# 打印请求内容print request# 给客户端返回内容conn.sendall('welcome!')# 关闭连接conn.close()

运行:

测试一下是否跑起来了:

上面是一个简单的脚本,模拟了客户端

服务器成功返回了信息welcome

同时服务器也接收到了信息:

接下来继续扩展

使用浏览器访问server尝试:这是打印出来的request数据

按照

对数据进行切分,新的代码:

# -*- coding:utf-8 -*-import socketif __name__ == '__main__':# family: 套接字家族可以使AF_UNIX或者AF_INET# type: 套接字类型可以根据是面向连接的还是非连接分为SOCK_STREAM或SOCK_DGRAMs = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 绑定地址(host,port)到套接字, 在AF_INET下,以元组(host,port)的形式表示地址。s.bind(('localhost', 8080))while(True):# 开始TCP监听。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。# 该值至少为1,大部分应用程序设为5就可以了。s.listen(3)# 被动接受TCP客户端连接,(阻塞式)等待连接的到来conn, addr = s.accept()# 接收TCP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。# flag提供有关消息的其他信息,通常可以忽略。request_content = conn.recv(1024)# 没有内容的连接,防止keep-alive导致错误断开if not request_content:conn.close()request_split = request_content.split('\r\n')# 请求行method, url, http_version = request_split[0].split(' ')# 请求头request_headers = {}for i in range(1, len(request_split)):if request_split[i] == '':breakelse:key, value = request_split[i].split(': ')request_headers[key] = value# 请求数据request_body = []for i in range(2+len(request_headers), len(request_split)):request_body.append(request_split[i])request_body = '\r\n'.join(request_body)# 打包成请求字典request = {'addr': addr,'method': method,'url': url,'http_version': http_version,'headers': request_headers,'body': request_body}print request# 给客户端返回内容conn.sendall('welcome!')# 关闭连接conn.close()

为了模拟客户端post数据,使用postman进行测试:

结果:

可以看到一个简单的request就被切分出来了

返回response

这个简单,按照格式写就是了:

common_response = '''HTTP/1.1 200 OKContent-Type: text/html<head><title>Hello world!</title></head><html><p>this is a easy python http server~</p><p>welcome~<p></html>'''.replace('\n', '\r\n')

然后返回回去:

# 给客户端返回内容conn.sendall(common_response)

使用浏览器访问127.0.0.1:8080

成功了!

写到现在,一个最基础的,能获取请求,能返回信息的httpServer就做好了。但是它现在的功能还是太简单,下一阶段,我们要再加个功能:

静态资源的访问

在继续写之前,先把解析请求信息的部分抽离成函数,方便使用:

def parseRequest(request_content):request_split = request_content.split('\r\n')# 请求行method, url, http_version = request_split[0].split(' ')# 请求头request_headers = {}for i in range(1, len(request_split)):if request_split[i] == '':breakelse:key, value = request_split[i].split(': ')request_headers[key] = value# 请求数据request_body = []for i in range(2+len(request_headers), len(request_split)):request_body.append(request_split[i])request_body = '\r\n'.join(request_body)# 打包成请求字典request = {'addr': addr,'method': method,'url': url,'http_version': http_version,'headers': request_headers,'body': request_body}return request

建立一个static目录用来存储静态文件:

index.html:

<!doctype html><html lang="en"><head><meta charset="UTF-8"><title>test</title><link rel="stylesheet" href="all.css"></head><body><img src="img.jpg" alt=""><h1 class="red">首页</h1></body></html>

all.css:

.red {color: red;}

img.jpg

我们首先要能把这些static目录下的文件都读取:

def get_files(files_dir='.'):""" 获取某个路径所有的文件路径"""files_dir = os.path.join(os.getcwd(), files_dir)files_all = []def get_files_(files_dir='.', r_path=''):if files_dir[-1:] != '/':files_dir += '/'files = os.listdir(files_dir)for file in files:file_path = os.path.join(files_dir, file)if os.path.isdir(file_path):get_files_(file_path, file)else:if r_path:files_all.append('%s/%s' % (r_path, file))else:files_all.append(file)get_files_(files_dir)return files_alldef loadStatic(static_path='static'):statics = get_files(static_path)static_path = os.path.join(os.getcwd(), static_path)statics_dict = {}# 设置下列文件后缀使用二进制读取byte_files_suf = ('jpg', 'png')for file_name in statics:file_suf = file_name.split('.')[-1]print file_suffile_path = os.path.join(static_path, file_name)if file_suf in byte_files_suf:file = open(file_path, 'rb')else:file = open(file_path, 'r')statics_dict['/'+file_name] = file.read()file.close()return statics_dict

这边做了一个判断,将图片类型的文件使用二进制来进行了读写。

然后会将stataic目录下的所有文件读取,并按照文件相对static的路径-文件内容的键值对返回。

这样我们要做的就很简单了,根据之前获取的request.url,来匹配路由,如果匹配到了静态文件路径,就将其内容返回。

在浏览器中访问http://127.0.0.1:8080/index.html

控制台中打印出了request信息:

根据要求修改__main__

if __name__ == '__main__':# 加载静态文件statics = loadStatic()s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.bind(('localhost', 8080))while(True):s.listen(3)conn, addr = s.accept()request_content = conn.recv(1024)# 没有内容的连接,防止keep-alive导致错误断开try:request = parseRequest(request_content)except e:conn.close()breakmime_type = {'jpg': 'image/jpeg','png': 'image/png','html': 'text/html'}file_suf = request['url'].split('.')[-1]if file_suf in mime_type:content_type = mime_type[file_suf]else:content_type = 'text/html'response = 'HTTP/1.1 200 OK\r\nContent-Type:%s\r\n\r\n' % content_typeprint request['url']if request['url'] in statics:print 'match static'response += statics[request['url']]# 给客户端返回内容conn.sendall(response)# 关闭连接conn.close()

再次访问http://127.0.0.1:8080/index.html

浏览器显示:

查看f12

到这里一个静态服务器就大功告成啦,哈哈哈哈

我们可以随便往static目录下放文件,然后重启server, 并访问对应的路径,比如,再新建个/img/img_1.jpg:

这张图片长这样:

重新运行easy_http_server_1.py,浏览器访问http://127.0.0.1:8080/img/img_1.jpg

访问成功。

总结

洋洋洒洒,一篇博客接近一万字了。

事实证明,一个简单的http server的实现并不复杂(当然是不追求性能和安全的前提下)。

代码仅仅100行左右。

下一篇博主将会进一步实现一个动态服务器。敬请期待。

博客代码github 链接:/numb-men/easy-http-server/blob/master/easy_http_server_1.py

ps:请随便转载

ps:发现错误请评论

ps:python 2.7.15

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。