forked from emicklei/go-restful
/
web_service_container.go
160 lines (145 loc) · 5.21 KB
/
web_service_container.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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// Copyright (c) 2012 Ernest Micklei. All rights reserved.
package restful
import (
"log"
"net/http"
"strings"
)
// The Dispatch function is responsible to delegating to the appropriate WebService that has been registered via Add.
// The default implementation is DefaultDispatch which also does some basic panic handling and closes the request body.
//
// Example of overriding it to add basic request logging:
// restful.Dispatch = func(w http.ResponseWriter, r *http.Request) {
// fmt.Println(r.Method, r.URL)
// restful.DefaultDispatch(w, r)
// }
//
// Deprecated: Use filters instead.
//
var Dispatch http.HandlerFunc
// If set the true then panics will not be caught to return HTTP 500.
// In that case, Route functions are responsible for handling any error situation.
// Default value is false = recover from panics. This has performance implications.
var DoNotRecover = false
// Collection of registered WebServices that can handle Http requests
var webServices = []*WebService{}
// Remember if any WebService is mapped on root /
var isRegisteredOnRoot = false
// Collection of Filter functions that apply to all requests.
var globalFilters = []FilterFunction{}
// Add registers a new WebService add it to the http listeners.
func Add(service *WebService) {
if service.pathExpr == nil {
service.Path("") // lazy initialize path
}
// If registered on root then no additional specific mapping is needed
if !isRegisteredOnRoot {
pattern := fixedPrefixPath(service.RootPath())
// check if root path registration is needed
if "/" == pattern || "" == pattern {
http.HandleFunc("/", Dispatch)
isRegisteredOnRoot = true
} else {
// detect if registration already exists
alreadyMapped := false
for _, each := range webServices {
if each.RootPath() == service.RootPath() {
alreadyMapped = true
break
}
}
if !alreadyMapped {
http.HandleFunc(pattern, Dispatch)
if !strings.HasSuffix(pattern, "/") {
http.HandleFunc(pattern+"/", Dispatch)
}
}
}
}
webServices = append(webServices, service)
}
// Filter appends a global FilterFunction. These are called before dispatch a http.Request to a WebService.
func Filter(filter FilterFunction) {
globalFilters = append(globalFilters, filter)
}
// RegisteredWebServices returns the collections of added Dispatchers (WebService is an implementation)
func RegisteredWebServices() []*WebService {
return webServices
}
// fixedPrefixPath returns the fixed part of the partspec ; it may include template vars {}
func fixedPrefixPath(pathspec string) string {
varBegin := strings.Index(pathspec, "{")
if -1 == varBegin {
return pathspec
}
return pathspec[:varBegin]
}
// Dispatch the incoming Http Request to a matching WebService.
// Matching algorithm is conform http://jsr311.java.net/nonav/releases/1.1/spec/spec.html, see jsr311.go
func DefaultDispatch(httpWriter http.ResponseWriter, httpRequest *http.Request) {
// Instal panic recovery unless told otherwise
if !DoNotRecover { // catch all for 500 response
defer func() {
if r := recover(); r != nil {
log.Println("[restful] recover from panic situation:", r)
httpWriter.WriteHeader(http.StatusInternalServerError)
return
}
}()
}
// Install closing the request body (if any)
defer func() {
if nil != httpRequest.Body {
httpRequest.Body.Close()
}
}()
// step 0. Process any global filters
if len(globalFilters) > 0 {
wrappedRequest, wrappedResponse := newBasicRequestResponse(httpWriter, httpRequest)
proceed := false
chain := FilterChain{Filters: globalFilters, Target: func(req *Request, resp *Response) {
// we passed all filters
proceed = true
}}
chain.ProcessFilter(wrappedRequest, wrappedResponse)
if !proceed {
return
}
}
// step 1. Identify the root resource class (WebService)
dispatcher, finalMatch, err := detectDispatcher(httpRequest.URL.Path, webServices)
if err != nil {
httpWriter.WriteHeader(http.StatusNotFound)
return
}
// step 2. Obtain the set of candidate methods (Routes)
routes := selectRoutes(dispatcher, finalMatch)
// step 3. Identify the method (Route) that will handle the request
route, detected := detectRoute(routes, httpWriter, httpRequest)
if detected {
// pass through filters (if any)
filters := dispatcher.filters
wrappedRequest, wrappedResponse := route.wrapRequestResponse(httpWriter, httpRequest)
if len(filters) > 0 {
chain := FilterChain{Filters: filters, Target: func(req *Request, resp *Response) {
// handle request by route
route.dispatch(wrappedRequest, wrappedResponse)
}}
chain.ProcessFilter(wrappedRequest, wrappedResponse)
} else {
// handle request by route
route.dispatch(wrappedRequest, wrappedResponse)
}
}
// else a non-200 response has already been written
}
// newBasicRequestResponse create a pair of Request,Response from its http versions.
// It is basic because no parameter or (produces) content-type information is given.
func newBasicRequestResponse(httpWriter http.ResponseWriter, httpRequest *http.Request) (*Request, *Response) {
accept := httpRequest.Header.Get(HEADER_Accept)
return &Request{httpRequest, map[string]string{}}, // empty parameters
&Response{httpWriter, accept, []string{}} // empty content-types
}
func init() {
Dispatch = DefaultDispatch
}