Done with chapter 1. Basic http/json log server.

This commit is contained in:
Mariano Uvalle 2021-07-29 16:52:03 -05:00
parent 407c8a15c9
commit 9c9c3b4697
5 changed files with 172 additions and 0 deletions

12
cmd/server/main.go Normal file
View file

@ -0,0 +1,12 @@
package main
import (
"log"
"github.com/AYM1607/proglog/internal/server"
)
func main() {
srv := server.NewHTTPServer(":8080")
log.Fatal(srv.ListenAndServe())
}

5
go.mod Normal file
View file

@ -0,0 +1,5 @@
module github.com/AYM1607/proglog
go 1.16
require github.com/gorilla/mux v1.8.0

2
go.sum Normal file
View file

@ -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=

107
internal/server/http.go Normal file
View file

@ -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
}
}

46
internal/server/log.go Normal file
View file

@ -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")