~didrocks/unity/altf10

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