~unity-2d-team/unity-2d/Shell-MultiMonitor

« back to all changes in this revision

Viewing changes to grackle/service.py

  • Committer: William Grant
  • Date: 2012-04-20 02:20:58 UTC
  • mfrom: (6.1.70 trunk)
  • Revision ID: william.grant@canonical.com-20120420022058-3nkracsmlg7akydu
Merge trunk.

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