自己动手写web服务器
时间: 2016-03-02来源:开源中国
前景提要
HDC调试需求开发(15万预算),能者速来!>>>
项目主要实现一个脚本启动即可当web服务器使用,目前封装web服务器常用功能。
欢迎加群一起学习进步: 339711102 (it民工群)
项目代码 https://git.oschina.net/feimat/fastpy
本项目尽量少使用语言原生库,期望网友一起开发不同语言版本
第一步: 首先要有个处理网络异步io的模块
这一步相信大部分做后台开发的程序员都做过,模式大同小异,处理流程如下


第二步: 支持多平台
第一步中的 epoll_fd用一个跨平台的事件通知类ev_fd代替
Linux unix可使用epoll,win使用Select,freebsd使用kqueue

这里拿epoll举例 类提供poll、register、unregi、modify函数
事件类型统一为EV_DIS、EV_IN、EV_OUT


第三步:http分包
对第一步中的3,tcp收到包后,由于是粘包的,需要进行http分包
服务器可以不考虑chunk模式,根据content-lenth来进行分包即可


第四步: http解析
拆分出一个完整的http包后接着就要解析这个http
这里其实可以使用python原生http解析类,不过为了以后扩展语言,自己再封装而且速度比原生快


对于k=v&k2=v2类似的内容通过&、=号分割解析
对multipart格式的带文件form内容用boundary分割解析
解析后存放到:
headers: 头部 (字典)
form: post参数,包括form表单 (字典)
getdic: url参数 (字典)
filedic: form表单中文件 (字典)
rfile: 原始http content内容 (字符串)
action: url/最后一个单词
command: (get or post or put or delete)
path: url (字符串)
http_version: http版本号 (http 1.1)

第五步:支持大文件上传
如果用户是上传大文件几百M甚至几G怎么办,socket收到的数据,要落地磁盘缓存

然后解析时,使用boundary分割并用文件句柄指向内容的开头即可,不需读进内存,使用时再读

另外每次当前socket关闭时要删除之前缓存文件,节省磁盘.

第六步:支持静态文件下载
一般静态文件下载都是使用sendfile系统调用实现,这样减少内存拷贝(sendfile是磁盘直接到socket缓冲区,调用send的话,用户程序还要从磁盘read到内存,多了一次拷贝)
然而
1、考虑到以后扩展语言有些不支持sendfile,python 3.5之后才支持sendfile系统调用,网上开源的sendfile功能不全,不支持自己控制每次send大小
2、另外实际上下载文件的瓶颈在网络上,多一次内存拷贝性能也不会有很大下降
3、对于频繁下载的文件加速,还得加载进内存缓存和压缩
这里我们直接使用send支持静态文件下载。

每次读100k去send,send完了再读。直到egain或全部发送完。

第七步:支持Gzip压缩、Etag客户端缓存、断点续传等小功能
Web服务器有好多小功能,这里不会全部覆盖,挑选了几个必须用到的功能举例
1、Gzip压缩 对于指定类型的文件或者http 头部指定要gzip压缩的返回结果使用gzip压缩

2、etag客户端缓存 last-modified检测
检查缓存有
时间戳
last-modified 服务器返回的最后修改时间
last-modified 客户端请求到的上次修改时间
文件hash
Etag 服务器返回文件hash
If-none-match 客户端请求到的上次文件hash
如果以上两对都为发生改变 则返回304 not modified
对于etag由于计算hash较慢,在只有修改时间戳不一致的条件后再计算文件hash
然后计算hash还有好多优化方法 例如分块计算hash遇到不一致马上返回,用修改后的时间戳做etag。
3、断点续传。就是http头带range:byte=100-499指定要下载文件哪部分,然后
HTTP/1.1 206 Partial Content
Content-Range: bytes 100-499/102400 返回那部分内容和总大小给客户端即可

第八步:支持http/https正向代理
大家应该会有时需要搭建Squid或apache翻墙看看新闻或者下...此处省略若干字,因为nginx不支持正向https代理,squid安装又比较麻烦,这里直接提供几十行脚本快速编写自己的http/https正向代理
代理原理很简单
1、如果是http,在代理进程proxy里,使用http头部的host(真实远程服务端)的地址建立个socket连接,然后把proxy recv到的数据send给远端,再把从远端recv到的数据send回client即可
2、如果是https,client会发来
CONNECT XXX HTTP1.1
HOST XXX:443 格式的内容包,我们取到host然后创建socket连接,然后返回给client
HTTP/1.1 200 Connection Established,然后就和http一样的流程继续走了
这里要注意的是connect是阻塞的,proxy需要使用多线程或起协程来connect呦
另外如果接受完数据远程服务器关闭proxy要记得等数据发送给client成功再关闭呦


第九步: 支持cgi编写和运行时更新
上面完成了支持静态服务器常用功能,下面提供支持动态cgi编写功能。这个实现方式太多了,这里就是简单的把解析后的http内容扔到多线程或协程然后去加载脚本来执行即可。
运行时加载就是每收到一个请求要查看下py脚本的修改时间有没和内存中的一样,不一样就重新加载
Python动态加载脚本



到此一个简单的web服务器和webcgi框架就写完了,不到800行的一个脚本fastpy.py,启动就能使用:
1、启动:
指定监听端口即可启动 ,可在linux win freebsd使用
python fastpy.py 8998

2、编写cgi (默认多线程模式,安装gevent后自动切换为协程模式)
在fastpy.py同一目录下
随便建一个python 文件
例如:
example.py:
#定义一个同名example类
#定义一个test函数:
FastpyAutoUpdate=True #表示支不支持动态更新
class example():
def test(self, request, response_head):
#print request.form
#print request.getdic
#fileitem = request.filedic["upload_file"]
#fileitem.filename
#fileitem.file.read()
request.ret(200,"ccb"+request.path)

则访问该函数的url为 http://ip:port/example.test


test函数必须带两个参数
request:表示请求的数据 默认带以下属性
headers: 头部 (字典)
form: post参数,包括form表单 (字典)
getdic: url参数 (字典)
filedic: form表单中文件 (字典)
rfile: 原始http content内容 (字符串)
action: python文件名 (这里为example)
method: 函数方法 (这里为tt)
command: (get or post or put or delete or head...)
path: url (字符串)
http_version: http版本号 (http 1.1)
response_head: 表示response内容的头部
例如如果要返回用gzip压缩
则增加头部
response_head["Content-Encoding"] = "gzip"

3、静态文件下载
虚拟目录默认放在fastpy同目录的static目录下
访问 http://ip:port/static/ 即可查看该文件夹


4、使用正向代理功能
python proxy.py 8997
指定端口启动后,浏览器配置使用即可



欢迎加群一起学习进步: 339711102 (it民工群)
项目代码 https://git.oschina.net/feimat/fastpy



科技资讯:

科技学院:

科技百科:

科技书籍:

网站大全:

软件大全:

热门排行