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