~didrocks/unity/altf10

« back to all changes in this revision

Viewing changes to grackle/service.py

  • Committer: Curtis Hovey
  • Date: 2012-01-31 04:27:13 UTC
  • mto: This revision was merged to the branch mainline in revision 37.
  • Revision ID: curtis.hovey@canonical.com-20120131042713-tsjq1nx6jtwiyw5p
Use make_message() to make test data consistent.

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