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