Skip to content

Instantly share code, notes, and snippets.

@johnpena
Created November 25, 2023 14:46
Show Gist options
  • Save johnpena/af2b111ca95dc235818185f19226fe1e to your computer and use it in GitHub Desktop.
Save johnpena/af2b111ca95dc235818185f19226fe1e to your computer and use it in GitHub Desktop.
Recurse Center key value server implementation
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