Created
December 27, 2017 18:05
-
-
Save cagedmantis/bc84b0d86f8ba1ed437cf4ab6ff1144c to your computer and use it in GitHub Desktop.
Simple storage server
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 ( | |
"fmt" | |
"log" | |
"net/http" | |
) | |
// web provides an interface for the setting and getting of | |
// keys via http endpoints. | |
type web interface { | |
setHandler(w http.ResponseWriter, r *http.Request) | |
getHandler(w http.ResponseWriter, r *http.Request) | |
} | |
func newStoreWeb(s store) web { | |
return &webStore{ | |
s: s, | |
} | |
} | |
type webStore struct { | |
s store | |
} | |
// setHandler reads the params as key value pairs. The pairs are stored in | |
// the data store. | |
func (ws *webStore) setHandler(w http.ResponseWriter, r *http.Request) { | |
v := r.URL.Query() | |
log.Printf("recieved set request: %s", r.URL.Path[1:]) | |
if r.Method != http.MethodGet { | |
w.WriteHeader(http.StatusNotFound) | |
log.Printf("incorrect method: %s", r.Method) | |
return | |
} | |
for k, vs := range v { | |
var val string | |
for _, v := range vs { | |
val = val + v | |
} | |
err := ws.s.save(r.Context(), k, val) | |
if err != nil { | |
w.WriteHeader(http.StatusInternalServerError) | |
log.Printf("error saving value: %s", err) | |
return | |
} | |
} | |
} | |
// getHandler requires a key param to be passed in as part of the url. The | |
// key is used to retrieve any value associated with that key in the data store. | |
func (ws *webStore) getHandler(w http.ResponseWriter, r *http.Request) { | |
v := r.URL.Query() | |
log.Printf("recieved get request: %s", r.URL.Path[1:]) | |
if r.Method != http.MethodGet { | |
log.Printf("incorrect method: %s", r.Method) | |
w.WriteHeader(http.StatusNotFound) | |
return | |
} | |
key, ok := v["key"] | |
if !ok { | |
w.WriteHeader(http.StatusNotFound) | |
log.Print("key not set") | |
return | |
} | |
if len(key) != 1 { | |
w.WriteHeader(http.StatusNotFound) | |
log.Print("invalid count of keys") | |
return | |
} | |
k := key[0] | |
val, ok, err := ws.s.retrieve(r.Context(), k) | |
if err != nil { | |
w.WriteHeader(http.StatusInternalServerError) | |
log.Print(err) | |
return | |
} | |
if !ok { | |
w.WriteHeader(http.StatusNotFound) | |
log.Printf("key=%s not found", k) | |
return | |
} | |
fmt.Fprintf(w, "%s", val) | |
} |
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 ( | |
"flag" | |
"fmt" | |
"log" | |
"net/http" | |
) | |
const ( | |
defaultPort = 4000 | |
defaultHost = "localhost" | |
) | |
func main() { | |
port := flag.Int("port", defaultPort, "Set the port for the web server to listen on") | |
host := flag.String("host", defaultHost, "Set the host or IP address to listen on") | |
flag.Parse() | |
addr := fmt.Sprintf("%s:%d", *host, *port) | |
log.Printf("Starting dataserv on %s", addr) | |
st := newMemStore() | |
ws := newStoreWeb(st) | |
http.HandleFunc("/get", ws.getHandler) | |
http.HandleFunc("/set", ws.setHandler) | |
err := http.ListenAndServe(addr, nil) | |
if err != nil { | |
log.Printf("error starting http server: %s", err) | |
} | |
} |
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 ( | |
"sync" | |
"golang.org/x/net/context" | |
) | |
type store interface { | |
save(ctx context.Context, k string, v string) error | |
retrieve(ctx context.Context, k string) (string, bool, error) | |
} | |
type memStore struct { | |
m sync.Map | |
} | |
func newMemStore() store { | |
return &memStore{} | |
} | |
// save inserts key value pairs into the thread safe | |
// backing map. | |
func (ms *memStore) save(ctx context.Context, k string, v string) error { | |
select { | |
case <-ctx.Done(): | |
return ctx.Err() | |
default: | |
ms.m.Store(k, v) | |
return nil | |
} | |
} | |
// retrieve checks if a value associated with a key is found in the backing | |
// store. It it is found it returns the value. If it is not found | |
// the bool value is set to false. | |
func (ms *memStore) retrieve(ctx context.Context, k string) (string, bool, error) { | |
select { | |
case <-ctx.Done(): | |
return "", false, ctx.Err() | |
default: | |
if v, ok := ms.m.Load(k); ok { | |
return v.(string), ok, nil | |
} | |
return "", false, nil | |
} | |
} |
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 ( | |
"context" | |
"testing" | |
"time" | |
) | |
func TestSave(t *testing.T) { | |
eCtx, c := context.WithTimeout(context.Background(), 0*time.Second) | |
defer c() | |
testCases := []struct { | |
desc string | |
ctx context.Context | |
k string | |
v string | |
wantErr error | |
wantVal interface{} | |
wantOk bool | |
}{ | |
{ | |
desc: "expired deadline", | |
ctx: eCtx, | |
k: "a", | |
v: "b", | |
wantErr: context.DeadlineExceeded, | |
wantVal: nil, | |
wantOk: false, | |
}, | |
{ | |
desc: "base store", | |
ctx: context.Background(), | |
k: "a", | |
v: "b", | |
wantErr: nil, | |
wantVal: "b", | |
wantOk: true, | |
}, | |
} | |
for _, tc := range testCases { | |
t.Run(tc.desc, func(t *testing.T) { | |
ms := &memStore{} | |
err := ms.save(tc.ctx, tc.k, tc.v) | |
if err != tc.wantErr { | |
t.Errorf("want %s got %s", tc.wantErr, err) | |
} | |
v, ok := ms.m.Load(tc.k) | |
if ok != tc.wantOk { | |
t.Errorf("want %t got %t", tc.wantOk, ok) | |
} | |
if v != tc.wantVal { | |
t.Errorf("want %s got %s", tc.wantVal, v) | |
} | |
}) | |
} | |
} | |
func TestRetrieve(t *testing.T) { | |
eCtx, c := context.WithTimeout(context.Background(), 0*time.Second) | |
defer c() | |
testCases := []struct { | |
desc string | |
ctx context.Context | |
k string | |
v string | |
wantErr error | |
wantVal string | |
wantOk bool | |
}{ | |
{ | |
desc: "expired deadline", | |
ctx: eCtx, | |
k: "a", | |
v: "b", | |
wantErr: context.DeadlineExceeded, | |
wantVal: "", | |
wantOk: false, | |
}, | |
{ | |
desc: "base retrieve", | |
ctx: context.Background(), | |
k: "a", | |
v: "b", | |
wantErr: nil, | |
wantVal: "b", | |
wantOk: true, | |
}, | |
} | |
for _, tc := range testCases { | |
t.Run(tc.desc, func(t *testing.T) { | |
ms := &memStore{} | |
ms.m.Store(tc.k, tc.v) | |
v, ok, err := ms.retrieve(tc.ctx, tc.k) | |
if err != tc.wantErr { | |
t.Errorf("want %s got %s", tc.wantErr, err) | |
} | |
if v != tc.wantVal { | |
t.Errorf("want %s got %s", tc.wantVal, v) | |
} | |
if ok != tc.wantOk { | |
t.Errorf("want %t got %t", tc.wantOk, ok) | |
} | |
}) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment