~didrocks/unity/altf10

« back to all changes in this revision

Viewing changes to grackle/service.py

  • Committer: Curtis Hovey
  • Date: 2012-03-17 20:57:24 UTC
  • Revision ID: curtis.hovey@canonical.com-20120317205724-2tqfvj1jqtn9fr8t
Converted gracle from a request handler to a wsgi app.
Keep Arrons brilliant ForkedFakeService.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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:
 
43
            reason = httplib.responses[code].upper()
 
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()