Skip to content

Instantly share code, notes, and snippets.

@scriptnull
Last active August 3, 2020 16:34
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 scriptnull/44ca83bce6f7f408be3760b300cac2e7 to your computer and use it in GitHub Desktop.
Save scriptnull/44ca83bce6f7f408be3760b300cac2e7 to your computer and use it in GitHub Desktop.
Simple Database for RC

This file is intended to be used as a rough work which could be used to take notes related to this project.

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)
}
}
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()))
}
}
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)
}
})
}
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
}
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