This file is intended to be used as a rough work which could be used to take notes related to this project.
Last active
August 3, 2020 16:34
-
-
Save scriptnull/44ca83bce6f7f408be3760b300cac2e7 to your computer and use it in GitHub Desktop.
Simple Database for RC
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
db |
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" | |
"os" | |
) | |
func main() { | |
config := struct { | |
store string | |
port uint | |
}{} | |
flag.StringVar(&config.store, "store", "inmemory", "Storage to be used") | |
flag.UintVar(&config.port, "port", 8080, "Port for HTTP server") | |
flag.Parse() | |
store, err := GetStore(config.store) | |
if err != nil { | |
fmt.Fprintf(os.Stderr, "Error configuring the store : %s \n", err) | |
os.Exit(1) | |
} | |
if (config.port < MIN_PORT) || (config.port > MAX_PORT) { | |
fmt.Fprintf(os.Stderr, "Invalid Port Number. Allowed port range: %d to %d \n", MIN_PORT, MAX_PORT) | |
os.Exit(1) | |
} | |
server := &HttpKVServer{ | |
store: store, | |
port: config.port, | |
} | |
fmt.Printf("Starting Server at %s \n", server.Address()) | |
err = server.Listen() | |
if err != nil { | |
fmt.Fprintf(os.Stderr, "Error bringing up server: %s \n", err) | |
os.Exit(1) | |
} | |
} |
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" | |
"net/http" | |
"strings" | |
) | |
const ( | |
MIN_PORT uint = 1024 | |
MAX_PORT uint = 65535 | |
) | |
type HttpKVServer struct { | |
ip string | |
port uint | |
store Store | |
} | |
func (hs *HttpKVServer) Address() string { | |
return fmt.Sprintf("%s:%d", hs.ip, hs.port) | |
} | |
func (hs *HttpKVServer) Listen() error { | |
http.HandleFunc("/set", hs.setHandler()) | |
http.HandleFunc("/get", hs.getHandler()) | |
return http.ListenAndServe(hs.Address(), nil) | |
} | |
func (hs *HttpKVServer) getHandler() http.HandlerFunc { | |
return func(res http.ResponseWriter, req *http.Request) { | |
q := req.URL.Query() | |
key := q.Get("key") | |
value, err := hs.store.Get(key) | |
if err != nil { | |
switch err { | |
case ErrKeyNotFound: | |
res.WriteHeader(http.StatusNotFound) | |
res.Write([]byte("NOT FOUND")) | |
return | |
default: | |
res.WriteHeader(http.StatusInternalServerError) | |
res.Write([]byte("SERVER ERROR")) | |
return | |
} | |
} | |
res.WriteHeader(http.StatusOK) | |
res.Write([]byte(value)) | |
} | |
} | |
func (hs *HttpKVServer) setHandler() http.HandlerFunc { | |
return func(res http.ResponseWriter, req *http.Request) { | |
result := make(map[string]string) | |
for key, values := range req.URL.Query() { | |
// For a query parameter with duplicated key, the first | |
// occuring value will be taken as the value | |
// Example: for query string ?a=b&c=d&a=x | |
// 'b' is the value of a and 'x' is omitted | |
value := values[0] | |
err := hs.store.Set(key, value) | |
if err != nil { | |
result[key] = err.Error() | |
continue | |
} | |
result[key] = "OK" | |
} | |
var str strings.Builder | |
for key, value := range result { | |
str.WriteString(fmt.Sprintf("KEY: %s\n", key)) | |
str.WriteString(fmt.Sprintf("RESULT: %s\n", value)) | |
} | |
res.WriteHeader(http.StatusOK) | |
res.Write([]byte(str.String())) | |
} | |
} |
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" | |
"io/ioutil" | |
"net/http" | |
"net/http/httptest" | |
"strings" | |
"sync" | |
"testing" | |
) | |
func TestHttpKVServer(t *testing.T) { | |
httpServer := &HttpKVServer{ | |
store: newInMemoryStore(), | |
} | |
getHandler := httpServer.getHandler() | |
setHandler := httpServer.setHandler() | |
t.Run("Get Unset Key", func(t *testing.T) { | |
rec := httptest.NewRecorder() | |
req := httptest.NewRequest("GET", "localhost:8080/get?key=unknown", nil) | |
getHandler(rec, req) | |
res := rec.Result() | |
if res.StatusCode != http.StatusNotFound { | |
t.Errorf("Expects HTTP response status to be 200 OK, but got %d", res.StatusCode) | |
} | |
resBytes, err := ioutil.ReadAll(res.Body) | |
if err != nil { | |
t.Errorf("Expects not to error out, but got %s", err) | |
} | |
result := string(resBytes) | |
if result != "NOT FOUND" { | |
t.Errorf("Expects response to be NOT FOUND, but got %s", result) | |
} | |
}) | |
t.Run("Set one key in one request", func(t *testing.T) { | |
key := "singlekey" | |
value := "singlevalue" | |
rec := httptest.NewRecorder() | |
setURL := fmt.Sprintf("localhost:8080/set?%s=%s", key, value) | |
req := httptest.NewRequest("GET", setURL, nil) | |
setHandler(rec, req) | |
res := rec.Result() | |
if res.StatusCode != http.StatusOK { | |
t.Errorf("Expects HTTP response status to be 200 OK, but got %d", res.StatusCode) | |
} | |
resBytes, err := ioutil.ReadAll(res.Body) | |
if err != nil { | |
t.Errorf("Expects not to error out, but got %s", err) | |
} | |
expected := fmt.Sprintf("KEY: %s\nRESULT: OK\n", key) | |
result := string(resBytes) | |
if result != expected { | |
t.Errorf("Expects correct response for set operation, got %s", result) | |
} | |
rec = httptest.NewRecorder() | |
getURL := fmt.Sprintf("localhost:8080/get?key=%s", key) | |
req = httptest.NewRequest("GET", getURL, nil) | |
getHandler(rec, req) | |
res = rec.Result() | |
if res.StatusCode != http.StatusOK { | |
t.Errorf("Expects HTTP response status to be 200 OK, but got %d", res.StatusCode) | |
} | |
resBytes, err = ioutil.ReadAll(res.Body) | |
if err != nil { | |
t.Errorf("Expects not to error out, but got %s", err) | |
} | |
result = string(resBytes) | |
expected = value | |
if result != expected { | |
t.Errorf("Expects %s, but got %s", expected, result) | |
} | |
}) | |
t.Run("Set multiple keys in one request", func(t *testing.T) { | |
var setURL strings.Builder | |
setURL.WriteString("localhost:8080/set?") | |
for i := 0; i < 10; i++ { | |
setURL.WriteString(fmt.Sprintf("multikey%d=multivalue%d&", i, i)) | |
} | |
rec := httptest.NewRecorder() | |
req := httptest.NewRequest("GET", setURL.String(), nil) | |
setHandler(rec, req) | |
res := rec.Result() | |
if res.StatusCode != http.StatusOK { | |
t.Errorf("Expects HTTP response status to be 200 OK, but got %d", res.StatusCode) | |
} | |
resBytes, err := ioutil.ReadAll(res.Body) | |
if err != nil { | |
t.Errorf("Expects not to error out, but got %s", err) | |
} | |
result := string(resBytes) | |
for i := 0; i < 10; i++ { | |
expected := fmt.Sprintf("KEY: multikey%d\nRESULT: OK\n", i) | |
if !strings.Contains(result, expected) { | |
t.Errorf("Expects correct response for set operation, got %s", result) | |
} | |
} | |
for i := 0; i < 10; i++ { | |
key := fmt.Sprintf("multikey%d", i) | |
value := fmt.Sprintf("multivalue%d", i) | |
rec = httptest.NewRecorder() | |
getURL := fmt.Sprintf("localhost:8080/get?key=%s", key) | |
req = httptest.NewRequest("GET", getURL, nil) | |
getHandler(rec, req) | |
res = rec.Result() | |
if res.StatusCode != http.StatusOK { | |
t.Errorf("Expects HTTP response status to be 200 OK, but got %d", res.StatusCode) | |
} | |
resBytes, err = ioutil.ReadAll(res.Body) | |
if err != nil { | |
t.Errorf("Expects not to error out, but got %s", err) | |
} | |
result := string(resBytes) | |
expected := value | |
if result != expected { | |
t.Errorf("Expects %s, but got %s", expected, result) | |
} | |
} | |
}) | |
t.Run("Set one key in serial requests", func(t *testing.T) { | |
key := "singlekey_serial" | |
value := "" | |
for i := 0; i < 100; i++ { | |
value = fmt.Sprintf("value%d", i) | |
rec := httptest.NewRecorder() | |
setURL := fmt.Sprintf("localhost:8080/set?%s=%s", key, value) | |
req := httptest.NewRequest("GET", setURL, nil) | |
setHandler(rec, req) | |
res := rec.Result() | |
if res.StatusCode != http.StatusOK { | |
t.Errorf("Expects HTTP response status to be 200 OK, but got %d", res.StatusCode) | |
} | |
resBytes, err := ioutil.ReadAll(res.Body) | |
if err != nil { | |
t.Errorf("Expects not to error out, but got %s", err) | |
} | |
expected := fmt.Sprintf("KEY: %s\nRESULT: OK\n", key) | |
result := string(resBytes) | |
if result != expected { | |
t.Errorf("Expects correct response for set operation, got %s", result) | |
} | |
} | |
rec := httptest.NewRecorder() | |
getURL := fmt.Sprintf("localhost:8080/get?key=%s", key) | |
req := httptest.NewRequest("GET", getURL, nil) | |
getHandler(rec, req) | |
res := rec.Result() | |
if res.StatusCode != http.StatusOK { | |
t.Errorf("Expects HTTP response status to be 200 OK, but got %d", res.StatusCode) | |
} | |
resBytes, err := ioutil.ReadAll(res.Body) | |
if err != nil { | |
t.Errorf("Expects not to error out, but got %s", err) | |
} | |
result := string(resBytes) | |
expected := value | |
if result != expected { | |
t.Errorf("Expects %s, but got %s", expected, result) | |
} | |
}) | |
t.Run("Set one key in parallel requests", func(t *testing.T) { | |
var wg sync.WaitGroup | |
key := "singlekey_parallel" | |
var latestValue string | |
var mu sync.Mutex | |
for i := 0; i < 100; i++ { | |
wg.Add(1) | |
go func(i int) { | |
defer wg.Done() | |
mu.Lock() | |
defer mu.Unlock() | |
value := fmt.Sprintf("value%d", i) | |
latestValue = value | |
rec := httptest.NewRecorder() | |
setURL := fmt.Sprintf("localhost:8080/set?%s=%s", key, value) | |
req := httptest.NewRequest("GET", setURL, nil) | |
setHandler(rec, req) | |
res := rec.Result() | |
if res.StatusCode != http.StatusOK { | |
t.Errorf("Expects HTTP response status to be 200 OK, but got %d", res.StatusCode) | |
} | |
resBytes, err := ioutil.ReadAll(res.Body) | |
if err != nil { | |
t.Errorf("Expects not to error out, but got %s", err) | |
} | |
expected := fmt.Sprintf("KEY: %s\nRESULT: OK\n", key) | |
result := string(resBytes) | |
if result != expected { | |
t.Errorf("Expects correct response for set operation, got %s", result) | |
} | |
}(i) | |
} | |
wg.Wait() | |
rec := httptest.NewRecorder() | |
getURL := fmt.Sprintf("localhost:8080/get?key=%s", key) | |
req := httptest.NewRequest("GET", getURL, nil) | |
getHandler(rec, req) | |
res := rec.Result() | |
if res.StatusCode != http.StatusOK { | |
t.Errorf("Expects HTTP response status to be 200 OK, but got %d", res.StatusCode) | |
} | |
resBytes, err := ioutil.ReadAll(res.Body) | |
if err != nil { | |
t.Errorf("Expects not to error out, but got %s", err) | |
} | |
result := string(resBytes) | |
expected := latestValue | |
if result != expected { | |
t.Errorf("Expects %s, but got %s", expected, result) | |
} | |
}) | |
} |
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 ( | |
"errors" | |
"sync" | |
) | |
var ( | |
ErrStoreNotFound = errors.New("Store not found") | |
ErrKeyNotFound = errors.New("Key not found") | |
) | |
type Store interface { | |
Set(key, value string) error | |
Get(key string) (string, error) | |
} | |
func GetStore(store string) (Store, error) { | |
switch store { | |
case "inmemory": | |
return newInMemoryStore(), nil | |
default: | |
return newInMemoryStore(), ErrStoreNotFound | |
} | |
} | |
type inMemoryStore struct { | |
mu sync.RWMutex | |
KVMap map[string]string | |
} | |
func newInMemoryStore() *inMemoryStore { | |
return &inMemoryStore{ | |
KVMap: make(map[string]string), | |
} | |
} | |
func (s *inMemoryStore) Set(key, value string) error { | |
s.mu.Lock() | |
defer s.mu.Unlock() | |
s.KVMap[key] = value | |
return nil | |
} | |
func (s *inMemoryStore) Get(key string) (string, error) { | |
s.mu.RLock() | |
defer s.mu.RUnlock() | |
value, ok := s.KVMap[key] | |
if !ok { | |
return "", ErrKeyNotFound | |
} | |
return value, 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 ( | |
"fmt" | |
"sync" | |
"testing" | |
) | |
func GetUnSetKeyTest(store Store) func(*testing.T) { | |
return func(t *testing.T) { | |
val, err := store.Get("unknown") | |
if err != ErrKeyNotFound { | |
t.Errorf("Expecting ErrKeyNotFound error, but got: %s", err) | |
} | |
if val != "" { | |
t.Errorf("Expecting val to be zero value, but got: %s", val) | |
} | |
} | |
} | |
func SerialSetAndGetTest(store Store) func(*testing.T) { | |
return func(t *testing.T) { | |
for i := 0; i < 100; i++ { | |
key := fmt.Sprintf("key%d", i) | |
value := fmt.Sprintf("value%d", i) | |
err := store.Set(key, value) | |
if err != nil { | |
t.Error(err) | |
} | |
} | |
for i := 0; i < 100; i++ { | |
key := fmt.Sprintf("key%d", i) | |
expectedValue := fmt.Sprintf("value%d", i) | |
value, err := store.Get(key) | |
if err != nil { | |
t.Error(err) | |
} | |
if value != expectedValue { | |
t.Errorf("Expected %s, Got %s", expectedValue, value) | |
} | |
} | |
} | |
} | |
func ParallelSetAndGetTest(store Store) func(*testing.T) { | |
return func(t *testing.T) { | |
key := "main" | |
var wg sync.WaitGroup | |
var lock sync.Mutex | |
var latestValue string | |
var doneMap sync.Map | |
for i := 0; i < 100; i++ { | |
wg.Add(1) | |
value := fmt.Sprintf("value%d", i) | |
doneMap.Store(value, false) | |
go func(value string) { | |
lock.Lock() | |
defer wg.Done() | |
defer lock.Unlock() | |
store.Set(key, value) | |
doneMap.Store(value, true) | |
latestValue = value | |
}(value) | |
} | |
wg.Wait() | |
value, err := store.Get(key) | |
if err != nil { | |
t.Error(err) | |
} | |
if value != latestValue { | |
t.Errorf("Expected %s, got %s", latestValue, value) | |
} | |
doneMap.Range(func(key, value interface{}) bool { | |
v := value.(bool) | |
if !v { | |
k := key.(string) | |
t.Errorf("%s is not SET. Expecting all values to be set", k) | |
} | |
return true | |
}) | |
} | |
} | |
func TestInMemoryStore(t *testing.T) { | |
t.Run("Get Unset key", GetUnSetKeyTest(newInMemoryStore())) | |
t.Run("Serial Set and Get", SerialSetAndGetTest(newInMemoryStore())) | |
t.Run("Parallel Set and Get", ParallelSetAndGetTest(newInMemoryStore())) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment