10
from signal import SIGKILL
13
from wsgiref.headers import Headers
14
from wsgiref.simple_server import make_server
15
from wsgiref.util import shift_path_info
17
from grackle.store import (
23
"""A request handler that forwards to an archive store."""
25
def __init__(self, store):
27
self.logger = logging.getLogger('http')
29
def __call__(self, environ, start_response):
30
self.environ = environ
31
self.start_response = start_response
32
self.headers = Headers([('content-type', 'application/json')])
33
self.method = environ['REQUEST_METHOD']
34
if '://' in environ['PATH_INFO']:
35
# All the needed information is embedded in PATH_INFO.
36
shift_path_info(environ) # shift the host and or port.
37
self.application = shift_path_info(environ)
38
path = environ['PATH_INFO'].split('/')
39
path.pop(0) # Pop the scheme.
41
elif environ['SCRIPT_NAME'] == '':
42
# Remove the application to set the path.
43
self.application = shift_path_info(environ)
44
self.path = environ['PATH_INFO'].split('/')
46
self.application = environ['SCRIPT_NAME']
47
self.path = environ['PATH_INFO'].split('/')
48
self.query_string = environ['QUERY_STRING']
49
return self.handle_request()
51
def handle_request(self):
52
"""Select the method to handle the request and return a response."""
53
if self.application != 'archive':
54
return self.send_response(httplib.NOT_FOUND)
55
elif self.method == 'PUT':
57
elif self.method == 'POST':
59
elif self.method == 'GET':
61
return self.send_response(httplib.METHOD_NOT_ALLOWED)
63
def send_response(self, code, response='', reason=None):
64
"""Set the status code and reason, then return the response."""
66
reason = httplib.responses[code]
67
response_status = '%s %s' % (code, reason)
68
self.start_response(response_status, self.headers.items())
72
"""Create an archive or message on PUT."""
73
if len(self.path) == 1:
74
# This expected path is /archive/archive_id.
76
self.store.put_archive(self.path[0])
77
return self.send_response(httplib.CREATED)
78
except Exception, error:
79
return self.send_response(
80
httplib.BAD_REQUEST, reason=error.__doc__)
81
elif len(self.path) == 2:
82
# This expected path is /archive/archive_id/message_id.
84
put_input = self.environ['wsgi.input']
85
message = put_input.read(int(self.environ['CONTENT_LENGTH']))
86
self.store.put_message(self.path[0], self.path[1], message)
87
return self.send_response(httplib.CREATED)
88
except Exception, error:
89
return self.send_response(
90
httplib.BAD_REQUEST, reason=error.__doc__)
93
"""Change a message on POST."""
94
if len(self.path) == 2:
95
# This expected path is /archive/archive_id/message_id.
97
# This expected path is /archive/archive_id/message_id.
98
response = self.store.hide_message(
99
self.path[0], self.path[1], self.query_string)
100
response = simplejson.dumps(response)
101
return self.send_response(httplib.OK, response=response)
102
except Exception, error:
103
return self.send_response(
104
httplib.BAD_REQUEST, reason=error.__doc__)
107
"""Retrieve a list of messages on GET."""
109
response = self.store.get_messages(
110
self.path[0], self.query_string)
111
response = simplejson.dumps(response)
112
return self.send_response(httplib.OK, response=response)
113
except Exception, error:
114
return self.send_response(
115
httplib.BAD_REQUEST, reason=error.__doc__)
117
def log_message(self, format, *args):
118
"""Override log_message to use standard Python logging."""
119
message = "%s - - [%s] %s\n" % (
120
self.address_string(), self.log_date_time_string(), format % args)
121
self.logger.info(message)
124
class ForkedFakeService:
125
"""A Grackle service fake, as a ContextManager."""
127
def __init__(self, port, message_archives=None, write_logs=False):
130
:param port: The tcp port to use.
131
:param message_archives: A dict of lists of dicts representing
132
archives of messages. The outer dict represents the archive,
133
the list represents the list of messages for that archive.
134
:param write_logs: If true, log messages will be written to stdout.
138
if message_archives is None:
139
self.message_archives = {}
141
self.message_archives = message_archives
142
self.read_end, self.write_end = os.pipe()
143
self.write_logs = write_logs
146
def from_client(client, message_archives=None):
147
"""Instantiate a ForkedFakeService from the client.
149
:param port: The client to provide service for.
150
:param message_archives: A dict of lists of dicts representing
151
archives of messages. The outer dict represents the archive,
152
the list represents the list of messages for that archive.
154
return ForkedFakeService(client.port, message_archives)
157
"""Tell the parent process that the server is ready for writes."""
158
os.write(self.write_end, 'asdf')
163
Fork and start a server in the child. Return when the server is ready
169
os.read(self.read_end, 1)
172
def start_server(self):
173
"""Start the HTTP server."""
174
app = GrackleService(MemoryStore(self.message_archives))
175
service = make_server('', self.port, app)
179
stream=sys.stderr, level=logging.INFO)
180
service.serve_forever()
182
def __exit__(self, exc_type, exc_val, traceback):
183
os.kill(self.pid, SIGKILL)
186
if __name__ == '__main__':
187
app = GrackleService(MemoryStore({}))
188
service = make_server('', 8787, app)
189
service.serve_forever()