[Golang] 简单Web服务到初级Web框架(Ⅰ)
要做什么
net/http库
- Go语言语法基础
- net/http库知识
最终实现的结构:
项目参考:https://github.com/flycash/toy-web @大明
最简单的HTTP服务
使用过Go语言开发就会知道,Go启动一个HTTP服务是十分简单的。
package main
import "net/http"
func main() {
http.HandleFunc("/hello", Hello)
http.ListenAndServe(":8088", nil)
}
func Hello(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello Go Web"))
}
http://localhost:8088/hello
原生HTTP库实现注册功能
net/http
实现代码如下:
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
http.HandleFunc("/hello", Hello)
http.HandleFunc("/SignUp", SignUp)
http.ListenAndServe(":8088", nil)
}
func Hello(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello Go Web"))
}
type signUpReq struct {
Email string `json:"email"`
Password string `json:"password"`
ConfirmedPassword string `json:"con"`
}
func SignUp(w http.ResponseWriter, r *http.Request) {
req := &signUpReq{}
// json
body, err := io.ReadAll(r.Body)
if nil != err {
fmt.Fprintf(w, "read body error %v \n", err)
return
}
err = json.Unmarshal(body, req)
if nil != err {
fmt.Fprintf(w, "json marshal errer %v", err)
return
}
// form
// err := r.ParseForm()
// if nil != err {
// w.WriteHeader(http.StatusBadRequest)
// fmt.Println("表单解析失败")
// return
// }
//email := r.FormValue("email")
// 返回一个虚拟的表示注册成功
fmt.Fprintf(w, "%d", err)
}
http://localhost:8088/SignUp
存在哪些问题?
现在我们可以看看,上面代码存在哪些问题:
- 没有响应码
- 不能返回格式化数据——比如json
- 仅仅只是解析传递过来的json,就需要那么多if else,有点冗余
- 强耦合了fmt包
上述问题是直观的问题,另外从从设计上来考虑,也有问题:
func (w http.ResponseWriter, r *http.Request)http.ListenAndServer()
解决问题
那么现在开始来解决问题。
针对响应码与序列化数据的
上面提到的问题,针对返回请求的响应码,并且能够返回序列化的json。
w.WriteHeader(http.StatutsOK)
type commonResponse struct {
BizCode int `json:"biz_code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
// ······
resp := &commonResponse{
BizCode: 4,
Msg: fmt.Sprintf("invalid request %v\n", err),
}
respBytes, _ := json.Marshal(resp)
fmt.Fprintf(w, string(respBytes))
请求封装为Context
(w http.ResponseWriter, r *http.Request)
type Context struct {
W http.ResponseWriter
R *http.Request
}
// 封装出来之后,就需要有一个构造方法
func NewContext(w http.ResponseWriter, r *http.Request) *Context {
return &Context{
W: w,
R: r,
}
}
这样操作带来什么好处呢?
内聚的东西,就允许我们当成一个整体来看了。从代码上的体现,就是能够针对Context对象定义出一系列方法(用来操作Request还有ResponseWrite)。
比如,我们上面提到的另外一个问题:只是简单读取出请求中的json数据,就需要if else 一大堆,十分冗余。那是因为代码的抽象不够,现在抽象出一个“请求”的对象,那么我们可以使用下面操作,为这个Context定义一些方法,将某些动作如解析,校验,返回等,封装起来。
// 返回对象
type commonResponse struct {
BizCode int `json:"biz_code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
// 从json中读取信息
func (c *Context) ReadJson(data interface{}) error {
body, err := io.ReadAll(c.R.Body)
if nil != err {
return err
}
return json.Unmarshal(body, data)
}
// 返回json
func (c *Context) WriteJson(states int, data interface{}) error {
bs, err := json.Marshal(data)
if err != nil {
return err
}
_, err = c.W.Write(bs)
if nil != err {
return err
}
c.W.WriteHeader(states)
return nil
}
另外,为了调用方便,还是可以进一步封装一些辅助方法:
func (c *Context) OkJson(data interface{}) error {
return c.WriteJson(http.StatusOK, data)
}
func (c *Context) SystemErrJson(data interface{}) error {
return c.WriteJson(http.StatusInternalServerError, data)
}
func (c *Context) BadRequestJson(data interface{}) error {
return c.WriteJson(http.StatusBadRequest, data)
}
另外,不得不提一句的是,Context到底需不需要抽象为一个接口呢? 看上去好像可以抽象。
这里的考虑其实是取决于框架开发者,抽不抽象出来表示你愿不愿意别人使用你这个框架的时候,自定义自己的Context。
(目前代码并没有抽象出Context接口 😃 )
将服务抽象为Server
http.WriteHeader()json包Context
现在剩下最后一个问题:
gin.Default()
怎么实现呢?上面出现的关键词就是 : 内聚、路由、监听。
还是一样,内聚封装,定义方法。
type HttpServer struct {
// Name 标识字段
Name string
// 拓展别的字段······
}
// 路由
func (s *HttpServer) Route(pattern string, handlerfunc http.HandlerFunc) {
http.HandleFunc(pattern, handlerfunc)
}
// 监听
func (s *HttpServer) Start(address string) error {
return http.ListenAndServe(address, nil)
}
这里定义一个接口:
type Server interface {
// Route 设定路由,直接执行handerfunec
Route(pattern string, hendlerFunc http.HandlerFunc)
// strat 启动服务器的指令
Start(address string) error
}
只要实现了这两个方法,就实现了Server的接口——允许使用者自定义Server。
修改后代码
package main
import (
"encoding/json"
"io"
"net/http"
)
func main() {
http.HandleFunc("/hello", Hello)
// http.HandleFunc("/SignUp", SignUp)
s := NewSignServer("web1")
s.Route("/SignUp", SignUp)
s.Start(":8089")
}
func Hello(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello Go Web"))
}
type signUpReq struct {
Email string `json:"email"`
Password string `json:"password"`
ConfirmedPassword string `json:"con"`
}
// -----------------------------增加Server-------------------------------
type Server interface {
// Route 设定路由
// Route(pattern string, hendlerFunc http.HandlerFunc)
Route(pattern string, hendlerFunc func(c *Context))
// strat 启动服务器的指令
Start(address string) error
}
type HttpServer struct {
// Name 标识字段
Name string
// 拓展别的字段······
}
// 路由
func (s *HttpServer) Route(pattern string, handlerfunc func(c *Context)) {
// http.HandleFunc(pattern, handlerfunc)
http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) { //这里的参数应该怎么传参?
c := NewContext(w, r)
handlerfunc(c)
})
}
// 监听
func (s *HttpServer) Start(address string) error {
return http.ListenAndServe(address, nil)
}
// 构造
func NewSignServer(name string) Server {
return &HttpServer{
Name: name,
}
}
// ---------------------------------------------------------------------
// -----------------------------增加Context-------------------------------
type Context struct {
W http.ResponseWriter
R *http.Request
}
// 封装出来之后,就需要有一个构造方法
func NewContext(w http.ResponseWriter, r *http.Request) *Context {
return &Context{
W: w,
R: r,
}
}
// 返回对象
type commonResponse struct {
BizCode int `json:"biz_code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
// 从json中读取信息
func (c *Context) ReadJson(data interface{}) error {
body, err := io.ReadAll(c.R.Body)
if nil != err {
return err
}
return json.Unmarshal(body, data)
}
// 返回json
func (c *Context) WriteJson(states int, data interface{}) error {
bs, err := json.Marshal(data)
if err != nil {
return err
}
_, err = c.W.Write(bs)
if nil != err {
return err
}
c.W.WriteHeader(states)
return nil
}
func (c *Context) OkJson(data interface{}) error {
return c.WriteJson(http.StatusOK, data)
}
func (c *Context) SystemErrJson(data interface{}) error {
return c.WriteJson(http.StatusInternalServerError, data)
}
func (c *Context) BadRequestJson(data interface{}) error {
return c.WriteJson(http.StatusBadRequest, data)
}
// ---------------------------------------------------------------------
// 这个实现不连贯(w,r)
// func SignUp(w http.ResponseWriter, r *http.Request) {
// c := NewContext(w, r)
// req := signUpReq{}
// err := c.ReadJson(req)
// if nil != err {
// _ = c.BadRequestJson(&commonResponse{
// BizCode: 4,
// Msg: "invaild request",
// })
// return
// }
// _ = c.OkJson(&commonResponse{
// Data: 1234,
// })
// }
func SignUp(c *Context) {
req := signUpReq{}
err := c.ReadJson(req)
if nil != err {
_ = c.BadRequestJson(&commonResponse{
BizCode: 4,
Msg: "invaild request",
})
return
}
_ = c.OkJson(&commonResponse{
Data: 1234,
})
}
可以把Context的内容放在另外一个文件中调用,这里为了方便,就都写在一个文件了。
上面的代码其实还有点问题需要进一步抽象与修改,可以找找看 :) 。
func (w http.ResponseWriter, r *http.Request)func (c *Context)
下一篇的内容(Ⅱ)
- 上面代码还存在的问题就是抽象度还不够——从上层往下看,请求的GET、POST怎么识别与处理。
- Gin相关的路由树操作
- 中间件与Hook函数等等