Bump pep8 version to 1.2
[ganeti-local] / lib / http / server.py
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           # pylint: disable=E1103
423           logging.info("%s:%s %s %s", client_addr[0], client_addr[1],
424                        request_msg.start_line, response_msg.start_line.code)
425           self._SendResponse(sock, request_msg, response_msg,
426                              self.WRITE_TIMEOUT)
427       finally:
428         http.ShutdownConnection(sock, self.CLOSE_TIMEOUT, self.WRITE_TIMEOUT,
429                                 request_msg_reader, force_close)
430
431       sock.close()
432     finally:
433       logging.debug("Disconnected %s:%s", client_addr[0], client_addr[1])
434
435   @staticmethod
436   def _ReadRequest(sock, timeout):
437     """Reads a request sent by client.
438
439     """
440     msg = http.HttpMessage()
441
442     try:
443       reader = _HttpClientToServerMessageReader(sock, msg, timeout)
444     except http.HttpSocketTimeout:
445       raise http.HttpError("Timeout while reading request")
446     except socket.error, err:
447       raise http.HttpError("Error reading request: %s" % err)
448
449     return (msg, reader)
450
451   @staticmethod
452   def _SendResponse(sock, req_msg, msg, timeout):
453     """Sends the response to the client.
454
455     """
456     try:
457       _HttpServerToClientMessageWriter(sock, req_msg, msg, timeout)
458     except http.HttpSocketTimeout:
459       raise http.HttpError("Timeout while sending response")
460     except socket.error, err:
461       raise http.HttpError("Error sending response: %s" % err)
462
463
464 class HttpServer(http.HttpBase, asyncore.dispatcher):
465   """Generic HTTP server class
466
467   """
468   MAX_CHILDREN = 20
469
470   def __init__(self, mainloop, local_address, port, handler,
471                ssl_params=None, ssl_verify_peer=False,
472                request_executor_class=None):
473     """Initializes the HTTP server
474
475     @type mainloop: ganeti.daemon.Mainloop
476     @param mainloop: Mainloop used to poll for I/O events
477     @type local_address: string
478     @param local_address: Local IP address to bind to
479     @type port: int
480     @param port: TCP port to listen on
481     @type ssl_params: HttpSslParams
482     @param ssl_params: SSL key and certificate
483     @type ssl_verify_peer: bool
484     @param ssl_verify_peer: Whether to require client certificate
485         and compare it with our certificate
486     @type request_executor_class: class
487     @param request_executor_class: an class derived from the
488         HttpServerRequestExecutor class
489
490     """
491     http.HttpBase.__init__(self)
492     asyncore.dispatcher.__init__(self)
493
494     if request_executor_class is None:
495       self.request_executor = HttpServerRequestExecutor
496     else:
497       self.request_executor = request_executor_class
498
499     self.mainloop = mainloop
500     self.local_address = local_address
501     self.port = port
502     self.handler = handler
503     family = netutils.IPAddress.GetAddressFamily(local_address)
504     self.socket = self._CreateSocket(ssl_params, ssl_verify_peer, family)
505
506     # Allow port to be reused
507     self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
508
509     self._children = []
510     self.set_socket(self.socket)
511     self.accepting = True
512     mainloop.RegisterSignal(self)
513
514   def Start(self):
515     self.socket.bind((self.local_address, self.port))
516     self.socket.listen(1024)
517
518   def Stop(self):
519     self.socket.close()
520
521   def handle_accept(self):
522     self._IncomingConnection()
523
524   def OnSignal(self, signum):
525     if signum == signal.SIGCHLD:
526       self._CollectChildren(True)
527
528   def _CollectChildren(self, quick):
529     """Checks whether any child processes are done
530
531     @type quick: bool
532     @param quick: Whether to only use non-blocking functions
533
534     """
535     if not quick:
536       # Don't wait for other processes if it should be a quick check
537       while len(self._children) > self.MAX_CHILDREN:
538         try:
539           # Waiting without a timeout brings us into a potential DoS situation.
540           # As soon as too many children run, we'll not respond to new
541           # requests. The real solution would be to add a timeout for children
542           # and killing them after some time.
543           pid, _ = os.waitpid(0, 0)
544         except os.error:
545           pid = None
546         if pid and pid in self._children:
547           self._children.remove(pid)
548
549     for child in self._children:
550       try:
551         pid, _ = os.waitpid(child, os.WNOHANG)
552       except os.error:
553         pid = None
554       if pid and pid in self._children:
555         self._children.remove(pid)
556
557   def _IncomingConnection(self):
558     """Called for each incoming connection
559
560     """
561     # pylint: disable=W0212
562     (connection, client_addr) = self.socket.accept()
563
564     self._CollectChildren(False)
565
566     pid = os.fork()
567     if pid == 0:
568       # Child process
569       try:
570         # The client shouldn't keep the listening socket open. If the parent
571         # process is restarted, it would fail when there's already something
572         # listening (in this case its own child from a previous run) on the
573         # same port.
574         try:
575           self.socket.close()
576         except socket.error:
577           pass
578         self.socket = None
579
580         # In case the handler code uses temporary files
581         utils.ResetTempfileModule()
582
583         self.request_executor(self, self.handler, connection, client_addr)
584       except Exception: # pylint: disable=W0703
585         logging.exception("Error while handling request from %s:%s",
586                           client_addr[0], client_addr[1])
587         os._exit(1)
588       os._exit(0)
589     else:
590       self._children.append(pid)
591
592
593 class HttpServerHandler(object):
594   """Base class for handling HTTP server requests.
595
596   Users of this class must subclass it and override the L{HandleRequest}
597   function.
598
599   """
600   def PreHandleRequest(self, req):
601     """Called before handling a request.
602
603     Can be overridden by a subclass.
604
605     """
606
607   def HandleRequest(self, req):
608     """Handles a request.
609
610     Must be overridden by subclass.
611
612     """
613     raise NotImplementedError()
614
615   @staticmethod
616   def FormatErrorMessage(values):
617     """Formats the body of an error message.
618
619     @type values: dict
620     @param values: dictionary with keys C{code}, C{message} and C{explain}.
621     @rtype: tuple; (string, string)
622     @return: Content-type and response body
623
624     """
625     return (DEFAULT_ERROR_CONTENT_TYPE, DEFAULT_ERROR_MESSAGE % values)