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