HTTP协议
HTTP协议是超文本传输协议,工作在应用层,它是基于TCP/IP通信协议来传输数据。
HTTP协议工作于客户端-服务端架构为上。浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。Web服务器根据接收到的请求后,向客户端发送响应信息。
主要特点有:
- 1、简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
- 2、灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。
- 3.无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
- 4.无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
- 5、支持B/S及C/S模式。
对其实现主要是客户端和服务端得实现,在Go语言中使用net/http包来实现服务端和客户端。
HTTP服务端
服务端的处理流程与Socket一样:
- 监听
- 等待请求
- 处理请求
默认Server
ListenAndServe使用指定的监听地址和处理器启动一个HTTP服务端。处理器参数通常是nil,这表示采用包变量DefaultServeMux作为处理器。
Handle和HandleFunc函数可以向DefaultServeMux添加处理器。http.Handle("/foo", fooHandler)http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))})log.Fatal(http.ListenAndServe(":8080", nil))
示例:
package mainimport ("fmt""net/http")func helloWorld(response http.ResponseWriter, request *http.Request) {fmt.Println("Hello World")response.Write([]byte("你好"))}func main() {// 接受请求并处理http.HandleFunc("/", helloWorld)// 监听err := http.ListenAndServe(":9000", nil)if err != nil {fmt.Println("http server listen failed. err", err)return}}
然后启动服务,在浏览器访问:
HTTP客户端
客户端主要是发一些GET、POST请求等,在Go语言中使用Get、Head、Post、PostForm函数来发送HTTP/HTTPS请求。
resp, err := http.Get("http://example.com/")...resp, err := http.Post("http://example.com/upload", "image/jpeg", &buf)...resp, err := http.PostForm("http://example.com/form",url.Values{"key": {"Value"}, "id": {"123"}})
程序在使用完response后必须关闭回复的主体。
resp, err := http.Get("http://example.com/")if err != nil {// handle error}defer resp.Body.Close()body, err := ioutil.ReadAll(resp.Body)// ...
Get请求
package mainimport ("fmt""io/ioutil""net/http")func main(){resp,err:=http.Get("https://www.baidu.com")if err != nil {fmt.Println("请求失败. err:",err)return}defer resp.Body.Close()fmt.Println(resp.Status)// 读取王内容content,err:=ioutil.ReadAll(resp.Body)if err != nil {fmt.Println("读取文件内容失败. err",err)return}fmt.Println(string(content))}
带参数的Get请求
用上面的方法也可以带参数,如下:
package mainimport ("fmt""io/ioutil""net/http")func main(){resp,err:=http.Get("https://baike.baidu.com/item/%E6%96%B0%E5%9E%A3%E7%BB%93%E8%A1%A3/8035884?fr=aladdin")if err != nil {fmt.Println("请求失败. err:",err)return}defer resp.Body.Close()fmt.Println(resp.Status)// 读取王内容content,err:=ioutil.ReadAll(resp.Body)if err != nil {fmt.Println("读取文件内容失败. err",err)return}fmt.Println(string(content))}
但是这种很多就不灵活。Go语言中net/url包可以用来处理加参数的请求。
如下:
Client端 :
package mainimport ("fmt""io/ioutil""net/http""net/url")func main() {// 请求的urlreqURL := "http://127.0.0.1:9000/get"// 构造请求数据data := url.Values{}// 向里面放入值data.Set("name", "joker")data.Set("age", "20")// 解析上面的urlu, err := url.ParseRequestURI(reqURL)if err != nil {fmt.Println("url解析失败. err:", err)return}// 对需要传输的数据进行编码u.RawQuery = data.Encode()fmt.Println(u.String())// 发送请求resp, err := http.Get(u.String())if err != nil {fmt.Println("请求失败. err:", err)return}fmt.Println(resp.Status)defer resp.Body.Close()// 读取请求到的内容bn, err := ioutil.ReadAll(resp.Body)if err != nil {fmt.Println("读取内容失败。err:", err)return}// 输出内容fmt.Println(string(bn))}
Server端 :
package mainimport ("fmt""net/http")func helloWorld(response http.ResponseWriter, request *http.Request) {fmt.Println("Hello World")response.Write([]byte("你好"))}func getTest(response http.ResponseWriter, request *http.Request) {defer request.Body.Close()fmt.Println(request.URL.Query())answer := []byte("ok")response.Write(answer)}func main() {// 接受请求并处理http.HandleFunc("/", helloWorld)http.HandleFunc("/get", getTest)// 监听err := http.ListenAndServe(":9000", nil)if err != nil {fmt.Println("http server listen failed. err", err)return}}
POST请求
Client端 :
package mainimport ("fmt""net/http""strings")func main() {// post请求urlpostURL := "http://127.0.0.1:9000/post"// 定义文件类型contentType := "application/json"// 定义post的数据data := `{"name":"joker","age":20}`// 发送post请求resp, err := http.Post(postURL, contentType, strings.NewReader(data))if err != nil {fmt.Println("post请求失败。err:", err)return}defer resp.Body.Close()fmt.Println(resp.Status)}
Server端 :
package mainimport ("fmt""io/ioutil""net/http")func helloWorld(response http.ResponseWriter, request *http.Request) {fmt.Println("Hello World")response.Write([]byte("你好"))}func getTest(response http.ResponseWriter, request *http.Request) {defer request.Body.Close()fmt.Println(request.URL.Query())answer := []byte("ok")response.Write(answer)}func postTest(response http.ResponseWriter, request *http.Request) {defer request.Body.Close()// 解析form数据request.ParseForm()fmt.Println(request.ParseForm())fmt.Println(request.PostForm.Get("name"), request.PostForm.Get("age"))b, err := ioutil.ReadAll(request.Body)if err != nil {fmt.Println("数据读取失败。err:", err)return}fmt.Println(string(b))}func main() {// 接受请求并处理http.HandleFunc("/", helloWorld)http.HandleFunc("/get", getTest)http.HandleFunc("/post", postTest)// 监听err := http.ListenAndServe(":9000", nil)if err != nil {fmt.Println("http server listen failed. err", err)return}}
自定义Client
要管理HTTP客户端的头域、重定向策略和其他设置,创建一个Client:
package mainimport ("net/http""strings""fmt""io/ioutil""log""encoding/json")func main() {client := &http.Client{}req, err := http.NewRequest("POST", "http://www.maimaiche.com/loginRegister/login.do",strings.NewReader("mobile=xxxxxxxxx&isRemberPwd=1"))if err != nil {log.Println(err)return}req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")resp, err := client.Do(req)defer resp.Body.Close()body, err := ioutil.ReadAll(resp.Body)if err != nil {log.Println(err)return}fmt.Println(resp.Header.Get("Content-Type")) //application/json;charset=UTF-8type Result struct {Msg stringStatus stringObj string}result := &Result{}json.Unmarshal(body, result) //解析json字符串if result.Status == "1" {fmt.Println(result.Msg)} else {fmt.Println("login error")}fmt.Println(result)}
自定义Transport
要管理代理、TLS配置、keep-alive、压缩和其他设置,创建一个Transport:
tr := &http.Transport{TLSClientConfig: &tls.Config{RootCAs: pool},DisableCompression: true,}client := &http.Client{Transport: tr}resp, err := client.Get("https://example.com")
Client和Transport类型都可以安全的被多个goroutine同时使用。出于效率考虑,应该一次建立、尽量重用。
