~didrocks/unity/altf10

« back to all changes in this revision

Viewing changes to grackle/service.py

  • Committer: Curtis Hovey
  • Date: 2012-03-16 19:38:35 UTC
  • Revision ID: curtis.hovey@canonical.com-20120316193835-egu5tc1n0xlr5udj
Rename args to be honest about what is expected.

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
 
        if '://' in environ['PATH_INFO']:
32
 
            # All the needed information is embedded in PATH_INFO.
33
 
            shift_path_info(environ)  # shift the host and or port.
34
 
            self.application = shift_path_info(environ)
35
 
            path = environ['PATH_INFO'].split('/')
36
 
            path.pop(0)  # Pop the scheme.
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('/')
45
 
        self.query_string = environ['QUERY_STRING']
46
 
        return self.handle_request()
47
 
 
48
 
    def handle_request(self):
49
 
        """Select the method to handle the request and return a response."""
50
 
        if self.application != 'archive':
51
 
            return self.send_response(httplib.NOT_FOUND)
52
 
        elif self.method == 'PUT':
53
 
            return self.do_PUT()
54
 
        elif self.method == 'POST':
55
 
            return self.do_POST()
56
 
        elif self.method == 'GET':
57
 
            return self.do_GET()
58
 
        return self.send_response(httplib.METHOD_NOT_ALLOWED)
59
 
 
60
 
    def send_response(self, code, response='', reason=None, headers={}):
61
 
        """Set the status code and reason, then return the response."""
62
 
        if reason is None:
63
 
            reason = httplib.responses[code]
64
 
        response_status = '%s %s' % (code, reason)
65
 
        response_headers = {'content-type': 'application/json'}
66
 
        response_headers.update(headers.items())
67
 
        self.start_response(response_status, response_headers.items())
68
 
        return [response]
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])
76
 
                return self.send_response(httplib.CREATED)
77
 
            except Exception, error:
78
 
                return self.send_response(
79
 
                    httplib.BAD_REQUEST, reason=error.__doc__)
80
 
        elif len(self.path) == 2:
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)
86
 
                return self.send_response(httplib.CREATED)
87
 
            except Exception, error:
88
 
                return self.send_response(
89
 
                    httplib.BAD_REQUEST, reason=error.__doc__)
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)
99
 
                response = simplejson.dumps(response)
100
 
                return self.send_response(httplib.OK, response=response)
101
 
            except Exception, error:
102
 
                return self.send_response(
103
 
                    httplib.BAD_REQUEST, reason=error.__doc__)
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)
110
 
            response = simplejson.dumps(response)
111
 
            return self.send_response(httplib.OK, response=response)
112
 
        except Exception, error:
113
 
            return self.send_response(
114
 
                httplib.BAD_REQUEST, reason=error.__doc__)
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()