Skip to content

Commit

Permalink
feat: add Readiness middleware
Browse files Browse the repository at this point in the history
feat: update chi to v5
feat: update gh action
fix: update some comments
  • Loading branch information
exelban committed Jun 2, 2021
1 parent 2c0e19a commit a7a5c9b
Show file tree
Hide file tree
Showing 9 changed files with 77 additions and 19 deletions.
7 changes: 3 additions & 4 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ jobs:
steps:

- name: Set up Go
uses: actions/setup-go@v1
uses: actions/setup-go@v2
with:
go-version: 1.13
id: go
go-version: "^1.16"

- uses: actions/checkout@v1
- uses: actions/checkout@v2

- name: Test
run: go test -race -coverprofile=coverage.out -covermode=atomic
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.13

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-chi/chi v4.0.3+incompatible
github.com/go-chi/chi/v5 v5.0.3 // indirect
github.com/stretchr/testify v1.3.0
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 // indirect
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-chi/chi v4.0.3+incompatible h1:gakN3pDJnzZN5jqFV2TEdF66rTfKeITyR8qu6ekICEY=
github.com/go-chi/chi v4.0.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-chi/chi/v5 v5.0.3 h1:khYQBdPivkYG1s1TAzDQG1f6eX4kD2TItYVZexL5rS4=
github.com/go-chi/chi/v5 v5.0.3/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down
14 changes: 13 additions & 1 deletion middleware.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package rest

import (
"github.com/go-chi/chi/middleware"
"github.com/go-chi/chi/v5/middleware"
"log"
"net/http"
"net/url"
"strings"
"sync/atomic"
"time"
)

Expand Down Expand Up @@ -39,3 +40,14 @@ func Logger(next http.Handler) http.Handler {
}
return http.HandlerFunc(fn)
}

// Readiness - middleware for the readiness probe
func Readiness(isReady *atomic.Value) http.HandlerFunc {
return func(w http.ResponseWriter, _ *http.Request) {
if isReady == nil || !isReady.Load().(bool) {
http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusOK)
}
}
25 changes: 25 additions & 0 deletions middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"log"
"net/http"
"net/http/httptest"
"sync/atomic"
"testing"
)

Expand All @@ -26,3 +27,27 @@ func TestLogger(t *testing.T) {
fmt.Println(buf.String())
require.NotEmpty(t, buf)
}

func TestReadiness(t *testing.T) {
isReady := &atomic.Value{}
isReady.Store(false)

ts := httptest.NewServer(Readiness(isReady))
defer ts.Close()

resp, err := http.Get(ts.URL)
require.NoError(t, err)
require.Equal(t, http.StatusServiceUnavailable, resp.StatusCode)

isReady.Store(true)

resp, err = http.Get(ts.URL)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)

isReady.Store(false)

resp, err = http.Get(ts.URL)
require.NoError(t, err)
require.Equal(t, http.StatusServiceUnavailable, resp.StatusCode)
}
2 changes: 1 addition & 1 deletion request.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
var ErrEmptyRequest = errors.New("empty request")
var ErrNotPointer = errors.New("not pointer provided")

// Read body from request and trying to unmarshal to provided struct.
// ReadBody - read body from request and trying to unmarshal to provided struct
func ReadBody(w http.ResponseWriter, r *http.Request, str interface{}) error {
if r == nil {
return ErrEmptyRequest
Expand Down
12 changes: 6 additions & 6 deletions response.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"strings"
)

// HttpError - structure for http errors.
// HttpError - structure for http errors
type HttpError struct {
Err string `json:"error"`
Message string `json:"message,omitempty"`
Expand All @@ -23,12 +23,12 @@ var (
ErrNotFound = errors.New("NOT_FOUND")
)

// Just to confirm Error interface.
// Just to confirm Error interface
func (e HttpError) Error() string {
return e.Err
}

// RenderJSON sends data as json.
// RenderJSON sends data as json
func RenderJSON(w http.ResponseWriter, code int, data interface{}) {
buf := &bytes.Buffer{}
enc := json.NewEncoder(buf)
Expand All @@ -48,12 +48,12 @@ func RenderJSON(w http.ResponseWriter, code int, data interface{}) {
_, _ = w.Write(buf.Bytes())
}

// JsonResponse - write a response with application/json Content-Type header.
// JsonResponse - write a response with application/json Content-Type header
func JsonResponse(w http.ResponseWriter, data interface{}) {
RenderJSON(w, http.StatusOK, data)
}

// OKResponse - write a OK response with application/json Content-Type header.
// OkResponse - write a OK response with application/json Content-Type header
func OkResponse(w http.ResponseWriter) {
RenderJSON(w, http.StatusOK, struct {
OK bool `json:"ok"`
Expand All @@ -62,7 +62,7 @@ func OkResponse(w http.ResponseWriter) {
})
}

// JsonError - write a HttpError structure as response.
// ErrorResponse - write a HttpError structure as response
func ErrorResponse(w http.ResponseWriter, r *http.Request, code int, error error, msg string) {
err := HttpError{
Err: http.StatusText(code),
Expand Down
17 changes: 13 additions & 4 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import (
"fmt"
"log"
"net/http"
"sync/atomic"
"time"
)

// Server - http server struct.
// Server - http server struct
type Server struct {
Port int
Port int
IsReady *atomic.Value

ReadHeaderTimeout time.Duration
WriteTimeout time.Duration
Expand All @@ -25,11 +27,16 @@ func handler(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("."))
}

// Run - will initialize server and run it on provided port.
// Run - will initialize server and run it on provided port
func (s *Server) Run(router http.Handler) error {
if s.Port == 0 {
s.Port = 8080
}
if s.IsReady == nil {
s.IsReady = &atomic.Value{}
s.IsReady.Store(true)
}

if s.ReadHeaderTimeout == 0 {
s.ReadHeaderTimeout = 10 * time.Second
}
Expand All @@ -39,10 +46,12 @@ func (s *Server) Run(router http.Handler) error {
if s.IdleTimeout == 0 {
s.IdleTimeout = 60 * time.Second
}

if router == nil {
mux := http.NewServeMux()
mux.HandleFunc("/ping", handler)
mux.HandleFunc("/liveness", handler)
mux.HandleFunc("/readiness", Readiness(s.IsReady))
router = mux
}

Expand All @@ -63,7 +72,7 @@ func (s *Server) Run(router http.Handler) error {
return nil
}

// Shutdown - shutdown http server.
// Shutdown - shutdown http server
func (s *Server) Shutdown(ctx context.Context) error {
log.Print("[INFO] shutdown rest server")

Expand Down
15 changes: 13 additions & 2 deletions server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ func TestServer_Run_EmptyRouter(t *testing.T) {
srv := &Server{
Port: 1234,
}

defer func() {
require.NoError(t, srv.Shutdown(context.Background()))
}()
Expand All @@ -62,13 +63,23 @@ func TestServer_Run_EmptyRouter(t *testing.T) {
}()

host := fmt.Sprintf("http://localhost:%d", srv.Port)
resp, err := http.Get(host+"/ping")
resp, err := http.Get(host + "/ping")
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)

resp, err = http.Get(host + "/liveness")
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)

resp, err = http.Get(host+"/liveness")
resp, err = http.Get(host + "/readiness")
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)

srv.IsReady.Store(false)

resp, err = http.Get(host + "/readiness")
require.NoError(t, err)
require.Equal(t, http.StatusServiceUnavailable, resp.StatusCode)
}

func TestServer_Shutdown(t *testing.T) {
Expand Down

0 comments on commit a7a5c9b

Please sign in to comment.