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, 2010, 2012 Google Inc. 
  5  # All rights reserved. 
  6  # 
  7  # Redistribution and use in source and binary forms, with or without 
  8  # modification, are permitted provided that the following conditions are 
  9  # met: 
 10  # 
 11  # 1. Redistributions of source code must retain the above copyright notice, 
 12  # this list of conditions and the following disclaimer. 
 13  # 
 14  # 2. Redistributions in binary form must reproduce the above copyright 
 15  # notice, this list of conditions and the following disclaimer in the 
 16  # documentation and/or other materials provided with the distribution. 
 17  # 
 18  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 
 19  # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
 20  # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 21  # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
 22  # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 23  # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 24  # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
 25  # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
 26  # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
 27  # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 28  # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 29   
 30  """HTTP server module. 
 31   
 32  """ 
 33   
 34  import BaseHTTPServer 
 35  import cgi 
 36  import logging 
 37  import os 
 38  import socket 
 39  import time 
 40  import signal 
 41  import asyncore 
 42   
 43  from ganeti import http 
 44  from ganeti import utils 
 45  from ganeti import netutils 
 46  from ganeti import compat 
 47  from ganeti import errors 
 48   
 49   
 50  WEEKDAYNAME = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] 
 51  MONTHNAME = [None, 
 52               "Jan", "Feb", "Mar", "Apr", "May", "Jun", 
 53               "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] 
 54   
 55  # Default error message 
 56  DEFAULT_ERROR_CONTENT_TYPE = "text/html" 
 57  DEFAULT_ERROR_MESSAGE = """\ 
 58  <html> 
 59  <head> 
 60  <title>Error response</title> 
 61  </head> 
 62  <body> 
 63  <h1>Error response</h1> 
 64  <p>Error code %(code)d. 
 65  <p>Message: %(message)s. 
 66  <p>Error code explanation: %(code)s = %(explain)s. 
 67  </body> 
 68  </html> 
 69  """ 
70 71 72 -def _DateTimeHeader(gmnow=None):
73 """Return the current date and time formatted for a message header. 74 75 The time MUST be in the GMT timezone. 76 77 """ 78 if gmnow is None: 79 gmnow = time.gmtime() 80 (year, month, day, hh, mm, ss, wd, _, _) = gmnow 81 return ("%s, %02d %3s %4d %02d:%02d:%02d GMT" % 82 (WEEKDAYNAME[wd], day, MONTHNAME[month], year, hh, mm, ss))
83
84 85 -class _HttpServerRequest(object):
86 """Data structure for HTTP request on server side. 87 88 """
89 - def __init__(self, method, path, headers, body, sock):
90 # Request attributes 91 self.request_method = method 92 self.request_path = path 93 self.request_headers = headers 94 self.request_body = body 95 self.request_sock = sock 96 97 # Response attributes 98 self.resp_headers = {} 99 100 # Private data for request handler (useful in combination with 101 # authentication) 102 self.private = None
103
104 - def __repr__(self):
105 status = ["%s.%s" % (self.__class__.__module__, self.__class__.__name__), 106 self.request_method, self.request_path, 107 "headers=%r" % str(self.request_headers), 108 "body=%r" % (self.request_body, )] 109 110 return "<%s at %#x>" % (" ".join(status), id(self))
111
112 113 -class _HttpServerToClientMessageWriter(http.HttpMessageWriter):
114 """Writes an HTTP response to client. 115 116 """
117 - def __init__(self, sock, request_msg, response_msg, write_timeout):
118 """Writes the response to the client. 119 120 @type sock: socket 121 @param sock: Target socket 122 @type request_msg: http.HttpMessage 123 @param request_msg: Request message, required to determine whether 124 response may have a message body 125 @type response_msg: http.HttpMessage 126 @param response_msg: Response message 127 @type write_timeout: float 128 @param write_timeout: Write timeout for socket 129 130 """ 131 self._request_msg = request_msg 132 self._response_msg = response_msg 133 http.HttpMessageWriter.__init__(self, sock, response_msg, write_timeout)
134
135 - def HasMessageBody(self):
136 """Logic to detect whether response should contain a message body. 137 138 """ 139 if self._request_msg.start_line: 140 request_method = self._request_msg.start_line.method 141 else: 142 request_method = None 143 144 response_code = self._response_msg.start_line.code 145 146 # RFC2616, section 4.3: "A message-body MUST NOT be included in a request 147 # if the specification of the request method (section 5.1.1) does not allow 148 # sending an entity-body in requests" 149 # 150 # RFC2616, section 9.4: "The HEAD method is identical to GET except that 151 # the server MUST NOT return a message-body in the response." 152 # 153 # RFC2616, section 10.2.5: "The 204 response MUST NOT include a 154 # message-body [...]" 155 # 156 # RFC2616, section 10.3.5: "The 304 response MUST NOT contain a 157 # message-body, [...]" 158 159 return (http.HttpMessageWriter.HasMessageBody(self) and 160 (request_method is not None and 161 request_method != http.HTTP_HEAD) and 162 response_code >= http.HTTP_OK and 163 response_code not in (http.HTTP_NO_CONTENT, 164 http.HTTP_NOT_MODIFIED))
165
166 167 -class _HttpClientToServerMessageReader(http.HttpMessageReader):
168 """Reads an HTTP request sent by client. 169 170 """ 171 # Length limits 172 START_LINE_LENGTH_MAX = 8192 173 HEADER_LENGTH_MAX = 4096 174
175 - def ParseStartLine(self, start_line):
176 """Parses the start line sent by client. 177 178 Example: "GET /index.html HTTP/1.1" 179 180 @type start_line: string 181 @param start_line: Start line 182 183 """ 184 # Empty lines are skipped when reading 185 assert start_line 186 187 logging.debug("HTTP request: %s", start_line) 188 189 words = start_line.split() 190 191 if len(words) == 3: 192 [method, path, version] = words 193 if version[:5] != "HTTP/": 194 raise http.HttpBadRequest("Bad request version (%r)" % version) 195 196 try: 197 base_version_number = version.split("/", 1)[1] 198 version_number = base_version_number.split(".") 199 200 # RFC 2145 section 3.1 says there can be only one "." and 201 # - major and minor numbers MUST be treated as 202 # separate integers; 203 # - HTTP/2.4 is a lower version than HTTP/2.13, which in 204 # turn is lower than HTTP/12.3; 205 # - Leading zeros MUST be ignored by recipients. 206 if len(version_number) != 2: 207 raise http.HttpBadRequest("Bad request version (%r)" % version) 208 209 version_number = (int(version_number[0]), int(version_number[1])) 210 except (ValueError, IndexError): 211 raise http.HttpBadRequest("Bad request version (%r)" % version) 212 213 if version_number >= (2, 0): 214 raise http.HttpVersionNotSupported("Invalid HTTP Version (%s)" % 215 base_version_number) 216 217 elif len(words) == 2: 218 version = http.HTTP_0_9 219 [method, path] = words 220 if method != http.HTTP_GET: 221 raise http.HttpBadRequest("Bad HTTP/0.9 request type (%r)" % method) 222 223 else: 224 raise http.HttpBadRequest("Bad request syntax (%r)" % start_line) 225 226 return http.HttpClientToServerStartLine(method, path, version)
227
228 229 -def _HandleServerRequestInner(handler, req_msg, reader):
230 """Calls the handler function for the current request. 231 232 """ 233 handler_context = _HttpServerRequest(req_msg.start_line.method, 234 req_msg.start_line.path, 235 req_msg.headers, 236 req_msg.body, 237 reader.sock) 238 239 logging.debug("Handling request %r", handler_context) 240 241 try: 242 try: 243 # Authentication, etc. 244 handler.PreHandleRequest(handler_context) 245 246 # Call actual request handler 247 result = handler.HandleRequest(handler_context) 248 except (http.HttpException, errors.RapiTestResult, 249 KeyboardInterrupt, SystemExit): 250 raise 251 except Exception, err: 252 logging.exception("Caught exception") 253 raise http.HttpInternalServerError(message=str(err)) 254 except: 255 logging.exception("Unknown exception") 256 raise http.HttpInternalServerError(message="Unknown error") 257 258 if not isinstance(result, basestring): 259 raise http.HttpError("Handler function didn't return string type") 260 261 return (http.HTTP_OK, handler_context.resp_headers, result) 262 finally: 263 # No reason to keep this any longer, even for exceptions 264 handler_context.private = None
265
266 267 -class HttpResponder(object):
268 # The default request version. This only affects responses up until 269 # the point where the request line is parsed, so it mainly decides what 270 # the client gets back when sending a malformed request line. 271 # Most web servers default to HTTP 0.9, i.e. don't send a status line. 272 default_request_version = http.HTTP_0_9 273 274 responses = BaseHTTPServer.BaseHTTPRequestHandler.responses 275
276 - def __init__(self, handler):
277 """Initializes this class. 278 279 """ 280 self._handler = handler
281
282 - def __call__(self, fn):
283 """Handles a request. 284 285 @type fn: callable 286 @param fn: Callback for retrieving HTTP request, must return a tuple 287 containing request message (L{http.HttpMessage}) and C{None} or the 288 message reader (L{_HttpClientToServerMessageReader}) 289 290 """ 291 response_msg = http.HttpMessage() 292 response_msg.start_line = \ 293 http.HttpServerToClientStartLine(version=self.default_request_version, 294 code=None, reason=None) 295 296 force_close = True 297 298 try: 299 (request_msg, req_msg_reader) = fn() 300 301 response_msg.start_line.version = request_msg.start_line.version 302 303 # RFC2616, 14.23: All Internet-based HTTP/1.1 servers MUST respond 304 # with a 400 (Bad Request) status code to any HTTP/1.1 request 305 # message which lacks a Host header field. 306 if (request_msg.start_line.version == http.HTTP_1_1 and 307 not (request_msg.headers and 308 http.HTTP_HOST in request_msg.headers)): 309 raise http.HttpBadRequest(message="Missing Host header") 310 311 (response_msg.start_line.code, response_msg.headers, 312 response_msg.body) = \ 313 _HandleServerRequestInner(self._handler, request_msg, req_msg_reader) 314 except http.HttpException, err: 315 self._SetError(self.responses, self._handler, response_msg, err) 316 else: 317 # Only wait for client to close if we didn't have any exception. 318 force_close = False 319 320 return (request_msg, req_msg_reader, force_close, 321 self._Finalize(self.responses, response_msg))
322 323 @staticmethod
324 - def _SetError(responses, handler, response_msg, err):
325 """Sets the response code and body from a HttpException. 326 327 @type err: HttpException 328 @param err: Exception instance 329 330 """ 331 try: 332 (shortmsg, longmsg) = responses[err.code] 333 except KeyError: 334 shortmsg = longmsg = "Unknown" 335 336 if err.message: 337 message = err.message 338 else: 339 message = shortmsg 340 341 values = { 342 "code": err.code, 343 "message": cgi.escape(message), 344 "explain": longmsg, 345 } 346 347 (content_type, body) = handler.FormatErrorMessage(values) 348 349 headers = { 350 http.HTTP_CONTENT_TYPE: content_type, 351 } 352 353 if err.headers: 354 headers.update(err.headers) 355 356 response_msg.start_line.code = err.code 357 response_msg.headers = headers 358 response_msg.body = body
359 360 @staticmethod
361 - def _Finalize(responses, msg):
362 assert msg.start_line.reason is None 363 364 if not msg.headers: 365 msg.headers = {} 366 367 msg.headers.update({ 368 # TODO: Keep-alive is not supported 369 http.HTTP_CONNECTION: "close", 370 http.HTTP_DATE: _DateTimeHeader(), 371 http.HTTP_SERVER: http.HTTP_GANETI_VERSION, 372 }) 373 374 # Get response reason based on code 375 try: 376 code_desc = responses[msg.start_line.code] 377 except KeyError: 378 reason = "" 379 else: 380 (reason, _) = code_desc 381 382 msg.start_line.reason = reason 383 384 return msg
385
386 387 -class HttpServerRequestExecutor(object):
388 """Implements server side of HTTP. 389 390 This class implements the server side of HTTP. It's based on code of 391 Python's BaseHTTPServer, from both version 2.4 and 3k. It does not 392 support non-ASCII character encodings. Keep-alive connections are 393 not supported. 394 395 """ 396 # Timeouts in seconds for socket layer 397 WRITE_TIMEOUT = 10 398 READ_TIMEOUT = 10 399 CLOSE_TIMEOUT = 1 400
401 - def __init__(self, server, handler, sock, client_addr):
402 """Initializes this class. 403 404 """ 405 responder = HttpResponder(handler) 406 407 # Disable Python's timeout 408 sock.settimeout(None) 409 410 # Operate in non-blocking mode 411 sock.setblocking(0) 412 413 request_msg_reader = None 414 force_close = True 415 416 logging.debug("Connection from %s:%s", client_addr[0], client_addr[1]) 417 try: 418 # Block for closing connection 419 try: 420 # Do the secret SSL handshake 421 if server.using_ssl: 422 sock.set_accept_state() 423 try: 424 http.Handshake(sock, self.WRITE_TIMEOUT) 425 except http.HttpSessionHandshakeUnexpectedEOF: 426 # Ignore rest 427 return 428 429 (request_msg, request_msg_reader, force_close, response_msg) = \ 430 responder(compat.partial(self._ReadRequest, sock, self.READ_TIMEOUT)) 431 if response_msg: 432 # HttpMessage.start_line can be of different types 433 # Instance of 'HttpClientToServerStartLine' has no 'code' member 434 # pylint: disable=E1103,E1101 435 logging.info("%s:%s %s %s", client_addr[0], client_addr[1], 436 request_msg.start_line, response_msg.start_line.code) 437 self._SendResponse(sock, request_msg, response_msg, 438 self.WRITE_TIMEOUT) 439 finally: 440 http.ShutdownConnection(sock, self.CLOSE_TIMEOUT, self.WRITE_TIMEOUT, 441 request_msg_reader, force_close) 442 443 sock.close() 444 finally: 445 logging.debug("Disconnected %s:%s", client_addr[0], client_addr[1])
446 447 @staticmethod
448 - def _ReadRequest(sock, timeout):
449 """Reads a request sent by client. 450 451 """ 452 msg = http.HttpMessage() 453 454 try: 455 reader = _HttpClientToServerMessageReader(sock, msg, timeout) 456 except http.HttpSocketTimeout: 457 raise http.HttpError("Timeout while reading request") 458 except socket.error, err: 459 raise http.HttpError("Error reading request: %s" % err) 460 461 return (msg, reader)
462 463 @staticmethod
464 - def _SendResponse(sock, req_msg, msg, timeout):
465 """Sends the response to the client. 466 467 """ 468 try: 469 _HttpServerToClientMessageWriter(sock, req_msg, msg, timeout) 470 except http.HttpSocketTimeout: 471 raise http.HttpError("Timeout while sending response") 472 except socket.error, err: 473 raise http.HttpError("Error sending response: %s" % err)
474
475 476 -class HttpServer(http.HttpBase, asyncore.dispatcher):
477 """Generic HTTP server class 478 479 """ 480 MAX_CHILDREN = 20 481
482 - def __init__(self, mainloop, local_address, port, handler, 483 ssl_params=None, ssl_verify_peer=False, 484 request_executor_class=None, ssl_verify_callback=None):
485 """Initializes the HTTP server 486 487 @type mainloop: ganeti.daemon.Mainloop 488 @param mainloop: Mainloop used to poll for I/O events 489 @type local_address: string 490 @param local_address: Local IP address to bind to 491 @type port: int 492 @param port: TCP port to listen on 493 @type ssl_params: HttpSslParams 494 @param ssl_params: SSL key and certificate 495 @type ssl_verify_peer: bool 496 @param ssl_verify_peer: Whether to require client certificate 497 and compare it with our certificate 498 @type request_executor_class: class 499 @param request_executor_class: a class derived from the 500 HttpServerRequestExecutor class 501 502 """ 503 http.HttpBase.__init__(self) 504 asyncore.dispatcher.__init__(self) 505 506 if request_executor_class is None: 507 self.request_executor = HttpServerRequestExecutor 508 else: 509 self.request_executor = request_executor_class 510 511 self.mainloop = mainloop 512 self.local_address = local_address 513 self.port = port 514 self.handler = handler 515 family = netutils.IPAddress.GetAddressFamily(local_address) 516 self.socket = self._CreateSocket(ssl_params, ssl_verify_peer, family, 517 ssl_verify_callback) 518 519 # Allow port to be reused 520 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 521 522 self._children = [] 523 self.set_socket(self.socket) 524 self.accepting = True 525 mainloop.RegisterSignal(self)
526
527 - def Start(self):
528 self.socket.bind((self.local_address, self.port)) 529 self.socket.listen(1024)
530
531 - def Stop(self):
532 self.socket.close()
533
534 - def handle_accept(self):
536
537 - def OnSignal(self, signum):
538 if signum == signal.SIGCHLD: 539 self._CollectChildren(True)
540
541 - def _CollectChildren(self, quick):
542 """Checks whether any child processes are done 543 544 @type quick: bool 545 @param quick: Whether to only use non-blocking functions 546 547 """ 548 if not quick: 549 # Don't wait for other processes if it should be a quick check 550 while len(self._children) > self.MAX_CHILDREN: 551 try: 552 # Waiting without a timeout brings us into a potential DoS situation. 553 # As soon as too many children run, we'll not respond to new 554 # requests. The real solution would be to add a timeout for children 555 # and killing them after some time. 556 pid, _ = os.waitpid(0, 0) 557 except os.error: 558 pid = None 559 if pid and pid in self._children: 560 self._children.remove(pid) 561 562 for child in self._children: 563 try: 564 pid, _ = os.waitpid(child, os.WNOHANG) 565 except os.error: 566 pid = None 567 if pid and pid in self._children: 568 self._children.remove(pid)
569
570 - def _IncomingConnection(self):
571 """Called for each incoming connection 572 573 """ 574 # pylint: disable=W0212 575 (connection, client_addr) = self.socket.accept() 576 577 self._CollectChildren(False) 578 579 try: 580 pid = os.fork() 581 except OSError: 582 logging.exception("Failed to fork on request from %s:%s", 583 client_addr[0], client_addr[1]) 584 # Immediately close the connection. No SSL handshake has been done. 585 try: 586 connection.close() 587 except socket.error: 588 pass 589 return 590 591 if pid == 0: 592 # Child process 593 try: 594 # The client shouldn't keep the listening socket open. If the parent 595 # process is restarted, it would fail when there's already something 596 # listening (in this case its own child from a previous run) on the 597 # same port. 598 try: 599 self.socket.close() 600 except socket.error: 601 pass 602 self.socket = None 603 604 # In case the handler code uses temporary files 605 utils.ResetTempfileModule() 606 607 self.request_executor(self, self.handler, connection, client_addr) 608 except Exception: # pylint: disable=W0703 609 logging.exception("Error while handling request from %s:%s", 610 client_addr[0], client_addr[1]) 611 os._exit(1) 612 os._exit(0) 613 else: 614 self._children.append(pid)
615
616 617 -class HttpServerHandler(object):
618 """Base class for handling HTTP server requests. 619 620 Users of this class must subclass it and override the L{HandleRequest} 621 function. 622 623 """
624 - def PreHandleRequest(self, req):
625 """Called before handling a request. 626 627 Can be overridden by a subclass. 628 629 """
630
631 - def HandleRequest(self, req):
632 """Handles a request. 633 634 Must be overridden by subclass. 635 636 """ 637 raise NotImplementedError()
638 639 @staticmethod
640 - def FormatErrorMessage(values):
641 """Formats the body of an error message. 642 643 @type values: dict 644 @param values: dictionary with keys C{code}, C{message} and C{explain}. 645 @rtype: tuple; (string, string) 646 @return: Content-type and response body 647 648 """ 649 return (DEFAULT_ERROR_CONTENT_TYPE, DEFAULT_ERROR_MESSAGE % values)
650