在写go的http服务时使用handle来避免全局变量的使用

使用go可以很方便的编写http服务的应用, net/http基础包提供了很多实用的功能. 看下官网的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
    "io"
    "net/http"
    "log"
)

// hello world, the web server
func HelloServer(w http.ResponseWriter, req *http.Request) {
    io.WriteString(w, "hello, world!\n")
}

func main() {
    http.HandleFunc("/hello", HelloServer)
    err := http.ListenAndServe(":12345", nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

简介且明了~

问题

如果我的HelloServer函数里面需要使用到数据库, 一个最直观的做法是通过函数参数把数据库相关内容传进去, 然而遗憾的是没有类似函数. 一般碰到这种情况, 我大概就用 全局变量的方式去搞了. 但是由于本人不太喜欢全局变量, 一是不美观, 二是让自己的程序不太好测试, 所以还是想找找对应的解决办法. 然后在这里找到了. 抄一段里面的实现:

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
type appHandler func(http.ResponseWriter, *http.Request) (int, error)

// Our appHandler type will now satisify http.Handler 
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if status, err := fn(w, r); err != nil {
        // We could also log our errors centrally:
        // i.e. log.Printf("HTTP %d: %v", err)
        switch status {
        // We can have cases as granular as we like, if we wanted to
        // return custom errors for specific status codes.
        case http.StatusNotFound:
            notFound(w, r)
        case http.StatusInternalServerError:
            http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
        default:
            // Catch any other errors we haven't explicitly handled
            http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
        }
}

func myHandler(w http.ResponseWriter, r *http.Request) (int, error) {
    session, err := store.Get(r, "myapp")
    if err != nil {
        // Much better!
        return http.StatusInternalServerError, err
    }

    post := Post{ID: id}
    exists, err := db.GetPost(&post)
    if err != nil {
        return http.StatusInternalServerError, err
    }

    // We can shortcut this: since renderTemplate returns `error`,
    // our ServeHTTP method will return a HTTP 500 instead and won't 
    // attempt to write a broken template out with a HTTP 200 status.
    // (see the postscript for how renderTemplate is implemented)
    // If it doesn't return an error, things will go as planned.
    return http.StatusOK, renderTemplate(w, "post.tmpl", data)
}

func main() {
    // Cast myHandler to an appHandler
    http.Handle("/", appHandler(myHandler))
    http.ListenAndServe(":8000", nil)
}

简单说, 就是通过http.Handle方法. 我们要做的就是去实现Handler这个接口. 然而看到上面那段代码我还是凌乱的, 因为在我的印象当中, 实现接口需要有一个对应的struct, 里面那个类型转换真的是看的我云里雾里. 这里就又牵扯到函数和接口的问题了.

接口

这里就又牵扯到了函数和接口的一系列内容, 这里有两篇非常好的总结(强烈推荐):

摘一段我认为最有用的:

Function types in Go can also have methods. It’s hard to see, at first, why this would be useful. There are two side effects to the fact that function types can have methods in Go: firstly, since any type that can have methods can satisfy an interface, function types in Go can also be boxed into valid interface types. Secondly, since methods in Go can have either pointer or value receivers, we can use methods on function pointers to switch the function being pointed to in the method’s calling context.

看完这些再看看上面的代码, 估计就可以理解了;)

其他分享

  • go有个很好的文档工具: godoc, 有点类似perldoc. 在没有网络的情况下, 可以实用godoc -http=:port来启动一个http服务, 直接可以查看package文档, 很方便.
  • 一个不错的slide: 10 things you (probably) don’t know about Go
go

Comments