1 """Some sketches of implementing a WSGI 2 async API, in both sync and async servers"""
2
3 def WSGI2(app):
4 """Decorator that synchronously emulates WSGI2 futures under WSGI 1"""
5 def wsgi1_app(environ, start_response):
6 def process_response(shb):
7 s, h, body = shb
8 write = start_response(s, h)
9
10 def body_trampoline(routine, yielded):
11 if type(yielded) is bytes:
12 # only accept from outermost middleware
13 if len(routine)==1:
14 return routine.synchronously(write, yielded)
15 else:
16 return routine.RETURN(yielded)
17 else:
18 return base_trampoline(routine, yielded)
19 if not [body has send/throw methods]:
20 body = (item for item in body)
21 Coroutine(body, body_trampoline)()
22
23 def app_trampoline(routine, yielded):
24 if type(yielded) is tuple:
25 return routine.RETURN(yielded)
26 else:
27 return base_trampoline(routine, yielded)
28
29 def base_trampoline(routine, yielded):
30 if [yielded is a future of some sort]:
31 return routine.synchronously(future.result)
32 elif [yielded has send/throw methods]:
33 return routine.CALL(yielded)
34 else:
35 raise TypeError("Not a future, result, or generator:", yielded)
36
37 # [add an executor to environ here]
38 Coroutine(app(environ), app_trampoline, process_response)()
39 return []
40
41 return wsgi1_app
42
43
44 def start_request(app, environ):
45 """Template for asynchronous request handler"""
46 def process_response(shb):
47 s, h, body = shb
48 [do an asynchronous start_response analog here]
49 def body_trampoline(routine, yielded):
50 if type(yielded) is bytes:
51 # only accept from outermost middleware
52 if len(routine)==1:
53 [arrange for the bytes to be sent out]
54 [arrange to invoke routine() when send is completed]
55 return routine.PAUSE
56 else:
57 return routine.RETURN(yielded)
58 else:
59 return base_trampoline(routine, yielded)
60
61 if not [body has send/throw methods]:
62 body = (item for item in body)
63 Coroutine(body, body_trampoline, [optional termination callback])()
64
65 def app_trampoline(routine, yielded):
66 if type(yielded) is tuple:
67 return routine.RETURN(yielded)
68 else:
69 return base_trampoline(routine, yielded)
70
71 def base_trampoline(routine, yielded):
72 if [yielded is a future of some sort]:
73 def done(f):
74 routine(*routine.synchronously(f.result))
75 future.add_done_callback(done)
76 return routine.PAUSE
77 elif [yielded has send/throw methods]:
78 return routine.CALL(yielded)
79 else:
80 raise TypeError("Not a future, result, or generator:", yielded)
81
82 # [add an executor to environ here]
83 Coroutine(app(environ), app_trampoline, process_response)()
84
85
86 class Coroutine:
87 """A thread-like stack of collaborating generators"""
88
89 def __init__(iterator, trampoline=lambda r,v:"return", callback=lambda v:v):
90 """Create and launch a general-purpose pseudo-thread"""
91 self.stack = [iterator]
92 self.trampoline = trampoline
93 self.callback = callback
94
95 PAUSE = ()
96
97 def CALL(self, geniter):
98 self.stack.append(geniter)
99 return None, ()
100
101 def RETURN(self, value=None):
102 self.stack.pop()
103 return value, ()
104
105 def RESUME(self, value=None):
106 return value, ()
107
108 def RAISE(self, exc_info):
109 return None, exc_info
110
111 def __len__(self):
112 return len(self.stack)
113
114 def synchronously(self, func, *args, **kw):
115 try:
116 return self.RESUME(func(*args, **kw))
117 except BaseException:
118 return self.RAISE(sys.exc_info())
119
120 def close():
121 while self.stack:
122 self.stack.pop().close()
123
124 def __call__(self, value=None, exc_info=()):
125 stack = self.stack
126 while stack:
127 try:
128 it = stack[-1]
129 if exc_info:
130 try:
131 rv = it.throw(*exc_info)
132 finally:
133 exc_info = ()
134 else:
135 rv = it.send(value)
136 except BaseException:
137 value = None
138 exc_info = sys.exc_info()
139 if exc_info[0] is StopIteration:
140 # pass return value up the stack
141 value, = exc_info[1].args or (None,)
142 exc_info = () # but not the error
143 stack.pop()
144 else:
145 switch = self.trampoline(self, rv)
146 if switch:
147 value, exc_info = switch
148 else:
149 return rv
150
151 # Coroutine is entirely finished when the stack is empty
152 return self.callback(value)