Welcome, guest | Sign In | My Account | Store | Cart

Provide simple support for Java-style servlets.

The code in this module provides an incomplete port of Java's API for servlets. Only essential classes and methods are implemented here.

Python, 250 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
#! /usr/bin/env python
"""Provide simple support for Java-style servlets.

The code in this module provides an incomplete port of Java's API for
servlets. Only essential classes and methods are implemented here."""

################################################################################

__author__ = 'Stephen "Zero" Chappell <Noctis.Skytower@gmail.com>'
__date__ = '11 February 2010'
__version__ = '$Revision: 3 $'

################################################################################

import urllib.parse
import socketserver
import http.server
import webbrowser
import socket
import cgitb
import cgi
import sys
import io

################################################################################

class HttpServlet(http.server.BaseHTTPRequestHandler):

    """Provide a base class for implementing a servlet.

    The HttpServlet class should be inherited by any class that is to be
    run as a servlet. A few basic HTTP methods are automatically handled
    by this class and passed on to the service method to be written by
    the servlet designer. Request and response systems are handled here."""

    # These variables properly identify and setup the handler.
    server_version = 'HttpServlet/' + http.server.__version__
    protocol_version = 'HTTP/1.1'

    __debug = False # Determines how exceptions are reported.
    
    @classmethod
    def debug(cls, value):
        """Set the debugging status of servlet applications.

        This determines what happens when code raises an exception.
        if True: traceback is sent back to the browser for analysis.
        if False: the browser receives an ambiguous 500 error code."""
        cls.__debug = value

    def do_GET(self):
        """Handle the GET method sent via HTTP."""
        if self.path == '/favicon.ico':
            self.send_error(404)
            return
        self.__call_service(self.path)

    def do_POST(self):
        """Handle the POST method sent via HTTP."""
        try:
            length = int(self.headers.get('content-length'))
        except:
            self.send_error(411)
        else:
            self.__call_service('?' + self.rfile.read(length).decode())

    def __call_service(self, query):
        """Execute service method implemented by child class.

        When either a GET or POST request is received, this method is
        called with a string representing the query. Request and response
        objects are created for the child's service method, and an answer
        is sent back to the client with errors automatically being caught."""
        request = _HttpServletRequest(query)
        response = _HttpServletResponse()
        try:
            self.service(request, response)
        except Exception:
            if self.__debug:
                self.send_response(500)
                self.send_header('Content-Type', 'text/html')
                self.send_header('Connection', 'close')
                self.end_headers()
                klass, value, trace = sys.exc_info()
                # The next function call may raise an exception.
                html = cgitb.html((klass, value, trace.tb_next))
                self.wfile.write(html.encode())
            else:
                self.send_error(500)
        else:
            response_value = response._value
            self.send_response(200)
            self.send_header('Content-Type', response._type)
            self.send_header('Content-Length', str(len(response_value)))
            self.end_headers()
            self.wfile.write(response_value)
            
    def service(self, request, response):
        """Process the client's request and send back a response."""
        raise NotImplementedError()

################################################################################

class _HttpServletRequest:

    """Store the query from the client's request.

    Instances of this class are automatically created
    to store query variables obtained by parsing the
    path or post data sent from the client to the servlet."""

    def __init__(self, path):
        """Initialize the request object from the path."""
        query = urllib.parse.urlparse(path).query
        self.__dict = urllib.parse.parse_qs(query, True)

    def getParameter(self, name):
        """Compute the value of the named parameter.

        The parameter that has the specified name is found,
        and the first value associated with that name is
        returned. If the name cannot be found, then None
        is returned to the caller instead of an exception."""
        return self.__dict.get(name, [None])[0]

################################################################################

class _HttpServletResponse:

    """Allow servlet to send response to client.

    Servlet child classes have their service methods called with an
    instance of this class. It allows the responses to be configured
    for both the type of data and the content of the data being sent."""

    def __init__(self):
        """Initialize a blank, generic response object."""
        self.__content_type = 'text/plain'
        self.__print_writer = _PrintWriter()

    def setContentType(self, content_type):
        """Set the type of data being sent to client.

        This is the argument to the "Content-Type" header sent to
        the client. By default, it is set to "text/plain" but may
        be set to whatever is appropriate for the data type being
        sent. Note the data alterations in the _value property."""
        self.__content_type = content_type

    def getWriter(self):
        """Produce a writer object for printing.

        This method returns a PrintWriter object that caches the
        servlet's response to the client. Once the service method
        returns, the PrintWriter's value is sent back to the client."""
        return self.__print_writer

    @property
    def _type(self):
        """Read-only content-type property for __call_service."""
        return self.__content_type

    @property
    def _value(self):
        """Read-only response-value property for __call_service."""
        value = self.__print_writer.getvalue()
        lines = value.replace('\r\n', '\n').replace('\r', '\n')
        return lines.replace('\n', '\r\n').encode()

################################################################################

class _PrintWriter(io.StringIO):

    """Cache the response generated for the client.

    An instance of the class is automatically built by HTTP Servlet
    Response objects to cache the data being sent back to the client."""

    print = io.StringIO.write

    def println(self, string):
        """Print a line of data to the internal representation."""
        self.write(string + '\r\n')

################################################################################

class HttpServer(socketserver.ThreadingMixIn, http.server.HTTPServer):

    """Create a server with specified address and handler.

    A generic web server can be instantiated with this class. It will listen
    on the address given to its constructor and will use the handler class
    to process all incoming traffic. Running a server is greatly simplified."""

    # We should not be binding to an
    # address that is already in use.
    allow_reuse_address = False

    @classmethod
    def main(cls, RequestHandlerClass, port=80):
        """Start server with handler on given port.

        This static method provides an easy way to start, run, and exit
        a HttpServer instance. The server will be executed if possible,
        and the computer's web browser will be directed to the address."""
        try:
            server = cls(('', port), RequestHandlerClass)
            active = True
        except socket.error:
            active = False
        else:
            addr, port = server.socket.getsockname()
            print('Serving HTTP on', addr, 'port', port, '...')
        finally:
            port = '' if port == 80 else ':' + str(port)
            addr = 'http://localhost' + port + '/'
            webbrowser.open(addr)
        if active:
            try:
                server.serve_forever()
            except KeyboardInterrupt:
                print('Keyboard interrupt received: EXITING')
            finally:
                server.server_close()

    def handle_error(self, request, client_address):
        """Process exceptions raised by the RequestHandlerClass.

        Overriding this method is necessary for two different reasons:
        (1) SystemExit exceptions are incorrectly caught otherwise and
        (2) Socket errors should be silently passed in the server code"""
        klass, value = sys.exc_info()[:2]
        if klass is SystemExit:
            self.__exit = value
            self._BaseServer__serving = None
        elif issubclass(klass, socket.error):
            pass
        else:
            super().handle_error(request, client_address)

    def serve_forever(self, poll_interval=0.5):
        """Handle all incoming client requests forever.

        This method has been overridden so that SystemExit exceptions
        raised in the RequestHandlerClass can be re-raised after being
        caught in the handle_error method above. This allows servlet
        code to terminate server execution if so desired or required."""
        super().serve_forever(poll_interval)
        if self._BaseServer__serving is None:
            raise self.__exit
Created by Stephen Chappell on Thu, 11 Feb 2010 (MIT)
Python recipes (4591)
Stephen Chappell's recipes (233)
Bible Verse Quiz (11)

Required Modules

  • (none specified)

Other Information and Tasks