Statistics
| Branch: | Tag: | Revision:

root / lib / http / server.py @ e0003509

History | View | Annotate | Download (17.8 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
def HandleServerRequest(handler, req_msg):
218
  """Calls the handler function for the current request.
219

220
  """
221
  handler_context = _HttpServerRequest(req_msg.start_line.method,
222
                                       req_msg.start_line.path,
223
                                       req_msg.headers,
224
                                       req_msg.body)
225

    
226
  logging.debug("Handling request %r", handler_context)
227

    
228
  try:
229
    try:
230
      # Authentication, etc.
231
      handler.PreHandleRequest(handler_context)
232

    
233
      # Call actual request handler
234
      result = handler.HandleRequest(handler_context)
235
    except (http.HttpException, KeyboardInterrupt, SystemExit):
236
      raise
237
    except Exception, err:
238
      logging.exception("Caught exception")
239
      raise http.HttpInternalServerError(message=str(err))
240
    except:
241
      logging.exception("Unknown exception")
242
      raise http.HttpInternalServerError(message="Unknown error")
243

    
244
    if not isinstance(result, basestring):
245
      raise http.HttpError("Handler function didn't return string type")
246

    
247
    return (http.HTTP_OK, handler_context.resp_headers, result)
248
  finally:
249
    # No reason to keep this any longer, even for exceptions
250
    handler_context.private = None
251

    
252

    
253
class HttpServerRequestExecutor(object):
254
  """Implements server side of HTTP.
255

256
  This class implements the server side of HTTP. It's based on code of
257
  Python's BaseHTTPServer, from both version 2.4 and 3k. It does not
258
  support non-ASCII character encodings. Keep-alive connections are
259
  not supported.
260

261
  """
262
  # The default request version.  This only affects responses up until
263
  # the point where the request line is parsed, so it mainly decides what
264
  # the client gets back when sending a malformed request line.
265
  # Most web servers default to HTTP 0.9, i.e. don't send a status line.
266
  default_request_version = http.HTTP_0_9
267

    
268
  # Error message settings
269
  error_message_format = DEFAULT_ERROR_MESSAGE
270
  error_content_type = DEFAULT_ERROR_CONTENT_TYPE
271

    
272
  responses = BaseHTTPServer.BaseHTTPRequestHandler.responses
273

    
274
  # Timeouts in seconds for socket layer
275
  WRITE_TIMEOUT = 10
276
  READ_TIMEOUT = 10
277
  CLOSE_TIMEOUT = 1
278

    
279
  def __init__(self, server, handler, sock, client_addr):
280
    """Initializes this class.
281

282
    """
283
    self.server = server
284
    self.handler = handler
285
    self.sock = sock
286
    self.client_addr = client_addr
287

    
288
    self.request_msg = http.HttpMessage()
289
    self.response_msg = http.HttpMessage()
290

    
291
    self.response_msg.start_line = \
292
      http.HttpServerToClientStartLine(version=self.default_request_version,
293
                                       code=None, reason=None)
294

    
295
    # Disable Python's timeout
296
    self.sock.settimeout(None)
297

    
298
    # Operate in non-blocking mode
299
    self.sock.setblocking(0)
300

    
301
    logging.debug("Connection from %s:%s", client_addr[0], client_addr[1])
302
    try:
303
      request_msg_reader = None
304
      force_close = True
305
      try:
306
        # Do the secret SSL handshake
307
        if self.server.using_ssl:
308
          self.sock.set_accept_state()
309
          try:
310
            http.Handshake(self.sock, self.WRITE_TIMEOUT)
311
          except http.HttpSessionHandshakeUnexpectedEOF:
312
            # Ignore rest
313
            return
314

    
315
        try:
316
          try:
317
            request_msg_reader = self._ReadRequest()
318

    
319
            # RFC2616, 14.23: All Internet-based HTTP/1.1 servers MUST respond
320
            # with a 400 (Bad Request) status code to any HTTP/1.1 request
321
            # message which lacks a Host header field.
322
            if (self.request_msg.start_line.version == http.HTTP_1_1 and
323
                http.HTTP_HOST not in self.request_msg.headers):
324
              raise http.HttpBadRequest(message="Missing Host header")
325

    
326
            (self.response_msg.start_line.code, self.response_msg.headers,
327
             self.response_msg.body) = \
328
              HandleServerRequest(self.handler, self.request_msg)
329

    
330
            # Only wait for client to close if we didn't have any exception.
331
            force_close = False
332
          except http.HttpException, err:
333
            self._SetErrorStatus(err)
334
        finally:
335
          # Try to send a response
336
          self._SendResponse()
337
      finally:
338
        http.ShutdownConnection(sock, self.CLOSE_TIMEOUT, self.WRITE_TIMEOUT,
339
                                request_msg_reader, force_close)
340

    
341
      self.sock.close()
342
      self.sock = None
343
    finally:
344
      logging.debug("Disconnected %s:%s", client_addr[0], client_addr[1])
345

    
346
  def _ReadRequest(self):
347
    """Reads a request sent by client.
348

349
    """
350
    try:
351
      request_msg_reader = \
352
        _HttpClientToServerMessageReader(self.sock, self.request_msg,
353
                                         self.READ_TIMEOUT)
354
    except http.HttpSocketTimeout:
355
      raise http.HttpError("Timeout while reading request")
356
    except socket.error, err:
357
      raise http.HttpError("Error reading request: %s" % err)
358

    
359
    self.response_msg.start_line.version = self.request_msg.start_line.version
360

    
361
    return request_msg_reader
362

    
363
  def _SendResponse(self):
364
    """Sends the response to the client.
365

366
    """
367
    # HttpMessage.start_line can be of different types, pylint: disable=E1103
368
    if self.response_msg.start_line.code is None:
369
      return
370

    
371
    if not self.response_msg.headers:
372
      self.response_msg.headers = {}
373

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

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

    
389
    logging.info("%s:%s %s %s", self.client_addr[0], self.client_addr[1],
390
                 self.request_msg.start_line, response_code)
391

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

    
400
  def _SetErrorStatus(self, err):
401
    """Sets the response code and body from a HttpException.
402

403
    @type err: HttpException
404
    @param err: Exception instance
405

406
    """
407
    try:
408
      (shortmsg, longmsg) = self.responses[err.code]
409
    except KeyError:
410
      shortmsg = longmsg = "Unknown"
411

    
412
    if err.message:
413
      message = err.message
414
    else:
415
      message = shortmsg
416

    
417
    values = {
418
      "code": err.code,
419
      "message": cgi.escape(message),
420
      "explain": longmsg,
421
      }
422

    
423
    self.response_msg.start_line.code = err.code
424

    
425
    headers = {}
426
    if err.headers:
427
      headers.update(err.headers)
428
    headers[http.HTTP_CONTENT_TYPE] = self.error_content_type
429
    self.response_msg.headers = headers
430

    
431
    self.response_msg.body = self._FormatErrorMessage(values)
432

    
433
  def _FormatErrorMessage(self, values):
434
    """Formats the body of an error message.
435

436
    @type values: dict
437
    @param values: dictionary with keys code, message and explain.
438
    @rtype: string
439
    @return: the body of the message
440

441
    """
442
    return self.error_message_format % values
443

    
444

    
445
class HttpServer(http.HttpBase, asyncore.dispatcher):
446
  """Generic HTTP server class
447

448
  """
449
  MAX_CHILDREN = 20
450

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

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

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

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

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

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

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

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

    
499
  def Stop(self):
500
    self.socket.close()
501

    
502
  def handle_accept(self):
503
    self._IncomingConnection()
504

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

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

512
    @type quick: bool
513
    @param quick: Whether to only use non-blocking functions
514

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

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

    
538
  def _IncomingConnection(self):
539
    """Called for each incoming connection
540

541
    """
542
    # pylint: disable=W0212
543
    (connection, client_addr) = self.socket.accept()
544

    
545
    self._CollectChildren(False)
546

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

    
561
        # In case the handler code uses temporary files
562
        utils.ResetTempfileModule()
563

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

    
573

    
574
class HttpServerHandler(object):
575
  """Base class for handling HTTP server requests.
576

577
  Users of this class must subclass it and override the L{HandleRequest}
578
  function.
579

580
  """
581
  def PreHandleRequest(self, req):
582
    """Called before handling a request.
583

584
    Can be overridden by a subclass.
585

586
    """
587

    
588
  def HandleRequest(self, req):
589
    """Handles a request.
590

591
    Must be overridden by subclass.
592

593
    """
594
    raise NotImplementedError()