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()