~didrocks/unity/altf10

60 by Curtis Hovey
Converted gracle from a request handler to a wsgi app.
1
__metaclass__ = type
70 by Curtis Hovey
Clean up python-oddities.
2
__all__ = [
3
    'ForkedFakeService',
4
    'GrackleService',
5
    ]
60 by Curtis Hovey
Converted gracle from a request handler to a wsgi app.
6
7
import httplib
8
import logging
9
import os
10
from signal import SIGKILL
11
import simplejson
12
import sys
13
from wsgiref.simple_server import make_server
14
from wsgiref.util import shift_path_info
15
from grackle.store import (
16
    MemoryStore,
17
    )
18
19
20
class GrackleService:
70 by Curtis Hovey
Clean up python-oddities.
21
    """A request handler that forwards to an archive store."""
60 by Curtis Hovey
Converted gracle from a request handler to a wsgi app.
22
23
    def __init__(self, store):
24
        self.store = store
25
        self.logger = logging.getLogger('http')
26
27
    def __call__(self, environ, start_response):
28
        self.environ = environ
29
        self.start_response = start_response
30
        self.method = environ['REQUEST_METHOD']
74 by Curtis Hovey
Update rules to get the path and application based on PEP333.
31
        if '://' in environ['PATH_INFO']:
32
            # All the needed information is embedded in PATH_INFO.
75 by Curtis Hovey
Only store the unique information needed by grackle.
33
            shift_path_info(environ)  # shift the host and or port.
74 by Curtis Hovey
Update rules to get the path and application based on PEP333.
34
            self.application = shift_path_info(environ)
35
            path = environ['PATH_INFO'].split('/')
75 by Curtis Hovey
Only store the unique information needed by grackle.
36
            path.pop(0)  # Pop the scheme.
74 by Curtis Hovey
Update rules to get the path and application based on PEP333.
37
            self.path = path
38
        elif environ['SCRIPT_NAME'] == '':
39
            # Remove the application to set the path.
40
            self.application = shift_path_info(environ)
41
            self.path = environ['PATH_INFO'].split('/')
42
        else:
43
            self.application = environ['SCRIPT_NAME']
44
            self.path = environ['PATH_INFO'].split('/')
60 by Curtis Hovey
Converted gracle from a request handler to a wsgi app.
45
        self.query_string = environ['QUERY_STRING']
46
        return self.handle_request()
47
48
    def handle_request(self):
69 by Curtis Hovey
Handle usupported URLS and methods.
49
        """Select the method to handle the request and return a response."""
50
        if self.application != 'archive':
71 by Curtis Hovey
Use propert reasons.
51
            return self.send_response(httplib.NOT_FOUND)
72 by Curtis Hovey
Simplify logic.
52
        elif self.method == 'PUT':
60 by Curtis Hovey
Converted gracle from a request handler to a wsgi app.
53
            return self.do_PUT()
69 by Curtis Hovey
Handle usupported URLS and methods.
54
        elif self.method == 'POST':
60 by Curtis Hovey
Converted gracle from a request handler to a wsgi app.
55
            return self.do_POST()
69 by Curtis Hovey
Handle usupported URLS and methods.
56
        elif self.method == 'GET':
60 by Curtis Hovey
Converted gracle from a request handler to a wsgi app.
57
            return self.do_GET()
71 by Curtis Hovey
Use propert reasons.
58
        return self.send_response(httplib.METHOD_NOT_ALLOWED)
60 by Curtis Hovey
Converted gracle from a request handler to a wsgi app.
59
67 by Curtis Hovey
Change argument order to reflect common usage.
60
    def send_response(self, code, response='', reason=None, headers={}):
69 by Curtis Hovey
Handle usupported URLS and methods.
61
        """Set the status code and reason, then return the response."""
60 by Curtis Hovey
Converted gracle from a request handler to a wsgi app.
62
        if reason is None:
62 by Curtis Hovey
No need to uppercase the reason.
63
            reason = httplib.responses[code]
73 by Curtis Hovey
Use proper terminology.
64
        response_status = '%s %s' % (code, reason)
60 by Curtis Hovey
Converted gracle from a request handler to a wsgi app.
65
        response_headers = {'content-type': 'application/json'}
66
        response_headers.update(headers.items())
73 by Curtis Hovey
Use proper terminology.
67
        self.start_response(response_status, response_headers.items())
66 by Curtis Hovey
use send response to ensure a list of strings is returned.
68
        return [response]
60 by Curtis Hovey
Converted gracle from a request handler to a wsgi app.
69
70
    def do_PUT(self):
71
        """Create an archive or message on PUT."""
72
        if len(self.path) == 1:
73
            # This expected path is /archive/archive_id.
74
            try:
75
                self.store.put_archive(self.path[0])
66 by Curtis Hovey
use send response to ensure a list of strings is returned.
76
                return self.send_response(httplib.CREATED)
60 by Curtis Hovey
Converted gracle from a request handler to a wsgi app.
77
            except Exception, error:
66 by Curtis Hovey
use send response to ensure a list of strings is returned.
78
                return self.send_response(
60 by Curtis Hovey
Converted gracle from a request handler to a wsgi app.
79
                    httplib.BAD_REQUEST, reason=error.__doc__)
64 by Curtis Hovey
do_PUT aways returns a empty list.
80
        elif len(self.path) == 2:
60 by Curtis Hovey
Converted gracle from a request handler to a wsgi app.
81
            # This expected path is /archive/archive_id/message_id.
82
            try:
83
                put_input = self.environ['wsgi.input']
84
                message = put_input.read(int(self.environ['CONTENT_LENGTH']))
85
                self.store.put_message(self.path[0], self.path[1], message)
66 by Curtis Hovey
use send response to ensure a list of strings is returned.
86
                return self.send_response(httplib.CREATED)
65 by Curtis Hovey
Always return the error reason.
87
            except Exception, error:
66 by Curtis Hovey
use send response to ensure a list of strings is returned.
88
                return self.send_response(
65 by Curtis Hovey
Always return the error reason.
89
                    httplib.BAD_REQUEST, reason=error.__doc__)
60 by Curtis Hovey
Converted gracle from a request handler to a wsgi app.
90
91
    def do_POST(self):
92
        """Change a message on POST."""
93
        if len(self.path) == 2:
94
            # This expected path is /archive/archive_id/message_id.
95
            try:
96
                # This expected path is /archive/archive_id/message_id.
97
                response = self.store.hide_message(
98
                    self.path[0], self.path[1], self.query_string)
66 by Curtis Hovey
use send response to ensure a list of strings is returned.
99
                response = simplejson.dumps(response)
100
                return self.send_response(httplib.OK, response=response)
65 by Curtis Hovey
Always return the error reason.
101
            except Exception, error:
66 by Curtis Hovey
use send response to ensure a list of strings is returned.
102
                return self.send_response(
103
                    httplib.BAD_REQUEST, reason=error.__doc__)
60 by Curtis Hovey
Converted gracle from a request handler to a wsgi app.
104
105
    def do_GET(self):
106
        """Retrieve a list of messages on GET."""
107
        try:
108
            response = self.store.get_messages(
109
                self.path[0], self.query_string)
66 by Curtis Hovey
use send response to ensure a list of strings is returned.
110
            response = simplejson.dumps(response)
111
            return self.send_response(httplib.OK, response=response)
60 by Curtis Hovey
Converted gracle from a request handler to a wsgi app.
112
        except Exception, error:
66 by Curtis Hovey
use send response to ensure a list of strings is returned.
113
            return self.send_response(
114
                httplib.BAD_REQUEST, reason=error.__doc__)
60 by Curtis Hovey
Converted gracle from a request handler to a wsgi app.
115
116
    def log_message(self, format, *args):
117
        """Override log_message to use standard Python logging."""
118
        message = "%s - - [%s] %s\n" % (
119
            self.address_string(), self.log_date_time_string(), format % args)
120
        self.logger.info(message)
121
122
123
class ForkedFakeService:
124
    """A Grackle service fake, as a ContextManager."""
125
126
    def __init__(self, port, message_archives=None, write_logs=False):
127
        """Constructor.
128
129
        :param port: The tcp port to use.
130
        :param message_archives: A dict of lists of dicts representing
131
            archives of messages. The outer dict represents the archive,
132
            the list represents the list of messages for that archive.
133
        :param write_logs: If true, log messages will be written to stdout.
134
        """
135
        self.pid = None
136
        self.port = port
137
        if message_archives is None:
138
            self.message_archives = {}
139
        else:
140
            self.message_archives = message_archives
141
        self.read_end, self.write_end = os.pipe()
142
        self.write_logs = write_logs
143
144
    @staticmethod
145
    def from_client(client, message_archives=None):
146
        """Instantiate a ForkedFakeService from the client.
147
148
        :param port: The client to provide service for.
149
        :param message_archives: A dict of lists of dicts representing
150
            archives of messages. The outer dict represents the archive,
151
            the list represents the list of messages for that archive.
152
        """
153
        return ForkedFakeService(client.port, message_archives)
154
155
    def is_ready(self):
156
        """Tell the parent process that the server is ready for writes."""
157
        os.write(self.write_end, 'asdf')
158
159
    def __enter__(self):
160
        """Run the service.
161
162
        Fork and start a server in the child.  Return when the server is ready
163
        for use."""
164
        pid = os.fork()
165
        if pid == 0:
166
            self.start_server()
167
        self.pid = pid
168
        os.read(self.read_end, 1)
169
        return
170
171
    def start_server(self):
172
        """Start the HTTP server."""
173
        app = GrackleService(MemoryStore(self.message_archives))
174
        service = make_server('', self.port, app)
175
        self.is_ready()
176
        if self.write_logs:
177
            logging.basicConfig(
178
                stream=sys.stderr, level=logging.INFO)
179
        service.serve_forever()
180
181
    def __exit__(self, exc_type, exc_val, traceback):
182
        os.kill(self.pid, SIGKILL)
183
184
185
if __name__ == '__main__':
186
    app = GrackleService(MemoryStore({}))
187
    service = make_server('', 8787, app)
188
    service.serve_forever()