98 lines
2 KiB
Go
98 lines
2 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"encoding/json"
|
||
|
"log"
|
||
|
"net/http"
|
||
|
"path"
|
||
|
"strings"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
type application struct {
|
||
|
config config
|
||
|
v1 *v1Handler
|
||
|
}
|
||
|
|
||
|
type config struct {
|
||
|
addr string
|
||
|
}
|
||
|
|
||
|
func (h *application) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||
|
wWrapper := &wrappedResponseWriter{w, http.StatusOK}
|
||
|
w = wWrapper
|
||
|
|
||
|
// fullpath middleware
|
||
|
ctx := context.WithValue(r.Context(), "fullPath", r.URL.Path)
|
||
|
|
||
|
// logger middleware
|
||
|
startTime := time.Now()
|
||
|
defer func() {
|
||
|
log.Printf("request at %s, responded with status %d in %dms", ctx.Value("fullPath"), wWrapper.statusCode, time.Since(startTime).Milliseconds())
|
||
|
}()
|
||
|
|
||
|
// recoverer middleware
|
||
|
defer func() {
|
||
|
err := recover()
|
||
|
if err != nil {
|
||
|
log.Println(err)
|
||
|
|
||
|
jsonBody, _ := json.Marshal(map[string]string{
|
||
|
"error": "There was an internal server error",
|
||
|
})
|
||
|
|
||
|
w.Header().Set("Content-Type", "application/json")
|
||
|
w.WriteHeader(http.StatusInternalServerError)
|
||
|
w.Write(jsonBody)
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
// timeout middleware
|
||
|
ctx, cancel := context.WithTimeout(ctx, time.Minute)
|
||
|
defer func() {
|
||
|
cancel()
|
||
|
if ctx.Err() == context.DeadlineExceeded {
|
||
|
w.WriteHeader(http.StatusGatewayTimeout)
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
r = r.WithContext(ctx)
|
||
|
|
||
|
var head string
|
||
|
head, r.URL.Path = shiftPath(r.URL.Path)
|
||
|
|
||
|
switch head {
|
||
|
case "v1":
|
||
|
h.v1.ServeHTTP(w, r)
|
||
|
default:
|
||
|
http.Error(w, "Not Found", http.StatusNotFound)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (app *application) run() error {
|
||
|
srv := &http.Server{
|
||
|
Addr: app.config.addr,
|
||
|
Handler: app,
|
||
|
WriteTimeout: time.Second * 30,
|
||
|
ReadTimeout: time.Second * 10,
|
||
|
IdleTimeout: time.Minute,
|
||
|
}
|
||
|
|
||
|
log.Printf("server has started at %s", app.config.addr)
|
||
|
|
||
|
return srv.ListenAndServe()
|
||
|
}
|
||
|
|
||
|
// ShiftPath splits off the first component of p, which will be cleaned of
|
||
|
// relative components before processing. head will never contain a slash and
|
||
|
// tail will always be a rooted path without trailing slash.
|
||
|
func shiftPath(p string) (head, tail string) {
|
||
|
p = path.Clean("/" + p)
|
||
|
i := strings.Index(p[1:], "/") + 1
|
||
|
if i <= 0 {
|
||
|
return p[1:], "/"
|
||
|
}
|
||
|
return p[1:i], p[i:]
|
||
|
}
|