美文网首页Golang 入门资料+笔记PHP实战Golang
【轻知识】Go入门学习整理——第四节web开发,http请求处理

【轻知识】Go入门学习整理——第四节web开发,http请求处理

作者: 言十年 | 来源:发表于2019-05-02 20:16 被阅读35次

    先从 hello world开始

    关于web的开发知识,其实绕不过astaxie(beego作者)写的《build-web-application-with-golang》。我开发公司的beego项目之前看了一遍,现在再次拜读。

    用http服务要引入net/http包。

    我们看下那个启动http服务的方法是哪个。

    func ListenAndServe(addr string, handler Handler) error {
        server := &Server{Addr: addr, Handler: handler}
        return server.ListenAndServe()
    }
    

    ok ,接着看handler是什么鬼?

    type Handler interface {
        ServeHTTP(ResponseWriter, *Request)
    }
    

    ResponseWriter呢?

    type ResponseWriter interface {
        Header() Header
        Write([]byte) (int, error)
        WriteHeader(statusCode int)
    }
    

    ok , 那也就是说有个结构体实现了ServeHTTP方法就可以传入到ListenAndServe中了,对吧?buddy!

    package main
    
    import (
        "fmt"
        "net/http"
    )
    
    type HttpHandler struct {
    }
    // 回到的参数名名自己随便命名对吧。w也好response也好。只要按照参数的格式写就OK。http.ResponseWriter,当然就是package http中的ResponseWriter这个结构体。Request也一样。
    func (h HttpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello world!") //这个写入到w的是输出到客户端的
    }
    
    func main() {
        h := new(HttpHandler)
        http.ListenAndServe(":9090", h)
    }
    

    get 请求http://localhost:9090,ok没问题是hello world!

    但,我们的接口通常都是好几个对吧。比如,我要添加数据,我要查数据。走不同的接口。也即是根据不同的Path做不同的事情。OK,没问题。

    package main
    
    import (
        "fmt"
        "net/http"
    )
    
    type HttpHandler struct {
    }
    
    func search(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "select from db")
    }
    func add(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "insert into db")
    }
    func (h HttpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
        switch r.URL.Path {
        case "/search":
            search(w, r)
        case "/add":
            add(w, r)
        default:
            fmt.Fprintf(w, "Hello world!")
        }
    }
    
    func main() {
        h := new(HttpHandler)
        http.ListenAndServe(":9090", h)
    }
    

    这样相当于我手动写了一个路由。那么有没有像laravel那样的路由方式呢?

    Route::get('foo', function () {
        return 'Hello World';
    });
    

    http包里面有个HandleFunc函数可以实现。那把上面的代码改造下。

    http.HandleFunc("/search", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "select from db")
    })
    http.HandleFunc("/add", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "select from db")
    })
    http.ListenAndServe(":9090", nil)
    

    ok,这样分开之后看起来舒服多了。关于web服务的执行流程。astaxie写的文章可以琢磨下哈。

    我是看明白了哈。简单的追下代码哈!主要是我想知道路由怎么分发的。

    1.点入ListenAndServe
    2.点入server.ListenAndServe()
    3.点入 srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
    4.点入go c.serve(ctx)(函数中找哈)(这行代码就是高性能的点,开启协程)
    5.然后你会发现这行代码serverHandler{c.server}.ServeHTTP(w, w.req)(就是执行了我们的配置的handler)继续点入ServeHTTP

    ok。重点来了,路由如何分发的。奥妙就在ServeHTTP中。

    func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
        handler := sh.srv.Handler
        if handler == nil {
            handler = DefaultServeMux
        }
        if req.RequestURI == "*" && req.Method == "OPTIONS" {
            handler = globalOptionsHandler{}
        }
        handler.ServeHTTP(rw, req)
    }
    

    回顾下,我们上来先自定义的那个结构体了么?然后传给了ListenAndServe。实际上server保存了它。于是,在ServeHTTP方法中的体现就是这行代码handler := sh.srv.Handler。因为我传了所以不为nil。于是走handler.ServeHTTP(rw, req)。执行了我自定义的HttpHandler中的ServeHTTP。

    OK,那么 handler == nil ,也就是handler为空呢?也就是我们用HandleFunc改造过的写法。ListenAndServe里面穿的nil。也就意味着server保存的handler为nil。于是在ServeHTTP方法中走这里handler = DefaultServeMux

    这里再提两点。剩下的自己追下代码。

    1.HandleFunc 作用就是给DefaultServeMux配置了路由。保存到了map中。 2.DefaultServeMux 类型是*ServeMux。ServeMux里面实现了ServeHTTP(想起来了么,只要实现了ServeHTTP就能处理http请求与响应),实现的ServeHTTP方法里面根据path做了路由转发,也就是从之前保存的map里面拿出来对应的handler。最后执行。

    更多看3.4 Go的http包详解

    来了?表单!

    OK,接下来要做的就是,准备好简单的前端页面。一个框框(输入名字)一个按钮(提交),一个列表(成功后刷新列表)。姑且就叫做名单功能吧。

    接着之前的代码哈。已经有了add方法(添加名单),search方法(展示列表)。当然还没有实现。还需要一个display方法(渲染页面)。三个方法就OK了。

    功能

    image.png

    1.包级作用域的nameList数组,存放名单。
    2.页面加载请求search方法(也就是列表),添加把name添加到nameList中。

    目录结构

    ─web
        └───views
                  └─── list.html
        └───main.go
    

    贴代码

    先准备页面,js用vue。贴代码。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>名单列表</title>
        <script type="text/javascript" src="https://unpkg.com/vue@2.1.10/dist/vue.js"></script>
        <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue-resource@1.3.4"></script>
    </head>
    <body>
        <div id="app">
            <div>
                <input type="text" placeholder="name" v-model="name"> <button @click="add()">add</button>
            </div>
            <div>
                <ul>
                    <li v-for="value in nameList">
                        <p>ID: {{value.id}}</p>
                        <p>Name: {{value.name}}</p>
                    </li>
                </ul>
            </div>
        </div>
        <script>
            Vue.http.options.emulateJSON = true;
            Vue.http.options.headers = {
            'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
            };  //Vue-resource中post请求将data数据以request payload转换为form data的形式
            var app = new Vue({
                    el: '#app',
                    mounted: function() {
                        let that = this
                        this.$http.post("/search", {}).then(function(res) {
                            that.nameList = JSON.parse(res.bodyText).name_list
                        })
                    },
                    data: {
                        name:'',
                        nameList: []
                    },
                    methods: {
                        add: function() {
                            let that = this
                            // 发送请求给 后台
                            this.$http.post("/add", {name: this.name}).then(function(res) {
                                this.$http.post("/search", {}).then(function(res) {
                                    that.nameList = JSON.parse(res.bodyText).name_list
                                })
    
                            }) 
                        }
                    }
            });
        </script>
    </body>
    </html>
    

    贴go代码

    package main
    
    import (
        "encoding/json"
        "fmt"
        "html/template"
        "net/http"
    )
    
    var nameList []map[string]interface{}
    
    func main() {
        http.HandleFunc("/list", func(w http.ResponseWriter, r *http.Request) {
            pageFile := "./views/list.html"
            t, error := template.New("list.html").Delims("<<<", ">>>").ParseFiles(pageFile)
            if error != nil {
                http.Error(w, error.Error(), http.StatusInternalServerError)
            }
            t.Execute(w, nil)
        })
        http.HandleFunc("/search", func(w http.ResponseWriter, r *http.Request) {
            data := map[string]interface{}{"name_list":nameList}
            list, err := json.Marshal(data)
            if err != nil {
                fmt.Println("json.Marshal failed:", err)
                return
            }
            w.Header().Add("Content-Type","application/json")
            fmt.Fprintf(w, string(list)) //这个写入到w的是输出到客户端的
        })
        http.HandleFunc("/add", func(w http.ResponseWriter, r *http.Request) {
            fmt.Println(r.FormValue("name"))
            fmt.Println(r.Method)
            name := r.FormValue("name")
            if name == "" {
                fmt.Fprintf(w, "don't ")
                return 
            }
            var nameMap map[string]interface{} = map[string]interface{}{"id": len(nameList) + 1, "name": name}
            nameList = append(nameList, nameMap) // evaluated but not used 这种报错是需要接收返回值
            fmt.Println(nameList)
            fmt.Fprintf(w, "add from db")
        })
        http.ListenAndServe(":9090", nil)
    }
    
    

    总结,关于这个程序呢。有些不足之处,比如校验没做,比如错误处理没做。比如没有统一返回的函数与格式。对吧!但先到这里。

    下一步要做的就是操作mysql或者再加redis。而不是放到包级作用域的数组中。

    参考资料:

    *《build-web-application-with-golang》https://github.com/astaxie/build-web-application-with-golang
    *《Go语言实战笔记(二十)| Go Context》https://www.flysnow.org/2017/05/12/go-in-action-go-context.html
    *《Change default delimiters templating with “template.Delims”》https://medium.com/@etiennerouzeaud/change-default-delimiters-templating-with-template-delims-857938a0b661
    *《Vue-resource中post请求将data数据以request payload转换为form data的形式》https://blog.csdn.net/qq_35844177/article/details/70170416

    相关文章

      网友评论

        本文标题:【轻知识】Go入门学习整理——第四节web开发,http请求处理

        本文链接:https://www.haomeiwen.com/subject/jgsnnqtx.html