~didrocks/unity/altf10

« back to all changes in this revision

Viewing changes to grackle/service.py

  • Committer: Curtis Hovey
  • Date: 2012-03-17 21:41:08 UTC
  • Revision ID: curtis.hovey@canonical.com-20120317214108-wk8i7dyvve1std4u
Use propert reasons.

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
        if 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()