web框架之environment处理

試著忘記壹切 提交于 2020-02-06 16:59:36

从现在开始,我们将一步步完成一个WSGI的WEB框架,从而了解WEB框架的内部机制。

WSGI请求environ处理

WSGI服务器程序会帮我们处理HTTP请求报文,但是提供的environ还是一个用起来不方便的字典。

http://127.0.0.1:9999/python/index.html?id=1234&name=tom
('SERVER_PROTOCOL', 'HTTP/1.1')
('wsgi.url_scheme', 'http')
('HTTP_HOST', '127.0.0.1:9999')
('SERVER_PORT', '9999')
('REMOTE_ADDR', '127.0.0.1')
('REQUEST_METHOD', 'GET')
('CONTENT_TYPE', 'text/plain')
('PATH_INFO', '/python/index.html')
('QUERY_STRING', 'id=1234&name=tom')
('HTTP_USER_AGENT', 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko)
Maxthon/5.0 Chrome/55.0.2883.75 Safari/537.36')

QUERY_STRING 查询字符串的解析

WSGI服务器程序处理过HTTP报文后,返回一个字典,可以得到查询字符串 ('QUERY_STRING','id=1234&name=tom') 。这个键值对用起来不方便。

1、编程序解析

# id=5&name=wayne
qstr = environ.get('QUERY_STRING')
print(qstr)
if qstr:
for pair in qstr.split('&'):
k, _, v = pair.partition('=')
print("k={}, v={}".format(k,v))
# id=5&name=wayne
querystr = environ.get('QUERY_STRING')
if querystr:
querydict = {k:v for k,_,v in map(lambda item: item.partition('='), querystr.split('&'))}
print(querydict)
from wsgiref.simple_server import make_server

#127.0.0.1:8000?id=1&name=tom@age=20
def simple_app(environ, start_response):
    #查询字符串
    query_string = environ.get('QUERY_STRING')
    print(query_string)
    for item in  query_string.split('&'):
        k,_,v= item.partition('=')
        print(k,v)

    #加入到一个字典中
    querydict = {k: v for k, _, v in map(lambda item: item.partition('='), query_string.split('&'))}
    print(querydict)

    status = '200 OK'
    headers = [('Content-type', 'text/plain; charset=utf-8')]

    start_response(status, headers)

    ret = [query_string.encode()]
    return ret #返回要求可迭代对象,正文就是这个列表的元素,可以是一个元素——字符串。

class A:

    def __init__(self,name,age):
        pass
    def __call__(self, environ, start_response):
        pass


with make_server('0.0.0.0', 8000, simple_app) as httpd:#创建server,创建的server调用simple_app,请求来了就调用它。
    print("Serving on port 8000...")
    httpd.serve_forever()

结果为:

Serving on port 8000...
127.0.0.1 - - [06/Feb/2020 10:45:05] "GET /?id=1&name=tom&age=20 HTTP/1.1" 200 20
id=1&name=tom&age=20
id 1
name tom
age 20
{'id': '1', 'name': 'tom', 'age': '20'}
127.0.0.1 - - [06/Feb/2020 10:45:06] "GET /favicon.ico HTTP/1.1" 200 0

 

2、使用cgi模块

# id=5&name=wayne
qstr = environ.get('QUERY_STRING')
print(qstr)
print(cgi.parse_qs(qstr))
# {'name': ['wayne'], 'id': ['5']}

可以看到使用这个库,可以解析查询字符串,请注意value是列表,为什么?

这是因为同一个key可以有多个值。

cgi模块过期了,建议使用urllib

3、使用urllib库

from  urllib.parse import parse_qs
# http://127.0.0.1:9999/?id=5&name=wayne&age=&comment=1,a,c&age=19&age=20
qstr = environ.get('QUERY_STRING')
print(qstr)
print(parse.parse_qs(qstr)) # 字典
print(parse.parse_qsl(qstr)) # 二元组列表
# 运行结果
id=5&name=wayne&age=&comment=1,a,c&age=19&age=20
{'name': ['wayne'], 'age': ['19', '20'], 'id': ['5'], 'comment': ['1,a,c']}
[('id', '5'), ('name', 'wayne'), ('comment', '1,a,c'), ('age', '19'), ('age', '20')]

parse_qs函数,将同一个名称的多值,保存在字典中,使用了列表保存。
comment=1,a,c 这不是多值,这是一个值。
age 是多值。

environ的解析——webob库

环境数据有很多,都是存在字典中的,字典的存取方式没有对象的属性访问方便。
使用第三方库webob,可以把环境数据的解析、封装成对象。

webob简介

Python下,可以对WSGI请求进行解析,并提供对响应进行高级封装的库。

$ pip install webob

官网文档 docs.webob.org

webob.Request对象

将环境参数解析并封装成request对象
GET方法,发送的数据是URL中Query string,在Request Header中。
request.GET就是一个字典MultiDict,里面就封装着查询字符串。
POST方法,"提交"的数据是放在Request Body里面,但是也可以同时使用Query String。
request.POST可以获取Request Body中的数据,也是个字典MultiDict。

不关心什么方法提交,只关心数据,可以使用request.params,它里面是所有提交数据的封装。

request = webob.Request(environ)
print(request.headers) # 类字典容器
print(request.method)
print(request.path)
print(request.query_string) # 查询字符串
print(request.GET) # GET方法的所有数据
print(request.POST) # POST方法的所有数据
print('params = {}'.format(request.params)) # 所有数据,参数
from wsgiref.simple_server import make_server
from webob import Request,Response


#127.0.0.1:8000?id=1&name=tom@age=20
def simple_app(environ, start_response):
    request = Request(environ)
    query_string = request.query_string
    method = request.method
    print(query_string,method)
    print(request.GET)
    print(type(request.GET))#dict
    print(request.POST)#dict
    print(request.path)#路径#用post方法试一试
    print(request.params)



    status = '200 OK'
    headers = [('Content-type', 'text/plain; charset=utf-8')]

    start_response(status, headers)

    ret = [query_string.encode()]
    return ret #返回要求可迭代对象,正文就是这个列表的元素,可以是一个元素——字符串。

class A:

    def __init__(self,name,age):
        pass
    def __call__(self, environ, start_response):
        pass


with make_server('0.0.0.0', 8000, simple_app) as httpd:#创建server,创建的server调用simple_app,请求来了就调用它。
    print("Serving on port 8000...")
    httpd.serve_forever()

 

MultiDict

MultiDict允许一个key存了好几个值。

from webob.multidict import MultiDict
md = MultiDict()
md.add(1, 'magedu')
md.add(1, '.com')
md.add('a', 1)
md.add('a', 2)
md.add('b', '3')
md['b'] = '4'
for pair in md.items():
  print(pair)print(md.getall(1))
#print(md.getone('a')) # 只能有一个值
print(md.get('a')) # 返回一个值
print(md.get('c')) # 不会抛异常KeyError,返回None

webob.Response对象

res = webob.Response()
print(res.status)
print(res.headerlist)
start_response(res.status, res.headerlist)
# 返回可迭代对象
html = '<h1>马哥教育欢迎你</h1>'.encode("utf-8")
return [html]

如果一个Application是一个类的实例,可以实现 __call__ 方法。
我们来看看webob.Response类的源代码

 

 

 由此可以得到下面代码

def application(environ:dict, start_response):
# 请求处理
  request = webob.Request(environ)
  print(request.method)
  print(request.path)
  print(request.query_string)
  print(request.GET)
  print(request.POST)
  print('params = {}'.format(request.params))
# 响应处理
  res = webob.Response() # [('Content-Type', 'text/html; charset=UTF-8'), ('Content-Length',
  '0')]
  res.status_code = 200 # 默认200
  print(res.content_type)
  html = '<h1>马哥教育欢迎你</h1>'.encode("utf-8")
  res.body = html
  return res(environ, start_response)
from wsgiref.simple_server import make_server
from webob import Request,Response


#127.0.0.1:8000?id=1&name=tom@age=20
def simple_app(environ, start_response):
    #请求处理
    request = Request(environ)
    query_string = request.query_string
    method = request.method
    print(query_string,method)
    print(request.GET)
    print(type(request.GET))#dict
    print(request.POST)#dict
    print(request.path)#路径#用post方法试一试
    print(request.params)

    #响应处理
    res = Response()
    print(res.status_code)#200
    print(res.status)#200 ok
    print(res.headers)#object
    print(res.headerlist)#list
    
    # 返回可迭代对象
    html = '<h1>欢迎你</h1>'.encode("utf-8")
    res.body = html
    return res(environ,start_response)


    # status = '200 OK'
    # headers = [('Content-type', 'text/plain; charset=utf-8')]

    #start_response(status, headers)

    #ret = [query_string.encode()]
    #return res #返回要求可迭代对象,正文就是这个列表的元素,可以是一个元素——字符串。

class A:

    def __init__(self,name,age):
        pass
    def __call__(self, environ, start_response):
        pass


with make_server('0.0.0.0', 8000, simple_app) as httpd:#创建server,创建的server调用simple_app,请求来了就调用它。
    print("Serving on port 8000...")
    httpd.serve_forever()

 

webob.dec 装饰器

wsgify装饰器

文档:https://docs.pylonsproject.org/projects/webob/en/stable/api/dec.html

class webob.dec.wsgify(func=None, RequestClass=None, args=(), kwargs=None, middleware_wraps=None)

要求提供类似下面的可调用对象,以函数举例:

from webob.dec import wsgify
@wsgify
def app(request:webob.Request) -> webob.Response:
res = webob.Response('<h1>马哥教育欢迎你. magedu.com</h1>')
return res

wsgify装饰器装饰的函数应该具有一个参数,这个参数是webob.Request类型,是对字典environ的对象化后的实例。

from wsgiref.simple_server import make_server
from webob import Request,Response
from webob.dec import wsgify


#127.0.0.1:8000?id=1&name=tom@age=20
def simple_app(environ, start_response):
    #请求处理
    request = Request(environ)
    query_string = request.query_string
    method = request.method
    print(query_string,method)
    print(request.GET)
    print(type(request.GET))#dict
    print(request.POST)#dict
    print(request.path)#路径#用post方法试一试
    print(request.params)

    #响应处理
    res = Response()
    print(res.status_code)#200
    print(res.status)#200 ok
    print(res.headers)#object
    print(res.headerlist)#list

    # 返回可迭代对象
    html = '<h1>欢迎你</h1>'.encode("utf-8")
    res.body = html
    return res(environ,start_response)
@wsgify
def app(request:Request):#一个请求对应一个响应
    return Response(b'<h1>xpc.com</h1>')

    # status = '200 OK'
    # headers = [('Content-type', 'text/plain; charset=utf-8')]

    #start_response(status, headers)

    #ret = [query_string.encode()]
    #return res #返回要求可迭代对象,正文就是这个列表的元素,可以是一个元素——字符串。

class A:

    def __init__(self,name,age):
        pass
    def __call__(self, environ, start_response):
        pass


with make_server('0.0.0.0', 8000, app) as httpd:#创建server,创建的server调用simple_app,请求来了就调用它。
    print("Serving on port 8000...")
    httpd.serve_forever()

返回值
可以是一个webob.Response类型实例
可以是一个bytes类型实例,它会被封装成webob.Response类型实例的body属性
可以是一个字符串类型实例,它会被转换成bytes类型实例,然后会被封装成webob.Response类型实例的body属性。

总之,返回值会被封装成webob.Response类型实例返回

由此修改测试代码,如下

from wsgiref.simple_server import make_server
import webob
from webob.dec import wsgify
# application函数不用了,用来和app函数对比
def application(environ:dict, start_response):
# 请求处理
  request = webob.Request(environ)
  print(request.method)
  print(request.path)
  print(request.query_string)
  print(request.GET)
  print(request.POST)
  print('params = {}'.format(request.params))
# 响应处理
  res = webob.Response() # [('Content-Type', 'text/html; charset=UTF-8'), ('Content-Length',
'0')]
  res.status_code = 200 # 默认200
  print(res.content_type)
  html = '<h1>马哥教育欢迎你</h1>'.encode("utf-8")
  res.body = html
  return res(environ, start_response)
@wsgify
def app(request:webob.Request) -> webob.Response:
  print(request.method)
  print(request.path)
  print(request.query_string)
  print(request.GET)
  print(request.POST)
  print('params = {}'.format(request.params))
  res = webob.Response('<h1>马哥教育欢迎你. magedu.com</h1>')
  return res
if __name__ == '__main__':
ip = '127.0.0.1'
port = 9999
server = make_server(ip, port, app)
try:
  server.serve_forever() # server.handle_request() 一次
except KeyboardInterrupt:
  server.shutdown()
  server.server_close()

将上面的app函数封装成类

from webob import Response, Request
from webob.dec import wsgify
from wsgiref.simple_server import make_server
class App:
  @wsgify
  def __call__(self, request:Request):
    return '<h1>欢迎你. magedu.com</h1>'
if __name__ == '__main__':
  ip = '127.0.0.1'
  port = 9999
  server = make_server(ip, port, App())
  try:
    server.serve_forever() # server.handle_request() 一次
  except KeyboardInterrupt:
    server.shutdown()
    server.server_close()

上面的代码中,所有的请求,都有这个App类的实例处理,需要对它进行改造。

 
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!