/
web.go
249 lines (199 loc) · 5.47 KB
/
web.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
package web
import (
"fmt"
"net"
"net/http"
"strings"
"sync"
"time"
"github.com/gorilla/mux"
)
// Web is the main object of the framework. All things start from here.
// To create an Web object, call NewWeb().
//
// You can use this web framework to handle HTTP requests by writing your own functions
// and return results with JSON format (or other custom format if you want).
//
// Note that this framework is designed for API server. To keep it simple, it doesn't support
// static page, html template, css, javascript etc.
//
// Here is an example:
//
// import "github.com/tiaotiao/web"
//
// func Hello(c *web.Context) interface{} {
// return "hello world" // return a string will be write directly with out processing.
// }
//
// func Echo(c *web.Context) interface{} {
// args := struct {
// Message string `web:"message,required"`
// }{}
//
// // scheme args from c.Values into args by the indication of struct tags.
// if err := web.Scheme(c.Values, &args); err != nil {
// return err // error occured if lack of argument or wrong type.
// }
//
// return web.Result{"message": args.Message} // a map or struct will be formated to JSON
// }
//
// func main() {
// w := web.NewWeb()
//
// w.Handle("GET", "/api/hello", Hello)
// w.Handle("GET", "/api/echo", Echo)
//
// w.ListenAndServe("tcp", ":8082") // block forever until be closed
// }
//
type Web struct {
mux *mux.Router
router *router
handlers map[string]*handler
listeners []net.Listener
responser Responser
logger Logger
wg sync.WaitGroup
closed bool
}
// Create an Web object.
func NewWeb() *Web {
w := new(Web)
w.mux = mux.NewRouter().StrictSlash(true)
w.router = newRouter(w, "/", nil)
w.handlers = make(map[string]*handler, 128)
w.responser = new(DefaultResponser)
w.logger = NewStdLogger()
w.closed = false
return w
}
// ServeHTTP used for implements http.Handler interface. No need to be called by user.
func (w *Web) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
w.wg.Add(1)
if !w.closed {
w.mux.ServeHTTP(rw, req)
}
w.wg.Done()
}
// Listen an address and start to serve. Blocked until be closed or some error occurs.
// See Web.Listen and Web.Serve.
func (w *Web) ListenAndServe(protocol string, addr string) error {
var err error
err = w.Listen(protocol, addr)
if err != nil {
return err
}
return w.Serve()
}
// Listen on the local network address. Argument protocol usually be 'tcp' or 'unix'.
// Argument addr usually be the format as 'host:port'. For example:
// Listen("tcp", ":8080") // listen on 8080 port
// Listen("tcp", "google.com:http") // listen on 80 port from google.com
// Listen("unix", "/var/run/web_server.sock")
// See net.Listen for more.
func (w *Web) Listen(protocol string, addr string) error {
var err error
l, err := net.Listen(protocol, addr)
if err != nil {
return err
}
w.listeners = append(w.listeners, l)
return nil
}
// Start to serve all listeners. It will block until all listeners closed.
func (w *Web) Serve() error {
var err error
if len(w.listeners) == 0 {
return fmt.Errorf("not listening")
}
wg := sync.WaitGroup{}
serve := func(l net.Listener) {
svrMux := http.NewServeMux()
svrMux.Handle("/", w)
svr := http.Server{Handler: svrMux, ReadTimeout: HttpReadTimeout}
err = svr.Serve(l)
l.Close()
wg.Done()
}
for _, l := range w.listeners {
wg.Add(1)
go serve(l)
}
wg.Wait()
return err
}
// Close all listeners and stop serve HTTP.
func (w *Web) Close() {
for _, l := range w.listeners {
l.Close()
}
w.closed = true
w.wg.Wait()
}
// Register an Handler as a handler for this url. See Router.Handle
func (w *Web) Handle(method string, path string, fn Handler) *MiddlewaresManager {
return w.router.Handle(method, path, fn)
}
// Get a sub router with the perfix path. See Router.SubRouter
func (w *Web) SubRouter(pathPerfix string) Router {
return w.router.SubRouter(pathPerfix)
}
// Append a middleware. See Router.Append
func (w *Web) Append(midd Middleware) {
w.router.Append(midd)
}
// To setup a custom responser to process the result which returned from Handler and then to write into response body.
// The responser must implements the Responser interface.
//
// With out doing anything, the DefaultResponser will write string and []byte directly or write map, struct and
// other types in JSON format. See DefaultResponser for more detail.
func (w *Web) SetResponser(r Responser) {
w.responser = r
}
// Set a logger to track request logs.
func (w *Web) SetLogger(l Logger) {
w.logger = l
}
// Get all registed handlers.
// func (w *Web) GetHandlers() map[string]*handler {
// return w.handlers
// }
func (w *Web) handle(method, urlpath string, fn Handler, midwares *MiddlewaresManager) {
var h *handler
h = newHandler(fn, midwares, w.responser, w.logger)
// match prefix
var prefix bool
if strings.HasSuffix(urlpath, "*") {
urlpath = strings.TrimSuffix(urlpath, "*")
prefix = true
}
// register mux route
var rt *mux.Route
if prefix {
rt = w.mux.PathPrefix(urlpath).Handler(h)
} else {
rt = w.mux.Handle(urlpath, h)
}
rt.Methods(strings.ToUpper(method))
// add to map
url := methodUrl(method, urlpath)
_, ok := w.handlers[url]
if ok {
panic("url conflict: " + url)
}
w.handlers[url] = h
return
}
func methodUrl(method string, path string) string {
return method + " " + strings.ToLower(path)
}
var HttpReadTimeout = time.Minute
const (
POST = "POST"
GET = "GET"
DELETE = "DELETE"
PUT = "PUT"
OPTIONS = "OPTIONS"
)
var _ Router = (*Web)(nil)