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(
43
httplib.BAD_REQUEST, reason='Unsupported URL')
44
if self.method == 'PUT':
46
elif self.method == 'POST':
48
elif self.method == 'GET':
50
return self.send_response(
51
httplib.BAD_REQUEST, reason='Unsupported method')
53
def send_response(self, code, response='', reason=None, headers={}):
54
"""Set the status code and reason, then return the response."""
56
reason = httplib.responses[code]
57
response_code = '%s %s' % (code, reason)
58
response_headers = {'content-type': 'application/json'}
59
response_headers.update(headers.items())
60
self.start_response(response_code, response_headers.items())
64
"""Create an archive or message on PUT."""
65
if len(self.path) == 1:
66
# This expected path is /archive/archive_id.
68
self.store.put_archive(self.path[0])
69
return self.send_response(httplib.CREATED)
70
except Exception, error:
71
return self.send_response(
72
httplib.BAD_REQUEST, reason=error.__doc__)
73
elif len(self.path) == 2:
74
# This expected path is /archive/archive_id/message_id.
76
put_input = self.environ['wsgi.input']
77
message = put_input.read(int(self.environ['CONTENT_LENGTH']))
78
self.store.put_message(self.path[0], self.path[1], message)
79
return self.send_response(httplib.CREATED)
80
except Exception, error:
81
return self.send_response(
82
httplib.BAD_REQUEST, reason=error.__doc__)
85
"""Change a message on POST."""
86
if len(self.path) == 2:
87
# This expected path is /archive/archive_id/message_id.
89
# This expected path is /archive/archive_id/message_id.
90
response = self.store.hide_message(
91
self.path[0], self.path[1], self.query_string)
92
response = simplejson.dumps(response)
93
return self.send_response(httplib.OK, response=response)
94
except Exception, error:
95
return self.send_response(
96
httplib.BAD_REQUEST, reason=error.__doc__)
99
"""Retrieve a list of messages on GET."""
101
response = self.store.get_messages(
102
self.path[0], self.query_string)
103
response = simplejson.dumps(response)
104
return self.send_response(httplib.OK, response=response)
105
except Exception, error:
106
return self.send_response(
107
httplib.BAD_REQUEST, reason=error.__doc__)
109
def log_message(self, format, *args):
110
"""Override log_message to use standard Python logging."""
111
message = "%s - - [%s] %s\n" % (
112
self.address_string(), self.log_date_time_string(), format % args)
113
self.logger.info(message)
116
class ForkedFakeService:
117
"""A Grackle service fake, as a ContextManager."""
119
def __init__(self, port, message_archives=None, write_logs=False):
122
:param port: The tcp port to use.
123
:param message_archives: A dict of lists of dicts representing
124
archives of messages. The outer dict represents the archive,
125
the list represents the list of messages for that archive.
126
:param write_logs: If true, log messages will be written to stdout.
130
if message_archives is None:
131
self.message_archives = {}
133
self.message_archives = message_archives
134
self.read_end, self.write_end = os.pipe()
135
self.write_logs = write_logs
138
def from_client(client, message_archives=None):
139
"""Instantiate a ForkedFakeService from the client.
141
:param port: The client to provide service for.
142
:param message_archives: A dict of lists of dicts representing
143
archives of messages. The outer dict represents the archive,
144
the list represents the list of messages for that archive.
146
return ForkedFakeService(client.port, message_archives)
149
"""Tell the parent process that the server is ready for writes."""
150
os.write(self.write_end, 'asdf')
155
Fork and start a server in the child. Return when the server is ready
161
os.read(self.read_end, 1)
164
def start_server(self):
165
"""Start the HTTP server."""
166
app = GrackleService(MemoryStore(self.message_archives))
167
service = make_server('', self.port, app)
171
stream=sys.stderr, level=logging.INFO)
172
service.serve_forever()
174
def __exit__(self, exc_type, exc_val, traceback):
175
os.kill(self.pid, SIGKILL)
178
if __name__ == '__main__':
179
app = GrackleService(MemoryStore({}))
180
service = make_server('', 8787, app)
181
service.serve_forever()