~didrocks/unity/altf10

« back to all changes in this revision

Viewing changes to grackle/service.py

  • Committer: Aaron Bentley
  • Date: 2012-01-11 14:04:41 UTC
  • Revision ID: aaron@canonical.com-20120111140441-l4sanxq1en07oblx
Test filtering by message-id.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
__metaclass__ = type
2
 
__all__ = [
3
 
    'ForkedFakeService',
4
 
    'GrackleService',
5
 
    ]
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:
21
 
    """A request handler that forwards to an archive store."""
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)
33
 
        path = environ['PATH_INFO'].split('/')
34
 
        self.scheme = path.pop(0)
35
 
        self.path = path
36
 
        self.query_string = environ['QUERY_STRING']
37
 
        return self.handle_request()
38
 
 
39
 
    def handle_request(self):
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')
44
 
        if self.method == 'PUT':
45
 
            return self.do_PUT()
46
 
        elif self.method == 'POST':
47
 
            return self.do_POST()
48
 
        elif self.method == 'GET':
49
 
            return self.do_GET()
50
 
        return self.send_response(
51
 
            httplib.BAD_REQUEST, reason='Unsupported method')
52
 
 
53
 
    def send_response(self, code, response='', reason=None, headers={}):
54
 
        """Set the status code and reason, then return the response."""
55
 
        if reason is None:
56
 
            reason = httplib.responses[code]
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())
61
 
        return [response]
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])
69
 
                return self.send_response(httplib.CREATED)
70
 
            except Exception, error:
71
 
                return self.send_response(
72
 
                    httplib.BAD_REQUEST, reason=error.__doc__)
73
 
        elif len(self.path) == 2:
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)
79
 
                return self.send_response(httplib.CREATED)
80
 
            except Exception, error:
81
 
                return self.send_response(
82
 
                    httplib.BAD_REQUEST, reason=error.__doc__)
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)
92
 
                response = simplejson.dumps(response)
93
 
                return self.send_response(httplib.OK, response=response)
94
 
            except Exception, error:
95
 
                return self.send_response(
96
 
                    httplib.BAD_REQUEST, reason=error.__doc__)
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)
103
 
            response = simplejson.dumps(response)
104
 
            return self.send_response(httplib.OK, response=response)
105
 
        except Exception, error:
106
 
            return self.send_response(
107
 
                httplib.BAD_REQUEST, reason=error.__doc__)
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()