Eurasia/标准web服务器
模板:Eurasia top 框架的关键应用之一便是web 。首先通过 httpserver(addr, handler) 创建标准 http 服务器。
地址格式[ ]
接口 httpserver 允许多种形式的 addr 参数。
httpserver('127.0.0.1:8080', handler) # IPv4 地址,端口 8080 httpserver('[::1]:8080', handler) # IPv6 地址 httpserver(':8080', handler) # 本机 8080 端口 httpserver(('127.0.0.1', 8080), handler) # IPv4 httpserver(('::1', 8080), handler) # IPv6 # 同时指定地址及 family: from socket import AF_INET6, AF_UNIX from eurasia.web import httpserver httpserver((('::1', 8080), AF_INET6), handler) httpserver(('/var/httpd.sock', AF_UNIX), handler) # unix socket # 绑定到 fileno 为 0 的资源描述符,默认 family 是 AF_INET: httpserver(0, handler) # 绑定到 fileno 为 0 的描述符,指定 family 为 AF_INET6: from socket import AF_INET6 from eurasia.web import httpserver httpserver((AF_INET6, 0), handler)
启动和暂停服务器[ ]
通过 httpserver.start() 和 httpserver.stop() 启动和暂停服务器,暂停的服务器可以通过 httpserver.start() 重新启动。
from eurasia.web import httpserver, mianloop # 工作服务器,绑定到 8080 def handler(httpfile): httpfile.write('hello world!') httpfile.close() httpd = httpserver(':8080', handler) httpd.start() # 管理服务器,绑定到 8090 def manager(httpfile): # 如果请求地址是 /start 则启动工作服务器 if httpfile.path_info == '/start': httpd.start() # 如果请求地址是 /pause 则停止工作服务器 elif httpfile.path_info == '/pause': httpd.stop() man = httpserver('8090', manager) man.start() mainloop() # 开始调度
注意区分 httpserver.start() 和 mainloop():
- mainloop() 是整个程序的调度器
- httpserver.start() 是启动服务器(开始监听)
- httpserver 可以有多个而 mainloop() 只有一个
CGI规范适配[ ]
httpfile 对象是服务器关键接口,其设计在很大程度上与 CGI/1.1 规范适配,以下是一些对应关系:
可以通过 httpfile.environ[envname] 获取的环境变量:
也可以使用 httpfile.request_uri、httpfile.path_info、httpfile.query_string 操作环境变量:
# 操作 httpfile.request_uri 会同时影响到 PATH_INFO 和 QUERY_STRING httpfile.request_uri = '/login?username=tom&passwd=***' print httpfile.path_info, httpfile.query_string # 分别是 "/login" 和 "username=tom&passwd=***" print httpfile.environ['PATH_INFO'], httpfile.environ['QUERY_STRING'] # 同上 # 操作 httpfile.path_info 会影响到 REQUEST_URI httpfile.path_info = '/check' print httpfile.request_uri # uri 变成 "/check?username=tom&passwd=***",query_string 不变 print httpfile.environ['REQUEST_URI'] # 同上 # 操作 httpfile.query_string 会影响到 REQUEST_URI httpfile.query_string = 'username=jerry&passwd=***' print httpfile.request_uri # uri 变成 "/check?username=jerry&passwd=***",path_info 不变
- 修改 httpfile.request_uri、httpfile.path_info、httpfile.query_string 时,这三个值会联动
- 设置 httpfile.environ 这三个值不会发生联动,一般避免通过 httpfile.environ 直接操作这三个变量
解读请求[ ]
通过 httpfile.read(size=-1, timeout=-1) 和 httpfile.readline(size=-1, timeout=-1) 读取报文体。
eurasia.cgietc 模块提供了解析 POST 形式表单的工具 form(httpfile, max_size=1048576, timeout=-1):
- 需要指定一个 httpfile 对象
- 使用 max_size 限定允许用户提交表单的最大字节数,超过限制会抛出 ValueError 并立即杀死客户端,默认最多传输 1M 数据
- 使用 timeout 指定读取表单的超时时间
- 使用 dict 返回解析的表单
- 正常情况下每个表单项的取值都是 str
- 如果提交了多个同名的表单项,那么该表单项取值就是一个 list,在其中保存多个 str 值
# -*- conding: utf-8 -*- from eurasia.cgietc import form from eurasia.web import httpserver, mainloop # 返回表单内容 def handler(httpfile): form1 = form(httpfile) # 假定提交的表单:a=hello&b=world&c=1&c=2&c=3 # 则输出 {'a': 'hello', 'b': 'world', 'c': ['1', '2', '3']} httpfile.write(repr(form1)) httpfile.close()
- form() 会同时处理 QUERY_STRING 和 POST 报文体
- 注意,不能处理 multipart 报文,比如文件上传
- 注意,httpfile.read() / httpfile.readline() 不能与 form() 混用,会导内容混乱
- form() 处理完请求以后将结果以新的 dict 对象返回,不增加 httpfile 的引用
完成响应[ ]
我们提供了接口 httpfile.start_response(status, response_headers, timeout=-1) 用来简化 httpfile[...]和 httpfile.status 的操作:
def handler(httpfile): response_status = '200 OK' response_headers = [('Content-Type', 'text/html')] httpfile.start_response(response_status, response_headers) httpfile.sendall('hello world!') httpfile.close()
- httpfile.start_response() 发送指定的 status 和 headers 报文头,这和 wsgi 规范(pep333)中 start_response() 接口的定义比较接近。
- 通过 httpfile.start_response() 完成报文头部以后,就可以通过 httpfile.sendall(data, timeout=-1) 发送报文体了。
- httpfile.write() 与 httpfile.sendall() 的功能相当接近,都用于发送报文体,他们的区别是:
- httpfile.write() 会判断头部如果没有发送,会首先发送 httpfile.status 和 httpfile[...] 的头部设置,再发送报文
- httpfile.sendall() 不会理会头部是否已发送,而直接发送报文,在没有调用 httpfile.start_response() 的情况下这会导致严重问题
- 一般情况下,httpfile.status、httpfile[...] 与 httpfile.write() 配合使用;httpfile.start_response() 与 httpfile.sendall() 是一对
- 需要注意 httpfile.start_response() 会覆盖 httpfile[...] 和 htttpfile.status 的设定
- 严格说 httpfile.start_response() 的头部内容会追加在 httpfile[...] 的设定之后
wsgiserver[ ]
使用 wsgiserver(addr, app) 创建标准的 wsgi 服务器。
from eurasia.web import wsgiserver, mainloop def app(environ, start_response): start_response('200 OK', [('Content-Type', 'text/plain')]) return ['hello world!'] httpd = wsgiserver(':8080', app) httpd.start() mainloop()
- wsgiserver 使用和 httpserver 相同的地址描述格式
- wsgiserver 中应使用框架自带的文件 IO 接口
或者使用 wsgi(app) 将 app 转换为普通的 http handler。
from eurasia.web import httpserver, wsgi, mainloop def app(environ, start_response): start_response('200 OK', [('Content-Type', 'text/plain')]) return ['hello world!'] def handler(httpfile): wsgihandler = wsgi(app) wsgihandler(httpfile) httpd = httpserver(':8080', handler) httpd.start() mainloop()