Skip to content

Instantly share code, notes, and snippets.

@chbrandt
Created May 16, 2024 16:34
Show Gist options
  • Save chbrandt/87b36edd9b7ed3650ff60f387adc9eb3 to your computer and use it in GitHub Desktop.
Save chbrandt/87b36edd9b7ed3650ff60f387adc9eb3 to your computer and use it in GitHub Desktop.
Recurse Center Database Server code sample
"""
RC Database Server code sample
> Before your interview, write a program that runs a server that is accessible
> on `http://localhost:4000/`. When your server receives a request
> on `http://localhost:4000/set?somekey=somevalue` it should store
> the passed key and value in memory. When it receives a request
> on `http://localhost:4000/get?key=somekey` it should return
> the value stored at `somekey`.
> During your interview, you will pair on saving the data to a file.
> You can start with simply appending each write to the file,
> and work on making it more efficient if you have time.
How to Run
----------
To run this server, all you need is Python 3 installed (developed with Python 3.12).
Save this file/code as 'myserver.py' (for example), and run it with
```
$ python myserver.py
```
The "myserver" will accept requests at `http://localhost:4000`.
You can then use paths `/set` to set a key-value pair in the internal database,
and `/get` to retrieve a value from the database. For example:
- `http://localhost:4000/set?thekey=thevalue` will set the pair `thekey`, `thevalue`;
- `http://localhost:4000/get?key=thekey` will get the value (`thevalue`) for database.
Implementation
--------------
Python provides an API for implementing a simple HTTP server in its standard library:
- https://docs.python.org/3/library/http.server.html
If you are not well versed in client-server workflow (like me), this doc may be useful:
- https://developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps/Client-Server_overview
We are going to use two classes from Python's `http.server` library:
- HTTPServer, responsible for listening for http requests;
- BaseHTTPRequestHandler, responsible for handling the requests.
BaseHTTPRequestHandler is a base class, needs to be customized to answer for
specific methods. All requests we're handling here are GET requests. According
to the path in the URI request, database will be written or read:
- `set` : request server to store a *key-value* pair. Only one pair at a time.
- `get` : request server to retrieve a *value* given a *key*.
The *query* component is necessarily composed by an attribute `key`
indicating the *key* (value) to retrieve.
Success/Error outputs
---------------------
Errors may happen in different situation, the status codes and situations covered:
- `400`, "bad request"
* when (URL) path is different than `/get` or `/set`
* when query is not correct (e.g. "somekey=somevalue" (set), or "key=somekey" (get))
- `404`, "not found"
* when `/get` key does not exist in database
- `200`, success
* `/set` or `/get` were executed successfully.
"""
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs
from typing import Tuple
# Database
_DB = {}
class MyHandler(BaseHTTPRequestHandler):
"""
Handles all requests of this server
"""
def do_GET(self):
"""
Handles GET requests, send fail/success responses
"""
_actions = {
'/set': set_db,
'/get': get_db
}
# Our (valid) requests are always GET queries, ie. URLs with the
# structure: /{get,set}?key=value
# So, we can use and trust urllib's `urlparse` and `parse_qs`
# to decide if this is a valid request and how to answer.
_parts = urlparse(self.path)
path = _parts.path
query = _parts.query
# Return "bad request" if not '/get' nor '/set' in URL's path
if not (path and path in _actions.keys()):
code = 400
text = "Invalid path, try '/set' or '/get'"
self.log_error('%s', text)
# Return "bad request" if there is no query component in URI
elif not query:
code = 400
text = "Invalid query, try '/set?somekey=somevalue' or '/get?key=somekey'"
self.log_error('%s', text)
else:
code, text = _actions[path](_DB, query)
if code >= 400:
self.log_error('%s', text)
self.log_request(code)
# Send request status code (in header)
self.send_response(code)
self.end_headers()
# Write data value (or message error) in response body
if isinstance(text, str):
self.wfile.write(bytes(text, encoding='utf8'))
else:
assert isinstance(text, bytes), type(text)
self.wfile.write(text)
def set_db(db:dict, query:str) -> Tuple[int,str]:
"""
Set key-value in db if 'query' is valid, return (code, message)
A valid query for us contains ONE pair of key-value, no blanks (eg, "key=value").
Input:
- db: database
- query: URI query (string) component (e.g. "key=value")
Output:
- `(code,message)`: tuple containing request status-code and status message
Possible codes are "200" or "400" in case of exception parsing 'query'
"""
try:
kv = parse_qs(query,
keep_blank_values=False,
strict_parsing=True,
max_num_fields=1)
except Exception as err:
return (400, str(err))
assert len(kv) == 1
for k,v in kv.items():
db[k] = v[0]
return (200, F"Key-Value pair successfully set")
def get_db(db:dict, query:str) -> Tuple[int,str]:
"""
Get value associate to key in 'db', key is given in 'query' string
A valid query for us contains ONE pair as "key=somekey", "key" is mandatory.
Input:
- db : database
- query: URI query (string) component (e.g. "key=somekey")
Output:
- `(code,message)`: tuple containing request status-code and status message
Possible codes are "200" or "400" in case of exception parsing 'query',
and "404" in case the requested key (eg, "somekey") is not found in Db.
"""
try:
kv = parse_qs(query,
keep_blank_values=False,
strict_parsing=True,
max_num_fields=1)
assert 'key' in kv
except Exception as err:
return (400, str(err))
key = kv['key'][0]
if not key in db:
return (404, F"Key {key} not found")
return (200, db[key])
if __name__ == '__main__':
host = 'localhost'
port = 4000
server_address = (host, port)
httpd = HTTPServer(server_address, MyHandler)
print(F"Server started at http://{host}:{port}")
print(F"- Use '/set?somekey=somevalue' to set a key-value pair in the database.")
print(F"- Use '/get?key=somekey' to get the value associated to 'somekey' in the database.")
try:
httpd.serve_forever()
except KeyboardInterrupt:
print()
httpd.server_close()
print("Server stopped.")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment