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
if '://' in environ['PATH_INFO']:
32
# All the needed information is embedded in PATH_INFO.
33
self.host_port = shift_path_info(environ)
34
self.application = shift_path_info(environ)
35
path = environ['PATH_INFO'].split('/')
36
self.scheme = path.pop(0)
38
elif environ['SCRIPT_NAME'] == '':
39
# Remove the application to set the path.
40
self.application = shift_path_info(environ)
41
self.path = environ['PATH_INFO'].split('/')
43
self.application = environ['SCRIPT_NAME']
44
self.path = environ['PATH_INFO'].split('/')
45
self.query_string = environ['QUERY_STRING']
46
return self.handle_request()
48
def handle_request(self):
49
"""Select the method to handle the request and return a response."""
50
if self.application != 'archive':
51
return self.send_response(httplib.NOT_FOUND)
52
elif self.method == 'PUT':
54
elif self.method == 'POST':
56
elif self.method == 'GET':
58
return self.send_response(httplib.METHOD_NOT_ALLOWED)
60
def send_response(self, code, response='', reason=None, headers={}):
61
"""Set the status code and reason, then return the response."""
63
reason = httplib.responses[code]
64
response_status = '%s %s' % (code, reason)
65
response_headers = {'content-type': 'application/json'}
66
response_headers.update(headers.items())
67
self.start_response(response_status, response_headers.items())
71
"""Create an archive or message on PUT."""
72
if len(self.path) == 1:
73
# This expected path is /archive/archive_id.
75
self.store.put_archive(self.path[0])
76
return self.send_response(httplib.CREATED)
77
except Exception, error:
78
return self.send_response(
79
httplib.BAD_REQUEST, reason=error.__doc__)
80
elif len(self.path) == 2:
81
# This expected path is /archive/archive_id/message_id.
83
put_input = self.environ['wsgi.input']
84
message = put_input.read(int(self.environ['CONTENT_LENGTH']))
85
self.store.put_message(self.path[0], self.path[1], message)
86
return self.send_response(httplib.CREATED)
87
except Exception, error:
88
return self.send_response(
89
httplib.BAD_REQUEST, reason=error.__doc__)
92
"""Change a message on POST."""
93
if len(self.path) == 2:
94
# This expected path is /archive/archive_id/message_id.
96
# This expected path is /archive/archive_id/message_id.
97
response = self.store.hide_message(
98
self.path[0], self.path[1], 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__)
106
"""Retrieve a list of messages on GET."""
108
response = self.store.get_messages(
109
self.path[0], self.query_string)
110
response = simplejson.dumps(response)
111
return self.send_response(httplib.OK, response=response)
112
except Exception, error:
113
return self.send_response(
114
httplib.BAD_REQUEST, reason=error.__doc__)
116
def log_message(self, format, *args):
117
"""Override log_message to use standard Python logging."""
118
message = "%s - - [%s] %s\n" % (
119
self.address_string(), self.log_date_time_string(), format % args)
120
self.logger.info(message)
123
class ForkedFakeService:
124
"""A Grackle service fake, as a ContextManager."""
126
def __init__(self, port, message_archives=None, write_logs=False):
129
:param port: The tcp port to use.
130
:param message_archives: A dict of lists of dicts representing
131
archives of messages. The outer dict represents the archive,
132
the list represents the list of messages for that archive.
133
:param write_logs: If true, log messages will be written to stdout.
137
if message_archives is None:
138
self.message_archives = {}
140
self.message_archives = message_archives
141
self.read_end, self.write_end = os.pipe()
142
self.write_logs = write_logs
145
def from_client(client, message_archives=None):
146
"""Instantiate a ForkedFakeService from the client.
148
:param port: The client to provide service for.
149
:param message_archives: A dict of lists of dicts representing
150
archives of messages. The outer dict represents the archive,
151
the list represents the list of messages for that archive.
153
return ForkedFakeService(client.port, message_archives)
156
"""Tell the parent process that the server is ready for writes."""
157
os.write(self.write_end, 'asdf')
162
Fork and start a server in the child. Return when the server is ready
168
os.read(self.read_end, 1)
171
def start_server(self):
172
"""Start the HTTP server."""
173
app = GrackleService(MemoryStore(self.message_archives))
174
service = make_server('', self.port, app)
178
stream=sys.stderr, level=logging.INFO)
179
service.serve_forever()
181
def __exit__(self, exc_type, exc_val, traceback):
182
os.kill(self.pid, SIGKILL)
185
if __name__ == '__main__':
186
app = GrackleService(MemoryStore({}))
187
service = make_server('', 8787, app)
188
service.serve_forever()