Statistics
| Branch: | Tag: | Revision:

root / lib / http / server.py @ 9f7b4967

History | View | Annotate | Download (17.5 kB)

1
#
2
#
3

    
4
# Copyright (C) 2007, 2008 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

    
37

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

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

    
59

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

63
  The time MUST be in the GMT timezone.
64

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

    
72

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

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

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

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

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

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

    
99

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

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

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

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

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

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

    
131
    response_code = self._response_msg.start_line.code
132

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

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

    
153

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

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

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

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

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

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

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

    
176
    words = start_line.split()
177

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

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

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

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

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

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

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

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

    
215

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

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

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

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

    
235
  responses = BaseHTTPServer.BaseHTTPRequestHandler.responses
236

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

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

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

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

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

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

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

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

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

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

    
288
            self._HandleRequest()
289

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

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

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

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

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

    
321
    return request_msg_reader
322

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
440

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

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

446
  """
447
  MAX_CHILDREN = 20
448

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

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

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

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

    
478
    self.mainloop = mainloop
479
    self.local_address = local_address
480
    self.port = port
481

    
482
    self.socket = self._CreateSocket(ssl_params, ssl_verify_peer)
483

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

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

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

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

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

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

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

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

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

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

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

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

    
542
    self._CollectChildren(False)
543

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

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

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

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

573
    Can be overridden by a subclass.
574

575
    """
576

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

580
    Must be overridden by subclass.
581

582
    """
583
    raise NotImplementedError()