-
Notifications
You must be signed in to change notification settings - Fork 204
/
http_service.go
226 lines (206 loc) · 6.38 KB
/
http_service.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
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
/**********************************************************\
| |
| hprose |
| |
| Official WebSite: http://www.hprose.com/ |
| http://www.hprose.org/ |
| |
\**********************************************************/
/**********************************************************\
* *
* rpc/http_service.go *
* *
* hprose http service for Go. *
* *
* LastModified: Nov 24, 2016 *
* Author: Ma Bingyao <andot@hprose.com> *
* *
\**********************************************************/
package rpc
import (
"io"
"io/ioutil"
"net/http"
"reflect"
"strings"
"sync"
"github.com/hprose/hprose-golang/util"
)
// HTTPContext is the hprose http context
type HTTPContext struct {
BaseServiceContext
Response http.ResponseWriter
Request *http.Request
}
// InitHTTPContext initializes HTTPContext
func (context *HTTPContext) InitHTTPContext(
service Service,
response http.ResponseWriter,
request *http.Request) {
context.InitServiceContext(service)
context.Response = response
context.Request = request
}
// HTTPService is the hprose http service
type HTTPService struct {
BaseHTTPService
contextPool sync.Pool
}
type sendHeaderEvent interface {
OnSendHeader(context *HTTPContext)
}
type sendHeaderEvent2 interface {
OnSendHeader(context *HTTPContext) error
}
func httpFixArguments(args []reflect.Value, context ServiceContext) {
i := len(args) - 1
switch args[i].Type() {
case httpContextType:
if c, ok := context.(*HTTPContext); ok {
args[i] = reflect.ValueOf(c)
}
case httpRequestType:
if c, ok := context.(*HTTPContext); ok {
args[i] = reflect.ValueOf(c.Request)
}
default:
DefaultFixArguments(args, context)
}
}
// NewHTTPService is the constructor of HTTPService
func NewHTTPService() (service *HTTPService) {
service = new(HTTPService)
service.InitHTTPService()
return
}
// InitHTTPService initializes HTTPService
func (service *HTTPService) InitHTTPService() {
service.InitBaseHTTPService()
service.contextPool = sync.Pool{
New: func() interface{} { return new(HTTPContext) },
}
service.FixArguments = httpFixArguments
}
func (service *HTTPService) acquireContext() (context *HTTPContext) {
return service.contextPool.Get().(*HTTPContext)
}
func (service *HTTPService) releaseContext(context *HTTPContext) {
service.contextPool.Put(context)
}
func (service *HTTPService) xmlFileHandler(
response http.ResponseWriter, request *http.Request,
path string, context []byte) bool {
if context == nil || strings.ToLower(request.URL.Path) != path {
return false
}
if request.Header.Get("if-modified-since") == service.LastModified &&
request.Header.Get("if-none-match") == service.Etag {
response.WriteHeader(304)
} else {
contentLength := len(context)
header := response.Header()
header.Set("Last-Modified", service.LastModified)
header.Set("Etag", service.Etag)
header.Set("Content-Type", "text/xml")
header.Set("Content-Length", util.Itoa(contentLength))
response.Write(context)
}
return true
}
func (service *HTTPService) crossDomainXMLHandler(
response http.ResponseWriter, request *http.Request) bool {
path := "/crossdomain.xml"
context := service.crossDomainXMLContent
return service.xmlFileHandler(response, request, path, context)
}
func (service *HTTPService) clientAccessPolicyXMLHandler(
response http.ResponseWriter, request *http.Request) bool {
path := "/clientaccesspolicy.xml"
context := service.clientAccessPolicyXMLContent
return service.xmlFileHandler(response, request, path, context)
}
func (service *HTTPService) fireSendHeaderEvent(
context *HTTPContext) (err error) {
defer func() {
if e := recover(); e != nil {
err = NewPanicError(e)
}
}()
switch event := service.Event.(type) {
case sendHeaderEvent:
event.OnSendHeader(context)
case sendHeaderEvent2:
err = event.OnSendHeader(context)
}
return err
}
func (service *HTTPService) sendHeader(context *HTTPContext) (err error) {
if err = service.fireSendHeaderEvent(context); err != nil {
return err
}
header := context.Response.Header()
header.Set("Content-Type", "text/plain")
if service.P3P {
header.Set("P3P",
`CP="CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi `+
`CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL `+
`UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE GOV"`)
}
if service.CrossDomain {
origin := context.Request.Header.Get("origin")
if origin != "" && origin != "null" {
if len(service.AccessControlAllowOrigins) == 0 ||
service.AccessControlAllowOrigins[origin] {
header.Set("Access-Control-Allow-Origin", origin)
header.Set("Access-Control-Allow-Credentials", "true")
}
} else {
header.Set("Access-Control-Allow-Origin", "*")
}
}
return nil
}
func readAllFromHTTPRequest(request *http.Request) ([]byte, error) {
if request.ContentLength > 0 {
data := make([]byte, request.ContentLength)
_, err := io.ReadFull(request.Body, data)
return data, err
}
if request.ContentLength < 0 {
return ioutil.ReadAll(request.Body)
}
return nil, nil
}
// ServeHTTP is the hprose http handler method
func (service *HTTPService) ServeHTTP(
response http.ResponseWriter, request *http.Request) {
if service.clientAccessPolicyXMLHandler(response, request) ||
service.crossDomainXMLHandler(response, request) {
return
}
context := service.acquireContext()
context.InitHTTPContext(service, response, request)
var resp []byte
err := service.sendHeader(context)
if err == nil {
switch request.Method {
case "GET":
if service.GET {
resp = service.DoFunctionList(context)
} else {
response.WriteHeader(403)
}
case "POST":
var req []byte
if req, err = readAllFromHTTPRequest(request); err == nil {
resp = service.Handle(req, context)
}
}
}
if err != nil {
resp = service.EndError(err, context)
}
service.releaseContext(context)
response.Header().Set("Content-Length", util.Itoa(len(resp)))
response.Write(resp)
}