forked from goadesign/goa
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapplication.go
More file actions
278 lines (245 loc) · 9.63 KB
/
application.go
File metadata and controls
278 lines (245 loc) · 9.63 KB
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
package goa
import (
"encoding/json"
"fmt"
"net/http"
"os"
"github.com/julienschmidt/httprouter"
"golang.org/x/net/context"
log "gopkg.in/inconshreveable/log15.v2"
)
type (
// Service is the interface implemented by all goa services.
// It provides methods for configuring a service and running it.
Service interface {
// Logging methods, configure the log handler using the Logger global variable.
log.Logger
// Name is the name of the goa application.
Name() string
// SetErrorHandler allows setting the global service error handler.
SetErrorHandler(ErrorHandler)
// HandleError invokes the global error handler.
HandleError(*Context, error)
// Use adds a middleware to the application middleware chain.
// It is a convenient method for doing append(service.MiddlewareChain(), m)
Use(m Middleware)
// MiddlewareChain returns the current middleware chain.
MiddlewareChain() []Middleware
// ListenAndServe starts a HTTP server on the given port.
ListenAndServe(addr string) error
// ListenAndServeTLS starts a HTTPS server on the given port.
ListenAndServeTLS(add, certFile, keyFile string) error
// HTTPHandler returns a http.Handler interface to the underlying service.
// Note: using the handler directly bypasses the graceful shutdown behavior of
// services instantiated with NewGraceful.
HTTPHandler() http.Handler
}
// Application represents a goa application. At the basic level an application consists of
// a set of controllers, each implementing a given resource actions. goagen generates
// global functions - one per resource - that make it possible to mount the corresponding
// controller onto an application. An application contains the middleware, logger and error
// handler shared by all its controllers. Setting up an application might look like:
//
// api := goa.New("my api")
// api.Use(SomeMiddleware())
// api.Use(SomeOtherMiddleware())
// rc := NewResourceController()
// app.MountResourceController(api, rc)
// api.ListenAndServe(":80")
//
// where NewResourceController returns an object that implements the resource actions as
// defined by the corresponding interface generated by goagen.
Application struct {
log.Logger // Application logger
name string // Application name
errorHandler ErrorHandler // Application error handler
middleware []Middleware // Middleware chain
Router *httprouter.Router // Application router
}
// Handler defines the controller handler signatures.
// Controller handlers accept a context and return an error.
// The context provides typed access to the request and response state. It implements
// the golang.org/x/net/context package Context interface so that handlers may define
// deadlines and cancelation signals - see the Timeout middleware as an example.
// If a controller handler returns an error then the application error handler is invoked
// with the request context and the error. The error handler is responsible for writing the
// HTTP response. See DefaultErrorHandler and TerseErrorHandler.
Handler func(*Context) error
// ErrorHandler defines the application error handler signature.
ErrorHandler func(*Context, error)
)
var (
// Log is the global logger from which other loggers (e.g. request specific loggers) are
// derived. Configure it by setting its handler prior to calling New.
// See https://godoc.org/github.com/inconshreveable/log15
Log log.Logger
// RootContext is the root context from which all request contexts are derived.
// Set values in the root context prior to starting the server to make these values
// available to all request handlers:
//
// goa.RootContext = goa.RootContext.WithValue(key, value)
//
RootContext context.Context
// cancel is the root context CancelFunc.
// Call Cancel to send a cancellation signal to all the active request handlers.
cancel context.CancelFunc
)
// Log to STDOUT by default.
func init() {
Log = log.New()
Log.SetHandler(log.StdoutHandler)
RootContext, cancel = context.WithCancel(context.Background())
}
// New instantiates an application with the given name.
func New(name string) Service {
return &Application{
Logger: Log.New("app", name),
name: name,
errorHandler: DefaultErrorHandler,
Router: httprouter.New(),
}
}
// Cancel sends a cancellation signal to all handlers through the action context.
// see https://godoc.org/golang.org/x/net/context for details on how to handle the signal.
func Cancel() {
cancel()
}
// Name returns the application name.
func (app *Application) Name() string {
return app.name
}
// Use adds a middleware to the application middleware chain.
// See NewMiddleware for wrapping goa and http handlers into goa middleware.
// goa comes with a set of commonly used middleware, see middleware.go.
func (app *Application) Use(m Middleware) {
app.middleware = append(app.middleware, m)
}
// MiddlewareChain returns the current middleware chain.
func (app *Application) MiddlewareChain() []Middleware {
return app.middleware
}
// SetErrorHandler defines an application wide error handler.
// The default error handler (DefaultErrorHandler) responds with a 500 status code and the error
// message in the response body.
// TerseErrorHandler provides an alternative implementation that does not write the error message
// to the response body for internal errors (e.g. for production).
// Set it with SetErrorHandler(TerseErrorHandler).
func (app *Application) SetErrorHandler(handler ErrorHandler) {
app.errorHandler = handler
}
// HandleError invokes the application error handler.
func (app *Application) HandleError(ctx *Context, err error) {
app.errorHandler(ctx, err)
}
// ListenAndServe starts a HTTP server and sets up a listener on the given host/port.
func (app *Application) ListenAndServe(addr string) error {
app.Info("listen", "addr", addr)
return http.ListenAndServe(addr, app.Router)
}
// ListenAndServeTLS starts a HTTPS server and sets up a listener on the given host/port.
func (app *Application) ListenAndServeTLS(addr, certFile, keyFile string) error {
app.Info("listen ssl", "addr", addr)
return http.ListenAndServeTLS(addr, certFile, keyFile, app.Router)
}
// HTTPHandler returns a http.Handler to the service.
func (app *Application) HTTPHandler() http.Handler {
return app.Router
}
// DefaultErrorHandler returns a 400 response for request validation errors (instances of
// BadRequestError) and a 500 response for other errors. It writes the error message to the
// response body in both cases.
func DefaultErrorHandler(c *Context, e error) {
status := 500
if _, ok := e.(*BadRequestError); ok {
c.Header().Set("Content-Type", "application/json")
status = 400
}
if err := c.Respond(status, []byte(e.Error())); err != nil {
Log.Error("failed to send default error handler response", "err", err)
}
}
// TerseErrorHandler behaves like DefaultErrorHandler except that it does not set the response
// body for internal errors.
func TerseErrorHandler(c *Context, e error) {
status := 500
var body []byte
if _, ok := e.(*BadRequestError); ok {
c.Header().Set("Content-Type", "application/json")
status = 400
body = []byte(e.Error())
}
if err := c.Respond(status, body); err != nil {
Log.Error("failed to send terse error handler response", "err", err)
}
}
// Fatal logs a critical message and exits the process with status code 1.
// This function is meant to be used by initialization code to prevent the application from even
// starting up when something is obviously wrong.
// In particular this function should probably not be used when serving requests.
func Fatal(msg string, ctx ...interface{}) {
log.Crit(msg, ctx...)
os.Exit(1)
}
// NewHTTPRouterHandle returns a httprouter handle from a goa handler. This handle initializes the
// request context by loading the request state, invokes the handler and in case of error invokes
// the application error handler.
// This function is intended for the controller generated code. User code should not need to call
// it directly.
func NewHTTPRouterHandle(service Service, resName, actName string, h Handler) httprouter.Handle {
// Setup middleware outside of closure
chain := service.MiddlewareChain()
ml := len(chain)
middleware := func(ctx *Context) error {
if !ctx.ResponseWritten() {
if err := h(ctx); err != nil {
service.HandleError(ctx, err)
}
}
return nil
}
for i := range chain {
middleware = chain[ml-i-1](middleware)
}
logger := service.New("ctrl", resName, "action", actName)
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
// Collect URL and query string parameters
params := make(map[string]string, len(p))
for _, param := range p {
params[param.Key] = param.Value
}
q := r.URL.Query()
query := make(map[string][]string, len(q))
for name, value := range q {
query[name] = value
}
// Load body if any
var payload interface{}
var err error
if r.ContentLength > 0 {
decoder := json.NewDecoder(r.Body)
err = decoder.Decode(&payload)
}
// Build context
gctx, cancel := context.WithCancel(RootContext)
defer cancel() // Signal completion of request to any child goroutine
ctx := NewContext(gctx, r, w, params, query, payload)
ctx.Logger = logger
// Handle invalid payload
handler := middleware
if err != nil {
handler = func(ctx *Context) error {
ctx.Respond(400, []byte(fmt.Sprintf(`{"kind":"invalid request","msg":"invalid JSON: %s"}`, err)))
return nil
}
for i := range chain {
handler = chain[ml-i-1](handler)
}
}
// Invoke middleware chain
handler(ctx)
// Make sure a response is sent back to client.
if ctx.ResponseStatus() == 0 {
service.HandleError(ctx, fmt.Errorf("unhandled request"))
}
}
}