Skip to content

Instantly share code, notes, and snippets.

@CharlesHolbrow
Created December 18, 2017 18:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save CharlesHolbrow/35347b0c76c9109786c987855f65954b to your computer and use it in GitHub Desktop.
Save CharlesHolbrow/35347b0c76c9109786c987855f65954b to your computer and use it in GitHub Desktop.
HTTP Server for Recurse Center Interview
package main
import (
"fmt"
"io"
"log"
"net/http"
"net/url"
"sync"
"testing"
)
func main() {
server := http.Server{
Handler: &RcHandler{},
Addr: "127.0.0.1:4000",
}
err := server.ListenAndServe()
errorpanic("http.Server failed to listen", err)
}
// No-op if err is nil. Otherwise, panic with reason
func errorpanic(reason string, err error) {
if err != nil {
panic(reason + ": " + err.Error())
}
}
// RcHandler is a minimal http request handler.
//
// It implements the http.Handler interface, and adds Get/Set methods that
// read and write local storage.
type RcHandler struct {
lock sync.RWMutex
db map[string]string
}
// ServeHTTP is RcServer's request handler. It satisfies the http.Handler
// interface.
func (rcs *RcHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
// When ServeHTTP returns, whatever is stored in result will be sent to the
// client.
result := ""
defer func() {
log.Println(req.Method, "-", req.RequestURI, "-", result)
_, err := io.WriteString(res, result)
errorpanic("ServeHTTP failed to write to client", err)
}()
if req.URL.Path == "/set" {
result = rcs.handleSetQuery(req.URL)
return
}
if req.URL.Path == "/get" {
result = rcs.handleGetQuery(req.URL)
return
}
result = fmt.Sprintf("Error: %s not a supported request", req.RequestURI)
}
// Set a value in our server's storage. Safe for concurrent calls.
func (rcs *RcHandler) Set(key, value string) {
rcs.lock.Lock()
defer rcs.lock.Unlock()
// Lazily init storage map
if rcs.db == nil {
rcs.db = make(map[string]string)
}
rcs.db[key] = value
}
// Get a value from our server's storage. Safe for concurrent calls.
func (rcs *RcHandler) Get(key string) (value string) {
rcs.lock.RLock()
defer rcs.lock.RUnlock()
if rcs.db == nil {
return // empty string
}
return rcs.db[key]
}
func (rcs *RcHandler) handleGetQuery(url *url.URL) (result string) {
key := url.Query().Get("key")
if key != "" {
return rcs.Get(key)
}
log.Println("Error: missing 'key=somekey' in /get query")
return
}
func (rcs *RcHandler) handleSetQuery(url *url.URL) (result string) {
// query is of type map[string][]string, and has some helper methods
query := url.Query()
// allow multiple keys
for key := range query {
value := query.Get(key)
rcs.Set(key, value)
result = fmt.Sprintf("%s%s=%s&", result, key, value)
}
return
}
// TestRcHandlerGetSet tests the exported Get and Set methods
func TestRcHandlerGetSet(t *testing.T) {
rcs := RcHandler{}
// Test .Get on an unset value
expected := ""
actual := rcs.Get("testkey")
if actual != expected {
t.Errorf("Get before Set expected %s to be %s", expected, actual)
}
// Test .Get after wetting a value
rcs.Set("testkey", "testvalue")
expected = "testvalue"
actual = rcs.Get("testkey")
if actual != expected {
t.Errorf("Get after Set expected %s to be %s", expected, actual)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment