-
Notifications
You must be signed in to change notification settings - Fork 0
/
http.go
140 lines (124 loc) · 3.65 KB
/
http.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package imageservice
import (
"encoding/json"
"fmt"
"net/http"
"os"
"runtime"
"strings"
"time"
log "github.com/Sirupsen/logrus"
"github.com/bakins/logrus-middleware"
"github.com/bakins/net-http-recover"
"github.com/gorilla/context"
"github.com/gorilla/mux"
"github.com/justinas/alice"
"github.com/tylerb/graceful"
)
const ctxKey string = "mistifyImageServiceContext"
type (
// HTTPResponse is a wrapper for http.ResponseWriter which provides access
// to several convenience methods
HTTPResponse struct {
http.ResponseWriter
}
// HTTPError contains information for http error responses
HTTPError struct {
Message string `json:"message"`
Code int `json:"code"`
Stack []string `json:"stack"`
}
)
// Run starts the server
func Run(ctx *Context, port int) *graceful.Server {
router := mux.NewRouter()
router.StrictSlash(true)
// Common middleware applied to every request
logrusMiddleware := logrusmiddleware.Middleware{
Name: "mistify-image-service",
}
commonMiddleware := alice.New(
func(h http.Handler) http.Handler {
return logrusMiddleware.Handler(h, "")
},
func(h http.Handler) http.Handler {
return recovery.Handler(os.Stderr, h, true)
},
func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
SetContext(r, ctx)
h.ServeHTTP(w, r)
})
},
)
// NOTE: Due to weirdness with PrefixPath and StrictSlash, can't just pass
// a prefixed subrouter to the register functions and have the base path
// work cleanly. The register functions need to add a base path handler to
// the main router before setting subhandlers on either main or subrouter
RegisterImageRoutes("/images", router)
server := &graceful.Server{
Timeout: 5 * time.Second,
Server: &http.Server{
Addr: fmt.Sprintf(":%d", port),
Handler: commonMiddleware.Then(router),
MaxHeaderBytes: 1 << 20,
},
}
go listenAndServe(server)
return server
}
func listenAndServe(server *graceful.Server) {
if err := server.ListenAndServe(); err != nil {
// Ignore the error from closing the listener, which is involved in the
// graceful shutdown
if !strings.Contains(err.Error(), "use of closed network connection") {
log.WithField("error", err).Fatal("server error")
}
}
}
// JSON writes appropriate headers and JSON body to the http response
func (hr *HTTPResponse) JSON(code int, obj interface{}) {
hr.Header().Set("Content-Type", "application/json")
hr.WriteHeader(code)
encoder := json.NewEncoder(hr)
if err := encoder.Encode(obj); err != nil {
hr.JSONError(http.StatusInternalServerError, err)
}
}
// JSONError prepares an HTTPError with a stack trace and writes it with
// HTTPResponse.JSON
func (hr *HTTPResponse) JSONError(code int, err error) {
httpError := &HTTPError{
Message: err.Error(),
Code: code,
Stack: make([]string, 0, 4),
}
for i := 1; ; i++ { //
pc, file, line, ok := runtime.Caller(i)
if !ok {
break
}
// Print this much at least. If we can't find the source, it won't show.
httpError.Stack = append(httpError.Stack, fmt.Sprintf("%s:%d (0x%x)", file, line, pc))
}
hr.JSON(code, httpError)
}
// JSONMsg is a convenience method to write a JSON response with just a message
// string
func (hr *HTTPResponse) JSONMsg(code int, msg string) {
msgObj := map[string]string{
"message": msg,
}
hr.JSON(code, msgObj)
}
// SetContext sets a Context value for a request
func SetContext(r *http.Request, ctx *Context) {
context.Set(r, ctxKey, ctx)
}
// GetContext retrieves a Context value for a request
func GetContext(r *http.Request) *Context {
if value := context.Get(r, ctxKey); value != nil {
return value.(*Context)
}
return nil
}