Go语言内置的net/http包十分优秀,提供了http客户端和服务器的实现。
超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络传输协议,所有的www文件都必须遵循这个标准。设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。
一. HTTP客户端
基本的HTTP/HTTPS请求Get,Head,Post和PostForm函数发出HTTP/HTTPS请求。
resp, err := http.Get("http://example.com")
//...
resp, err = http.Post("http://example.com", "image/jpeg", &buf)
//...
resp, err = http.PostForm("http://example.com", url.Values{"key": {"value"}, "id": {"123"}})
程序在使用完response后必须关闭回复的主体。
package main
import (
"io/ioutil"
"log"
"net/http"
)
func main() {
resp, err := http.Get("http://example.com")
if err != nil {
log.Println("http get fail")
return
}
//关闭回复主体
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
//...
}
1.1 Get请求
函数签名:
func Get(url string) (resp *Response, err error)
Get向指定的URL发出Get请求,如果回应的状态码如下,Get会调用c.CheckRedirect后执行重定向。
301(Moved Permanently)
302(Found)
303(See Other)
307(Temporary Redirect)
如果c.CheckRedirect执行失败或存在HTTP协议错误时,本方法返回错误。如果回应的状态码不是2xx,本方法并不会返回错误。如果返回值err为nil,resp.Body总是非nil的,调用者应该在读取完resp.Body后关闭它。
Get是对DefaultClient的Get方法的包装。
1.2 post和get示例服务端代码
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func getHandleFunc(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
data := r.URL.Query() //获得get方法的参数
fmt.Println(data.Get("name"))
fmt.Println(data.Get("age"))
answer := `{"status":"ok"}`
w.Write([]byte(answer))
}
func postHandleFunc(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
//请求数据类型是application/json
b, err := ioutil.ReadAll(r.Body)
if err != nil {
fmt.Println("read request body fail ", err)
return
}
fmt.Println(string(b))
answer := `{"statue":"ok"}`
w.Write([]byte(answer))
}
func handleFunc(w http.ResponseWriter, r *http.Request) {
fmt.Println("get a link....")
switch r.Method {
case http.MethodGet:
http.HandleFunc("/get", getHandleFunc)
case http.MethodPost:
http.HandleFunc("/post", postHandleFunc)
}
}
func main() {
http.HandleFunc("/", handleFunc)
err := http.ListenAndServe(":9090", nil)
if err != nil {
fmt.Println("http server fail ", err)
return
}
}
1.3 get方法示例
- 不带参数客户端
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
)
func main() {
resp, err := http.Get("https://www.baidu.com")
if err != nil {
log.Println("http get fail ", err)
return
}
//关闭回复主体
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println("read resp body fail ", err)
return
}
fmt.Print(string(body))
}
- 带参数客户端
关于Get请求带参数需要使用Go语言内置的net/url标准库来处理。
package main
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
)
func main() {
apiUrl := "http://127.0.0.1:9090/get"
//URL参数
data := url.Values{}
data.Set("name", "张三")
data.Set("age", "20")
//URL参数需要编码,因为有的特殊字符被赋予了特殊含义
u, err := url.ParseRequestURI(apiUrl)
if err != nil {
fmt.Println("url parse fail ", err)
}
u.RawQuery = data.Encode() //url encode
fmt.Println(u.String())
resp, err := http.Get(u.String())
if err != nil {
fmt.Printf("url %v get fail %v", u.String(), err)
return
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("read body fail ", err)
return
}
fmt.Println(string(b))
}
1.4 post请求
函数签名:
func Post(url string, bodyType string, body io.Reader) (resp *Response, err error)
Post向指定的URL发送一个POST请求。bodyType为POST的数据类型,Body为POST数据,作为请求的主体。如果参数body实现了io.Closer接口,它会在发送请求后被关闭。调用者有责任在读取完返回值resp的主体后关闭它。
Post是对包变量DefaultClient的Post方法的包装。
1.5 post示例
package main
import (
"fmt"
"io/ioutil"
"net/http"
"strings"
)
func main() {
apiUrl := "http://127.0.0.1:9090/post"
//表单数据
contenttype := "application/json"
data := `{"name":"张三", "age":18}`
resp, err := http.Post(apiUrl, contenttype, strings.NewReader(data))
if err != nil {
fmt.Printf("url : %v post fail err : %v\n", apiUrl, err)
return
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("read body err ", err)
return
}
fmt.Println(string(b))
}
1.6 自定义Client
要管理HTTP客户端头域,重定向和其它策略,创建一个client:
type Client struct {
// Transport指定执行独立、单次HTTP请求的机制。
// 如果Transport为nil,则使用DefaultTransport。
Transport RoundTripper
// CheckRedirect指定处理重定向的策略。
// 如果CheckRedirect不为nil,客户端会在执行重定向之前调用本函数字段。
// 参数req和via是将要执行的请求和已经执行的请求(切片,越新的请求越靠后)。
// 如果CheckRedirect返回一个错误,本类型的Get方法不会发送请求req,
// 而是返回之前得到的最后一个回复和该错误。(包装进url.Error类型里)
//
// 如果CheckRedirect为nil,会采用默认策略:连续10此请求后停止。
CheckRedirect func(req *Request, via []*Request) error
// Jar指定cookie管理器。
// 如果Jar为nil,请求中不会发送cookie,回复中的cookie会被忽略。
Jar CookieJar
// Timeout指定本类型的值执行请求的时间限制。
// 该超时限制包括连接时间、重定向和读取回复主体的时间。
// 计时器会在Head、Get、Post或Do方法返回后继续运作并在超时后中断回复主体的读取。
//
// Timeout为零值表示不设置超时。
//
// Client实例的Transport字段必须支持CancelRequest方法,
// 否则Client会在试图用Head、Get、Post或Do方法执行请求时返回错误。
// 本类型的Transport字段默认值(DefaultTransport)支持CancelRequest方法。
Timeout time.Duration
}
Client类型代表HTTP客户端。它的零值(DefaultClient)是一个可用的使用DefaultTransport的客户端。
Client的Transport字段一般会含有内部状态(缓存TCP连接),因此Client类型值应尽量被重用而不是每次需要都创建新的。Client类型值可以安全的被多个go程同时使用。
Client类型的层次比RoundTripper接口(如Transport)高,还会管理HTTP的cookie和重定向等细节。
//设置重定向
client := &http.Client{
CheckRedirect : redirectPolicyFunc,
}
resp, err := client.Get("http://example.com")
//...
//加请求头部
req, err := http.NewRequest("Get", "http://example.com", nil)
//...
req.Header.Add("If-None-Match", `W/"wyzzy"`)
resp, err := client.Do(req)
//...
1.7 自定义Transport
go语言中的http.transport是一个高性能的http客户端库,它提供了连接池、重试、超时控制等功能,可以方便地进行 http 请求。
要管理代理,TLS配置,keep-alive,压缩和其它设置,可以创建一个Transport,对应Client的Transport字段。
type Transport struct {
// Proxy指定一个对给定请求返回代理的函数。
// 如果该函数返回了非nil的错误值,请求的执行就会中断并返回该错误。
// 如果Proxy为nil或返回nil的*URL置,将不使用代理。
Proxy func(*Request) (*url.URL, error)
// Dial指定创建TCP连接的拨号函数。如果Dial为nil,会使用net.Dial。
Dial func(network, addr string) (net.Conn, error)
// TLSClientConfig指定用于tls.Client的TLS配置信息。
// 如果该字段为nil,会使用默认的配置信息。
TLSClientConfig *tls.Config
// TLSHandshakeTimeout指定等待TLS握手完成的最长时间。零值表示不设置超时。
TLSHandshakeTimeout time.Duration
// 如果DisableKeepAlives为真,会禁止不同HTTP请求之间TCP连接的重用。
DisableKeepAlives bool
// 如果DisableCompression为真,会禁止Transport在请求中没有Accept-Encoding头时,
// 主动添加"Accept-Encoding: gzip"头,以获取压缩数据。
// 如果Transport自己请求gzip并得到了压缩后的回复,它会主动解压缩回复的主体。
// 但如果用户显式的请求gzip压缩数据,Transport是不会主动解压缩的。
DisableCompression bool
// 如果MaxIdleConnsPerHost!=0,会控制每个主机下的最大闲置连接。
// 如果MaxIdleConnsPerHost==0,会使用DefaultMaxIdleConnsPerHost。
MaxIdleConnsPerHost int
// ResponseHeaderTimeout指定在发送完请求(包括其可能的主体)之后,
// 等待接收服务端的回复的头域的最大时间。零值表示不设置超时。
// 该时间不包括获取回复主体的时间。
ResponseHeaderTimeout time.Duration
// 内含隐藏或非导出字段
}
tr := &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: pool,
},
DisableCompression: true,
}
client := &http.Client{
Transport: tr,
}
resp, err := client.Get("http://example.com")
//...
二. 服务端
2.1 默认server
ListenAndServer使用指定的监听地址和处理器启动一个HTTP服务端。处理器参数通常是nil,这表示采用包变量DefaultServeMux作为处理器。
Handle和HandleFunc函数可以向DefaultServeMux添加处理器。
http.Handle("/foo", fooHandler)
http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello %q", html.EscapeString(r.URL.Path))
})
log.Fatal(http.ListenAndServe(":8080", nil))
2.2 示例
package main
import (
"fmt"
"net/http"
)
func sayHello(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
fmt.Fprintln(w, "hello 张三")
}
func main() {
http.HandleFunc("/", sayHello)
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println("http server fail, err ", err)
return
}
}
2.3 自定义Server
type Server struct {
Addr string // 监听的TCP地址,如果为空字符串会使用":http"
Handler Handler // 调用的处理器,如为nil会调用http.DefaultServeMux
ReadTimeout time.Duration // 请求的读取操作在超时前的最大持续时间
WriteTimeout time.Duration // 回复的写入操作在超时前的最大持续时间
MaxHeaderBytes int // 请求的头域最大长度,如为0则用DefaultMaxHeaderBytes
TLSConfig *tls.Config // 可选的TLS配置,用于ListenAndServeTLS方法
// TLSNextProto(可选地)指定一个函数来在一个NPN型协议升级出现时接管TLS连接的所有权。
// 映射的键为商谈的协议名;映射的值为函数,该函数的Handler参数应处理HTTP请求,
// 并且初始化Handler.ServeHTTP的*Request参数的TLS和RemoteAddr字段(如果未设置)。
// 连接在函数返回时会自动关闭。
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
// ConnState字段指定一个可选的回调函数,该函数会在一个与客户端的连接改变状态时被调用。
// 参见ConnState类型和相关常数获取细节。
ConnState func(net.Conn, ConnState)
// ErrorLog指定一个可选的日志记录器,用于记录接收连接时的错误和处理器不正常的行为。
// 如果本字段为nil,日志会通过log包的标准日志记录器写入os.Stderr。
ErrorLog *log.Logger
// 内含隐藏或非导出字段
}
要管理服务端的行为,可以创建一个自定义的Server:
s := &http.Server{
Addr: ":8080",
Handler: myHander,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}