Package ganeti :: Package http :: Module server
[hide private]
[frames] | no frames]

Source Code for Module ganeti.http.server

  1  # 
  2  # 
  3   
  4  # Copyright (C) 2007, 2008 Google Inc. 
  5  # 
  6  # This program is free software; you can redistribute it and/or modify 
  7  # it under the terms of the GNU General Public License as published by 
  8  # the Free Software Foundation; either version 2 of the License, or 
  9  # (at your option) any later version. 
 10  # 
 11  # This program is distributed in the hope that it will be useful, but 
 12  # WITHOUT ANY WARRANTY; without even the implied warranty of 
 13  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
 14  # General Public License for more details. 
 15  # 
 16  # You should have received a copy of the GNU General Public License 
 17  # along with this program; if not, write to the Free Software 
 18  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 
 19  # 02110-1301, USA. 
 20   
 21  """HTTP server module. 
 22   
 23  """ 
 24   
 25  import BaseHTTPServer 
 26  import cgi 
 27  import logging 
 28  import os 
 29  import select 
 30  import socket 
 31  import time 
 32  import signal 
 33   
 34  from ganeti import http 
 35   
 36   
 37  WEEKDAYNAME = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] 
 38  MONTHNAME = [None, 
 39               'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 
 40               'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] 
 41   
 42  # Default error message 
 43  DEFAULT_ERROR_CONTENT_TYPE = "text/html" 
 44  DEFAULT_ERROR_MESSAGE = """\ 
 45  <html> 
 46  <head> 
 47  <title>Error response</title> 
 48  </head> 
 49  <body> 
 50  <h1>Error response</h1> 
 51  <p>Error code %(code)d. 
 52  <p>Message: %(message)s. 
 53  <p>Error code explanation: %(code)s = %(explain)s. 
 54  </body> 
 55  </html> 
 56  """ 
 57   
 58   
59 -def _DateTimeHeader(gmnow=None):
60 """Return the current date and time formatted for a message header. 61 62 The time MUST be in the GMT timezone. 63 64 """ 65 if gmnow is None: 66 gmnow = time.gmtime() 67 (year, month, day, hh, mm, ss, wd, _, _) = gmnow 68 return ("%s, %02d %3s %4d %02d:%02d:%02d GMT" % 69 (WEEKDAYNAME[wd], day, MONTHNAME[month], year, hh, mm, ss))
70 71
72 -class _HttpServerRequest(object):
73 """Data structure for HTTP request on server side. 74 75 """
76 - def __init__(self, request_msg):
77 # Request attributes 78 self.request_method = request_msg.start_line.method 79 self.request_path = request_msg.start_line.path 80 self.request_headers = request_msg.headers 81 self.request_body = request_msg.decoded_body 82 83 # Response attributes 84 self.resp_headers = {} 85 86 # Private data for request handler (useful in combination with 87 # authentication) 88 self.private = None
89 90
91 -class _HttpServerToClientMessageWriter(http.HttpMessageWriter):
92 """Writes an HTTP response to client. 93 94 """
95 - def __init__(self, sock, request_msg, response_msg, write_timeout):
96 """Writes the response to the client. 97 98 @type sock: socket 99 @param sock: Target socket 100 @type request_msg: http.HttpMessage 101 @param request_msg: Request message, required to determine whether 102 response may have a message body 103 @type response_msg: http.HttpMessage 104 @param response_msg: Response message 105 @type write_timeout: float 106 @param write_timeout: Write timeout for socket 107 108 """ 109 self._request_msg = request_msg 110 self._response_msg = response_msg 111 http.HttpMessageWriter.__init__(self, sock, response_msg, write_timeout)
112
113 - def HasMessageBody(self):
114 """Logic to detect whether response should contain a message body. 115 116 """ 117 if self._request_msg.start_line: 118 request_method = self._request_msg.start_line.method 119 else: 120 request_method = None 121 122 response_code = self._response_msg.start_line.code 123 124 # RFC2616, section 4.3: "A message-body MUST NOT be included in a request 125 # if the specification of the request method (section 5.1.1) does not allow 126 # sending an entity-body in requests" 127 # 128 # RFC2616, section 9.4: "The HEAD method is identical to GET except that 129 # the server MUST NOT return a message-body in the response." 130 # 131 # RFC2616, section 10.2.5: "The 204 response MUST NOT include a 132 # message-body [...]" 133 # 134 # RFC2616, section 10.3.5: "The 304 response MUST NOT contain a 135 # message-body, [...]" 136 137 return (http.HttpMessageWriter.HasMessageBody(self) and 138 (request_method is not None and 139 request_method != http.HTTP_HEAD) and 140 response_code >= http.HTTP_OK and 141 response_code not in (http.HTTP_NO_CONTENT, 142 http.HTTP_NOT_MODIFIED))
143 144
145 -class _HttpClientToServerMessageReader(http.HttpMessageReader):
146 """Reads an HTTP request sent by client. 147 148 """ 149 # Length limits 150 START_LINE_LENGTH_MAX = 4096 151 HEADER_LENGTH_MAX = 4096 152
153 - def ParseStartLine(self, start_line):
154 """Parses the start line sent by client. 155 156 Example: "GET /index.html HTTP/1.1" 157 158 @type start_line: string 159 @param start_line: Start line 160 161 """ 162 # Empty lines are skipped when reading 163 assert start_line 164 165 logging.debug("HTTP request: %s", start_line) 166 167 words = start_line.split() 168 169 if len(words) == 3: 170 [method, path, version] = words 171 if version[:5] != 'HTTP/': 172 raise http.HttpBadRequest("Bad request version (%r)" % version) 173 174 try: 175 base_version_number = version.split("/", 1)[1] 176 version_number = base_version_number.split(".") 177 178 # RFC 2145 section 3.1 says there can be only one "." and 179 # - major and minor numbers MUST be treated as 180 # separate integers; 181 # - HTTP/2.4 is a lower version than HTTP/2.13, which in 182 # turn is lower than HTTP/12.3; 183 # - Leading zeros MUST be ignored by recipients. 184 if len(version_number) != 2: 185 raise http.HttpBadRequest("Bad request version (%r)" % version) 186 187 version_number = (int(version_number[0]), int(version_number[1])) 188 except (ValueError, IndexError): 189 raise http.HttpBadRequest("Bad request version (%r)" % version) 190 191 if version_number >= (2, 0): 192 raise http.HttpVersionNotSupported("Invalid HTTP Version (%s)" % 193 base_version_number) 194 195 elif len(words) == 2: 196 version = http.HTTP_0_9 197 [method, path] = words 198 if method != http.HTTP_GET: 199 raise http.HttpBadRequest("Bad HTTP/0.9 request type (%r)" % method) 200 201 else: 202 raise http.HttpBadRequest("Bad request syntax (%r)" % start_line) 203 204 return http.HttpClientToServerStartLine(method, path, version)
205 206
207 -class HttpServerRequestExecutor(object):
208 """Implements server side of HTTP. 209 210 This class implements the server side of HTTP. It's based on code of 211 Python's BaseHTTPServer, from both version 2.4 and 3k. It does not 212 support non-ASCII character encodings. Keep-alive connections are 213 not supported. 214 215 """ 216 # The default request version. This only affects responses up until 217 # the point where the request line is parsed, so it mainly decides what 218 # the client gets back when sending a malformed request line. 219 # Most web servers default to HTTP 0.9, i.e. don't send a status line. 220 default_request_version = http.HTTP_0_9 221 222 # Error message settings 223 error_message_format = DEFAULT_ERROR_MESSAGE 224 error_content_type = DEFAULT_ERROR_CONTENT_TYPE 225 226 responses = BaseHTTPServer.BaseHTTPRequestHandler.responses 227 228 # Timeouts in seconds for socket layer 229 WRITE_TIMEOUT = 10 230 READ_TIMEOUT = 10 231 CLOSE_TIMEOUT = 1 232
233 - def __init__(self, server, sock, client_addr):
234 """Initializes this class. 235 236 """ 237 self.server = server 238 self.sock = sock 239 self.client_addr = client_addr 240 241 self.request_msg = http.HttpMessage() 242 self.response_msg = http.HttpMessage() 243 244 self.response_msg.start_line = \ 245 http.HttpServerToClientStartLine(version=self.default_request_version, 246 code=None, reason=None) 247 248 # Disable Python's timeout 249 self.sock.settimeout(None) 250 251 # Operate in non-blocking mode 252 self.sock.setblocking(0) 253 254 logging.debug("Connection from %s:%s", client_addr[0], client_addr[1]) 255 try: 256 request_msg_reader = None 257 force_close = True 258 try: 259 # Do the secret SSL handshake 260 if self.server.using_ssl: 261 self.sock.set_accept_state() 262 try: 263 http.Handshake(self.sock, self.WRITE_TIMEOUT) 264 except http.HttpSessionHandshakeUnexpectedEOF: 265 # Ignore rest 266 return 267 268 try: 269 try: 270 request_msg_reader = self._ReadRequest() 271 self._HandleRequest() 272 273 # Only wait for client to close if we didn't have any exception. 274 force_close = False 275 except http.HttpException, err: 276 self._SetErrorStatus(err) 277 finally: 278 # Try to send a response 279 self._SendResponse() 280 finally: 281 http.ShutdownConnection(sock, self.CLOSE_TIMEOUT, self.WRITE_TIMEOUT, 282 request_msg_reader, force_close) 283 284 self.sock.close() 285 self.sock = None 286 finally: 287 logging.debug("Disconnected %s:%s", client_addr[0], client_addr[1])
288
289 - def _ReadRequest(self):
290 """Reads a request sent by client. 291 292 """ 293 try: 294 request_msg_reader = \ 295 _HttpClientToServerMessageReader(self.sock, self.request_msg, 296 self.READ_TIMEOUT) 297 except http.HttpSocketTimeout: 298 raise http.HttpError("Timeout while reading request") 299 except socket.error, err: 300 raise http.HttpError("Error reading request: %s" % err) 301 302 self.response_msg.start_line.version = self.request_msg.start_line.version 303 304 return request_msg_reader
305
306 - def _HandleRequest(self):
307 """Calls the handler function for the current request. 308 309 """ 310 handler_context = _HttpServerRequest(self.request_msg) 311 312 try: 313 try: 314 # Authentication, etc. 315 self.server.PreHandleRequest(handler_context) 316 317 # Call actual request handler 318 result = self.server.HandleRequest(handler_context) 319 except (http.HttpException, KeyboardInterrupt, SystemExit): 320 raise 321 except Exception, err: 322 logging.exception("Caught exception") 323 raise http.HttpInternalServerError(message=str(err)) 324 except: 325 logging.exception("Unknown exception") 326 raise http.HttpInternalServerError(message="Unknown error") 327 328 # TODO: Content-type 329 encoder = http.HttpJsonConverter() 330 self.response_msg.start_line.code = http.HTTP_OK 331 self.response_msg.body = encoder.Encode(result) 332 self.response_msg.headers = handler_context.resp_headers 333 self.response_msg.headers[http.HTTP_CONTENT_TYPE] = encoder.CONTENT_TYPE 334 finally: 335 # No reason to keep this any longer, even for exceptions 336 handler_context.private = None
337
338 - def _SendResponse(self):
339 """Sends the response to the client. 340 341 """ 342 if self.response_msg.start_line.code is None: 343 return 344 345 if not self.response_msg.headers: 346 self.response_msg.headers = {} 347 348 self.response_msg.headers.update({ 349 # TODO: Keep-alive is not supported 350 http.HTTP_CONNECTION: "close", 351 http.HTTP_DATE: _DateTimeHeader(), 352 http.HTTP_SERVER: http.HTTP_GANETI_VERSION, 353 }) 354 355 # Get response reason based on code 356 response_code = self.response_msg.start_line.code 357 if response_code in self.responses: 358 response_reason = self.responses[response_code][0] 359 else: 360 response_reason = "" 361 self.response_msg.start_line.reason = response_reason 362 363 logging.info("%s:%s %s %s", self.client_addr[0], self.client_addr[1], 364 self.request_msg.start_line, response_code) 365 366 try: 367 _HttpServerToClientMessageWriter(self.sock, self.request_msg, 368 self.response_msg, self.WRITE_TIMEOUT) 369 except http.HttpSocketTimeout: 370 raise http.HttpError("Timeout while sending response") 371 except socket.error, err: 372 raise http.HttpError("Error sending response: %s" % err)
373
374 - def _SetErrorStatus(self, err):
375 """Sets the response code and body from a HttpException. 376 377 @type err: HttpException 378 @param err: Exception instance 379 380 """ 381 try: 382 (shortmsg, longmsg) = self.responses[err.code] 383 except KeyError: 384 shortmsg = longmsg = "Unknown" 385 386 if err.message: 387 message = err.message 388 else: 389 message = shortmsg 390 391 values = { 392 "code": err.code, 393 "message": cgi.escape(message), 394 "explain": longmsg, 395 } 396 397 self.response_msg.start_line.code = err.code 398 399 headers = {} 400 if err.headers: 401 headers.update(err.headers) 402 headers[http.HTTP_CONTENT_TYPE] = self.error_content_type 403 self.response_msg.headers = headers 404 405 self.response_msg.body = self._FormatErrorMessage(values)
406
407 - def _FormatErrorMessage(self, values):
408 """Formats the body of an error message. 409 410 @type values: dict 411 @param values: dictionary with keys code, message and explain. 412 @rtype: string 413 @return: the body of the message 414 415 """ 416 return self.error_message_format % values
417
418 -class HttpServer(http.HttpBase):
419 """Generic HTTP server class 420 421 Users of this class must subclass it and override the HandleRequest function. 422 423 """ 424 MAX_CHILDREN = 20 425
426 - def __init__(self, mainloop, local_address, port, 427 ssl_params=None, ssl_verify_peer=False, 428 request_executor_class=None):
429 """Initializes the HTTP server 430 431 @type mainloop: ganeti.daemon.Mainloop 432 @param mainloop: Mainloop used to poll for I/O events 433 @type local_address: string 434 @param local_address: Local IP address to bind to 435 @type port: int 436 @param port: TCP port to listen on 437 @type ssl_params: HttpSslParams 438 @param ssl_params: SSL key and certificate 439 @type ssl_verify_peer: bool 440 @param ssl_verify_peer: Whether to require client certificate 441 and compare it with our certificate 442 @type request_executor_class: class 443 @param request_executor_class: an class derived from the 444 HttpServerRequestExecutor class 445 446 """ 447 http.HttpBase.__init__(self) 448 449 if request_executor_class is None: 450 self.request_executor = HttpServerRequestExecutor 451 else: 452 self.request_executor = request_executor_class 453 454 self.mainloop = mainloop 455 self.local_address = local_address 456 self.port = port 457 458 self.socket = self._CreateSocket(ssl_params, ssl_verify_peer) 459 460 # Allow port to be reused 461 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 462 463 self._children = [] 464 465 mainloop.RegisterIO(self, self.socket.fileno(), select.POLLIN) 466 mainloop.RegisterSignal(self)
467
468 - def Start(self):
469 self.socket.bind((self.local_address, self.port)) 470 self.socket.listen(1024)
471
472 - def Stop(self):
473 self.socket.close()
474
475 - def OnIO(self, fd, condition):
476 if condition & select.POLLIN: 477 self._IncomingConnection()
478
479 - def OnSignal(self, signum):
480 if signum == signal.SIGCHLD: 481 self._CollectChildren(True)
482
483 - def _CollectChildren(self, quick):
484 """Checks whether any child processes are done 485 486 @type quick: bool 487 @param quick: Whether to only use non-blocking functions 488 489 """ 490 if not quick: 491 # Don't wait for other processes if it should be a quick check 492 while len(self._children) > self.MAX_CHILDREN: 493 try: 494 # Waiting without a timeout brings us into a potential DoS situation. 495 # As soon as too many children run, we'll not respond to new 496 # requests. The real solution would be to add a timeout for children 497 # and killing them after some time. 498 pid, _ = os.waitpid(0, 0) 499 except os.error: 500 pid = None 501 if pid and pid in self._children: 502 self._children.remove(pid) 503 504 for child in self._children: 505 try: 506 pid, status = os.waitpid(child, os.WNOHANG) 507 except os.error: 508 pid = None 509 if pid and pid in self._children: 510 self._children.remove(pid)
511
512 - def _IncomingConnection(self):
513 """Called for each incoming connection 514 515 """ 516 (connection, client_addr) = self.socket.accept() 517 518 self._CollectChildren(False) 519 520 pid = os.fork() 521 if pid == 0: 522 # Child process 523 try: 524 self.request_executor(self, connection, client_addr) 525 except Exception: 526 logging.exception("Error while handling request from %s:%s", 527 client_addr[0], client_addr[1]) 528 os._exit(1) 529 os._exit(0) 530 else: 531 self._children.append(pid)
532
533 - def PreHandleRequest(self, req):
534 """Called before handling a request. 535 536 Can be overridden by a subclass. 537 538 """
539
540 - def HandleRequest(self, req):
541 """Handles a request. 542 543 Must be overridden by subclass. 544 545 """ 546 raise NotImplementedError()
547