~didrocks/unity/altf10

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
__metaclass__ = type
__all__ = [
    'ForkedFakeService',
    'GrackleService',
    ]

import httplib
import logging
import os
from signal import SIGKILL
import simplejson
import sys
from wsgiref.simple_server import make_server
from wsgiref.util import shift_path_info
from grackle.store import (
    MemoryStore,
    )


class GrackleService:
    """A request handler that forwards to an archive store."""

    def __init__(self, store):
        self.store = store
        self.logger = logging.getLogger('http')

    def __call__(self, environ, start_response):
        self.environ = environ
        self.start_response = start_response
        self.method = environ['REQUEST_METHOD']
        if '://' in environ['PATH_INFO']:
            # All the needed information is embedded in PATH_INFO.
            shift_path_info(environ)  # shift the host and or port.
            self.application = shift_path_info(environ)
            path = environ['PATH_INFO'].split('/')
            path.pop(0)  # Pop the scheme.
            self.path = path
        elif environ['SCRIPT_NAME'] == '':
            # Remove the application to set the path.
            self.application = shift_path_info(environ)
            self.path = environ['PATH_INFO'].split('/')
        else:
            self.application = environ['SCRIPT_NAME']
            self.path = environ['PATH_INFO'].split('/')
        self.query_string = environ['QUERY_STRING']
        return self.handle_request()

    def handle_request(self):
        """Select the method to handle the request and return a response."""
        if self.application != 'archive':
            return self.send_response(httplib.NOT_FOUND)
        elif self.method == 'PUT':
            return self.do_PUT()
        elif self.method == 'POST':
            return self.do_POST()
        elif self.method == 'GET':
            return self.do_GET()
        return self.send_response(httplib.METHOD_NOT_ALLOWED)

    def send_response(self, code, response='', reason=None, headers={}):
        """Set the status code and reason, then return the response."""
        if reason is None:
            reason = httplib.responses[code]
        response_status = '%s %s' % (code, reason)
        response_headers = {'content-type': 'application/json'}
        response_headers.update(headers.items())
        self.start_response(response_status, response_headers.items())
        return [response]

    def do_PUT(self):
        """Create an archive or message on PUT."""
        if len(self.path) == 1:
            # This expected path is /archive/archive_id.
            try:
                self.store.put_archive(self.path[0])
                return self.send_response(httplib.CREATED)
            except Exception, error:
                return self.send_response(
                    httplib.BAD_REQUEST, reason=error.__doc__)
        elif len(self.path) == 2:
            # This expected path is /archive/archive_id/message_id.
            try:
                put_input = self.environ['wsgi.input']
                message = put_input.read(int(self.environ['CONTENT_LENGTH']))
                self.store.put_message(self.path[0], self.path[1], message)
                return self.send_response(httplib.CREATED)
            except Exception, error:
                return self.send_response(
                    httplib.BAD_REQUEST, reason=error.__doc__)

    def do_POST(self):
        """Change a message on POST."""
        if len(self.path) == 2:
            # This expected path is /archive/archive_id/message_id.
            try:
                # This expected path is /archive/archive_id/message_id.
                response = self.store.hide_message(
                    self.path[0], self.path[1], self.query_string)
                response = simplejson.dumps(response)
                return self.send_response(httplib.OK, response=response)
            except Exception, error:
                return self.send_response(
                    httplib.BAD_REQUEST, reason=error.__doc__)

    def do_GET(self):
        """Retrieve a list of messages on GET."""
        try:
            response = self.store.get_messages(
                self.path[0], self.query_string)
            response = simplejson.dumps(response)
            return self.send_response(httplib.OK, response=response)
        except Exception, error:
            return self.send_response(
                httplib.BAD_REQUEST, reason=error.__doc__)

    def log_message(self, format, *args):
        """Override log_message to use standard Python logging."""
        message = "%s - - [%s] %s\n" % (
            self.address_string(), self.log_date_time_string(), format % args)
        self.logger.info(message)


class ForkedFakeService:
    """A Grackle service fake, as a ContextManager."""

    def __init__(self, port, message_archives=None, write_logs=False):
        """Constructor.

        :param port: The tcp port to use.
        :param message_archives: A dict of lists of dicts representing
            archives of messages. The outer dict represents the archive,
            the list represents the list of messages for that archive.
        :param write_logs: If true, log messages will be written to stdout.
        """
        self.pid = None
        self.port = port
        if message_archives is None:
            self.message_archives = {}
        else:
            self.message_archives = message_archives
        self.read_end, self.write_end = os.pipe()
        self.write_logs = write_logs

    @staticmethod
    def from_client(client, message_archives=None):
        """Instantiate a ForkedFakeService from the client.

        :param port: The client to provide service for.
        :param message_archives: A dict of lists of dicts representing
            archives of messages. The outer dict represents the archive,
            the list represents the list of messages for that archive.
        """
        return ForkedFakeService(client.port, message_archives)

    def is_ready(self):
        """Tell the parent process that the server is ready for writes."""
        os.write(self.write_end, 'asdf')

    def __enter__(self):
        """Run the service.

        Fork and start a server in the child.  Return when the server is ready
        for use."""
        pid = os.fork()
        if pid == 0:
            self.start_server()
        self.pid = pid
        os.read(self.read_end, 1)
        return

    def start_server(self):
        """Start the HTTP server."""
        app = GrackleService(MemoryStore(self.message_archives))
        service = make_server('', self.port, app)
        self.is_ready()
        if self.write_logs:
            logging.basicConfig(
                stream=sys.stderr, level=logging.INFO)
        service.serve_forever()

    def __exit__(self, exc_type, exc_val, traceback):
        os.kill(self.pid, SIGKILL)


if __name__ == '__main__':
    app = GrackleService(MemoryStore({}))
    service = make_server('', 8787, app)
    service.serve_forever()