~didrocks/unity/altf10

« back to all changes in this revision

Viewing changes to grackle/service.py

  • Committer: Aaron Bentley
  • Date: 2012-01-12 09:54:51 UTC
  • Revision ID: aaron@canonical.com-20120112095451-6hwsm1c1demeokl5
Support order by date

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