Last active
May 3, 2025 17:40
-
-
Save drio/d64a8d05a5ccce486046cd787acd3513 to your computer and use it in GitHub Desktop.
RC database
This file contains hidden or 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" | |
"sync" | |
) | |
/* | |
Progress: | |
- [X] Basic functionality | |
- [X] Refactor server to be able to test | |
- [X] Add basic tests | |
- [X] Synchronize the store so it works concurrently without race conditions | |
- [X] Refactor the store to work with different backends (mem, file) | |
- [ ] Enumerate the many things that can go wrong and that we should look for, | |
particularly in a production setup | |
Extras: | |
- [ ] Add explicit tests to test the locking mechanism (currently using -race with test) | |
*/ | |
type Store interface { | |
Get(key string) (string, bool) | |
Set(key, value string) | |
} | |
type Server struct { | |
store Store | |
} | |
type MemoryStore struct { | |
mu sync.RWMutex | |
data map[string]string | |
} | |
func NewMemoryStore() Store { | |
return &MemoryStore{ | |
data: make(map[string]string), | |
} | |
} | |
func (m *MemoryStore) Get(key string) (string, bool) { | |
m.mu.RLock() | |
defer m.mu.RUnlock() | |
val, ok := m.data[key] | |
return val, ok | |
} | |
func (m *MemoryStore) Set(key, value string) { | |
m.mu.Lock() | |
defer m.mu.Unlock() | |
m.data[key] = value | |
} | |
func (s *Server) setHandler(w http.ResponseWriter, r *http.Request) { | |
log.Printf("GET %s?%s", r.URL.Path, r.URL.RawQuery) | |
query := r.URL.Query() | |
// NOTE: I am allowing for multiple keys. Maybe that is not what we want. | |
// Also, when we send multiple values of a key, I pick the first one. | |
for key, values := range query { | |
s.store.Set(key, values[0]) | |
} | |
fmt.Fprint(w, "ok") | |
} | |
func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) { | |
log.Printf("GET %s?%s", r.URL.Path, r.URL.RawQuery) | |
query := r.URL.Query() | |
keys := query["key"] | |
if len(keys) != 1 { | |
http.Error(w, "Exactly one 'key' parameter is required", http.StatusBadRequest) | |
return | |
} | |
if value, ok := s.store.Get(keys[0]); ok { | |
fmt.Fprintf(w, "%s\n", value) | |
} else { | |
http.Error(w, "key not found", http.StatusNotFound) | |
} | |
} | |
func main() { | |
s := &Server{ | |
store: NewMemoryStore(), | |
} | |
http.HandleFunc("/set", s.setHandler) | |
http.HandleFunc("/get", s.getHandler) | |
fmt.Println("Server is running on http://localhost:4000.") | |
err := http.ListenAndServe(":4000", nil) | |
if err != nil { | |
panic(err) | |
} | |
} |
This file contains hidden or 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 ( | |
"net/http" | |
"net/http/httptest" | |
"testing" | |
) | |
func TestSetAndGet(t *testing.T) { | |
s := &Server{ | |
store: NewMemoryStore(), | |
} | |
// The handler needs a http response writer and a request | |
// Fake both and ... | |
reqSet := httptest.NewRequest("GET", "/set?foo=bar", nil) | |
wSet := httptest.NewRecorder() | |
s.setHandler(wSet, reqSet) | |
// the fake writer for testing | |
if wSet.Code != http.StatusOK { | |
t.Fatalf("when sending a valid /set request I expect 200, but got %d", wSet.Code) | |
} | |
reqGet := httptest.NewRequest("GET", "/get?key=foo", nil) | |
wGet := httptest.NewRecorder() | |
s.getHandler(wGet, reqGet) | |
if wGet.Code != http.StatusOK { | |
t.Fatalf("expected 200, got %d", wGet.Code) | |
} | |
expected := "bar\n" | |
if wGet.Body.String() != expected { | |
t.Errorf("expected %q, got %q", expected, wGet.Body.String()) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment