Skip to content

Commit

Permalink
Refactor (#20)
Browse files Browse the repository at this point in the history
* Firts refactor commit

* Update .gitignore

* Switch over to using the conf package

* More refactoring

* Finish refactoring

* Add missing server package
  • Loading branch information
raphaelreyna committed Aug 4, 2020
1 parent f6ac4e7 commit b28df50
Show file tree
Hide file tree
Showing 3 changed files with 242 additions and 0 deletions.
86 changes: 86 additions & 0 deletions internal/server/add-route.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package server

import (
"net/http"
"sync"
)

// AddRoute adds a new single fire route to the server.
func (s *Server) AddRoute(route *Route) {
if s.wg == nil {
s.wg = &sync.WaitGroup{}
s.wg.Add(1)
go func() {
s.wg.Wait()
s.Done <- s.finishedRoutes
close(s.Done)
}()
}

okMetric := true
if route.MaxRequests != 0 {
okMetric = false
} else if route.MaxOK == 0 {
route.MaxOK = 1
}

rr := s.router.HandleFunc(route.Pattern, func(w http.ResponseWriter, r *http.Request) {
var rc int64
var err error
route.Lock()
route.reqCount++

if okMetric {
switch {
case route.okCount >= route.MaxOK:
route.DoneHandlerFunc(w, r)
case route.okCount < route.MaxOK:
err = route.HandlerFunc(w, r)

if err == nil || err == OKDoneErr {
route.okCount++
err = OKDoneErr
} else if err != OKNotDoneErr {
s.internalError(err.Error())
}

if route.okCount == route.MaxOK {
s.Lock()
s.finishedRoutes[route] = err
s.Unlock()
s.wg.Done()
}
}
route.Unlock()
return
}

rc = route.reqCount
route.Unlock()
switch {
case rc > route.MaxRequests:
route.DoneHandlerFunc(w, r)
case rc <= route.MaxRequests:
err = route.HandlerFunc(w, r)
if err == nil || err == OKDoneErr {
route.Lock()
route.okCount++
route.Unlock()
err = OKDoneErr
} else if err != OKNotDoneErr {
s.internalError(err.Error())
}

if rc == route.MaxRequests {
s.Lock()
s.finishedRoutes[route] = err
s.Unlock()
s.wg.Done()
}
}
})

if len(route.Methods) > 0 {
rr.Methods(route.Methods...)
}
}
29 changes: 29 additions & 0 deletions internal/server/route.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package server

import (
"net/http"
"sync"
"sync/atomic"
)

type Route struct {
Pattern string
Methods []string
HandlerFunc func(w http.ResponseWriter, r *http.Request) error
DoneHandlerFunc http.HandlerFunc
MaxOK int64
MaxRequests int64

reqCount int64
okCount int64

sync.Mutex
}

func (r *Route) RequestCount() int64 {
return atomic.LoadInt64(&r.reqCount)
}

func (r *Route) OkCount() int64 {
return atomic.LoadInt64(&r.okCount)
}
127 changes: 127 additions & 0 deletions internal/server/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package server

import (
"context"
"errors"
"log"
"net/http"
"sync"

"github.com/gorilla/mux"
)

var OKDoneErr = errors.New("route done")
var OKNotDoneErr = errors.New("not done")

type Server struct {
Port string

// Certfile is the public certificate that should be used for TLS
CertFile string
// Keyfile is the private key that should be used for TLS
KeyFile string

// Done signals when the server has shutdown regardless of value.
// Each route that finished will have an error message in the map.
// Routes that finish successfully will have an OKDoneErr error.
Done chan map[*Route]error

ErrorLog *log.Logger
InfoLog *log.Logger

HostAddresses []string

router *mux.Router
server *http.Server

serving bool // Set to true after Serve() is called, false after Stop() or Close()

wg *sync.WaitGroup
sync.Mutex

finishedRoutes map[*Route]error
}

func NewServer() *Server {
s := &Server{
router: mux.NewRouter(),
finishedRoutes: make(map[*Route]error),
}
s.server = &http.Server{Handler: s}
return s
}

func (s *Server) Serve() error {
s.server.Addr = ":" + s.Port

scheme := "http://"
listenAndServe := func() error {
return s.server.ListenAndServe()
}

// Are we using HTTPS?
if s.CertFile != "" || s.KeyFile != "" {
// Error out if only cert or key is given
var err error
switch {
case s.CertFile == "":
err = errors.New("given cert file for HTTPS but no key file. exit")
case s.KeyFile == "":
err = errors.New("given key file for HTTPS but no cert file. exit")
}
if err != nil {
s.internalError(err.Error())
return err
}

scheme = "https://"
listenAndServe = func() error {
return s.server.ListenAndServeTLS(s.CertFile, s.KeyFile)
}
}

var addresses string
for _, ip := range s.HostAddresses {
addresses += "\t - " + scheme + ip + "\n"
}

s.infoLog("listening:\n" + addresses)
s.serving = true
return listenAndServe()
}

func (s *Server) Shutdown(ctx context.Context) error {
if !s.serving {
return nil
}

return s.server.Shutdown(ctx)
}

func (s *Server) Close() error {
if !s.serving {
return nil
}

err := s.server.Close()
return err
}

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if !s.serving {
s.serving = true
}
s.router.ServeHTTP(w, r)
}

func (s *Server) internalError(format string, v ...interface{}) {
if s.ErrorLog != nil {
s.ErrorLog.Printf(format, v...)
}
}

func (s *Server) infoLog(format string, v ...interface{}) {
if s.InfoLog != nil {
s.InfoLog.Printf(format, v...)
}
}

0 comments on commit b28df50

Please sign in to comment.