Skip to content

Instantly share code, notes, and snippets.

@cagedmantis
Created December 27, 2017 18:05
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 cagedmantis/bc84b0d86f8ba1ed437cf4ab6ff1144c to your computer and use it in GitHub Desktop.
Save cagedmantis/bc84b0d86f8ba1ed437cf4ab6ff1144c to your computer and use it in GitHub Desktop.
Simple storage server
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)
}
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)
}
}
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
}
}
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