/
router.go
261 lines (224 loc) · 6.69 KB
/
router.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
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
package main
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"runtime"
"sync"
"time"
"github.com/gorilla/mux"
"google.golang.org/grpc/grpclog"
)
type void = struct{}
type jerr struct {
error
}
// Unwrap returns inherited error object.
func (e *jerr) Unwrap() error {
return e.error
}
// MarshalJSON is standard JSON interface implementation to stream errors on Ajax.
func (e *jerr) MarshalJSON() ([]byte, error) {
return json.Marshal(e.Error())
}
// ErrAjax is error object on AJAX API handlers calls.
type ErrAjax struct {
What jerr `json:"what"` // message with problem description
When int64 `json:"when"` // time of error rising, in milliseconds of UNIX format
Code int `json:"code,omitempty"` // unique API error code
Info string `json:"info,omitempty"` // URL with problem detailed description
}
// MakeAjaxErr is ErrAjax simple constructor.
func MakeAjaxErr(what error, code int) *ErrAjax {
return &ErrAjax{
What: jerr{what},
When: UnixJSNow(),
Code: code,
}
}
// MakeAjaxInfo is ErrAjax constructor with info URL.
func MakeAjaxInfo(what error, code int, info string) *ErrAjax {
return &ErrAjax{
What: jerr{what},
When: UnixJSNow(),
Code: code,
Info: info,
}
}
func (e *ErrAjax) Error() string {
return fmt.Sprintf("error with code %d: %s", e.Code, e.What.Error())
}
// Unwrap returns inherited error object.
func (e *ErrAjax) Unwrap() error {
return e.What
}
// ErrPanic is error object that helps to get stack trace of goroutine within panic rises.
type ErrPanic struct {
ErrAjax
Stack string `json:"stack,omitempty"`
}
// MakeErrPanic is ErrPanic constructor.
func MakeErrPanic(what error, code int, stack string) *ErrPanic {
return &ErrPanic{
ErrAjax: ErrAjax{
What: jerr{what},
When: UnixJSNow(),
Code: code,
},
Stack: stack,
}
}
// UnixJS converts time to UNIX-time in milliseconds, compatible with javascript time format.
func UnixJS(u time.Time) int64 {
return u.UnixNano() / 1000000
}
// UnixJSNow returns same result as Date.now() in javascript.
func UnixJSNow() int64 {
return time.Now().UnixNano() / 1000000
}
////////////////
// Routes API //
////////////////
// Router is local alias for router type.
type Router = mux.Router
// NewRouter is local alias for router creation function.
var NewRouter = mux.NewRouter
const (
jsoncontent = "application/json;charset=utf-8"
htmlcontent = "text/html;charset=utf-8"
csscontent = "text/css;charset=utf-8"
jscontent = "text/javascript;charset=utf-8"
)
var serverlabel string
func makeServerLabel(label, version string) {
serverlabel = fmt.Sprintf("%s/%s (%s)", label, version, runtime.GOOS)
}
// AjaxGetArg fetch and unmarshal request argument.
func AjaxGetArg(w http.ResponseWriter, r *http.Request, arg interface{}) (err error) {
if jb, _ := io.ReadAll(r.Body); len(jb) > 0 {
if err = json.Unmarshal(jb, arg); err != nil {
WriteError400(w, err, AECbadjson)
return
}
} else {
err = ErrNoJSON
WriteError400(w, err, AECnoreq)
return
}
return
}
// WriteStdHeader setup common response headers.
func WriteStdHeader(w http.ResponseWriter) {
w.Header().Set("Connection", "keep-alive")
w.Header().Set("Server", serverlabel)
w.Header().Set("X-Frame-Options", "sameorigin")
}
// WriteHTMLHeader setup standard response headers for message with HTML content.
func WriteHTMLHeader(w http.ResponseWriter) {
WriteStdHeader(w)
w.Header().Set("Content-Type", htmlcontent)
}
// WriteJSONHeader setup standard response headers for message with JSON content.
func WriteJSONHeader(w http.ResponseWriter) {
WriteStdHeader(w)
w.Header().Set("Content-Type", jsoncontent)
w.Header().Set("X-Content-Type-Options", "nosniff")
}
// WriteJSON writes to response given status code and marshaled body.
func WriteJSON(w http.ResponseWriter, status int, body interface{}) {
if body == nil {
w.WriteHeader(status)
WriteJSONHeader(w)
return
}
/*if b, ok := body.([]byte); ok {
w.WriteHeader(status)
WriteJSONHeader(w)
w.Write(b)
return
}*/
var b, err = json.Marshal(body)
if err == nil {
w.WriteHeader(status)
WriteJSONHeader(w)
w.Write(b)
} else {
b, _ = json.Marshal(MakeAjaxErr(err, AECbadbody))
w.WriteHeader(http.StatusInternalServerError)
WriteJSONHeader(w)
w.Write(b)
}
}
// WriteOK puts 200 status code and some data to response.
func WriteOK(w http.ResponseWriter, body interface{}) {
WriteJSON(w, http.StatusOK, body)
}
// WriteError puts to response given error status code and ErrAjax formed by given error object.
func WriteError(w http.ResponseWriter, status int, err error, code int) {
WriteJSON(w, status, MakeAjaxErr(err, code))
}
// WriteError400 puts to response 400 status code and ErrAjax formed by given error object.
func WriteError400(w http.ResponseWriter, err error, code int) {
WriteJSON(w, http.StatusBadRequest, MakeAjaxErr(err, code))
}
// WriteError500 puts to response 500 status code and ErrAjax formed by given error object.
func WriteError500(w http.ResponseWriter, err error, code int) {
WriteJSON(w, http.StatusInternalServerError, MakeAjaxErr(err, code))
}
//////////////////
// Routes table //
//////////////////
// Transaction locker, locks until handler will be done.
var handwg sync.WaitGroup
// AjaxMiddleware is base handler middleware for AJAX API calls.
func AjaxMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if what := recover(); what != nil {
var err error
switch v := what.(type) {
case error:
err = v
case string:
err = errors.New(v)
case fmt.Stringer:
err = errors.New(v.String())
default:
err = errors.New("panic was thrown at handler")
}
var buf [2048]byte
var stacklen = runtime.Stack(buf[:], false)
var str = string(buf[:stacklen])
grpclog.Errorln(str)
WriteJSON(w, http.StatusInternalServerError, MakeErrPanic(err, AECpanic, str))
}
}()
// lock before exit check
handwg.Add(1)
defer handwg.Done()
// check on exit during handler is called
select {
case <-exitctx.Done():
return
default:
}
// call the next handler, which can be another middleware in the chain, or the final handler
next.ServeHTTP(w, r)
})
}
// RegisterRoutes puts application routes to given router.
func RegisterRoutes(gmux *Router) {
// API routes
var tool = gmux.PathPrefix("/api/tool").Subrouter()
tool.Path("/ping").HandlerFunc(apiToolPing)
var port = gmux.PathPrefix("/api/port").Subrouter()
port.Use(AjaxMiddleware)
port.Path("/set").HandlerFunc(apiPortSet)
port.Path("/get").HandlerFunc(apiPortGet)
port.Path("/name").HandlerFunc(apiPortName)
port.Path("/near").HandlerFunc(apiPortNear)
port.Path("/circle").HandlerFunc(apiPortCircle)
port.Path("/text").HandlerFunc(apiPortText)
}