golang系列——实战http服务器

上一篇讲解了client端相关的知识,这一篇讲解服务端。golang语言可以快速实现一个简单的server端,如下所示:

package main import ( "net/http" "log" ) type TestHandler struct { str string } func SayHello(w http.ResponseWriter, r *http.Request){ log.Printf("HandleFunc") w.Write([]byte(string("HandleFunc"))) } //ServeHTTP方法,绑定TestHandler func (th *TestHandler)ServeHTTP(w http.ResponseWriter, r *http.Request){ log.Printf("Handle") w.Write([]byte(string("Handle"))) } func main(){ http.Handle("/", &TestHandler{"Hi"})//根路由 http.HandleFunc("/test", SayHello) //test路由 http.ListenAndServe("127.0.0.1:8000",nil)}

上述代码就轻松实现一个监听本地8000端口的服务端。大家可能注意到,代码调用两个路由的处理函数:Handle和HandleFunc,大家可以任选一个使用。大多数情况下我们选择HandleFunc,因为其第二个参数是我们想要的处理函数;而Handle的第二个参数是一个handler对象,该对象必须实现ServeHTTP方法,我们在ServeHTTP方法中完成我们的处理逻辑,显然直接使用HandleFunc要方便一些。实际上从源码中可以看到HandleFunc最终也是调用了Handle函数完成操作。在接下来的代码中将只用HandleFunc进行演示。

假设做一个登录功能,结合上一篇文章的知识点,很容易写出client端的代码如下:

package main import ( "io/ioutil" "log" "net/http" "sync" ) var wc sync.WaitGroup func HelloClient(client *http.Client, url string, method string){ defer wc.Done() req, err := http.NewRequest(method, url, nil) if err != nil { log.Fatal(err) } rep, err := client.Do(req) if err != nil { log.Fatal(err) } data, err := ioutil.ReadAll(rep.Body) rep.Body.Close() if err != nil { log.Fatal(err) } log.Printf("%s", data) } func main() { client := &http.Client{} rootURL := ":8000/" wc.Add(1) go HelloClient(client, rootURL + string("login"),"GET") wc.Wait() }

那么服务端经过改造后得到如下代码:

package main import ( "net/http" "log" ) func login(w http.ResponseWriter, r *http.Request) { log.Printf("login") w.Write([]byte(string("login"))) } func main(){ http.HandleFunc("/login",login) http.ListenAndServe("127.0.0.1:8000", nil) }

这就可以实现client端访问server端,当然也可以从浏览器直接访问。然而这种方式存在一些问题,在如下所示的HandleFunc函数的源码中发现其调用的实际上是默认的DefaultServeMux对象。

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) }

那么如果我们只要自定义ServeMux对象,然后再调用HandleFunc函数就可以实现更加灵活的路由功能,接下来我们对mian函数进行改造。

func main() { mux := http.NewServeMux()//声明多路复用mux对象 mux.HandleFunc("/login", login) //通过实现mux的ServeHTTP方法可实现路由功能 http.ListenAndServe("127.0.0.1:8000", mux)//路由注册 }

另外可以自定义server对象,设置读超时、写超时等多种参数。如下所示,我们声明一个server变量,设置超时时间为2秒。

func main() { server := &http.Server{ Addr:"127.0.0.1:8000", ReadTimeout:2 * time.Second, WriteTimeout:2 * time.Second, } mux := http.NewServeMux() mux.HandleFunc("/login", login) server.Handler = mux server.ListenAndServe() }

同时将login函数修改一下,产生3秒的睡眠,超过设置的2秒超时,服务器端则不会返回任何数据,所以此时client端也无法获取数据。

func login(w http.ResponseWriter, r *http.Request) { log.Printf("login") time.Sleep(time.Second * 3) w.Write([]byte(string("login"))) }

下面演示一下client如何用POST方法向服务器提交数据,需要将GET方法修改成POST方法。

var wc sync.WaitGroup //HelloClient sdfsa. func HelloClient(client *http.Client, url string, method string) { defer wc.Done() if method=="POST": reqData := "name=ali&age=19" req, err := http.NewRequest(method, url, strings.NewReader(reqData)) else: req, err := http.NewRequest(method, url, nil) //GET if err != nil { log.Fatal(err) } //设置Content-Type很重要 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") rep, err := client.Do(req) if err != nil { log.Fatal(err) } data, err := ioutil.ReadAll(rep.Body) rep.Body.Close() if err != nil { log.Fatal(err) } log.Printf("%s", data) } func main() { client := &http.Client{} rootURL := ":8001/post" wc.Add(1) go HelloClient(client, rootURL, "POST") wc.Wait() }

而对应的server端需要在login函数中加上接受数据的逻辑即可:

func login(w http.ResponseWriter, r *http.Request) { w.Write([]byte(string("post"))) r.ParseForm() fmt.Println(r.Form) fmt.Println(r.Form["name"]) fmt.Println(r.Form["age"]) }