Statistics
| Branch: | Tag: | Revision:

root / lib / http / server.py @ 7c4c22f5

History | View | Annotate | Download (17.6 kB)

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

    
38

    
39
WEEKDAYNAME = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
40
MONTHNAME = [None,
41
             'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
42
             'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
43

    
44
# Default error message
45
DEFAULT_ERROR_CONTENT_TYPE = "text/html"
46
DEFAULT_ERROR_MESSAGE = """\
47
<html>
48
<head>
49
<title>Error response</title>
50
</head>
51
<body>
52
<h1>Error response</h1>
53
<p>Error code %(code)d.
54
<p>Message: %(message)s.
55
<p>Error code explanation: %(code)s = %(explain)s.
56
</body>
57
</html>
58
"""
59

    
60

    
61
def _DateTimeHeader(gmnow=None):
62
  """Return the current date and time formatted for a message header.
63

64
  The time MUST be in the GMT timezone.
65

66
  """
67
  if gmnow is None:
68
    gmnow = time.gmtime()
69
  (year, month, day, hh, mm, ss, wd, _, _) = gmnow
70
  return ("%s, %02d %3s %4d %02d:%02d:%02d GMT" %
71
          (WEEKDAYNAME[wd], day, MONTHNAME[month], year, hh, mm, ss))
72

    
73

    
74
class _HttpServerRequest(object):
75
  """Data structure for HTTP request on server side.
76

77
  """
78
  def __init__(self, method, path, headers, body):
79
    # Request attributes
80
    self.request_method = method
81
    self.request_path = path
82
    self.request_headers = headers
83
    self.request_body = body
84

    
85
    # Response attributes
86
    self.resp_headers = {}
87

    
88
    # Private data for request handler (useful in combination with
89
    # authentication)
90
    self.private = None
91

    
92
  def __repr__(self):
93
    status = ["%s.%s" % (self.__class__.__module__, self.__class__.__name__),
94
              self.request_method, self.request_path,
95
              "headers=%r" % str(self.request_headers),
96
              "body=%r" % (self.request_body, )]
97

    
98
    return "<%s at %#x>" % (" ".join(status), id(self))
99

    
100

    
101
class _HttpServerToClientMessageWriter(http.HttpMessageWriter):
102
  """Writes an HTTP response to client.
103

104
  """
105
  def __init__(self, sock, request_msg, response_msg, write_timeout):
106
    """Writes the response to the client.
107

108
    @type sock: socket
109
    @param sock: Target socket
110
    @type request_msg: http.HttpMessage
111
    @param request_msg: Request message, required to determine whether
112
        response may have a message body
113
    @type response_msg: http.HttpMessage
114
    @param response_msg: Response message
115
    @type write_timeout: float
116
    @param write_timeout: Write timeout for socket
117

118
    """
119
    self._request_msg = request_msg
120
    self._response_msg = response_msg
121
    http.HttpMessageWriter.__init__(self, sock, response_msg, write_timeout)
122

    
123
  def HasMessageBody(self):
124
    """Logic to detect whether response should contain a message body.
125

126
    """
127
    if self._request_msg.start_line:
128
      request_method = self._request_msg.start_line.method
129
    else:
130
      request_method = None
131

    
132
    response_code = self._response_msg.start_line.code
133

    
134
    # RFC2616, section 4.3: "A message-body MUST NOT be included in a request
135
    # if the specification of the request method (section 5.1.1) does not allow
136
    # sending an entity-body in requests"
137
    #
138
    # RFC2616, section 9.4: "The HEAD method is identical to GET except that
139
    # the server MUST NOT return a message-body in the response."
140
    #
141
    # RFC2616, section 10.2.5: "The 204 response MUST NOT include a
142
    # message-body [...]"
143
    #
144
    # RFC2616, section 10.3.5: "The 304 response MUST NOT contain a
145
    # message-body, [...]"
146

    
147
    return (http.HttpMessageWriter.HasMessageBody(self) and
148
            (request_method is not None and
149
             request_method != http.HTTP_HEAD) and
150
            response_code >= http.HTTP_OK and
151
            response_code not in (http.HTTP_NO_CONTENT,
152
                                  http.HTTP_NOT_MODIFIED))
153

    
154

    
155
class _HttpClientToServerMessageReader(http.HttpMessageReader):
156
  """Reads an HTTP request sent by client.
157

158
  """
159
  # Length limits
160
  START_LINE_LENGTH_MAX = 4096
161
  HEADER_LENGTH_MAX = 4096
162

    
163
  def ParseStartLine(self, start_line):
164
    """Parses the start line sent by client.
165

166
    Example: "GET /index.html HTTP/1.1"
167

168
    @type start_line: string
169
    @param start_line: Start line
170

171
    """
172
    # Empty lines are skipped when reading
173
    assert start_line
174

    
175
    logging.debug("HTTP request: %s", start_line)
176

    
177
    words = start_line.split()
178

    
179
    if len(words) == 3:
180
      [method, path, version] = words
181
      if version[:5] != 'HTTP/':
182
        raise http.HttpBadRequest("Bad request version (%r)" % version)
183

    
184
      try:
185
        base_version_number = version.split("/", 1)[1]
186
        version_number = base_version_number.split(".")
187

    
188
        # RFC 2145 section 3.1 says there can be only one "." and
189
        #   - major and minor numbers MUST be treated as
190
        #      separate integers;
191
        #   - HTTP/2.4 is a lower version than HTTP/2.13, which in
192
        #      turn is lower than HTTP/12.3;
193
        #   - Leading zeros MUST be ignored by recipients.
194
        if len(version_number) != 2:
195
          raise http.HttpBadRequest("Bad request version (%r)" % version)
196

    
197
        version_number = (int(version_number[0]), int(version_number[1]))
198
      except (ValueError, IndexError):
199
        raise http.HttpBadRequest("Bad request version (%r)" % version)
200

    
201
      if version_number >= (2, 0):
202
        raise http.HttpVersionNotSupported("Invalid HTTP Version (%s)" %
203
                                      base_version_number)
204

    
205
    elif len(words) == 2:
206
      version = http.HTTP_0_9
207
      [method, path] = words
208
      if method != http.HTTP_GET:
209
        raise http.HttpBadRequest("Bad HTTP/0.9 request type (%r)" % method)
210

    
211
    else:
212
      raise http.HttpBadRequest("Bad request syntax (%r)" % start_line)
213

    
214
    return http.HttpClientToServerStartLine(method, path, version)
215

    
216

    
217
class HttpServerRequestExecutor(object):
218
  """Implements server side of HTTP.
219

220
  This class implements the server side of HTTP. It's based on code of
221
  Python's BaseHTTPServer, from both version 2.4 and 3k. It does not
222
  support non-ASCII character encodings. Keep-alive connections are
223
  not supported.
224

225
  """
226
  # The default request version.  This only affects responses up until
227
  # the point where the request line is parsed, so it mainly decides what
228
  # the client gets back when sending a malformed request line.
229
  # Most web servers default to HTTP 0.9, i.e. don't send a status line.
230
  default_request_version = http.HTTP_0_9
231

    
232
  # Error message settings
233
  error_message_format = DEFAULT_ERROR_MESSAGE
234
  error_content_type = DEFAULT_ERROR_CONTENT_TYPE
235

    
236
  responses = BaseHTTPServer.BaseHTTPRequestHandler.responses
237

    
238
  # Timeouts in seconds for socket layer
239
  WRITE_TIMEOUT = 10
240
  READ_TIMEOUT = 10
241
  CLOSE_TIMEOUT = 1
242

    
243
  def __init__(self, server, sock, client_addr):
244
    """Initializes this class.
245

246
    """
247
    self.server = server
248
    self.sock = sock
249
    self.client_addr = client_addr
250

    
251
    self.request_msg = http.HttpMessage()
252
    self.response_msg = http.HttpMessage()
253

    
254
    self.response_msg.start_line = \
255
      http.HttpServerToClientStartLine(version=self.default_request_version,
256
                                       code=None, reason=None)
257

    
258
    # Disable Python's timeout
259
    self.sock.settimeout(None)
260

    
261
    # Operate in non-blocking mode
262
    self.sock.setblocking(0)
263

    
264
    logging.debug("Connection from %s:%s", client_addr[0], client_addr[1])
265
    try:
266
      request_msg_reader = None
267
      force_close = True
268
      try:
269
        # Do the secret SSL handshake
270
        if self.server.using_ssl:
271
          self.sock.set_accept_state()
272
          try:
273
            http.Handshake(self.sock, self.WRITE_TIMEOUT)
274
          except http.HttpSessionHandshakeUnexpectedEOF:
275
            # Ignore rest
276
            return
277

    
278
        try:
279
          try:
280
            request_msg_reader = self._ReadRequest()
281

    
282
            # RFC2616, 14.23: All Internet-based HTTP/1.1 servers MUST respond
283
            # with a 400 (Bad Request) status code to any HTTP/1.1 request
284
            # message which lacks a Host header field.
285
            if (self.request_msg.start_line.version == http.HTTP_1_1 and
286
                http.HTTP_HOST not in self.request_msg.headers):
287
              raise http.HttpBadRequest(message="Missing Host header")
288

    
289
            self._HandleRequest()
290

    
291
            # Only wait for client to close if we didn't have any exception.
292
            force_close = False
293
          except http.HttpException, err:
294
            self._SetErrorStatus(err)
295
        finally:
296
          # Try to send a response
297
          self._SendResponse()
298
      finally:
299
        http.ShutdownConnection(sock, self.CLOSE_TIMEOUT, self.WRITE_TIMEOUT,
300
                                request_msg_reader, force_close)
301

    
302
      self.sock.close()
303
      self.sock = None
304
    finally:
305
      logging.debug("Disconnected %s:%s", client_addr[0], client_addr[1])
306

    
307
  def _ReadRequest(self):
308
    """Reads a request sent by client.
309

310
    """
311
    try:
312
      request_msg_reader = \
313
        _HttpClientToServerMessageReader(self.sock, self.request_msg,
314
                                         self.READ_TIMEOUT)
315
    except http.HttpSocketTimeout:
316
      raise http.HttpError("Timeout while reading request")
317
    except socket.error, err:
318
      raise http.HttpError("Error reading request: %s" % err)
319

    
320
    self.response_msg.start_line.version = self.request_msg.start_line.version
321

    
322
    return request_msg_reader
323

    
324
  def _HandleRequest(self):
325
    """Calls the handler function for the current request.
326

327
    """
328
    handler_context = _HttpServerRequest(self.request_msg.start_line.method,
329
                                         self.request_msg.start_line.path,
330
                                         self.request_msg.headers,
331
                                         self.request_msg.body)
332

    
333
    logging.debug("Handling request %r", handler_context)
334

    
335
    try:
336
      try:
337
        # Authentication, etc.
338
        self.server.PreHandleRequest(handler_context)
339

    
340
        # Call actual request handler
341
        result = self.server.HandleRequest(handler_context)
342
      except (http.HttpException, KeyboardInterrupt, SystemExit):
343
        raise
344
      except Exception, err:
345
        logging.exception("Caught exception")
346
        raise http.HttpInternalServerError(message=str(err))
347
      except:
348
        logging.exception("Unknown exception")
349
        raise http.HttpInternalServerError(message="Unknown error")
350

    
351
      if not isinstance(result, basestring):
352
        raise http.HttpError("Handler function didn't return string type")
353

    
354
      self.response_msg.start_line.code = http.HTTP_OK
355
      self.response_msg.headers = handler_context.resp_headers
356
      self.response_msg.body = result
357
    finally:
358
      # No reason to keep this any longer, even for exceptions
359
      handler_context.private = None
360

    
361
  def _SendResponse(self):
362
    """Sends the response to the client.
363

364
    """
365
    if self.response_msg.start_line.code is None:
366
      return
367

    
368
    if not self.response_msg.headers:
369
      self.response_msg.headers = {}
370

    
371
    self.response_msg.headers.update({
372
      # TODO: Keep-alive is not supported
373
      http.HTTP_CONNECTION: "close",
374
      http.HTTP_DATE: _DateTimeHeader(),
375
      http.HTTP_SERVER: http.HTTP_GANETI_VERSION,
376
      })
377

    
378
    # Get response reason based on code
379
    response_code = self.response_msg.start_line.code
380
    if response_code in self.responses:
381
      response_reason = self.responses[response_code][0]
382
    else:
383
      response_reason = ""
384
    self.response_msg.start_line.reason = response_reason
385

    
386
    logging.info("%s:%s %s %s", self.client_addr[0], self.client_addr[1],
387
                 self.request_msg.start_line, response_code)
388

    
389
    try:
390
      _HttpServerToClientMessageWriter(self.sock, self.request_msg,
391
                                       self.response_msg, self.WRITE_TIMEOUT)
392
    except http.HttpSocketTimeout:
393
      raise http.HttpError("Timeout while sending response")
394
    except socket.error, err:
395
      raise http.HttpError("Error sending response: %s" % err)
396

    
397
  def _SetErrorStatus(self, err):
398
    """Sets the response code and body from a HttpException.
399

400
    @type err: HttpException
401
    @param err: Exception instance
402

403
    """
404
    try:
405
      (shortmsg, longmsg) = self.responses[err.code]
406
    except KeyError:
407
      shortmsg = longmsg = "Unknown"
408

    
409
    if err.message:
410
      message = err.message
411
    else:
412
      message = shortmsg
413

    
414
    values = {
415
      "code": err.code,
416
      "message": cgi.escape(message),
417
      "explain": longmsg,
418
      }
419

    
420
    self.response_msg.start_line.code = err.code
421

    
422
    headers = {}
423
    if err.headers:
424
      headers.update(err.headers)
425
    headers[http.HTTP_CONTENT_TYPE] = self.error_content_type
426
    self.response_msg.headers = headers
427

    
428
    self.response_msg.body = self._FormatErrorMessage(values)
429

    
430
  def _FormatErrorMessage(self, values):
431
    """Formats the body of an error message.
432

433
    @type values: dict
434
    @param values: dictionary with keys code, message and explain.
435
    @rtype: string
436
    @return: the body of the message
437

438
    """
439
    return self.error_message_format % values
440

    
441

    
442
class HttpServer(http.HttpBase, asyncore.dispatcher):
443
  """Generic HTTP server class
444

445
  Users of this class must subclass it and override the HandleRequest function.
446

447
  """
448
  MAX_CHILDREN = 20
449

    
450
  def __init__(self, mainloop, local_address, port,
451
               ssl_params=None, ssl_verify_peer=False,
452
               request_executor_class=None):
453
    """Initializes the HTTP server
454

455
    @type mainloop: ganeti.daemon.Mainloop
456
    @param mainloop: Mainloop used to poll for I/O events
457
    @type local_address: string
458
    @param local_address: Local IP address to bind to
459
    @type port: int
460
    @param port: TCP port to listen on
461
    @type ssl_params: HttpSslParams
462
    @param ssl_params: SSL key and certificate
463
    @type ssl_verify_peer: bool
464
    @param ssl_verify_peer: Whether to require client certificate
465
        and compare it with our certificate
466
    @type request_executor_class: class
467
    @param request_executor_class: an class derived from the
468
        HttpServerRequestExecutor class
469

470
    """
471
    http.HttpBase.__init__(self)
472
    asyncore.dispatcher.__init__(self)
473

    
474
    if request_executor_class is None:
475
      self.request_executor = HttpServerRequestExecutor
476
    else:
477
      self.request_executor = request_executor_class
478

    
479
    self.mainloop = mainloop
480
    self.local_address = local_address
481
    self.port = port
482
    family = netutils.IPAddress.GetAddressFamily(local_address)
483
    self.socket = self._CreateSocket(ssl_params, ssl_verify_peer, family)
484

    
485
    # Allow port to be reused
486
    self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
487

    
488
    self._children = []
489
    self.set_socket(self.socket)
490
    self.accepting = True
491
    mainloop.RegisterSignal(self)
492

    
493
  def Start(self):
494
    self.socket.bind((self.local_address, self.port))
495
    self.socket.listen(1024)
496

    
497
  def Stop(self):
498
    self.socket.close()
499

    
500
  def handle_accept(self):
501
    self._IncomingConnection()
502

    
503
  def OnSignal(self, signum):
504
    if signum == signal.SIGCHLD:
505
      self._CollectChildren(True)
506

    
507
  def _CollectChildren(self, quick):
508
    """Checks whether any child processes are done
509

510
    @type quick: bool
511
    @param quick: Whether to only use non-blocking functions
512

513
    """
514
    if not quick:
515
      # Don't wait for other processes if it should be a quick check
516
      while len(self._children) > self.MAX_CHILDREN:
517
        try:
518
          # Waiting without a timeout brings us into a potential DoS situation.
519
          # As soon as too many children run, we'll not respond to new
520
          # requests. The real solution would be to add a timeout for children
521
          # and killing them after some time.
522
          pid, _ = os.waitpid(0, 0)
523
        except os.error:
524
          pid = None
525
        if pid and pid in self._children:
526
          self._children.remove(pid)
527

    
528
    for child in self._children:
529
      try:
530
        pid, _ = os.waitpid(child, os.WNOHANG)
531
      except os.error:
532
        pid = None
533
      if pid and pid in self._children:
534
        self._children.remove(pid)
535

    
536
  def _IncomingConnection(self):
537
    """Called for each incoming connection
538

539
    """
540
    # pylint: disable-msg=W0212
541
    (connection, client_addr) = self.socket.accept()
542

    
543
    self._CollectChildren(False)
544

    
545
    pid = os.fork()
546
    if pid == 0:
547
      # Child process
548
      try:
549
        # The client shouldn't keep the listening socket open. If the parent
550
        # process is restarted, it would fail when there's already something
551
        # listening (in this case its own child from a previous run) on the
552
        # same port.
553
        try:
554
          self.socket.close()
555
        except socket.error:
556
          pass
557
        self.socket = None
558

    
559
        # In case the handler code uses temporary files
560
        utils.ResetTempfileModule()
561

    
562
        self.request_executor(self, connection, client_addr)
563
      except Exception: # pylint: disable-msg=W0703
564
        logging.exception("Error while handling request from %s:%s",
565
                          client_addr[0], client_addr[1])
566
        os._exit(1)
567
      os._exit(0)
568
    else:
569
      self._children.append(pid)
570

    
571
  def PreHandleRequest(self, req):
572
    """Called before handling a request.
573

574
    Can be overridden by a subclass.
575

576
    """
577

    
578
  def HandleRequest(self, req):
579
    """Handles a request.
580

581
    Must be overridden by subclass.
582

583
    """
584
    raise NotImplementedError()