-
Notifications
You must be signed in to change notification settings - Fork 19
/
gostserver.go
executable file
·217 lines (184 loc) · 5.69 KB
/
gostserver.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
package http
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"time"
odata "github.com/gost/server/sensorthings/odata"
gostLog "github.com/gost/server/log"
"github.com/gost/server/sensorthings/models"
log "github.com/sirupsen/logrus"
)
var logger *log.Entry
func setupLogger() {
l, err := gostLog.GetLoggerInstance()
if err != nil {
log.Error(err)
}
//Setting default fields for main logger
logger = l.WithFields(log.Fields{"package": "gost.server.http"})
}
// Server interface for starting and stopping the HTTP server
type Server interface {
Start()
Stop()
}
// GostServer is the type that contains all of the relevant information to set
// up the GOST HTTP Server
type GostServer struct {
host string // Hostname for example "localhost" or "192.168.1.14"
port int // Port number where you want to run your http server on
api *models.API // SensorThings api to interact with from the HttpServer
https bool
httpsCert string
httpsKey string
httpServer *http.Server
}
// CreateServer initialises a new GOST HTTPServer based on the given parameters
func CreateServer(host string, port int, api *models.API, https bool, httpsCert, httpsKey string) Server {
setupLogger()
a := *api
router := CreateRouter(api)
return &GostServer{
host: host,
port: port,
api: api,
https: https,
httpsCert: httpsCert,
httpsKey: httpsKey,
httpServer: &http.Server{
Addr: fmt.Sprintf("%s:%s", host, strconv.Itoa(port)),
Handler: PostProcessHandler(RequestErrorHandler(LowerCaseURI(router)), a.GetConfig().Server.ExternalURI),
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
},
}
}
// Start command to start the GOST HTTPServer
func (s *GostServer) Start() {
t := "HTTP"
if s.https {
t = "HTTPS"
}
logger.Infof("Started GOST %v Server on %v:%v", t, s.host, s.port)
var err error
if s.https {
err = s.httpServer.ListenAndServeTLS(s.httpsCert, s.httpsKey)
} else {
err = s.httpServer.ListenAndServe()
}
if err != nil {
logger.Panicf("GOST server not properly stopped: %v", err)
}
}
// Stop command to stop the GOST HTTP server
func (s *GostServer) Stop() {
if s.httpServer != nil {
logger.Info("Stopping HTTP(S) Server")
s.httpServer.Shutdown(context.Background())
}
}
// RequestErrorHandler is a middleware function that lower cases the url path
func RequestErrorHandler(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
query := r.URL.RawQuery
if query != "" {
if logger.Logger.Level == log.DebugLevel {
logger.Info("query given:" + query)
}
// todo: maybe add some other checks
isValid := odata.IsValidOdataQuery(query)
if !isValid {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Error: Not a valid Odata query given"))
return
}
}
h.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
// LowerCaseURI is a middleware function that lower cases the url path
func LowerCaseURI(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
lowerCasePath := strings.ToLower(r.URL.Path)
// temporarily disabled checking on paths due to problems in serving with /$value
/*
api := *s.api
split := strings.Split(lowerCasePath, "/")
for i, s := range split {
if len(s) == 0 || i+1 == len(split) {
continue
}
found := false
for _, a := range api.GetAcceptedPaths() {
if strings.HasPrefix(s, a) {
found = true
}
}
if !found {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusNotFound)
w.Write([]byte(""))
return
}
}
*/
r.URL.RawPath = r.URL.Path
r.URL.Path = lowerCasePath
h.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
// PostProcessHandler runs after all the other handlers and can be used to modify
// the response. In this case modify links (due to proxy running) or handle CORS functionality
// Basically we catch all the response using httptest.NewRecorder (headers + body), modify and
// write to response. Is this a right approach?
func PostProcessHandler(h http.Handler, externalURI string) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
if logger.Logger.Level == log.DebugLevel {
logger.Debugf("%s start: %s", r.Method, r.URL.Path)
defer gostLog.DebugfWithElapsedTime(logger, time.Now(), "%s done: %s", r.Method, r.URL.Path)
}
origURI := externalURI
forwardedURI := r.Header.Get("X-Forwarded-For")
rec := httptest.NewRecorder()
// first run the next handler and get results
h.ServeHTTP(rec, r)
// read response body and replace links
bytes := rec.Body.Bytes()
s := string(bytes)
if len(s) > 0 {
if len(forwardedURI) > 0 {
// if both are changed (X-Forwarded-For and External uri environment variabele) use the last one
if origURI == "http://localhost:8080/" {
s = strings.Replace(s, "localhost", forwardedURI, -1)
}
}
}
// handle headers too...
for k, v := range rec.HeaderMap {
val := v[0]
// if there is a location header and a proxy running, change
// the location url too
if k == "Location" && len(forwardedURI) > 0 {
if origURI == "http://localhost:8080/" {
logger.Debugf("proxy + location header detected. forwarded uri: %s", forwardedURI)
// idea: run net.LookupAddr(forwardeduri) to get hostname instead of ip address?
val = strings.Replace(val, "localhost", forwardedURI, -1)
}
}
// add the header to response
w.Header().Add(k, val)
}
// handle status code
w.WriteHeader(rec.Code)
// write modified response
w.Write([]byte(s))
}
return http.HandlerFunc(fn)
}