~didrocks/unity/altf10

« back to all changes in this revision

Viewing changes to grackle/service.py

  • Committer: Curtis Hovey
  • Date: 2012-03-17 21:28:19 UTC
  • Revision ID: curtis.hovey@canonical.com-20120317212819-qb4izc3zxecemxso
Handle usupported URLS and methods.

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