~openerp/openobject-server/web-dashboard

« back to all changes in this revision

Viewing changes to python25-compat/BaseHTTPServer.py

  • Committer: P. Christeas
  • Date: 2009-09-10 00:02:41 UTC
  • mto: (1884.1.1 p_christeas_server)
  • mto: This revision was merged to the branch mainline in revision 1900.
  • Revision ID: p_christ@hol.gr-20090910000241-3o1o5hcll10efa2y
Backport the HTTP server code from python 2.6 to 2.5

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""HTTP server base class.
 
2
 
 
3
Note: the class in this module doesn't implement any HTTP request; see
 
4
SimpleHTTPServer for simple implementations of GET, HEAD and POST
 
5
(including CGI scripts).  It does, however, optionally implement HTTP/1.1
 
6
persistent connections, as of version 0.3.
 
7
 
 
8
Contents:
 
9
 
 
10
- BaseHTTPRequestHandler: HTTP request handler base class
 
11
- test: test function
 
12
 
 
13
XXX To do:
 
14
 
 
15
- log requests even later (to capture byte count)
 
16
- log user-agent header and other interesting goodies
 
17
- send error log to separate file
 
18
"""
 
19
 
 
20
 
 
21
# See also:
 
22
#
 
23
# HTTP Working Group                                        T. Berners-Lee
 
24
# INTERNET-DRAFT                                            R. T. Fielding
 
25
# <draft-ietf-http-v10-spec-00.txt>                     H. Frystyk Nielsen
 
26
# Expires September 8, 1995                                  March 8, 1995
 
27
#
 
28
# URL: http://www.ics.uci.edu/pub/ietf/http/draft-ietf-http-v10-spec-00.txt
 
29
#
 
30
# and
 
31
#
 
32
# Network Working Group                                      R. Fielding
 
33
# Request for Comments: 2616                                       et al
 
34
# Obsoletes: 2068                                              June 1999
 
35
# Category: Standards Track
 
36
#
 
37
# URL: http://www.faqs.org/rfcs/rfc2616.html
 
38
 
 
39
# Log files
 
40
# ---------
 
41
#
 
42
# Here's a quote from the NCSA httpd docs about log file format.
 
43
#
 
44
# | The logfile format is as follows. Each line consists of:
 
45
# |
 
46
# | host rfc931 authuser [DD/Mon/YYYY:hh:mm:ss] "request" ddd bbbb
 
47
# |
 
48
# |        host: Either the DNS name or the IP number of the remote client
 
49
# |        rfc931: Any information returned by identd for this person,
 
50
# |                - otherwise.
 
51
# |        authuser: If user sent a userid for authentication, the user name,
 
52
# |                  - otherwise.
 
53
# |        DD: Day
 
54
# |        Mon: Month (calendar name)
 
55
# |        YYYY: Year
 
56
# |        hh: hour (24-hour format, the machine's timezone)
 
57
# |        mm: minutes
 
58
# |        ss: seconds
 
59
# |        request: The first line of the HTTP request as sent by the client.
 
60
# |        ddd: the status code returned by the server, - if not available.
 
61
# |        bbbb: the total number of bytes sent,
 
62
# |              *not including the HTTP/1.0 header*, - if not available
 
63
# |
 
64
# | You can determine the name of the file accessed through request.
 
65
#
 
66
# (Actually, the latter is only true if you know the server configuration
 
67
# at the time the request was made!)
 
68
 
 
69
__version__ = "0.3"
 
70
 
 
71
__all__ = ["HTTPServer", "BaseHTTPRequestHandler"]
 
72
 
 
73
import sys
 
74
import time
 
75
import socket # For gethostbyaddr()
 
76
import mimetools
 
77
import SocketServer
 
78
 
 
79
# Default error message template
 
80
DEFAULT_ERROR_MESSAGE = """\
 
81
<head>
 
82
<title>Error response</title>
 
83
</head>
 
84
<body>
 
85
<h1>Error response</h1>
 
86
<p>Error code %(code)d.
 
87
<p>Message: %(message)s.
 
88
<p>Error code explanation: %(code)s = %(explain)s.
 
89
</body>
 
90
"""
 
91
 
 
92
DEFAULT_ERROR_CONTENT_TYPE = "text/html"
 
93
 
 
94
def _quote_html(html):
 
95
    return html.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
 
96
 
 
97
class HTTPServer(SocketServer.TCPServer):
 
98
 
 
99
    allow_reuse_address = 1    # Seems to make sense in testing environment
 
100
 
 
101
    def server_bind(self):
 
102
        """Override server_bind to store the server name."""
 
103
        SocketServer.TCPServer.server_bind(self)
 
104
        host, port = self.socket.getsockname()[:2]
 
105
        self.server_name = socket.getfqdn(host)
 
106
        self.server_port = port
 
107
 
 
108
 
 
109
class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler):
 
110
 
 
111
    """HTTP request handler base class.
 
112
 
 
113
    The following explanation of HTTP serves to guide you through the
 
114
    code as well as to expose any misunderstandings I may have about
 
115
    HTTP (so you don't need to read the code to figure out I'm wrong
 
116
    :-).
 
117
 
 
118
    HTTP (HyperText Transfer Protocol) is an extensible protocol on
 
119
    top of a reliable stream transport (e.g. TCP/IP).  The protocol
 
120
    recognizes three parts to a request:
 
121
 
 
122
    1. One line identifying the request type and path
 
123
    2. An optional set of RFC-822-style headers
 
124
    3. An optional data part
 
125
 
 
126
    The headers and data are separated by a blank line.
 
127
 
 
128
    The first line of the request has the form
 
129
 
 
130
    <command> <path> <version>
 
131
 
 
132
    where <command> is a (case-sensitive) keyword such as GET or POST,
 
133
    <path> is a string containing path information for the request,
 
134
    and <version> should be the string "HTTP/1.0" or "HTTP/1.1".
 
135
    <path> is encoded using the URL encoding scheme (using %xx to signify
 
136
    the ASCII character with hex code xx).
 
137
 
 
138
    The specification specifies that lines are separated by CRLF but
 
139
    for compatibility with the widest range of clients recommends
 
140
    servers also handle LF.  Similarly, whitespace in the request line
 
141
    is treated sensibly (allowing multiple spaces between components
 
142
    and allowing trailing whitespace).
 
143
 
 
144
    Similarly, for output, lines ought to be separated by CRLF pairs
 
145
    but most clients grok LF characters just fine.
 
146
 
 
147
    If the first line of the request has the form
 
148
 
 
149
    <command> <path>
 
150
 
 
151
    (i.e. <version> is left out) then this is assumed to be an HTTP
 
152
    0.9 request; this form has no optional headers and data part and
 
153
    the reply consists of just the data.
 
154
 
 
155
    The reply form of the HTTP 1.x protocol again has three parts:
 
156
 
 
157
    1. One line giving the response code
 
158
    2. An optional set of RFC-822-style headers
 
159
    3. The data
 
160
 
 
161
    Again, the headers and data are separated by a blank line.
 
162
 
 
163
    The response code line has the form
 
164
 
 
165
    <version> <responsecode> <responsestring>
 
166
 
 
167
    where <version> is the protocol version ("HTTP/1.0" or "HTTP/1.1"),
 
168
    <responsecode> is a 3-digit response code indicating success or
 
169
    failure of the request, and <responsestring> is an optional
 
170
    human-readable string explaining what the response code means.
 
171
 
 
172
    This server parses the request and the headers, and then calls a
 
173
    function specific to the request type (<command>).  Specifically,
 
174
    a request SPAM will be handled by a method do_SPAM().  If no
 
175
    such method exists the server sends an error response to the
 
176
    client.  If it exists, it is called with no arguments:
 
177
 
 
178
    do_SPAM()
 
179
 
 
180
    Note that the request name is case sensitive (i.e. SPAM and spam
 
181
    are different requests).
 
182
 
 
183
    The various request details are stored in instance variables:
 
184
 
 
185
    - client_address is the client IP address in the form (host,
 
186
    port);
 
187
 
 
188
    - command, path and version are the broken-down request line;
 
189
 
 
190
    - headers is an instance of mimetools.Message (or a derived
 
191
    class) containing the header information;
 
192
 
 
193
    - rfile is a file object open for reading positioned at the
 
194
    start of the optional input data part;
 
195
 
 
196
    - wfile is a file object open for writing.
 
197
 
 
198
    IT IS IMPORTANT TO ADHERE TO THE PROTOCOL FOR WRITING!
 
199
 
 
200
    The first thing to be written must be the response line.  Then
 
201
    follow 0 or more header lines, then a blank line, and then the
 
202
    actual data (if any).  The meaning of the header lines depends on
 
203
    the command executed by the server; in most cases, when data is
 
204
    returned, there should be at least one header line of the form
 
205
 
 
206
    Content-type: <type>/<subtype>
 
207
 
 
208
    where <type> and <subtype> should be registered MIME types,
 
209
    e.g. "text/html" or "text/plain".
 
210
 
 
211
    """
 
212
 
 
213
    # The Python system version, truncated to its first component.
 
214
    sys_version = "Python/" + sys.version.split()[0]
 
215
 
 
216
    # The server software version.  You may want to override this.
 
217
    # The format is multiple whitespace-separated strings,
 
218
    # where each string is of the form name[/version].
 
219
    server_version = "BaseHTTP/" + __version__
 
220
 
 
221
    # The default request version.  This only affects responses up until
 
222
    # the point where the request line is parsed, so it mainly decides what
 
223
    # the client gets back when sending a malformed request line.
 
224
    # Most web servers default to HTTP 0.9, i.e. don't send a status line.
 
225
    default_request_version = "HTTP/0.9"
 
226
 
 
227
    def parse_request(self):
 
228
        """Parse a request (internal).
 
229
 
 
230
        The request should be stored in self.raw_requestline; the results
 
231
        are in self.command, self.path, self.request_version and
 
232
        self.headers.
 
233
 
 
234
        Return True for success, False for failure; on failure, an
 
235
        error is sent back.
 
236
 
 
237
        """
 
238
        self.command = None  # set in case of error on the first line
 
239
        self.request_version = version = self.default_request_version
 
240
        self.close_connection = 1
 
241
        requestline = self.raw_requestline
 
242
        if requestline[-2:] == '\r\n':
 
243
            requestline = requestline[:-2]
 
244
        elif requestline[-1:] == '\n':
 
245
            requestline = requestline[:-1]
 
246
        self.requestline = requestline
 
247
        words = requestline.split()
 
248
        if len(words) == 3:
 
249
            [command, path, version] = words
 
250
            if version[:5] != 'HTTP/':
 
251
                self.send_error(400, "Bad request version (%r)" % version)
 
252
                return False
 
253
            try:
 
254
                base_version_number = version.split('/', 1)[1]
 
255
                version_number = base_version_number.split(".")
 
256
                # RFC 2145 section 3.1 says there can be only one "." and
 
257
                #   - major and minor numbers MUST be treated as
 
258
                #      separate integers;
 
259
                #   - HTTP/2.4 is a lower version than HTTP/2.13, which in
 
260
                #      turn is lower than HTTP/12.3;
 
261
                #   - Leading zeros MUST be ignored by recipients.
 
262
                if len(version_number) != 2:
 
263
                    raise ValueError
 
264
                version_number = int(version_number[0]), int(version_number[1])
 
265
            except (ValueError, IndexError):
 
266
                self.send_error(400, "Bad request version (%r)" % version)
 
267
                return False
 
268
            if version_number >= (1, 1) and self.protocol_version >= "HTTP/1.1":
 
269
                self.close_connection = 0
 
270
            if version_number >= (2, 0):
 
271
                self.send_error(505,
 
272
                          "Invalid HTTP Version (%s)" % base_version_number)
 
273
                return False
 
274
        elif len(words) == 2:
 
275
            [command, path] = words
 
276
            self.close_connection = 1
 
277
            if command != 'GET':
 
278
                self.send_error(400,
 
279
                                "Bad HTTP/0.9 request type (%r)" % command)
 
280
                return False
 
281
        elif not words:
 
282
            return False
 
283
        else:
 
284
            self.send_error(400, "Bad request syntax (%r)" % requestline)
 
285
            return False
 
286
        self.command, self.path, self.request_version = command, path, version
 
287
 
 
288
        # Examine the headers and look for a Connection directive
 
289
        self.headers = self.MessageClass(self.rfile, 0)
 
290
 
 
291
        conntype = self.headers.get('Connection', "")
 
292
        if conntype.lower() == 'close':
 
293
            self.close_connection = 1
 
294
        elif (conntype.lower() == 'keep-alive' and
 
295
              self.protocol_version >= "HTTP/1.1"):
 
296
            self.close_connection = 0
 
297
        return True
 
298
 
 
299
    def handle_one_request(self):
 
300
        """Handle a single HTTP request.
 
301
 
 
302
        You normally don't need to override this method; see the class
 
303
        __doc__ string for information on how to handle specific HTTP
 
304
        commands such as GET and POST.
 
305
 
 
306
        """
 
307
        self.raw_requestline = self.rfile.readline()
 
308
        if not self.raw_requestline:
 
309
            self.close_connection = 1
 
310
            return
 
311
        if not self.parse_request(): # An error code has been sent, just exit
 
312
            return
 
313
        mname = 'do_' + self.command
 
314
        if not hasattr(self, mname):
 
315
            self.send_error(501, "Unsupported method (%r)" % self.command)
 
316
            return
 
317
        method = getattr(self, mname)
 
318
        method()
 
319
 
 
320
    def handle(self):
 
321
        """Handle multiple requests if necessary."""
 
322
        self.close_connection = 1
 
323
 
 
324
        self.handle_one_request()
 
325
        while not self.close_connection:
 
326
            self.handle_one_request()
 
327
 
 
328
    def send_error(self, code, message=None):
 
329
        """Send and log an error reply.
 
330
 
 
331
        Arguments are the error code, and a detailed message.
 
332
        The detailed message defaults to the short entry matching the
 
333
        response code.
 
334
 
 
335
        This sends an error response (so it must be called before any
 
336
        output has been generated), logs the error, and finally sends
 
337
        a piece of HTML explaining the error to the user.
 
338
 
 
339
        """
 
340
 
 
341
        try:
 
342
            short, long = self.responses[code]
 
343
        except KeyError:
 
344
            short, long = '???', '???'
 
345
        if message is None:
 
346
            message = short
 
347
        explain = long
 
348
        self.log_error("code %d, message %s", code, message)
 
349
        # using _quote_html to prevent Cross Site Scripting attacks (see bug #1100201)
 
350
        content = (self.error_message_format %
 
351
                   {'code': code, 'message': _quote_html(message), 'explain': explain})
 
352
        self.send_response(code, message)
 
353
        self.send_header("Content-Type", self.error_content_type)
 
354
        self.send_header('Connection', 'close')
 
355
        self.end_headers()
 
356
        if self.command != 'HEAD' and code >= 200 and code not in (204, 304):
 
357
            self.wfile.write(content)
 
358
 
 
359
    error_message_format = DEFAULT_ERROR_MESSAGE
 
360
    error_content_type = DEFAULT_ERROR_CONTENT_TYPE
 
361
 
 
362
    def send_response(self, code, message=None):
 
363
        """Send the response header and log the response code.
 
364
 
 
365
        Also send two standard headers with the server software
 
366
        version and the current date.
 
367
 
 
368
        """
 
369
        self.log_request(code)
 
370
        if message is None:
 
371
            if code in self.responses:
 
372
                message = self.responses[code][0]
 
373
            else:
 
374
                message = ''
 
375
        if self.request_version != 'HTTP/0.9':
 
376
            self.wfile.write("%s %d %s\r\n" %
 
377
                             (self.protocol_version, code, message))
 
378
            # print (self.protocol_version, code, message)
 
379
        self.send_header('Server', self.version_string())
 
380
        self.send_header('Date', self.date_time_string())
 
381
 
 
382
    def send_header(self, keyword, value):
 
383
        """Send a MIME header."""
 
384
        if self.request_version != 'HTTP/0.9':
 
385
            self.wfile.write("%s: %s\r\n" % (keyword, value))
 
386
 
 
387
        if keyword.lower() == 'connection':
 
388
            if value.lower() == 'close':
 
389
                self.close_connection = 1
 
390
            elif value.lower() == 'keep-alive':
 
391
                self.close_connection = 0
 
392
 
 
393
    def end_headers(self):
 
394
        """Send the blank line ending the MIME headers."""
 
395
        if self.request_version != 'HTTP/0.9':
 
396
            self.wfile.write("\r\n")
 
397
 
 
398
    def log_request(self, code='-', size='-'):
 
399
        """Log an accepted request.
 
400
 
 
401
        This is called by send_response().
 
402
 
 
403
        """
 
404
 
 
405
        self.log_message('"%s" %s %s',
 
406
                         self.requestline, str(code), str(size))
 
407
 
 
408
    def log_error(self, format, *args):
 
409
        """Log an error.
 
410
 
 
411
        This is called when a request cannot be fulfilled.  By
 
412
        default it passes the message on to log_message().
 
413
 
 
414
        Arguments are the same as for log_message().
 
415
 
 
416
        XXX This should go to the separate error log.
 
417
 
 
418
        """
 
419
 
 
420
        self.log_message(format, *args)
 
421
 
 
422
    def log_message(self, format, *args):
 
423
        """Log an arbitrary message.
 
424
 
 
425
        This is used by all other logging functions.  Override
 
426
        it if you have specific logging wishes.
 
427
 
 
428
        The first argument, FORMAT, is a format string for the
 
429
        message to be logged.  If the format string contains
 
430
        any % escapes requiring parameters, they should be
 
431
        specified as subsequent arguments (it's just like
 
432
        printf!).
 
433
 
 
434
        The client host and current date/time are prefixed to
 
435
        every message.
 
436
 
 
437
        """
 
438
 
 
439
        sys.stderr.write("%s - - [%s] %s\n" %
 
440
                         (self.address_string(),
 
441
                          self.log_date_time_string(),
 
442
                          format%args))
 
443
 
 
444
    def version_string(self):
 
445
        """Return the server software version string."""
 
446
        return self.server_version + ' ' + self.sys_version
 
447
 
 
448
    def date_time_string(self, timestamp=None):
 
449
        """Return the current date and time formatted for a message header."""
 
450
        if timestamp is None:
 
451
            timestamp = time.time()
 
452
        year, month, day, hh, mm, ss, wd, y, z = time.gmtime(timestamp)
 
453
        s = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
 
454
                self.weekdayname[wd],
 
455
                day, self.monthname[month], year,
 
456
                hh, mm, ss)
 
457
        return s
 
458
 
 
459
    def log_date_time_string(self):
 
460
        """Return the current time formatted for logging."""
 
461
        now = time.time()
 
462
        year, month, day, hh, mm, ss, x, y, z = time.localtime(now)
 
463
        s = "%02d/%3s/%04d %02d:%02d:%02d" % (
 
464
                day, self.monthname[month], year, hh, mm, ss)
 
465
        return s
 
466
 
 
467
    weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
 
468
 
 
469
    monthname = [None,
 
470
                 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
 
471
                 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
 
472
 
 
473
    def address_string(self):
 
474
        """Return the client address formatted for logging.
 
475
 
 
476
        This version looks up the full hostname using gethostbyaddr(),
 
477
        and tries to find a name that contains at least one dot.
 
478
 
 
479
        """
 
480
 
 
481
        host, port = self.client_address[:2]
 
482
        return socket.getfqdn(host)
 
483
 
 
484
    # Essentially static class variables
 
485
 
 
486
    # The version of the HTTP protocol we support.
 
487
    # Set this to HTTP/1.1 to enable automatic keepalive
 
488
    protocol_version = "HTTP/1.0"
 
489
 
 
490
    # The Message-like class used to parse headers
 
491
    MessageClass = mimetools.Message
 
492
 
 
493
    # Table mapping response codes to messages; entries have the
 
494
    # form {code: (shortmessage, longmessage)}.
 
495
    # See RFC 2616.
 
496
    responses = {
 
497
        100: ('Continue', 'Request received, please continue'),
 
498
        101: ('Switching Protocols',
 
499
              'Switching to new protocol; obey Upgrade header'),
 
500
 
 
501
        200: ('OK', 'Request fulfilled, document follows'),
 
502
        201: ('Created', 'Document created, URL follows'),
 
503
        202: ('Accepted',
 
504
              'Request accepted, processing continues off-line'),
 
505
        203: ('Non-Authoritative Information', 'Request fulfilled from cache'),
 
506
        204: ('No Content', 'Request fulfilled, nothing follows'),
 
507
        205: ('Reset Content', 'Clear input form for further input.'),
 
508
        206: ('Partial Content', 'Partial content follows.'),
 
509
 
 
510
        300: ('Multiple Choices',
 
511
              'Object has several resources -- see URI list'),
 
512
        301: ('Moved Permanently', 'Object moved permanently -- see URI list'),
 
513
        302: ('Found', 'Object moved temporarily -- see URI list'),
 
514
        303: ('See Other', 'Object moved -- see Method and URL list'),
 
515
        304: ('Not Modified',
 
516
              'Document has not changed since given time'),
 
517
        305: ('Use Proxy',
 
518
              'You must use proxy specified in Location to access this '
 
519
              'resource.'),
 
520
        307: ('Temporary Redirect',
 
521
              'Object moved temporarily -- see URI list'),
 
522
 
 
523
        400: ('Bad Request',
 
524
              'Bad request syntax or unsupported method'),
 
525
        401: ('Unauthorized',
 
526
              'No permission -- see authorization schemes'),
 
527
        402: ('Payment Required',
 
528
              'No payment -- see charging schemes'),
 
529
        403: ('Forbidden',
 
530
              'Request forbidden -- authorization will not help'),
 
531
        404: ('Not Found', 'Nothing matches the given URI'),
 
532
        405: ('Method Not Allowed',
 
533
              'Specified method is invalid for this server.'),
 
534
        406: ('Not Acceptable', 'URI not available in preferred format.'),
 
535
        407: ('Proxy Authentication Required', 'You must authenticate with '
 
536
              'this proxy before proceeding.'),
 
537
        408: ('Request Timeout', 'Request timed out; try again later.'),
 
538
        409: ('Conflict', 'Request conflict.'),
 
539
        410: ('Gone',
 
540
              'URI no longer exists and has been permanently removed.'),
 
541
        411: ('Length Required', 'Client must specify Content-Length.'),
 
542
        412: ('Precondition Failed', 'Precondition in headers is false.'),
 
543
        413: ('Request Entity Too Large', 'Entity is too large.'),
 
544
        414: ('Request-URI Too Long', 'URI is too long.'),
 
545
        415: ('Unsupported Media Type', 'Entity body in unsupported format.'),
 
546
        416: ('Requested Range Not Satisfiable',
 
547
              'Cannot satisfy request range.'),
 
548
        417: ('Expectation Failed',
 
549
              'Expect condition could not be satisfied.'),
 
550
 
 
551
        500: ('Internal Server Error', 'Server got itself in trouble'),
 
552
        501: ('Not Implemented',
 
553
              'Server does not support this operation'),
 
554
        502: ('Bad Gateway', 'Invalid responses from another server/proxy.'),
 
555
        503: ('Service Unavailable',
 
556
              'The server cannot process the request due to a high load'),
 
557
        504: ('Gateway Timeout',
 
558
              'The gateway server did not receive a timely response'),
 
559
        505: ('HTTP Version Not Supported', 'Cannot fulfill request.'),
 
560
        }
 
561
 
 
562
 
 
563
def test(HandlerClass = BaseHTTPRequestHandler,
 
564
         ServerClass = HTTPServer, protocol="HTTP/1.0"):
 
565
    """Test the HTTP request handler class.
 
566
 
 
567
    This runs an HTTP server on port 8000 (or the first command line
 
568
    argument).
 
569
 
 
570
    """
 
571
 
 
572
    if sys.argv[1:]:
 
573
        port = int(sys.argv[1])
 
574
    else:
 
575
        port = 8000
 
576
    server_address = ('', port)
 
577
 
 
578
    HandlerClass.protocol_version = protocol
 
579
    httpd = ServerClass(server_address, HandlerClass)
 
580
 
 
581
    sa = httpd.socket.getsockname()
 
582
    print "Serving HTTP on", sa[0], "port", sa[1], "..."
 
583
    httpd.serve_forever()
 
584
 
 
585
 
 
586
if __name__ == '__main__':
 
587
    test()