Skip to content

Commit

Permalink
feat: Add source code for "Let's Go" chapter (#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
igor-baiborodine authored Mar 8, 2023
1 parent fc92289 commit 16ee475
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 1 deletion.
22 changes: 22 additions & 0 deletions LetsGo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
## Let's Go

### Test Your API

Start the server:
```shell
$ go run cmd/server/main.go
```

Add records:
```shell
$ curl -X POST localhost:8080 -d '{"record": { "value": "TGV0J3MgR28gIzMK" }}'
$ curl -X POST localhost:8080 -d '{"record": { "value": "TGV0J3MgR28gIzEK" }}'
$ curl -X POST localhost:8080 -d '{"record": { "value": "TGV0J3MgR28gIzIK" }}'
```

Retrieve records:
```shell
$ curl -X GET localhost:8080 -d '{"offset": 0}'
$ curl -X GET localhost:8080 -d '{"offset": 1}'
$ curl -X GET localhost:8080 -d '{"offset": 2}'
```
12 changes: 12 additions & 0 deletions LetsGo/cmd/server/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package main

import (
"log"

"github.com/igor-baiborodine/proglog/internal/server"
)

func main() {
srv := server.NewHTTPServer(":8080")
log.Fatal(srv.ListenAndServe())
}
5 changes: 5 additions & 0 deletions LetsGo/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/igor-baiborodine/proglog

go 1.13

require github.com/gorilla/mux v1.7.3
2 changes: 2 additions & 0 deletions LetsGo/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
89 changes: 89 additions & 0 deletions LetsGo/internal/server/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package server

import (
"encoding/json"
"net/http"

"github.com/gorilla/mux"
)

func NewHTTPServer(addr string) *http.Server {
httpsrv := newHTTPServer()
r := mux.NewRouter()
r.HandleFunc("/", httpsrv.handleProduce).Methods("POST")
r.HandleFunc("/", httpsrv.handleConsume).Methods("GET")
return &http.Server{
Addr: addr,
Handler: r,
}
}

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 {
Offset uint64 `json:"offset"`
}

type ConsumeResponse 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 {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
off, err := s.Log.Append(req.Record)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
res := ProduceResponse{Offset: off}
err = json.NewEncoder(w).Encode(res)
if err != nil {
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 {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
record, err := s.Log.Read(req.Offset)
if err == ErrOffsetNotFound {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
res := ConsumeResponse{Record: record}
err = json.NewEncoder(w).Encode(res)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
39 changes: 39 additions & 0 deletions LetsGo/internal/server/log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package server

import (
"fmt"
"sync"
)

type Log struct {
mu sync.Mutex
records []Record
}

func NewLog() *Log {
return &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
}

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
}

type Record struct {
Value []byte `json:"value"`
Offset uint64 `json:"offset"`
}

var ErrOffsetNotFound = fmt.Errorf("offset not found")
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ but is updated and executed against the latest versions of dependent Go packages

**Table of Contents**

- [ ] 1.Let's Go
- [X] 1.[Let's Go](/LetsGo)
- [ ] 2.Structure Data with Protocol Buffers
- [ ] 3.Write a Log Package
- [ ] 4.Serve Requests with gRPC
Expand Down

0 comments on commit 16ee475

Please sign in to comment.