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