Go http编程
Go http编程
http服务端编程
1.简单的http服务器模板
package main import ( "fmt" "net/http" ) func HelloHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello World") } func main () { http.HandleFunc("/", HelloHandler) http.ListenAndServe(":8000", nil) }
运行代码之后,在浏览器中打开localhost:8000就可以看到Hello World。这段代码先利用http.HandleFunc在根路由/上注册了一个HelloHandler, 然后利用http.ListenAndServe启动服务器并监听本地的 8000 端口。当有请求过来时,则根据路由执行对应的handler函数。
2.http.Request 结构
type Request struct { Method string // 请求方法,"" 默认为 GET URL *url.URL // URL 对象引用 Proto string // 协议 "HTTP/1.0" ProtoMajor int // 1 ProtoMinor int // 0 Header Header // 请求头对象 Body io.ReadCloser // 请求主体对象 GetBody func() (io.ReadCloser, error) // 获取请求主体的拷贝函数,用于客户端场景 ContentLength int64 // 请求主体长度 TransferEncoding []string // 转换编码 Close bool // 是否在结束后关闭连接 Host string // 主机 Form url.Values // 解析好的 form 数据,同时包含 URL 中的 QueryString,调用 ParseForm() 后生效 PostForm url.Values // 解析好的 POST、PUT、PATCH 的 form 数据,调用 ParseForm() 后生效 MultipartForm *multipart.Form // 解析好的 multipart form,包含上传文件 Trailer Header // 指定请求发送后的附加头信息 RemoteAddr string // 请求来源地址 RequestURI string // 请求行中未修改的请求URI,通常使用 URL 字段代替 TLS *tls.ConnectionState // TLS 状态信息对象 Cancel <-chan struct{} // 指示客户端请求可取消的闭包通道 Response *Response // 重定向到引发本次请求的响应对象 ctx context.Context // 服务器与客户端的上下文 }
3.请求方式
func IndexAction(w http.ResponseWriter, r *http.Request) { log.Print(r.Method) }
4.请求参数
通过 URL.Query() 方法可以获取查询字符串值 URL.values 对象,是一个映射结构。对象上的 .Get(key string) 方法获取 key 对应的第一个值。
// http://localhost:8888/?name=Hank func IndexAction(w http.ResponseWriter, r *http.Request) { log.Print(r.URL.Query()) log.Print(r.URL.Query()["name"]) log.Print(r.URL.Query().Get("name")) } // map[name:[Hank]] // [Hank] // Hank
除此之外,请求值对象 URL.Values 还支持:
- func (v Values) Set(key, value string) 设置方法
- func (v Values) Add(key, value string) 添加值方法
- func (v Values) Del(key string) 删除值方法
- func (v Values) Encode() string URL编码 Values
5.请求头
请求对象的 Header 属性可以访问到请求头信息。是映射结构,提供了 Get(key string) 方法获取 key 对应的第一个值。
// http://localhost:8888/ func IndexAction(w http.ResponseWriter, r *http.Request) { log.Print(r.Header) log.Print(r.Header["User-Agent"]) log.Print(r.Header.Get("User-Agent")) } // map[User-Agent:[Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36] Accept:[text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8] Accept-Encoding:[gzip, deflate, br] Accept-Language:[zh-CN,zh;q=0.9,en;q=0.8] Connection:[keep-alive] Cache-Control:[max-age=0] Upgrade-Insecure-Requests:[1]] // [Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36] // Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36
除此之外,Header 对象还支持:
- func (h Header) Set(key, value string) 设置头
- func (h Header) Add(key, value string) 添加头
- func (h Header) Del(key string) 删除头
- func (h Header) Write(w io.Writer) error // 使用线模式(in wire format)写头信息
6.获取 cookie
请求对象的 Cookie() 和 Cookies() 方法可以访问到请求携带的 Cookie 数据,Cookies() 获取 http.Cookie 对象切片,Cookie(key) 根据 key 得到具体的 Cookie 对象,演示如下:
func Test(w http.ResponseWriter, r *http.Request) { log.Println(r.Cookies()) c, _ := r.Cookie("User") log.Println(c.Name)
7.请求 URL
Request.URL 引用的是 url.URL 结构体类型,利用该对象可以获取 URL 相关信息。其定义结构为:
type URL struct { Scheme string // 协议 Opaque string // 编码数据 User *Userinfo // username 和 password 信息 Host string // 主机,格式为 host:port Path string // 路径(相对路径会省略前导斜钱) RawPath string // 编码 path (see EscapedPath method) ForceQuery bool // 追加查询 ('?') 即使 RawQuery 为空 RawQuery string // 编码查询字符串, 不包括 '?' Fragment string // 引用片段, 不包括 '#' }
典型的URL格式为: scheme://[userinfo@]host/path[?query][#fragment]。
注意,服务器端程序会获取 URI 信息而客户端信息会获取 URL 信息。
示例:
// http://localhost:8888/path/to/script.html func IndexAction(w http.ResponseWriter, r *http.Request) { log.Print(r.URL.Path) } // /path/to/script.html
http客户端编程
1.http.Client
net/http 包提供了最简洁的 HTTP 客户端实现,无需借助第三方网络通信库(比如 libcurl)就可以直接使用最常见的 GET 和 POST 方式发起 HTTP 请求。
具体来说,我们可以通过 net/http 包里面的 Client 类提供的如下方法发起 HTTP 请求:
func (c *Client) Do(req *Request) (*Response, error) func (c *Client) Get(url string) (resp *Response, err error) func (c *Client) Head(url string) (resp *Response, err error) func (c *Client) Post(url, contentType string, body io.Reader) (resp *Response, err error) func (c *Client) PostForm(url string, data url.Values) (resp *Response, err error)
2.http.Get
要发起一个 GET 请求,只需调用 http.Get() 方法并传入请求 URL 即可,示例代码如下:
resp, err := http.Get("https://xueyuanjun.com") if err != nil { // 处理错误 ... return } defer resp.Body.Close() io.Copy(os.Stdout, resp.Body)
上面这段代码用于对学院君首页发起请求,并将其网页内容打印到标准输出流中。
底层调用
其实通过 http.Get 发起请求时,默认调用的是上述 http.Client 缺省对象上的 Get 方法:
func Get(url string) (resp *Response, err error) { return DefaultClient.Get(url) }
而 DefaultClient 默认指向的正是 http.Client 对象实例:
var DefaultClient = &Client{}
它是 net/http 包的一个公开属性,当我们在 http 上调用 Get、Post、PostForm、Head 方法时,最终调用的都是该对象上的对应方法。
返回值
回到 http.Get() 方法本身,该方法返回值有两个,第一个是响应对象,第二个是 error 对象,如果请求过程中出现错误,则 error 对象不为空,否则,可以通过响应对象获取状态码、响应头、响应实体等信息,响应对象所属的类是 http.Response,你可以查看 API 文档或者源码了解该类型的具体信息,一般我们可以通过 resp.Body 获取响应实体,通过 resp.Header 获取响应头,通过 resp.StatusCode 获取响应状态码。
获取响应成功后记得调用 resp.Body 上的 Close 方法结束网络请求释放资源。
3.http.Post
要以 POST 的方式发送数据,也很简单,只需调用 http.Post() 方法并依次传递下面这 3 个参数即可:
- 请求目标的 URL
- POST 请求数据的资源类型(MIME Type)
- 数据的比特流([]byte 形式)
下面的示例代码演示了如何上传用户头像:
resp, err := http.Post("https://xueyuanjun.com/avatar", "image/jpeg", &imageDataBuf) if err != nil { // 处理错误 return } if resp.StatusCode != http.StatusOK { // 处理错误 return } // ...
底层实现及返回值和 http.Get 一样。
4.http.PostForm
http.PostForm() 方法实现了标准编码格式为 application/x-www-form-urlencoded 的 POST 表单提交。
下面的示例代码模拟 HTML 登录表单提交:
resp, err := http.PostForm("https://xueyuanjun.com/login", url.Values{"name":{"学院君"}, "password": {"test-passwd"}}) if err != nil { // 处理错误 return } if resp.StatusCode != http.StatusOK { // 处理错误 return } // ...
注意,POST 请求参数需要通过 url.Values 方法进行编码和封装。
底层实现及返回值和 http.Get 一样。
5.http.Head
HTTP 的 Head 请求表示只请求目标 URL 的响应头信息,不返回响应实体。我们可以通过 http.Head() 方法发起 Head 请求,该方法和 http.Get() 方法一样,只需传入目标 URL 参数即可。
下面的示例代码用于请求学院君首页的 HTTP 响应头信息:
resp, err := http.Head("https://xueyuanjun.com") if err != nil { fmt.Println("Request Failed: ", err.Error()) return } defer resp.Body.Close() // 打印头信息 for key, value := range resp.Header { fmt.Println(key, ":", value) }
通过 http.Head() 方法返回的响应实体 resp.Body 值为空。
底层实现及返回值和 http.Get 一样。
6.(*http.Client).Do
最后,我们来看一下 http.Client 类的 Do 方法。
在多数情况下,http.Get、http.Post 和 http.PostForm 就可以满足需求,但是如果我们发起的 HTTP 请求需要设置更多的自定义请求头信息,比如:
设置自定义的 User-Agent,而不是默认的 Go http package;
传递 Cookie 信息;
发起其它方式的 HTTP 请求,比如 PUT、PATCH、DELETE 等。
此时可以通过 http.Client 类提供的 Do() 方法来实现,使用该方法时,就不再是通过缺省的 DefaultClient 对象调用 http.Client 类中的方法了,而是需要我们手动实例化 Client 对象并传入添加了自定义请求头信息的请求对象来发起 HTTP 请求:
// 初始化客户端请求对象 req, err := http.NewRequest("GET", "https://xueyuanjun.com", nil) if err != nil { // ... 出错处理 return } // 添加自定义请求头 req.Header.Add("Custom-Header", "Custom-Value") // ... 其它请求头配置 client := &http.Client{ // ... 设置客户端属性 } resp, err := client.Do(req) if err != nil { // ... 出错处理 return } defer resp.Body.Close() io.Copy(os.Stdout, resp.Body)
用于初始化请求对象的 http.NewRequest 方法需要传入三个参数,第一个是请求方法,第二个是目标 URL,第三个是请求实体,只有 POST、PUT、DELETE 之类的请求才需要设置请求实体,对于 HEAD、GET 而言,传入 nil 即可。http.NewRequest 方法返回的第一个值就是请求对象实例 req,该实例所属的类是 http.Request,你可以调用该类上的公开方法和属性对请求对象进行自定义配置,比如请求方法、URL、请求头等。
设置完成后,就可以将请求对象传入 client.Do() 方法发起 HTTP 请求,之后的操作和前面四个基本方法一样。
http编程实例
服务端
package main import ( "fmt" "net/http" ) //服务端编写的业务逻辑处理程序 //hander函数: 具有func(w http.ResponseWriter, r *http.Requests)签名的函数 func myHandler(w http.ResponseWriter, r *http.Request) { fmt.Println(r.RemoteAddr, "连接成功") //r.RemoteAddr远程网络地址 fmt.Println("method = ", r.Method) //请求方法 fmt.Println("url = ", r.URL.Path) fmt.Println("header = ", r.Header) fmt.Println("body = ", r.Body) w.Write([]byte("hello go")) //给客户端恢复的数据 } func main() { http.HandleFunc("/go", myHandler) //该方法用于在指定的 TCP 网络地址 addr 进行监听,然后调用服务端处理程序来处理传入的连接请求。 //该方法有两个参数:第一个参数 addr 即监听地址;第二个参数表示服务端处理程序,通常为空 //第二个参数为空意味着服务端调用 http.DefaultServeMux 进行处理 http.ListenAndServe("127.0.0.1:8006", nil) }
客户端
package main import ( "fmt" "io" "log" "net/http" ) func main() { //get方式请求一个资源 //resp, err := http.Get("http://www.baidu.com") //resp, err := http.Get("http://www.neihan8.com/article/index.html") resp, err := http.Get("http://127.0.0.1:8000/go") if err != nil { log.Println(err) return } defer resp.Body.Close() //关闭 fmt.Println("header = ", resp.Header) fmt.Printf("resp status %s\nstatusCode %d\n", resp.Status, resp.StatusCode) fmt.Printf("body type = %T\n", resp.Body) buf := make([]byte, 2048) //切片缓冲区 var tmp string for { n, err := resp.Body.Read(buf) //读取body包内容 if err != nil && err != io.EOF { fmt.Println(err) return } if n == 0 { fmt.Println("读取内容结束") break } tmp += string(buf[:n]) //累加读取的内容 } fmt.Println("buf = ", string(tmp)) }