Go语言通过引入net/http包来实现http网络访问,并提供HTTP客户端和服务端的实现。
先看服务端的实现,几行代码就可以启动1个HTTP服务,还是很简单的。
package main import ( "fmt" "net/http" ) func getHandler(w http.ResponseWriter, r *http.Request){ _, _ = fmt.Fprintf(w, "hello go!") } func main(){ //绑定路由 http.HandleFunc("/", getHandler) //监听端口,开启服务 err := http.ListenAndServe(":9090", nil) if err != nil{ fmt.Println("ListenAndServe: ", err) } }简单的分析一下这几行代码的意思。
http.HandleFunc("/", getHandler) 表示绑定路由,URL上 是 / 的请求,都它走 getHandler 方法去执行。http.ListenAndServe(":9090", nil) 表示的监听本地的9090端口,并启动服务,第二参数handler 为 nil,表示不传,这个在下面进行分析。getHandler 函数,他接受2个参数,固定写法,一个是给客户端返回的Respone,一个是接受请求的Request。简单的几句代码,就完成了一个HTTP服务,还是很方便的。
go里面路由是得自己手动进行绑定的,而且是强匹配。你没绑定,就访问不了。所以得1个1个进行绑定,这是很痛苦的一件事情。
//绑定路由 http.HandleFunc("/", getHandler1) http.HandleFunc("/about", getHandler2) http.HandleFunc("/user/getUserInfo", getHandler3) http.HandleFunc("/goods/getList", getHandler4) ......如果你的项目有很多个接口,那就完蛋了。这样写下去得累死不说,而且很难维护,代码的可读性以及整体感觉会很乱。
最关键是没法实现一些很常见的正则的匹配,比如:
http.HandleFunc("/user/{id}", getHandler4) http.HandleFunc("/user/{id}/name/{name}", getHandler4)所以,我想想,怎么去搞一套自动匹配路由的机制,和PHP一样方便。先来看一下,第二种启动服务的方式。
func ListenAndServe(addr string, handler Handler) error {}ListenAndServe 的第一个参数addr,第二个参数类型是Handler。我们看下Handler的定义:
type Handler interface { ServeHTTP(ResponseWriter, *Request) }他是一个接口。只要实现了接口里面的ServeHTTP的方法的,都可以传入。我们可以利用这个参数,来实现自动化路由:
package main import ( "fmt" "net/http" ) func getHandler(w http.ResponseWriter, r *http.Request){ if r.URL.Path == "/" { _, _ = fmt.Fprintf(w, "hello go!") return } if r.URL.Path == "/about" { _, _ = fmt.Fprintf(w, "about go!") return } http.NotFound(w, r) } func main(){ //http.HandleFunc("/b", getHandler) err := http.ListenAndServe(":9090", http.HandlerFunc(getHandler)) if err != nil{ fmt.Println("ListenAndServe: ", err) } }差不多的代码,http.HandlerFunc() 是将一个普调的函数转换成Handler类型的。这样,我们就可以在函数里面通过判断r.URL.Path的方式,来实现自动路由了。
但是这一种效率比第一张直接绑定的效率要低一点。
也可以自定义方法,只要实现ServeHTTP就行:
package webser import ( "strings" "fmt" "net/http" "log" ) type MyMux struct{ } func (p *MyMux)ServeHTTP(w http.ResponseWriter, r *http.Request){ if r.URL.Path == "/"{ sayHelloName(w, r) return } if r.URL.Path == "/about"{ about(w, r) return } http.NotFound(w,r) return } func sayHelloName(w http.ResponseWriter, r *http.Request){ r.ParseForm() fmt.Println(r.Form) fmt.Println("path: ", r.URL.Path) fmt.Println("scheme: ", r.URL.Scheme) for k, v := range r.Form{ fmt.Println("key: ", k) fmt.Println("val: ", strings.Join(v, " ")) } fmt.Fprintf(w, "hello chain!") } func about(w http.ResponseWriter, r *http.Request){ fmt.Fprintf(w, "i am chain, from shanghai") } func Start(){ mux := &MyMux{} err := http.ListenAndServe(":9090", mux) if err != nil{ log.Fatal("ListenAndServe: ", err) } }我们自定义了1个Mux的结构体,并且给他绑定了一个ServeHTTP的方法,也就是,他实现了Handler接口。那就是一个Handler类型的数据,就可以传入到http.ListenAndServe的第二个参数中。
以上就是使用内建的http包搭建server服务的过程。更加高级的路由功能,等学习其他优秀的框架的时候再深入吧。
参考:
https://www.jianshu.com/p/ae9b5628d18b https://blog.csdn.net/qq_34777600/article/details/81157875 https://zhuanlan.zhihu.com/p/161637770
在go里面,使用http的客户端,也同样是导入net/http包,可以使用 Get、Head、Post函数发出HTTP/HTTPS请求。
我们一个一个来看,先看下发起一个get请求:
package main import ( "fmt" "io/ioutil" "net/http" ) func main() { Get() } func Get() { //发起请求 resp, err := http.Get("http://localhost:9090/?a=1&b=232323") if err !=nil { fmt.Println(err) } //延迟关闭 defer resp.Body.Close() //读取body数据 body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Println("read from resp.Body failed, err", err) return } //转换成string输出 fmt.Println(string(body)) }这是最简单,也是最常见的方式。还有一种就是借助url包的方式,来设置参数:
package main import ( "fmt" "io/ioutil" "net/http" "net/url" ) func main() { Get2() } //带参数的方法2 func Get2() { apiUrl := "http://127.0.0.1:9090/" data := url.Values{} data.Set("a", "123") data.Set("b", "456") data.Set("c", "789") //转换成url对象 u, err := url.ParseRequestURI(apiUrl) if err != nil { fmt.Println(err) } //赋值 u.RawQuery = data.Encode() //打印url完整的值 fmt.Println(u.String()) //输出: http://127.0.0.1:9090/?a=123&b=456&c=789 //开始请求 resp, err := http.Get(u.String()) if err != nil { fmt.Println("get failed, err:", err) return } //延迟关闭 defer resp.Body.Close() //读取内容 b, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Println("get resp failed, err:", err) return } //输出 fmt.Println(string(b)) }我们来看下post请求如何实现的,需要注意的是,post请求分成好几种方式,有传统默认的表单模式的application/x-www-urlencoded,已经有文件上传的multipart/form-data,也有用json格式的application/json。这些配置需要设置在header里面。
我们先看默认的表单模式:
func Post() { //url url := "http://127.0.0.1:9090/" //post的内容 content := "a=1&b=2" //表单格式的数据 contentType := "application/x-www-form-urlencoded" //发起请求 resp, err := http.Post(url, contentType, strings.NewReader(content)) if err != nil { fmt.Println("post failed, err:", err) return } defer resp.Body.Close() b, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Println("get resp failed, err", err) return } fmt.Println(string(b)) }需要注意的是,Post的第二个参数,我们可以需要指定post的是什么格式。第三个参数,需要转换成io.Reader格式。
再看下json格式的请求:
func Post() { //url url := "http://127.0.0.1:9090/" //post的内容 content := `{"a":1, "b":2}` //json格式的数据 contentType := "application/json" //发起请求 resp, err := http.Post(url, contentType, strings.NewReader(content)) if err != nil { fmt.Println("post failed, err:", err) return } defer resp.Body.Close() b, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Println("get resp failed, err", err) return } fmt.Println(string(b)) }只需要把第二个和第三个参数,切换成json就可以了。也很简单。
值得注意的是,在HTTP服务端,json和表单方式的数据,接受方式不一样:
func postHandler(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() //1. 请求类型是aplication/x-www-form-urlencode时解析form数据 r.ParseForm() fmt.Println(r.PostForm) //打印form数数据 fmt.Println(r.PostForm.Get("a"), r.PostForm.Get("b")) //2. 请求类型是application/json时从r.Body读取数据 b, err := ioutil.ReadAll(r.Body) if err != nil { fmt.Println("read request.Body failed, err", err) return } fmt.Println(string(b)) answer := `{"status":"ok"}` w.Write([]byte(answer)) }同时,http还提供了1个postForm方式,专门用于表单的提单。
func PostForm(url string, data url.Values) (resp *Response, err error) { return DefaultClient.PostForm(url, data) } func (c *Client) PostForm(url string, data url.Values) (resp *Response, err error) { return c.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) }通过源码,我们可以知道,他实际上是contentType = application/x-www-form-urlencoded的Post的快捷方式。
那如何调用呢?
func Post2() { // postUrl := "http://127.0.0.1:9090/" //url Value data := url.Values{} data.Set("a", "123") data.Set("b", "456") data.Set("c", "789") resp, err := http.PostForm(postUrl, data) if err != nil { fmt.Println("post failed, err:", err) return } defer resp.Body.Close() b, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Println("get resp failed, err", err) return } fmt.Println(string(b)) }通过设置URL包的value值来实现的。
