自定义web框架

  1 #! /usr/bin/env python
  2 # -*- coding: utf-8 -*-
  3 # __author__ = "Ellis"
  4 # Date: 2017/12/10
  5 import socket
  6 import select
  7 import time
  8 import re
  9 from types import GeneratorType
 10 
 11 
 12 class HttpRequest(object):
 13     """
 14     封装用户请求信息
 15     """
 16 
 17     def __init__(self, conn):
 18         self.conn = conn
 19 
 20         self.headers_bytes = bytes()
 21         self.header_dict = {}
 22         self.body_bytes = bytes()
 23 
 24         self.method = ''  # 请求方式
 25         self.url = ''
 26         self.protocol = ''  # 协议
 27 
 28         self.initialize()
 29         self.initialize_headers()
 30 
 31     def initialize(self):
 32         """
 33         分割请求头与请求体
 34         :return:
 35         """
 36         header_flag = False
 37         while True:
 38             try:
 39                 received = self.conn.recv(1024)
 40             except Exception as e:
 41                 received = None
 42             if not received:
 43                 break
 44             if header_flag:
 45                 self.body_bytes += received
 46                 continue
 47             temp = received.split(b'\r\n\r\n', 1)  # 拿到请求头,1表示只分割一次
 48             if len(temp) == 1:  # 分割字符串的时候,如果没有这个\r\n\r\n,结果就会是列表中只有一个元素,就是字符串本身
 49                 self.headers_bytes += temp
 50             else:  # 有请求体的情况
 51                 h, b = temp
 52                 self.headers_bytes += h
 53                 self.body_bytes += b
 54                 header_flag = True
 55 
 56     @property
 57     def header_str(self):
 58         # 把bytes转换成字符串
 59         return str(self.headers_bytes, encoding='utf-8')
 60 
 61     def initialize_headers(self):
 62         """
 63         分割请求头,拿到请求方式,url和。。。
 64         :return:
 65         """
 66         headers = self.header_str.split('\r\n')
 67         first_line = headers[0].split(' ')  # 分割拿到的第一个值比较特殊,是'GET / HTTP/1.1',请求方式,url和协议三个用空格隔开的
 68         if len(first_line) == 3:
 69             self.method, self.url, self.protocol = headers[0].split(' ')
 70             for line in headers:
 71                 kv = line.split(':', 1)  # 后面的k:v形式的请求头
 72                 if len(kv) == 2:  # ip端口那个就不是长度为2,所以我觉得应该是把上面加个1
 73                     k, v = kv
 74                     self.header_dict[k] = v
 75 
 76 
 77 class Future(object):
 78     """
 79     异步非阻塞模式时封装回调函数以及是否准备就绪
 80     自定义tornado异步非阻塞的future对象
 81     """
 82 
 83     def __init__(self, callback):
 84         self.callback = callback
 85         self._ready = False
 86         self.value = None
 87 
 88     def set_result(self, value=None):
 89         """
 90         Future里面最重要的就是这个方法,一旦socket变化,就执行set_result方法,把self._ready改为True,然后就会执行回调函数
 91         :param value:
 92         :return:
 93         """
 94         self.value = value
 95         self._ready = True
 96 
 97     @property
 98     def ready(self):
 99         return self._ready
100 
101 
102 class TimeoutFuture(Future):
103     """
104     设置异步非阻塞超时
105     """
106 
107     def __init__(self, timeout,callback):
108         super().__init__(callback=callback)
109         self.timeout = timeout  # 以秒为单位
110         self.start_time = time.time()
111 
112     @property
113     def ready(self):
114         current_time = time.time()
115         if current_time > self.start_time + self.timeout:  # 超时
116             self._ready = True
117         return self._ready
118 
119 
120 class Httpresponse(object):
121     """
122     封装响应信息
123     """
124 
125     def __init__(self, content="default"):
126         self.content = content
127 
128         self.headers = {}
129         self.cookies = {}
130 
131     def response(self):
132         return bytes("HTTP/1.1 200 OK\r\n\r\n<html><body><h1>{0}</h1></body></html>".format(self.content),
133                      'utf-8')  # 注意后面要指定这个编码方式,而且返回结果需要是这种格式,带http样式的,ie不用
134 
135 
136 class HttpNotFound(Httpresponse):
137     """
138     路由不匹配时的返回信息
139     """
140 
141     def __init__(self):
142         super().__init__(content='404 Page Not Found')
143 
144 
145 class Ellis(object):
146     """
147     微型web框架的主逻辑
148     """
149 
150     def __init__(self, routes):
151         self.routes = routes  # 路由,需要自己配置
152         self.inputs = set()  # select监听的对象集合
153         self.request = None  # 请求
154         self.async_request_handler = {}
155 
156     def run(self, host='localhost', port=8081):
157         """
158         主要循环逻辑
159         :param host:   ip
160         :param port:    端口号
161         :return:
162         """
163         # 创建socket
164         server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
165         server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
166         server.setblocking(False)  # 不等待
167         server.bind(('127.0.0.1', port), )  # 注意两层括号
168         server.listen(5)
169         server.setblocking(0)
170         self.inputs.add(server)  # 把当前这个socket对象加到select监听的集合中
171         try:
172             while True:
173                 # 分别为read,write,error,最后那个0.005表示最多阻塞0.005秒
174                 r_list, w_list, err_list = select.select(self.inputs, [], [], 0.005)  # 监听socket是否发生变化
175                 for conn in r_list:
176                     if conn == server:  # 如果是最开始那个socket,是用来连接对象的
177                         client, addr = conn.accept()  # 接收新的连接对象
178                         client.setblocking(False)  # 设置成不阻塞
179                         self.inputs.add(client)
180                     else:
181                         gen = self.process(conn)  # 处理路由的函数
182                         print(gen)
183                         if isinstance(gen, Httpresponse):  # 普通请求,返回值
184                             conn.sendall(gen.response())  # 向浏览器发送数据
185                             self.inputs.remove(conn)  # 这个对象任务结束,要把它从监听的那个集合中删除
186                             conn.close()  # 不仅要代码级别移除,还要关闭(系统级别)
187                         elif isinstance(gen, GeneratorType):  # 异步请求,视图函数中是yield返回的生成器
188                             yielded = next(gen)  # 拿到的应该是一个生成器
189                             self.async_request_handler[
190                                 conn] = yielded  # 异步请求不断开,要把这个请求加到select中,以conn为key,这是个tips,因为一会要用这个conn
191 
192                 self.polling_callback()
193         except Exception as e:
194             import traceback
195             print(traceback.print_exc())        # 打印详细的错误信息
196         finally:
197             server.close()  # 不管是否发生错误,最后都要关闭这个socket对象
198 
199     def polling_callback(self):
200         """
201         遍历触发异步非阻塞的回调函数
202         :return:
203         """
204         for conn in list(self.async_request_handler.keys()):
205             yielded = self.async_request_handler[conn]
206             if not yielded.ready:   # 只有执行了set_result,ready才会为True
207                 continue
208             if yielded.callback:
209                 ret = yielded.callback(self.request, yielded)   # 执行回调函数
210                 conn.sendall(ret.response())    # 要用这个conn对象来返回数据呢,所以用conn为key
211             self.inputs.remove(conn)
212             del self.async_request_handler[conn]    # 处理完就从字典中删除这个future对象
213             conn.close()
214 
215     def process(self, conn):
216         """
217         处理路由系统以及执行函数
218 
219         :param conn:
220         :return:
221         """
222         self.request = HttpRequest(conn)
223         func = None
224         for route in self.routes:
225             if re.match(route[0], self.request.url):
226                 func = route[1]
227                 break
228         if not func:
229             return HttpNotFound()
230         else:
231             return func(self.request)
232 
233 
234 # 使用框架
235 
236 # 应用1  基本使用
237 # def index(request):  # 视图函数
238 #     print('index')
239 #     return Httpresponse('index')
240 #
241 #
242 # routes = [
243 #     (r'/index', index),  # 配置路由
244 # ]
245 #
246 # app = Ellis(routes)
247 # app.run()
248 
249 
250 # 应用2  异步非阻塞,等待
251 # 访问http://127.0.0.1:8081/req后会一直等待,直到访问http://127.0.0.1:8081/stop,前一个页面才会停止,并返回done
252 # request_list = []
253 #
254 #
255 # def callback(request, future):
256 #     return Httpresponse(future.value)
257 #
258 #
259 # def req(request):
260 #     obj = Future(callback=callback)
261 #     request_list.append(obj)
262 #     yield obj
263 #
264 #
265 # def stop(request):
266 #     obj = request_list[0]
267 #     del request_list[0]
268 #     obj.set_result('done')  # future 那个对象返回的值
269 #     return Httpresponse('stop')
270 #
271 #
272 # routes = [
273 #     (r'/req', req),
274 #     (r'/stop', stop),
275 # ]
276 #
277 # app = Ellis(routes)
278 # app.run()
279 
280 
281 # 应用3,设置超时
282 
283 request_list = []
284 def callback(request,future):
285     return Httpresponse('gasdga')
286 def time_out(request):
287     obj = TimeoutFuture(5,callback=callback)    # 5秒后返回结果
288     yield obj
289 
290 def index(request):
291     return Httpresponse('index')
292 
293 routes = [
294     (r'/time_out',time_out),
295     (r'/index',index),
296 
297 ]
298 
299 app = Ellis(routes)
300 app.run()

 

posted @ 2017-12-17 15:36  张璨  阅读(120)  评论(0)    收藏  举报