-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmain.go
307 lines (301 loc) · 9.38 KB
/
main.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
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
package main
import (
"bytes"
"context"
"fmt"
xerr "github.com/goclub/error"
xhttp "github.com/goclub/http"
"io/ioutil"
"log"
"mime/multipart"
"net/http"
"os"
"path"
"runtime/debug"
"strconv"
"time"
)
func main() {
r := xhttp.NewRouter(xhttp.RouterOption{
// error 拦截器 会在 r.Use() r.HandleFunc() 返回 err 不为 nil 时执行
OnCatchError: func(c *xhttp.Context, err error) error {
switch true {
case xerr.Is(context.Canceled, err):
return c.WriteBytes([]byte("请求取消"))
case xerr.Is(context.DeadlineExceeded, err):
return c.WriteBytes([]byte("请求超时"))
}
log.Print(err)
debug.PrintStack()
return nil
},
// panic 拦截器 会在 r.Use() r.HandleFunc() 中出现 panic 时通过 recover 捕获并触发
OnCatchPanic: func(c *xhttp.Context, recoverValue interface{}) error {
log.Print(recoverValue)
debug.PrintStack()
return nil
},
})
// 静态资源
publicPath := path.Join(os.Getenv("GOPATH"), "src/github.com/goclub/http/example/internal/basic/public")
log.Print("publicPath:", publicPath)
r.FileServer("/public", publicPath, true, func(c *xhttp.Context, next xhttp.Next) (err error) {
// 拦截部分静态资源,要求必须授权或需要密码才能访问 (这里需要匹配 /auth.js 不是 /public/auth.js 因为上面配置了前缀 /public)
if c.Request.URL.Path == "/auth.js" {
if c.Request.URL.Query().Get("p") == "abc" {
return next()
} else {
c.WriteStatusCode(http.StatusForbidden)
return c.WriteBytes([]byte("Forbidden"))
}
}
return next()
})
r.HandleFunc(xhttp.Route{xhttp.GET, "/user/{name}"}, func(c *xhttp.Context) (err error) {
name, err := c.Param("name")
if err != nil {
return
}
return c.WriteBytes([]byte(name))
})
r.HandleFunc(xhttp.Route{xhttp.GET, "/welcome"}, func(c *xhttp.Context) (err error) {
query := c.Request.URL.Query()
firstName := query.Get("firstname")
if firstName == "" {
firstName = "Guest"
}
lastName := query.Get("lastname")
return c.WriteBytes([]byte("hello " + firstName + " " + lastName))
})
/*
curl --location --request POST 'http://127.0.0.1:1111/from_post' \
--form 'message="abc"' \
--form 'nike="123"'
*/
r.HandleFunc(xhttp.Route{xhttp.POST, "/from_post"}, func(c *xhttp.Context) (err error) {
message := c.Request.FormValue("message")
nick := c.Request.FormValue("nick")
if nick == "" {
nick = "anonymous"
}
return c.WriteJSON(map[string]interface{}{
"status": "posted",
"message": message,
"nick": nick,
})
})
/*
curl --location --request POST 'http://127.0.0.1:1111/post?id=1&page=2' \
--form 'name="goclub"' \
--form 'message="abc"'
*/
r.HandleFunc(xhttp.Route{xhttp.POST, "/post"}, func(c *xhttp.Context) (err error) {
query := c.Request.URL.Query()
id := query.Get("id")
pageStr := query.Get("page")
if pageStr == "" {
pageStr = "0"
}
page, err := strconv.ParseUint(pageStr, 10, 64)
if err != nil {
return
}
name := c.Request.FormValue("name")
message := c.Request.FormValue("message")
return c.WriteJSON(map[string]interface{}{
"id": id,
"page": page,
"name": name,
"message": message,
})
})
/*
curl -X POST http://localhost:1111/upload \
-F "file=@/Users/nimo/Desktop/1.txt" \
-H "Content-Type: multipart/form-data"
*/
r.HandleFunc(xhttp.Route{xhttp.POST, "/upload"}, func(c *xhttp.Context) (err error) {
file, fileHeader, err := c.Request.FormFile("file")
if err != nil {
return
}
log.Print(fileHeader.Filename)
data, err := ioutil.ReadAll(file)
if err != nil {
return
}
log.Print(string(data))
return c.WriteBytes([]byte(fileHeader.Filename))
})
/*
curl -X POST http://localhost:1111/multi_file_upload \
-F "upload[]=@/Users/nimo/Desktop/1.txt" \
-F "upload[]=@/Users/nimo/Desktop/2.txt" \
-H "Content-Type: multipart/form-data"
*/
r.HandleFunc(xhttp.Route{xhttp.POST, "/multi_file_upload"}, func(c *xhttp.Context) (err error) {
err = c.Request.ParseMultipartForm(8 << 20)
if err != nil {
return
}
form := c.Request.MultipartForm
files := form.File["upload[]"]
for _, fileHeader := range files {
log.Println(fileHeader.Filename)
var data []byte
var file multipart.File
file, err = fileHeader.Open()
if err != nil {
return
}
data, err = ioutil.ReadAll(file)
if err != nil {
return
}
log.Print(string(data))
}
return c.WriteBytes([]byte(fmt.Sprintf("%d files uploaded!", len(files))))
})
// goclub/http 不希望在 Group() 中传递前缀,这样会导致url分散
v1 := r.Group()
v1.HandleFunc(xhttp.Route{xhttp.GET, "/v1/login"}, func(c *xhttp.Context) (err error) {
return c.WriteBytes([]byte("v1 login"))
})
v2 := r.Group()
v2.HandleFunc(xhttp.Route{xhttp.GET, "/v2/login"}, func(c *xhttp.Context) (err error) {
return c.WriteBytes([]byte("v2 login"))
})
// 中间件
r.Use(func(c *xhttp.Context, next xhttp.Next) (err error) {
requestTime := time.Now()
log.Print("Request: ", c.Request.Method, c.Request.URL.String())
if err = next(); err != nil {
return
}
responseTime := time.Now().Sub(requestTime)
log.Print("Response: (", responseTime.String(), ") ", c.Request.Method, c.Request.URL.String())
return nil
})
// goclub/http 自己实现了绑定器,用于绑定各种 http 请求
// 使用自定义结构绑定表单数据
/*
curl --location --request POST 'http://127.0.0.1:1111/bind_query_form/1?name=goclub' \
--form 'age=18'
*/
r.HandleFunc(xhttp.Route{xhttp.POST, "/bind_query_form/{id}"}, func(c *xhttp.Context) (err error) {
request := struct {
ID string `param:"id"`
Name string `query:"name"`
// 会将 string int 自动转换
Age int `form:"age"`
}{}
err = c.BindRequest(&request)
if err != nil {
return
}
return c.WriteJSON(request)
})
/*
curl --location --request POST 'http://127.0.0.1:1111/bind_query_json?name=goclub' \
--header 'Content-Type: application/json' \
--data-raw '{"id": "1", "age": 18}'
*/
r.HandleFunc(xhttp.Route{xhttp.POST, "/bind_query_json"}, func(c *xhttp.Context) (err error) {
request := struct {
Name string `query:"name"`
// 会将 string int 自动转换
Age int `json:"age"`
// 会将 string int 自动转换
ID int `json:"id"`
}{}
err = c.BindRequest(&request)
if err != nil {
return
}
return c.WriteJSON(request)
})
// 绑定uri, 绑定Get参数或者Post参数 均可以使用 c.BindRequest(ptr) 实现
// 绑定 xml 使用 xjson.NewDecoder(c.Request.Body).Decode(&data) 科技
// 请求验证由 goclub/validator 实现, 注意数据验证应当在业务逻辑层(biz/service)验证,而不是协议层(http/rpc)验证
// 返回第三方获取的数据 goclub/http 单独提供了一些函数来支持
// https://pkg.go.dev/github.com/goclub/http#example-Client.Send
// https://pkg.go.dev/github.com/goclub/http#example-Client.Do
// https://cn.bing.com/search?q=go+html+%E6%A8%A1%E6%9D%BF%E6%B8%B2%E6%9F%93%E5%BA%93%E9%80%89%E5%9E%8B
r.HandleFunc(xhttp.Route{xhttp.GET, "/index"}, func(c *xhttp.Context) (err error) {
// 为了更方面的支持各种模板引擎, goclub/http 提供 render 接口让使用者自己组合
return c.Render(func(buffer *bytes.Buffer) error {
buffer.WriteString(`<a href="https://github.com/goclub">goclub</a>`)
return nil
})
})
r.HandleFunc(xhttp.Route{xhttp.GET, "/redirect"}, func(c *xhttp.Context) (err error) {
http.Redirect(c.Writer, c.Request, "https://goclub.vip", 302)
return nil
})
r.HandleFunc(xhttp.Route{xhttp.GET, "/cookie/set"}, func(c *xhttp.Context) (err error) {
c.SetCookie(&http.Cookie{
Name: "time",
Value: time.Now().Format("15:04:05"),
})
return c.WriteBytes([]byte("set"))
})
r.HandleFunc(xhttp.Route{xhttp.GET, "/cookie/get"}, func(c *xhttp.Context) (err error) {
cookie, hasCookie, err := c.Cookie("time")
if err != nil {
return
}
message := ""
if hasCookie == false {
message = "no cookie"
} else {
message = cookie.Value
}
return c.WriteBytes([]byte(message))
})
r.HandleFunc(xhttp.Route{xhttp.GET, "/cookie/clear"}, func(c *xhttp.Context) (err error) {
c.ClearCookie(&http.Cookie{Name: "time"})
return c.WriteBytes([]byte("clear"))
})
// 为了演示,将测试放在注释中,实际上应该在 xxx_test.go 文件中写
// goclub/http 的测试是不需要 ListenAndServe 就能测试的,因为测试阶段监听端口会有安全隐患
// NewTest 只需要传入 *testing.T 和 *xhttp.Router
// goclub/http test 自带 CookieJar
// https://pkg.go.dev/github.com/goclub/http#Test
/*
test := xhttp.NewTest(t, r)
request, err := http.NewRequest("GET", "/user/nimo", nil) ; if err != nil {
panic(err)
}
resp := test.Request(request)
resp.ExpectString(200, "nimo")
*/
addr := ":1111"
server := &http.Server{
Addr: addr,
Handler: r,
// 自定义HTTP配置
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
r.LogPatterns(server)
log.Print("http://localhost" + addr)
// 如果需要使用 https 证书 则通过 server.ListenAndServeTLS() 启动服务
// 或者在 cdn 环节部署 https
go func() {
listenErr := server.ListenAndServe()
if listenErr != nil {
if listenErr != http.ErrServerClosed {
panic(listenErr)
}
}
}()
// 优雅重启或停止
xhttp.GracefulClose(func() {
log.Print("Shuting down server...")
if err := server.Shutdown(context.Background()); err != nil {
log.Fatal("Server forced to shutdown:", err)
}
log.Println("Server exiting")
})
}