Created
November 25, 2023 14:46
-
-
Save johnpena/af2b111ca95dc235818185f19226fe1e to your computer and use it in GitHub Desktop.
Recurse Center key value server implementation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"encoding/json" | |
"log" | |
"net/http" | |
) | |
const port = ":4000" | |
type KVStore struct { | |
records map[string]*Record | |
} | |
func (kv *KVStore) Set(key string, value any) *Record { | |
record := &Record{ | |
Key: key, | |
Value: value, | |
} | |
kv.records[key] = record | |
return record | |
} | |
func (kv *KVStore) Get(key string) (*Record, bool) { | |
record, ok := kv.records[key] | |
return record, ok | |
} | |
type Record struct { | |
Key string `json:"key"` | |
Value any `json:"value"` | |
} | |
func NewKVStore() *KVStore { | |
records := make(map[string]*Record) | |
return &KVStore{records: records} | |
} | |
type ErrorResponse struct { | |
Message string `json:"message"` | |
} | |
func WriteError( | |
resp http.ResponseWriter, | |
code int, | |
message string, | |
) { | |
resp.WriteHeader(code) | |
errResp := ErrorResponse{ | |
Message: message, | |
} | |
body, err := json.Marshal(errResp) | |
if err == nil { | |
resp.Write(body) | |
} else { | |
log.Printf("unable to marshal error response: %v", err) | |
} | |
} | |
func main() { | |
log.Printf("starting server on port %s", port) | |
kvStore := NewKVStore() | |
http.HandleFunc("/set", func(resp http.ResponseWriter, req *http.Request) { | |
// always respond with JSON | |
resp.Header().Set("Content-Type", "application/json") | |
values := req.URL.Query() | |
// multiple key-value pairs can be set in one request, e.g. /set?k1=v1&k2=v2 | |
for key, value := range values { | |
// multiple values can be set per key, e.g. /set?k1=v1&k1=v2 | |
// for now, the last set value is stored. | |
// in the future, we could store all values as an array, or reject multiple values. | |
for _, v := range value { | |
record := kvStore.Set(key, v) | |
log.Printf("SET: %s=%v", record.Key, record.Value) | |
body, err := json.Marshal(record) | |
if err != nil { | |
log.Printf("unable to marshal record for response: %s", err.Error()) | |
} else { | |
resp.Write(body) | |
} | |
} | |
} | |
resp.WriteHeader(http.StatusOK) | |
}) | |
http.HandleFunc("/get", func(resp http.ResponseWriter, req *http.Request) { | |
resp.Header().Set("Content-Type", "application/json") | |
values := req.URL.Query() | |
keys, keyIsSet := values["key"] | |
if !keyIsSet || len(keys) == 0 { | |
log.Printf("ERROR: no key provided for request") | |
WriteError( | |
resp, | |
http.StatusBadRequest, | |
"no key was provided. provide a key for a record to retrieve in the url, e.g. /get?key=somekey", | |
) | |
return | |
} | |
if len(keys) != 1 { | |
log.Printf("ERROR: multiple keys provided for request") | |
WriteError( | |
resp, | |
http.StatusBadRequest, | |
"multiple key retrievals are not supported. provide a single key for a record to retrieve in the url, e.g. /get?key=somekey", | |
) | |
return | |
} | |
key := keys[0] | |
record, ok := kvStore.Get(key) | |
if !ok { | |
log.Printf("ERROR?: no record found for key %s", key) | |
WriteError( | |
resp, | |
http.StatusNotFound, | |
"record for key not found", | |
) | |
return | |
} | |
body, err := json.Marshal(record) | |
if err != nil { | |
log.Printf("unable to marshal record: %s", err.Error()) | |
WriteError( | |
resp, | |
http.StatusInternalServerError, | |
"unable to formulate a response", | |
) | |
} else { | |
resp.WriteHeader(http.StatusOK) | |
resp.Write(body) | |
} | |
}) | |
log.Fatal(http.ListenAndServe(port, nil)) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment