From 9c9c3b469731624ecbc468c6e83963f690507cd8 Mon Sep 17 00:00:00 2001 From: AYM1607 Date: Thu, 29 Jul 2021 16:52:03 -0500 Subject: [PATCH] Done with chapter 1. Basic http/json log server. --- cmd/server/main.go | 12 +++++ go.mod | 5 ++ go.sum | 2 + internal/server/http.go | 107 ++++++++++++++++++++++++++++++++++++++++ internal/server/log.go | 46 +++++++++++++++++ 5 files changed, 172 insertions(+) create mode 100644 cmd/server/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/server/http.go create mode 100644 internal/server/log.go diff --git a/cmd/server/main.go b/cmd/server/main.go new file mode 100644 index 0000000..dec4593 --- /dev/null +++ b/cmd/server/main.go @@ -0,0 +1,12 @@ +package main + +import ( + "log" + + "github.com/AYM1607/proglog/internal/server" +) + +func main() { + srv := server.NewHTTPServer(":8080") + log.Fatal(srv.ListenAndServe()) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..0874d9d --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/AYM1607/proglog + +go 1.16 + +require github.com/gorilla/mux v1.8.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5350288 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= diff --git a/internal/server/http.go b/internal/server/http.go new file mode 100644 index 0000000..bf2e8f8 --- /dev/null +++ b/internal/server/http.go @@ -0,0 +1,107 @@ +package server + +import ( + "encoding/json" + "net/http" + + "github.com/gorilla/mux" +) + +// NewHTTPServer creates the configuration for a log http server. +func NewHTTPServer(addr string) *http.Server { + // httpsrv is the struct that contains the log. + httpsrv := newHTTPServer() + // r is the http router. + r := mux.NewRouter() + + r.HandleFunc("/", httpsrv.handleProduce).Methods("POST") + r.HandleFunc("/", httpsrv.handleConsume).Methods("GET") + + return &http.Server{ + Addr: addr, + Handler: r, + } +} + +// httpServer defines the handlers and the data necessary for the log server. +type httpServer struct { + Log *Log +} + +func newHTTPServer() *httpServer { + return &httpServer{ + Log: NewLog(), + } +} + +type ProduceRequest struct { + Record Record `json:"record"` +} + +type ProduceResponse struct { + Offset uint64 `json:"offset"` +} + +type ConsumeRequest struct { + Offsset uint64 `json:"offset"` +} + +type ConsumerResponse struct { + Record Record `json:"record"` +} + +func (s *httpServer) handleProduce(w http.ResponseWriter, r *http.Request) { + var req ProduceRequest + err := json.NewDecoder(r.Body).Decode(&req) + if err != nil { + // the request does not conform to the specified type in ProduceRequest. + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + off, err := s.Log.Append(req.Record) + if err != nil { + // The record could not be added to the log. + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + res := ProduceResponse{Offset: off} + // The header gets overrided if there's an encoding error. + w.WriteHeader(http.StatusCreated) + err = json.NewEncoder(w).Encode(res) + if err != nil { + // The information could not be encoded to the response struct. + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + +} + +func (s *httpServer) handleConsume(w http.ResponseWriter, r *http.Request) { + var req ConsumeRequest + err := json.NewDecoder(r.Body).Decode(&req) + if err != nil { + // The request does not conform to the specified type in ConsumeRequest. + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + record, err := s.Log.Read(req.Offsset) + // Handle possible errors when retrieving the log. + if err == ErrOffsetNotFound { + http.Error(w, err.Error(), http.StatusNotFound) + return + } + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + res := ConsumerResponse{Record: record} + err = json.NewEncoder(w).Encode(res) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} diff --git a/internal/server/log.go b/internal/server/log.go new file mode 100644 index 0000000..dbb5cd7 --- /dev/null +++ b/internal/server/log.go @@ -0,0 +1,46 @@ +package server + +import ( + "errors" + "sync" +) + +// Log represents a commit log. +type Log struct { + mu sync.Mutex + records []Record +} + +// NewLog returns a new ready to use Log. +func NewLog() *Log { + return &Log{} +} + +// Append adds a new record to a log. +func (c *Log) Append(record Record) (uint64, error) { + c.mu.Lock() + defer c.mu.Unlock() + record.Offset = uint64(len(c.records)) + c.records = append(c.records, record) + return record.Offset, nil +} + +// Read returns an individual record from the log given its offset. +// Returns an error if the offset does not exist. +func (c *Log) Read(offset uint64) (Record, error) { + c.mu.Lock() + defer c.mu.Unlock() + if offset >= uint64(len(c.records)) { + return Record{}, ErrOffsetNotFound + } + + return c.records[offset], nil +} + +// Record represents a single record in a commit log. +type Record struct { + Value []byte `json:"value"` + Offset uint64 `json:"offset"` +} + +var ErrOffsetNotFound = errors.New("offset not found")