Skip to content

Commit

Permalink
feat: added option to run rest server in HTTPS mode
Browse files Browse the repository at this point in the history
  • Loading branch information
exelban committed Oct 16, 2021
1 parent 34010e6 commit 7c3cd65
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 98 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
.idea
cert.pem
key.pem
4 changes: 2 additions & 2 deletions 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/v5 v5.0.3 // indirect
github.com/go-chi/chi/v5 v5.0.3
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.3.0
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 // indirect
)
10 changes: 2 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
146 changes: 120 additions & 26 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,53 @@ package rest

import (
"context"
"crypto/tls"
"fmt"
"github.com/go-chi/chi/v5"
"github.com/pkg/errors"
"log"
"net/http"
"os"
"sync"
"sync/atomic"
"time"
)

// Server - http server struct
type SSLConfig struct {
Port int // https server port
Redirect bool // defines if http requests will be redirected to the https
URL string // url where http requests will be redirected

CertPath string // path to the ssl certificate
KeyPath string // path to the ssl key
}

// Server - rest server struct
type Server struct {
Address string
Port int
IsReady *atomic.Value
SSL *SSLConfig

ReadHeaderTimeout time.Duration
WriteTimeout time.Duration
IdleTimeout time.Duration

httpServer *http.Server
}

func handler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("."))
}
httpServer *http.Server
httpsServer *http.Server

func ready(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("."))
mu sync.Mutex
}

// Run - will initialize server and run it on provided port
func (s *Server) Run(router http.Handler) error {
if s.Address == "*" {
s.Address = ""
}
if s.Port == 0 {
s.Port = 8080
}

if s.IsReady == nil {
s.IsReady = &atomic.Value{}
s.IsReady.Store(true)
Expand All @@ -57,40 +67,124 @@ func (s *Server) Run(router http.Handler) error {
if router == nil {
mux := chi.NewRouter()
mux.Use(Readiness("/readiness", s.IsReady))
mux.HandleFunc("/ping", handler)
mux.HandleFunc("/liveness", handler)
mux.HandleFunc("/ping", okHandler)
mux.HandleFunc("/liveness", okHandler)
router = mux
}

s.httpServer = &http.Server{
Addr: fmt.Sprintf(":%d", s.Port),
Handler: router,
ReadHeaderTimeout: s.ReadHeaderTimeout,
WriteTimeout: s.WriteTimeout,
IdleTimeout: s.IdleTimeout,
log.Printf("[INFO] http rest server on %s:%d", s.Address, s.Port)

httpRouter := router

if s.SSL != nil {
if s.SSL.Port == 0 {
s.SSL.Port = s.Port + 1
}

if s.SSL.Redirect {
mux := chi.NewRouter()
mux.Handle("/*", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
newURL := s.SSL.URL + r.URL.Path
if r.URL.RawQuery != "" {
newURL += "?" + r.URL.RawQuery
}
http.Redirect(w, r, newURL, http.StatusTemporaryRedirect)
}))
httpRouter = mux

log.Printf("[INFO] http redirect server on %s:%d", s.Address, s.Port)
}

if _, err := os.Stat(s.SSL.CertPath); os.IsNotExist(err) {
return errors.Wrap(err, "ssl certificate file not found")
}
if _, err := os.Stat(s.SSL.KeyPath); os.IsNotExist(err) {
return errors.Wrap(err, "ssl key file not found")
}

log.Printf("[INFO] https rest server on %s:%d", s.Address, s.SSL.Port)

s.mu.Lock()
s.httpsServer = s.https(s.Address, s.SSL.Port, router)
s.mu.Unlock()

go func() {
log.Printf("[WARN] https server terminated, %s", s.httpsServer.ListenAndServeTLS(s.SSL.CertPath, s.SSL.KeyPath))
}()
}

log.Printf("[INFO] rest server started on %s", s.httpServer.Addr)
s.mu.Lock()
s.httpServer = s.http(s.Address, s.Port, httpRouter)
s.mu.Unlock()

if err := s.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
return err
return errors.Wrap(err, "start http server")
}

return nil
}

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

ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
s.mu.Lock()
defer s.mu.Unlock()

if s.httpServer != nil {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()

if err := s.httpServer.Shutdown(ctx); err != nil {
return err
}
}
if s.httpsServer != nil {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()

if err := s.httpsServer.Shutdown(ctx); err != nil {
return err
}
}

return nil
}

func (s *Server) http(address string, port int, router http.Handler) *http.Server {
return &http.Server{
Addr: fmt.Sprintf("%s:%d", address, port),
Handler: router,
ReadHeaderTimeout: s.ReadHeaderTimeout,
WriteTimeout: s.WriteTimeout,
IdleTimeout: s.IdleTimeout,
}
}
func (s *Server) https(address string, port int, router http.Handler) *http.Server {
server := s.http(address, port, router)
server.TLSConfig = &tls.Config{
PreferServerCipherSuites: true,
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
},
MinVersion: tls.VersionTLS12,
CurvePreferences: []tls.CurveID{
tls.CurveP256,
tls.X25519,
tls.CurveP384,
},
}
return server
}

func okHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("."))
}
Loading

0 comments on commit 7c3cd65

Please sign in to comment.