Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(203)

Side by Side Diff: Tools/Scripts/webkitpy/thirdparty/webpagereplay/third_party/nbhttp/server.py

Issue 18418010: Check in the thirdparty libs needed for webkitpy. (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 #!/usr/bin/env python
2
3 __copyright__ = """\
4 Copyright (c) 2008-2009 Mark Nottingham
5
6 Permission is hereby granted, free of charge, to any person obtaining a copy
7 of this software and associated documentation files (the "Software"), to deal
8 in the Software without restriction, including without limitation the rights
9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 copies of the Software, and to permit persons to whom the Software is
11 furnished to do so, subject to the following conditions:
12
13 The above copyright notice and this permission notice shall be included in
14 all copies or substantial portions of the Software.
15
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 THE SOFTWARE.
23 """
24
25 """
26 Non-Blocking HTTP Server
27
28 This library allow implementation of an HTTP/1.1 server that is "non-blocking,"
29 "asynchronous" and "event-driven" -- i.e., it achieves very high performance
30 and concurrency, so long as the application code does not block (e.g.,
31 upon network, disk or database access). Blocking on one request will block
32 the entire server.
33
34 Instantiate a Server with the following parameters:
35 - host (string)
36 - port (int)
37 - req_start (callable)
38
39 req_start is called when a request starts. It must take the following arguments:
40 - method (string)
41 - uri (string)
42 - req_hdrs (list of (name, value) tuples)
43 - res_start (callable)
44 - req_body_pause (callable)
45 and return:
46 - req_body (callable)
47 - req_done (callable)
48
49 req_body is called when part of the request body is available. It must take the
50 following argument:
51 - chunk (string)
52
53 req_done is called when the request is complete, whether or not it contains a
54 body. It must take the following argument:
55 - err (error dictionary, or None for no error)
56
57 Call req_body_pause when you want the server to temporarily stop sending the
58 request body, or restart. You must provide the following argument:
59 - paused (boolean; True means pause, False means unpause)
60
61 Call res_start when you want to start the response, and provide the following
62 arguments:
63 - status_code (string)
64 - status_phrase (string)
65 - res_hdrs (list of (name, value) tuples)
66 - res_body_pause
67 It returns:
68 - res_body (callable)
69 - res_done (callable)
70
71 Call res_body to send part of the response body to the client. Provide the
72 following parameter:
73 - chunk (string)
74
75 Call res_done when the response is finished, and provide the
76 following argument if appropriate:
77 - err (error dictionary, or None for no error)
78
79 See the error module for the complete list of valid error dictionaries.
80
81 Where possible, errors in the request will be responded to with the appropriate
82 4xx HTTP status code. However, if a response has already been started, the
83 connection will be dropped (for example, when the request chunking or
84 indicated length are incorrect).
85 """
86
87 __author__ = "Mark Nottingham <mnot@mnot.net>"
88
89 import os
90 import sys
91 import logging
92
93 import push_tcp
94 from http_common import HttpMessageHandler, \
95 CLOSE, COUNTED, CHUNKED, \
96 WAITING, HEADERS_DONE, \
97 hop_by_hop_hdrs, \
98 dummy, get_hdr
99
100 from error import ERR_HTTP_VERSION, ERR_HOST_REQ, ERR_WHITESPACE_HDR, ERR_TRANSF ER_CODE
101
102 # FIXME: assure that the connection isn't closed before reading the entire req b ody
103 # TODO: filter out 100 responses to HTTP/1.0 clients that didn't ask for it.
104
105 class Server:
106 "An asynchronous HTTP server."
107 def __init__(self, host, port, request_handler):
108 self.request_handler = request_handler
109 self.server = push_tcp.create_server(host, port, self.handle_connection)
110 self.log = logging.getLogger('server')
111 self.log.setLevel(logging.WARNING)
112
113
114 def handle_connection(self, tcp_conn):
115 "Process a new push_tcp connection, tcp_conn."
116 conn = HttpServerConnection(self.request_handler, tcp_conn)
117 return conn._handle_input, conn._conn_closed, conn._res_body_pause
118
119
120 class HttpServerConnection(HttpMessageHandler):
121 "A handler for an HTTP server connection."
122 def __init__(self, request_handler, tcp_conn):
123 HttpMessageHandler.__init__(self)
124 self.request_handler = request_handler
125 self._tcp_conn = tcp_conn
126 self.req_body_cb = None
127 self.req_done_cb = None
128 self.method = None
129 self.req_version = None
130 self.connection_hdr = []
131 self._res_body_pause_cb = None
132
133 def res_start(self, status_code, status_phrase, res_hdrs, res_body_pause):
134 "Start a response. Must only be called once per response."
135 self._res_body_pause_cb = res_body_pause
136 res_hdrs = [i for i in res_hdrs \
137 if not i[0].lower() in hop_by_hop_hdrs ]
138
139 try:
140 body_len = int(get_hdr(res_hdrs, "content-length").pop(0))
141 except (IndexError, ValueError):
142 body_len = None
143 if body_len is not None:
144 delimit = COUNTED
145 res_hdrs.append(("Connection", "keep-alive"))
146 elif 2.0 > self.req_version >= 1.1:
147 delimit = CHUNKED
148 res_hdrs.append(("Transfer-Encoding", "chunked"))
149 else:
150 delimit = CLOSE
151 res_hdrs.append(("Connection", "close"))
152
153 self._output_start("HTTP/1.1 %s %s" % (status_code, status_phrase), res_ hdrs, delimit)
154 return self.res_body, self.res_done
155
156 def res_body(self, chunk):
157 "Send part of the response body. May be called zero to many times."
158 self._output_body(chunk)
159
160 def res_done(self, err):
161 """
162 Signal the end of the response, whether or not there was a body. MUST be
163 called exactly once for each response.
164
165 If err is not None, it is an error dictionary (see the error module)
166 indicating that an HTTP-specific (i.e., non-application) error occured
167 in the generation of the response; this is useful for debugging.
168 """
169 self._output_end(err)
170
171 def req_body_pause(self, paused):
172 "Indicate that the server should pause (True) or unpause (False) the req uest."
173 if self._tcp_conn and self._tcp_conn.tcp_connected:
174 self._tcp_conn.pause(paused)
175
176 # Methods called by push_tcp
177
178 def _res_body_pause(self, paused):
179 "Pause/unpause sending the response body."
180 if self._res_body_pause_cb:
181 self._res_body_pause_cb(paused)
182
183 def _conn_closed(self):
184 "The server connection has closed."
185 if self._output_state != WAITING:
186 pass # FIXME: any cleanup necessary?
187 # self.pause()
188 # self._queue = []
189 # self.tcp_conn.handler = None
190 # self.tcp_conn = None
191
192 # Methods called by common.HttpRequestHandler
193
194 def _output(self, chunk):
195 self._tcp_conn.write(chunk)
196
197 def _input_start(self, top_line, hdr_tuples, conn_tokens, transfer_codes, co ntent_length):
198 """
199 Take the top set of headers from the input stream, parse them
200 and queue the request to be processed by the application.
201 """
202 assert self._input_state == WAITING, "pipelining not supported" # FIXME: pipelining
203 try:
204 method, _req_line = top_line.split(None, 1)
205 uri, req_version = _req_line.rsplit(None, 1)
206 self.req_version = float(req_version.rsplit('/', 1)[1])
207 except ValueError:
208 self._handle_error(ERR_HTTP_VERSION, top_line) # FIXME: more fine-gr ained
209 raise ValueError
210 if self.req_version == 1.1 and 'host' not in [t[0].lower() for t in hdr_ tuples]:
211 self._handle_error(ERR_HOST_REQ)
212 raise ValueError
213 if hdr_tuples[:1][:1][:1] in [" ", "\t"]:
214 self._handle_error(ERR_WHITESPACE_HDR)
215 for code in transfer_codes: # we only support 'identity' and chunked' co des
216 if code not in ['identity', 'chunked']:
217 # FIXME: SHOULD also close connection
218 self._handle_error(ERR_TRANSFER_CODE)
219 raise ValueError
220 # FIXME: MUST 400 request messages with whitespace between name and colo n
221 self.method = method
222 self.connection_hdr = conn_tokens
223
224 self.log.info("%s server req_start %s %s %s",
225 id(self), method, uri, self.req_version)
226 self.req_body_cb, self.req_done_cb = self.request_handler(
227 method, uri, hdr_tuples, self.res_start, self.req_body_pause)
228 allows_body = (content_length) or (transfer_codes != [])
229 return allows_body
230
231 def _input_body(self, chunk):
232 "Process a request body chunk from the wire."
233 self.req_body_cb(chunk)
234
235 def _input_end(self):
236 "Indicate that the request body is complete."
237 self.req_done_cb(None)
238
239 def _input_error(self, err, detail=None):
240 "Indicate a parsing problem with the request body."
241 err['detail'] = detail
242 if self._tcp_conn:
243 self._tcp_conn.close()
244 self._tcp_conn = None
245 self.req_done_cb(err)
246
247 def _handle_error(self, err, detail=None):
248 "Handle a problem with the request by generating an appropriate response ."
249 # self._queue.append(ErrorHandler(status_code, status_phrase, body, self) )
250 assert self._output_state == WAITING
251 if detail:
252 err['detail'] = detail
253 status_code, status_phrase = err.get('status', ('400', 'Bad Request'))
254 hdrs = [
255 ('Content-Type', 'text/plain'),
256 ]
257 body = err['desc']
258 if err.has_key('detail'):
259 body += " (%s)" % err['detail']
260 self.res_start(status_code, status_phrase, hdrs, dummy)
261 self.res_body(body)
262 self.res_done()
263
264
265 def test_handler(method, uri, hdrs, res_start, req_pause):
266 """
267 An extremely simple (and limited) server request_handler.
268 """
269 code = "200"
270 phrase = "OK"
271 res_hdrs = [('Content-Type', 'text/plain')]
272 res_body, res_done = res_start(code, phrase, res_hdrs, dummy)
273 res_body('foo!')
274 res_done(None)
275 return dummy, dummy
276
277 if __name__ == "__main__":
278 sys.stderr.write("PID: %s\n" % os.getpid())
279 h, p = '127.0.0.1', int(sys.argv[1])
280 server = Server(h, p, test_handler)
281 push_tcp.run()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698