debuggers.hg

view tools/python/xen/web/http.py @ 4672:d781b9d08e80

bitkeeper revision 1.1327.2.4 (426918a34Af7gihN8mTkq-P3KrAZXg)

Remove twisted from save/migrate handling.
This needs to use threads, so add thread support for
http server requests.

Signed-off-by: Mike Wray <mike.wray@hp.com>
author mjw@wray-m-3.hpl.hp.com
date Fri Apr 22 15:30:43 2005 +0000 (2005-04-22)
parents a838a908e38e
children 16efdf7bbd57
line source
1 #============================================================================
2 # This library is free software; you can redistribute it and/or
3 # modify it under the terms of version 2.1 of the GNU Lesser General Public
4 # License as published by the Free Software Foundation.
5 #
6 # This library is distributed in the hope that it will be useful,
7 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
9 # Lesser General Public License for more details.
10 #
11 # You should have received a copy of the GNU Lesser General Public
12 # License along with this library; if not, write to the Free Software
13 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
14 #
15 #============================================================================
16 # Parts of this library are derived from Twisted:
17 # Copyright (C) 2001 Matthew W. Lefkowitz
18 #
19 # Copyright (C) 2005 Mike Wray <mike.wray@hp.com>
20 #============================================================================
22 from mimetools import Message
23 from cStringIO import StringIO
24 import math
25 import time
26 import cgi
28 CONTINUE = 100
29 SWITCHING_PROTOCOLS = 101
31 OK = 200
32 CREATED = 201
33 ACCEPTED = 202
34 NON_AUTHORITATIVE_INFORMATION = 203
35 NO_CONTENT = 204
36 RESET_CONTENT = 205
37 PARTIAL_CONTENT = 206
38 MULTI_STATUS = 207
40 MULTIPLE_CHOICE = 300
41 MOVED_PERMANENTLY = 301
42 FOUND = 302
43 SEE_OTHER = 303
44 NOT_MODIFIED = 304
45 USE_PROXY = 305
46 TEMPORARY_REDIRECT = 307
48 BAD_REQUEST = 400
49 UNAUTHORIZED = 401
50 PAYMENT_REQUIRED = 402
51 FORBIDDEN = 403
52 NOT_FOUND = 404
53 NOT_ALLOWED = 405
54 NOT_ACCEPTABLE = 406
55 PROXY_AUTH_REQUIRED = 407
56 REQUEST_TIMEOUT = 408
57 CONFLICT = 409
58 GONE = 410
59 LENGTH_REQUIRED = 411
60 PRECONDITION_FAILED = 412
61 REQUEST_ENTITY_TOO_LARGE = 413
62 REQUEST_URI_TOO_LONG = 414
63 UNSUPPORTED_MEDIA_TYPE = 415
64 REQUESTED_RANGE_NOT_SATISFIABLE = 416
65 EXPECTATION_FAILED = 417
67 INTERNAL_SERVER_ERROR = 500
68 NOT_IMPLEMENTED = 501
69 BAD_GATEWAY = 502
70 SERVICE_UNAVAILABLE = 503
71 GATEWAY_TIMEOUT = 504
72 VERSION_NOT_SUPPORTED = 505
73 INSUFFICIENT_STORAGE_SPACE = 507
74 NOT_EXTENDED = 510
76 NO_BODY_CODES = [ NO_CONTENT, NOT_MODIFIED ]
79 STATUS = {
80 CONTINUE : "Continue",
81 SWITCHING_PROTOCOLS : "Switching protocols",
83 OK : "OK",
84 CREATED : "Created",
85 ACCEPTED : "Accepted",
86 NON_AUTHORITATIVE_INFORMATION : "Non-authoritative information",
87 NO_CONTENT : "No content",
88 RESET_CONTENT : "Reset content",
89 PARTIAL_CONTENT : "Partial content",
90 MULTI_STATUS : "Multi-status",
92 MULTIPLE_CHOICE : "Multiple choice",
93 MOVED_PERMANENTLY : "Moved permanently",
94 FOUND : "Found",
95 SEE_OTHER : "See other",
96 NOT_MODIFIED : "Not modified",
97 USE_PROXY : "Use proxy",
98 TEMPORARY_REDIRECT : "Temporary redirect",
100 BAD_REQUEST : "Bad request",
101 UNAUTHORIZED : "Unauthorized",
102 PAYMENT_REQUIRED : "Payment required",
103 FORBIDDEN : "Forbidden",
104 NOT_FOUND : "Not found",
105 NOT_ALLOWED : "Not allowed",
106 NOT_ACCEPTABLE : "Not acceptable",
107 PROXY_AUTH_REQUIRED : "Proxy authentication required",
108 REQUEST_TIMEOUT : "Request timeout",
109 CONFLICT : "Conflict",
110 GONE : "Gone",
111 LENGTH_REQUIRED : "Length required",
112 PRECONDITION_FAILED : "Precondition failed",
113 REQUEST_ENTITY_TOO_LARGE : "Request entity too large",
114 REQUEST_URI_TOO_LONG : "Request URI too long",
115 UNSUPPORTED_MEDIA_TYPE : "Unsupported media type",
116 REQUESTED_RANGE_NOT_SATISFIABLE : "Requested range not satisfiable",
117 EXPECTATION_FAILED : "Expectation failed",
119 INTERNAL_SERVER_ERROR : "Internal server error",
120 NOT_IMPLEMENTED : "Not implemented",
121 BAD_GATEWAY : "Bad gateway",
122 SERVICE_UNAVAILABLE : "Service unavailable",
123 GATEWAY_TIMEOUT : "Gateway timeout",
124 VERSION_NOT_SUPPORTED : "HTTP version not supported",
125 INSUFFICIENT_STORAGE_SPACE : "Insufficient storage space",
126 NOT_EXTENDED : "Not extended",
127 }
129 def getStatus(code):
130 return STATUS.get(code, "unknown")
132 MULTIPART_FORM_DATA = 'multipart/form-data'
133 URLENCODED = 'application/x-www-form-urlencoded'
135 parseQueryArgs = cgi.parse_qs
137 def timegm(year, month, day, hour, minute, second):
138 """Convert time tuple in GMT to seconds since epoch, GMT"""
139 EPOCH = 1970
140 assert year >= EPOCH
141 assert 1 <= month <= 12
142 days = 365*(year-EPOCH) + calendar.leapdays(EPOCH, year)
143 for i in range(1, month):
144 days = days + calendar.mdays[i]
145 if month > 2 and calendar.isleap(year):
146 days = days + 1
147 days = days + day - 1
148 hours = days*24 + hour
149 minutes = hours*60 + minute
150 seconds = minutes*60 + second
151 return seconds
153 def stringToDatetime(dateString):
154 """Convert an HTTP date string to seconds since epoch."""
155 parts = dateString.split(' ')
156 day = int(parts[1])
157 month = int(monthname.index(parts[2]))
158 year = int(parts[3])
159 hour, min, sec = map(int, parts[4].split(':'))
160 return int(timegm(year, month, day, hour, min, sec))
162 class HttpRequest:
164 http_version = (1, 1)
166 http_version_string = ("HTTP/%d.%d" % http_version)
168 max_content_length = 10000
169 max_headers = 500
171 request_line = None
172 request_method = None
173 request_uri = None
174 request_path = None
175 request_query = None
176 request_version = None
177 content_length = 0
178 content = None
179 etag = None
180 close_connection = True
181 response_code = 200
182 response_status = "OK"
183 response_sent = False
184 cached = False
185 last_modified = None
187 forceSSL = False
189 def __init__(self, host, rin, out):
190 self.host = host
191 self.rin = rin
192 self.out = out
193 self.request_args = {}
194 self.args = self.request_args
195 self.request_headers = {}
196 self.request_cookies = {}
197 self.response_headers = {}
198 self.response_cookies = {}
199 self.output = StringIO()
200 self.parseRequest()
202 def isSecure(self):
203 return self.forceSSL
205 def getRequestMethod(self):
206 return self.request_method
208 def trim(self, str, ends):
209 for end in ends:
210 if str.endswith(end):
211 str = str[ : -len(end) ]
212 break
213 return str
215 def requestError(self, code, msg=None):
216 self.sendError(code, msg)
217 raise ValueError(self.response_status)
219 def sendError(self, code, msg=None):
220 self.setResponseCode(code, msg=msg)
221 self.sendResponse()
223 def parseRequestVersion(self, version):
224 try:
225 if not version.startswith('HTTP/'):
226 raise ValueError
227 version_string = version.split('/', 1)[1]
228 version_codes = version_string.split('.')
229 if len(version_codes) != 2:
230 raise ValueError
231 request_version = (int(version_codes[0]), int(version_codes[1]))
232 except (ValueError, IndexError):
233 self.requestError(400, "Bad request version (%s)" % `version`)
235 def parseRequestLine(self):
236 line = self.trim(self.request_line, ['\r\n', '\n'])
237 line_fields = line.split()
238 n = len(line_fields)
239 if n == 3:
240 [method, uri, version] = line_fields
241 elif n == 2:
242 [method, uri] = line_fields
243 version = 'HTTP/0.9'
244 else:
245 self.requestError(BAD_REQUEST,
246 "Bad request (%s)" % `line`)
248 request_version = self.parseRequestVersion(version)
250 if request_version > (2, 0):
251 self.requestError(VERSION_NOT_SUPPORTED,
252 "HTTP version not supported (%s)" % `version`)
253 #if request_version >= (1, 1) and self.http_version >= (1, 1):
254 # self.close_connection = False
255 #else:
256 # self.close_connection = True
258 self.request_method = method
259 self.method = method
260 self.request_uri = uri
261 self.request_version = version
263 uri_query = uri.split('?')
264 if len(uri_query) == 1:
265 self.request_path = uri
266 else:
267 self.request_path = uri_query[0]
268 self.request_query = uri_query[1]
269 self.request_args = parseQueryArgs(self.request_query)
270 self.args = self.request_args
273 def parseRequestHeaders(self):
274 header_bytes = ""
275 header_count = 0
276 while True:
277 if header_count >= self.max_headers:
278 self.requestError(BAD_REQUEST,
279 "Bad request (too many headers)")
280 line = self.rin.readline()
281 header_bytes += line
282 header_count += 1
283 if line == '\r\n' or line == '\n' or line == '':
284 break
285 header_input = StringIO(header_bytes)
286 self.request_headers = Message(header_input)
288 def parseRequestCookies(self):
289 cookie_hdr = self.getHeader("cookie")
290 if not cookie_hdr: return
291 for cookie in cookie_hdr.split(';'):
292 try:
293 cookie = cookie.lstrip()
294 (k, v) = cookie.split('=', 1)
295 self.request_cookies[k] = v
296 except ValueError:
297 pass
299 def parseRequestArgs(self):
300 if ((self.content is None) or
301 (self.request_method != "POST")):
302 return
303 content_type = self.getHeader('content-type')
304 if not content_type:
305 return
306 (encoding, params) = cgi.parse_header(content_type)
307 if encoding == URLENCODED:
308 xargs = cgi.parse_qs(self.content.getvalue(),
309 keep_blank_values=True)
310 elif encoding == MULTIPART_FORM_DATA:
311 xargs = cgi.parse_multipart(self.content, params)
312 else:
313 xargs = {}
314 self.request_args.update(xargs)
316 def getCookie(self, k):
317 return self.request_cookies[k]
319 def readContent(self):
320 try:
321 self.content_length = int(self.getHeader("Content-Length"))
322 except:
323 return
324 if self.content_length > self.max_content_length:
325 self.requestError(REQUEST_ENTITY_TOO_LARGE)
326 self.content = self.rin.read(self.content_length)
327 self.content = StringIO(self.content)
328 self.content.seek(0,0)
330 def parseRequest(self):
331 self.request_line = self.rin.readline()
332 self.parseRequestLine()
333 self.parseRequestHeaders()
334 self.parseRequestCookies()
335 connection_mode = self.getHeader('Connection')
336 self.setCloseConnection(connection_mode)
337 self.readContent()
338 self.parseRequestArgs()
340 def setCloseConnection(self, mode):
341 if not mode: return
342 mode = mode.lower()
343 if mode == 'close':
344 self.close_connection = True
345 elif (mode == 'keep-alive') and (self.http_version >= (1, 1)):
346 self.close_connection = False
348 def getCloseConnection(self):
349 return self.close_connection
351 def getHeader(self, k, v=None):
352 return self.request_headers.get(k, v)
354 def getRequestMethod(self):
355 return self.request_method
357 def getRequestPath(self):
358 return self.request_path
360 def setResponseCode(self, code, status=None, msg=None):
361 self.response_code = code
362 if not status:
363 status = getStatus(code)
364 self.response_status = status
366 def setResponseHeader(self, k, v):
367 k = k.lower()
368 self.response_headers[k] = v
369 if k == 'connection':
370 self.setCloseConnection(v)
372 setHeader = setResponseHeader
374 def setLastModified(self, when):
375 # time.time() may be a float, but the HTTP-date strings are
376 # only good for whole seconds.
377 when = long(math.ceil(when))
378 if (not self.last_modified) or (self.last_modified < when):
379 self.lastModified = when
381 modified_since = self.getHeader('if-modified-since')
382 if modified_since:
383 modified_since = stringToDatetime(modified_since)
384 if modified_since >= when:
385 self.setResponseCode(NOT_MODIFIED)
386 self.cached = True
388 def setContentType(self, ty):
389 self.setResponseHeader("Content-Type", ty)
391 def setEtag(self, etag):
392 if etag:
393 self.etag = etag
395 tags = self.getHeader("if-none-match")
396 if tags:
397 tags = tags.split()
398 if (etag in tags) or ('*' in tags):
399 if self.request_method in ("HEAD", "GET"):
400 code = NOT_MODIFIED
401 else:
402 code = PRECONDITION_FAILED
403 self.setResponseCode(code)
404 self.cached = True
406 def addCookie(self, k, v, expires=None, domain=None, path=None,
407 max_age=None, comment=None, secure=None):
408 cookie = v
409 if expires != None:
410 cookie += "; Expires=%s" % expires
411 if domain != None:
412 cookie += "; Domain=%s" % domain
413 if path != None:
414 cookie += "; Path=%s" % path
415 if max_age != None:
416 cookie += "; Max-Age=%s" % max_age
417 if comment != None:
418 cookie += "; Comment=%s" % comment
419 if secure:
420 cookie += "; Secure"
421 self.response_cookies[k] = cookie
423 def sendResponseHeaders(self):
424 if self.etag:
425 self.setResponseHeader("ETag", self.etag)
426 for (k, v) in self.response_headers.items():
427 self.send("%s: %s\r\n" % (k.capitalize(), v))
428 for (k, v) in self.response_cookies.items():
429 self.send("Set-Cookie: %s=%s\r\n" % (k, v))
430 self.send("\r\n")
432 def sendResponse(self):
433 if self.response_sent:
434 return
435 self.response_sent = True
436 send_body = self.hasBody()
437 if not self.close_connection:
438 self.setResponseHeader("Connection", "keep-alive")
439 if send_body:
440 self.output.seek(0, 0)
441 body = self.output.getvalue()
442 body_length = len(body)
443 self.setResponseHeader("Content-Length", body_length)
444 if self.http_version > (0, 9):
445 self.send("%s %d %s\r\n" % (self.http_version_string,
446 self.response_code,
447 self.response_status))
448 self.sendResponseHeaders()
449 if send_body:
450 self.send(body)
451 self.flush()
453 def write(self, data):
454 self.output.write(data)
456 def send(self, data):
457 #print 'send>', data
458 self.out.write(data)
460 def flush(self):
461 self.out.flush()
463 def hasNoBody(self):
464 return ((self.request_method == "HEAD") or
465 (self.response_code in NO_BODY_CODES) or
466 self.cached)
468 def hasBody(self):
469 return not self.hasNoBody()
471 def process(self):
472 pass
473 return self.close_connection
475 def getRequestHostname(self):
476 """Get the hostname that the user passed in to the request.
478 Uses the 'Host:' header if it is available, and the
479 host we are listening on otherwise.
480 """
481 return (self.getHeader('host') or
482 socket.gethostbyaddr(self.getHostAddr())[0]
483 ).split(':')[0]
485 def getHost(self):
486 return self.host
488 def getHostAddr(self):
489 return self.host[0]
491 def getPort(self):
492 return self.host[1]
494 def setHost(self, host, port, ssl=0):
495 """Change the host and port the request thinks it's using.
497 This method is useful for working with reverse HTTP proxies (e.g.
498 both Squid and Apache's mod_proxy can do this), when the address
499 the HTTP client is using is different than the one we're listening on.
501 For example, Apache may be listening on https://www.example.com, and then
502 forwarding requests to http://localhost:8080, but we don't want HTML produced
503 to say 'http://localhost:8080', they should say 'https://www.example.com',
504 so we do::
506 request.setHost('www.example.com', 443, ssl=1)
508 """
509 self.forceSSL = ssl
510 self.received_headers["host"] = host
511 self.host = (host, port)