6
from signal import SIGKILL
9
from wsgiref.simple_server import make_server
10
from wsgiref.util import shift_path_info
11
from grackle.store import (
17
"""A request handler that forwards to server.store."""
19
def __init__(self, store):
21
self.logger = logging.getLogger('http')
23
def __call__(self, environ, start_response):
24
self.environ = environ
25
self.start_response = start_response
26
self.method = environ['REQUEST_METHOD']
27
self.host_port = shift_path_info(environ)
28
self.application = shift_path_info(environ)
29
path = environ['PATH_INFO'].split('/')
30
self.scheme = path.pop(0)
32
self.query_string = environ['QUERY_STRING']
33
return self.handle_request()
35
def handle_request(self):
36
"""Select the method to handle the request and return a response."""
37
if self.application != 'archive':
38
return self.send_response(
39
httplib.BAD_REQUEST, reason='Unsupported URL')
40
if self.method == 'PUT':
42
elif self.method == 'POST':
44
elif self.method == 'GET':
46
return self.send_response(
47
httplib.BAD_REQUEST, reason='Unsupported method')
49
def send_response(self, code, response='', reason=None, headers={}):
50
"""Set the status code and reason, then return the response."""
52
reason = httplib.responses[code]
53
response_code = '%s %s' % (code, reason)
54
response_headers = {'content-type': 'application/json'}
55
response_headers.update(headers.items())
56
self.start_response(response_code, response_headers.items())
60
"""Create an archive or message on PUT."""
61
if len(self.path) == 1:
62
# This expected path is /archive/archive_id.
64
self.store.put_archive(self.path[0])
65
return self.send_response(httplib.CREATED)
66
except Exception, error:
67
return self.send_response(
68
httplib.BAD_REQUEST, reason=error.__doc__)
69
elif len(self.path) == 2:
70
# This expected path is /archive/archive_id/message_id.
72
put_input = self.environ['wsgi.input']
73
message = put_input.read(int(self.environ['CONTENT_LENGTH']))
74
self.store.put_message(self.path[0], self.path[1], message)
75
return self.send_response(httplib.CREATED)
76
except Exception, error:
77
return self.send_response(
78
httplib.BAD_REQUEST, reason=error.__doc__)
81
"""Change a message on POST."""
82
if len(self.path) == 2:
83
# This expected path is /archive/archive_id/message_id.
85
# This expected path is /archive/archive_id/message_id.
86
response = self.store.hide_message(
87
self.path[0], self.path[1], self.query_string)
88
response = simplejson.dumps(response)
89
return self.send_response(httplib.OK, response=response)
90
except Exception, error:
91
return self.send_response(
92
httplib.BAD_REQUEST, reason=error.__doc__)
95
"""Retrieve a list of messages on GET."""
97
response = self.store.get_messages(
98
self.path[0], 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__)
105
def log_message(self, format, *args):
106
"""Override log_message to use standard Python logging."""
107
message = "%s - - [%s] %s\n" % (
108
self.address_string(), self.log_date_time_string(), format % args)
109
self.logger.info(message)
112
class ForkedFakeService:
113
"""A Grackle service fake, as a ContextManager."""
115
def __init__(self, port, message_archives=None, write_logs=False):
118
:param port: The tcp port to use.
119
:param message_archives: A dict of lists of dicts representing
120
archives of messages. The outer dict represents the archive,
121
the list represents the list of messages for that archive.
122
:param write_logs: If true, log messages will be written to stdout.
126
if message_archives is None:
127
self.message_archives = {}
129
self.message_archives = message_archives
130
self.read_end, self.write_end = os.pipe()
131
self.write_logs = write_logs
134
def from_client(client, message_archives=None):
135
"""Instantiate a ForkedFakeService from the client.
137
:param port: The client to provide service for.
138
:param message_archives: A dict of lists of dicts representing
139
archives of messages. The outer dict represents the archive,
140
the list represents the list of messages for that archive.
142
return ForkedFakeService(client.port, message_archives)
145
"""Tell the parent process that the server is ready for writes."""
146
os.write(self.write_end, 'asdf')
151
Fork and start a server in the child. Return when the server is ready
157
os.read(self.read_end, 1)
160
def start_server(self):
161
"""Start the HTTP server."""
162
app = GrackleService(MemoryStore(self.message_archives))
163
service = make_server('', self.port, app)
167
stream=sys.stderr, level=logging.INFO)
168
service.serve_forever()
170
def __exit__(self, exc_type, exc_val, traceback):
171
os.kill(self.pid, SIGKILL)
174
def application(environ, start_response):
175
start_response('200 OK', [('Content-Type', 'text/plain')])
179
if __name__ == '__main__':
180
app = GrackleService(MemoryStore({}))
181
service = make_server('', 8787, app)
182
service.serve_forever()