HTTP
原生 HTTP 处理
Go 语言原生自带 HTTP 的处理能力,可以使用 http 包实现简单的 http 请求处理。
原生 HTTP 处理请求方式:
/**
* @author Real
* @since 2023/11/2 22:18
*/
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", indexHandler)
http.HandleFunc("/hello", helloHandler)
log.Fatal(http.ListenAndServe(":9999", nil))
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
_, err := fmt.Fprintf(w, "index hanlder, URL.path = %q\n", r.URL.Path)
if err != nil {
return
}
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
headers := r.Header
for index, _ := range headers {
_, err := fmt.Fprintf(w, "header[%s] = %s\n", index, headers[index])
if err != nil {
return
}
}
}
启动之后,可以访问 localhost:9999
和 localhost:9999/hello
访问对应的服务。
$ curl http://localhost:9999/
index hanlder, URL.path = "/"
$ curl http://localhost:9999/hello
header[User-Agent] = [curl/8.1.2]
header[Accept] = [*/*]
实现 Http.handler 接口
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
engine := new(Engine)
log.Fatal(http.ListenAndServe(":9999", engine))
}
// Engine is the uni handler for all requests
type Engine struct{
}
// ServeHTTP implements the http.Handler interface
func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/":
fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)
case "/hello":
for k, v := range r.Header {
fmt.Fprintf(w, "Header[%q] = %q\n", k, v)
}
default:
fmt.Fprintf(w, "404 NOT FOUND: %s\n", r.URL)
}
}
在这个里面,定义了一个 Engine 空结构体,实现了 HTTP 接口的 ServeHTTP 接口。这样就可以处理所有的请求了。第一个参数是 ResponseWriter,可以利用 ResponseWriter 构造针对该请求的响应。第二个参数是 Request,该对象包含了该 HTTP 请求的所有信息,比如请求地址、Header、Body 等信息。
在 main 函数中,给 ListenAndServe 方法的第二个参数传入了刚才创建的 engine 实例。这样就将所有的 HTTP 请求转向了我们自己的处理逻辑。在实现 Engine 之前,可以调用 http.HandleFunc 实现了路由和 Handler 的映射,也就是只能针对具体的路由写处理逻辑。但是在实现 Engine 之后就可以拦截所有的 HTTP 请求,拥有了统一的控制入口。在这里我们可以自由定义路由映射的规则,也可以统一添加一些处理逻辑,例如日志、异常处理等。这里的代码的运行结果与之前的是一致的。
Gin 的处理方式
import (
"fmt"
"net/http"
)
// HandlerFunc defines the request handler used by gee_module
type HandlerFunc func(http.ResponseWriter, *http.Request)
// Engine implement the interface of ServeHTTP
type Engine struct {
router map[string]HandlerFunc
}
// New is the constructor of gee.Engine
func New() *Engine {
return &Engine{
router: make(map[string]HandlerFunc)}
}
func (engine *Engine) addRoute(method string, pattern string, handler HandlerFunc) {
key := method + "-" + pattern
engine.router[key] = handler
}
// GET defines the method to add GET request
func (engine *Engine) GET(pattern string, handler HandlerFunc) {
engine.addRoute("GET", pattern, handler)
}
// POST defines the method to add POST request
func (engine *Engine) POST(pattern string, handler HandlerFunc) {
engine.addRoute("POST", pattern, handler)
}
// Run defines the method to start a http server
func (engine *Engine) Run(addr string) (err error) {
return http.ListenAndServe(addr, engine)
}
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
key := req.Method + "-" + req.URL.Path
if handler, ok := engine.router[key]; ok {
handler(w, req)
} else {
fmt.Fprintf(w, "404 NOT FOUND: %s\n", req.URL)
}
}
- 首先定义了类型 HandlerFunc,这是提供给框架用户的,用来定义路由映射的处理方法。我们在 Engine 中添加了一张路由映射表 router,key 由请求方法和静态路由地址构成,例如 GET-/、GET-/hello、POST-/hello,这样针对相同的路由,如果请求方法不同,可以映射不同的处理方法 Handler,value 是用户映射的处理方法。
- 当用户调用
(*Engine).GET()
方法时,会将路由和处理方法注册到映射表 router 中,(*Engine).Run()
方法是ListenAndServe
的包装。 - Engine 实现的 ServeHTTP 方法的作用就是,解析请求的路径,查找路由映射表。如果查到,就执行注册的处理方法。如果查不到,就返回 404 NOT FOUND。
Context
封装前的请求
如果不对 Gee 的请求方法做封装,那么每次使用的时候,都是直接将 request 和 Response 作为匿名函数的参数。
func main() {
engine := gee.New()
engine.GET("/", func(writer http.ResponseWriter, request *http.Request) {
})
}
这样的使用看起来会很臃肿,而且和使用原生的 http 包提供的方法,没有什么太大的区别。这个时候就需要将请求和返回参数做一些封装,顺带也支持更强大的功能。
因此,封装 Context 有这些必要:
- 简化接口调用:request 和 response 提供的接口粒度太细,需要每次请求都设置相同的 Header 等信息,这在构造完整的响应时需要写大量重复的代码;
- 支持额外功能:如果需要解析动态路由、需要支持中间件时,直接使用 Request 和 Response 接口,对于全局的请求来说不具备复用性。
Context 实现
封装 Context 的具体实现如下。
package gee
import (
"encoding/json"
"fmt"
"log"
"net/http"
)
const ContentType = "Content-Type"
// H 构造 JSON 数据的别名
type H map[string]interface{
}
type Context struct {
// 原始对象
Writer http.ResponseWriter
Request *http.Request
// 请求信息
Path string
Method string
// 返回信息
StatusCode int
}
func NewContext(w http.ResponseWriter, r *http.Request) *Context {
return &Context{
Writer: w,
Request: r,
Path: r.URL.Path,
Method: r.Method,
}
}
// ---------- 获取请求值 ----------
func (c *Context) GetPostFormVal(key string) string {
return c.Request.FormValue(key)
}
func (c *Context) GetQueryVal(key string) string {
return c.Request.URL.Query().Get(key)
}
// ---------- 设置返回值 ----------
func (c *Context) SetStatusCode(code int) {
c.StatusCode = code
c.Writer.WriteHeader(code)
}
func (c *Context) SetHeader(key, value string) {
c.Request.Header.Set(key, value)
}
// ---------- 设置返回值类型 ----------
func (c *Context) String(code int, format string, values ...interface{
}) {
c.SetHeader(ContentType, "text/plain")
c.SetStatusCode(code)
_, err := c.Writer.Write([]byte(fmt.Sprintf(format, values...)))
if err != nil {
log.Printf("Response String Error: %v", err)
}
}
func (c *Context) JSON(code int, data interface{
}) {
c.SetHeader(ContentType, "application/json")
c.SetStatusCode(code)
encoder := json.NewEncoder(c.Writer)
if err := encoder.Encode(data); err != nil {
log.Printf("Response JSON Error: %v", err)
http.Error(c.Writer, err.Error(), 500)
}
}
func (c *Context) HTML(code int, html string) {
c.SetHeader(ContentType, "text/html")
c.SetStatusCode(code)
if _, err := c.Writer.Write([]byte(html)); err != nil {
log.Printf