10
from signal import SIGKILL
13
from wsgiref.simple_server import make_server
14
from wsgiref.util import shift_path_info
15
from grackle.store import (
21
"""A request handler that forwards to an archive store."""
23
def __init__(self, store):
25
self.logger = logging.getLogger('http')
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)
36
self.query_string = environ['QUERY_STRING']
37
return self.handle_request()
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
elif self.method == 'PUT':
45
elif self.method == 'POST':
47
elif self.method == 'GET':
49
return self.send_response(httplib.METHOD_NOT_ALLOWED)
51
def send_response(self, code, response='', reason=None, headers={}):
52
"""Set the status code and reason, then return the response."""
54
reason = httplib.responses[code]
55
response_status = '%s %s' % (code, reason)
56
response_headers = {'content-type': 'application/json'}
57
response_headers.update(headers.items())
58
self.start_response(response_status, response_headers.items())
62
"""Create an archive or message on PUT."""
63
if len(self.path) == 1:
64
# This expected path is /archive/archive_id.
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.
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__)
83
"""Change a message on POST."""
84
if len(self.path) == 2:
85
# This expected path is /archive/archive_id/message_id.
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__)
97
"""Retrieve a list of messages on GET."""
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__)
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)
114
class ForkedFakeService:
115
"""A Grackle service fake, as a ContextManager."""
117
def __init__(self, port, message_archives=None, write_logs=False):
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.
128
if message_archives is None:
129
self.message_archives = {}
131
self.message_archives = message_archives
132
self.read_end, self.write_end = os.pipe()
133
self.write_logs = write_logs
136
def from_client(client, message_archives=None):
137
"""Instantiate a ForkedFakeService from the client.
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.
144
return ForkedFakeService(client.port, message_archives)
147
"""Tell the parent process that the server is ready for writes."""
148
os.write(self.write_end, 'asdf')
153
Fork and start a server in the child. Return when the server is ready
159
os.read(self.read_end, 1)
162
def start_server(self):
163
"""Start the HTTP server."""
164
app = GrackleService(MemoryStore(self.message_archives))
165
service = make_server('', self.port, app)
169
stream=sys.stderr, level=logging.INFO)
170
service.serve_forever()
172
def __exit__(self, exc_type, exc_val, traceback):
173
os.kill(self.pid, SIGKILL)
176
if __name__ == '__main__':
177
app = GrackleService(MemoryStore({}))
178
service = make_server('', 8787, app)
179
service.serve_forever()