一、golang http客户端

1. 基础知识:http请求四种常见的POST提交数据方式

**服务端通常是根据请求头(headers)中的 Content-Type 字段来获知请
求中的消息主体是用何种方式编码,再对主体进行解析。**也就是说, Content-Type 指定了消息主体中的编码方式 。因
此,POST 提交数据方案,直接跟 Content-Type 和消息主体两部分有关。

http请求常见的content-type分为4种:application/json、x-www-form-urlencoded、multipart/form-data、text/plain。
在这里插入图片描述
1)application/x-www-form-urlencoded

HTTP中默认的提交数据的方式。浏览器的原生表单,如果不设置enctype属性,那么最终就会以application/x-www-form-urlencoded方式提交数据

const (
    http_address = "http://yourIP:8080/****"
)

// 用url.values方式构造form-data参数
formValues := url.Values{}
formValues .Set("first",firstData)
formValues .Set("second",secondData)
formDataStr := formValues.Encode()
formDataBytes := []byte(formDataStr )
formBytesReader := bytes.NewReader(formDataBytes )

//生成post请求
client := &http.Client{}
req, err := http.NewRequest("POST", http_address, formBytesReader )
if err != nil {
	// handle error
	log.Fatal("生成请求失败!",err)
	return
}

//注意别忘了设置header
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

//Do方法发送请求
resp, err := client.Do(req)

2)multipart/form-data

一个常见的POST数据提交的方式。我们使用表单上传文件时,必须将enctype设为multipart/form-data。

上面两种 POST 数据方式,都是浏览器原生支持的,而且现阶段原生 form 表单也只支持这两种方式。但

3)application/json

application/json作为请求头,用来告诉服务端消息主体是序列化的JSON字符串。方便提交复杂的结构化数据,特别适合RESTFul接口。
各大抓包工具如Chrome自带的开发者工具、Firebug、Fiddler,都会以树形结构展示JSON数据,非常友好。

Google 的 AngularJS 中的 Ajax 功能,默认就是提交 JSON 字符串。

4)text/xml
XML-RPC(XML Remote Procdure Call)。它是一种使用HTTP作为传输协议,XML作为编码方式的远程调用规范。

二、golang 标准库net/http包

golang系列——实战http客户端
参考URL: https://zhuanlan.zhihu.com/p/134146738

golang的net/http包已经提供了强大了网络操作函数。

1. golang使用http client发起get请求示例

get请求:

package csdn

import (
	"io/ioutil"
	"log"
	"net/http"
	"testing"
)

func TestNewPublisher(t *testing.T) {
	res, err := http.Get("http://www.csdn.com")
	if err != nil {
		log.Fatal(err)
	}
	//利用ioutil包读取服务器返回的数据
	data, err := ioutil.ReadAll(res.Body)
	res.Body.Close()//一定要记得关闭连接
	if err != nil {
		log.Fatal(err)
	}
	log.Printf("%s", data)
}

2. golang语言发送json格式的http请求

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
)

type JsonParams struct {
	Content      string    `json:"content"`
	ActivityId 	 string     `json:"activityId"`
	Type       	string	 	`json:"type"`
}

func main() {
	requestBody := new(bytes.Buffer)
	json.NewEncoder(requestBody).Encode(JsonParams{
		Content:  "xxxxxxx",
		ActivityId: "111",
	})

	request, err := http.NewRequest("POST", "https://xxx.com", requestBody)
	if err != nil {
		log.Fatal(err)
	}

	request.Header.Set("content-type", "application/json;")
	//request.Header.Set("accept", "application/json, text/plain, */*")


	client := &http.Client{}
	resp, err := client.Do(request)
	if err != nil {
		log.Fatal("client.Do err:")
		log.Fatal(err)
	} else {
		body := &bytes.Buffer{}
		_, err := body.ReadFrom(resp.Body)
		if err != nil {
			log.Fatal(err)
		}
		resp.Body.Close()
		fmt.Println(resp.StatusCode)
		//fmt.Println(resp.Header)
		//fmt.Println(body)
	}

}

3. Http请求返回结果处理

返回json结果是乱码问题

情况一:页面是,是GBK,不是UTF-8时

问题:
比如原来的代码是

resp, err := this.cleint.Get(url)
body, err := ioutil.ReadAll(resp.Body)

此时body中读取到的是乱码。

解决办法:
引入"golang.org/x/text/encoding/simplifiedchinese"

只需增加一个reader

resp, err := this.cleint.Get(url)
reader := simplifiedchinese.GB18030.NewDecoder().Reader(resp.Body)
body, err := ioutil.ReadAll(reader)

此时,body中就是正确解码后的汉字。

情况二:accept-encoding的影响

accept-encoding

问题分析:
原因是内容被gzip压缩。
在网页请求的时候,为了减少网页请求所消耗的带宽,提高数据传输的速度,通常会把数据进行压缩传输,这里就需要用到’Accept-Encoding’,它的值’gzip, deflate, br’,这里的值代表的意思是数据压缩采用的编码方式。

通常我们还需要关注一个值,那就是响应头里面的’Content-Encoding’。

'Content-Encoding’是指服务端使用了哪种压缩方式传输数据给你,'Accept-Encoding’表示你发送请求时告诉服务器,我可以使用哪些编码解压缩你返回的数据。

服务端会根据你发送的’Accept-encoding’来决定用什么格式’Content-Encoding’压缩数据传输。

解决方法:
//request.Header.Set(“accept-encoding”, “gzip, deflate, br”)
request.Header.Set(“accept-encoding”, “”)

请求结果转化为Map

在 Go 中 Http 请求的返回结果为*http.Response 类型,Response.Body 类型为 io.Reader。

TODO

4. Golang http添加cookie

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
)

func main() {

	client := &http.Client{}

	req, _ := http.NewRequest("GET", "http://localhost:8082/cookie_test",nil)
	req.Header.Add("Cookie","ds=req.Header.Add")

	resp, err := client.Do(req)
	if err != nil {
		panic(nil)
	}

	body, _ := ioutil.ReadAll(resp.Body)
	defer resp.Body.Close()

	fmt.Println(string(body))

}

工作技巧: 使用浏览器的cookie参数时您可以先拿postman测试验证没有问题,再写到代码中。

5. Go post方式发送带有form-data参数的http请求

golang学习之如何构造一个multipart/form格式的HTTP请求
参考URL: http://blog.codeg.cn/2015/03/18/golang-how-to-make-a-multipart-http-request/
https://www.easck.com/cos/2020/1218/594591.shtml

思路和方法:通过Go标准库提供的mime/multipart包,我们可以很容易地构建出满足要求的包体。

5.1 go标准库的学习-mime/multipart

go标准库的学习-mime/multipart
参考URL: https://www.cnblogs.com/wanghui-garcia/p/10402796.html

import "mime/multipart"

multipart实现了MIME的multipart解析,参见RFC 2046。该实现适用于HTTP(RFC 2388)和常见浏览器生成的multipart主体。

案例一:普通普通表单上传后端程序

func main() {
    buf:=new(bytes.Buffer)
    bodywriter:=multipart.NewWriter(buf)
    bodywriter.WriteField("name","lisi")
    bodywriter.WriteField("age","40")
    contentType:=bodywriter.FormDataContentType()
    defer bodywriter.Close()
    url:="http://www.shop.com:8088/index/index/aa"
    http.Post(url,contentType,buf)
}
三、Sling 库

1. 什么是Sling 库

https://github.com/dghubble/sling
https://pkg.go.dev/github.com/dghubble/sling#section-readme

Sling是用于创建和发送API请求的Go HTTP客户端库。

项目开发中,发送http请求的场景,推荐使用Sling库。Sling本身是基于net/http来处理发送请求,同时做了较好的封装,既可以利用net/http的一些特性(如:httptrace),同时又不必关心net/http库的一些琐碎细节。

Sling的默认实现可以覆盖大部分对于http发送请求场景,同时可以通过实现Doer和ResponseDecoder接口来扩展个性化的场景。

Sling支持以下主要的功能:

  • 支持GET/POST/PUT/PATCH/DELETE/HEAD
  • 基于Base/Path可以扩展和复用Sling
  • query参数可以用结构体来Encode
  • Request Body支持form和json
  • 可将Json格式的Response直接Decode到定义好的结构体
  • 可扩展Response的Decoder,以及Doer。

Sling 存储HTTP请求属性以简化发送请求和解码响应。检查使用情况或示例以了解如何将Sling 组成API客户端。

由上使用案例,Sling 常被用来封装 http client sdk。

2. Sling使用方法

https://pkg.go.dev/github.com/dghubble/sling?utm_source=godoc
【GoCN酷Go推荐】灵活的Go http client库-Sling
参考URL: https://jishuin.proginn.com/p/763bfbd5b75f

Sling对http请求的要素method、baseUrl、Path、query、body、request、response等做了封装,基本使用可以参考https://github.com/dghubble/sling 上的示例代码。

可以通过实现ResponseDecoder和Doer的接口,来定制响应的decoder和发送请求的具体实现。

Sling拓展

每个Sling都会创建一个标准的http.request Request()调用(例如,使用某些路径和查询参数)。您可能希望扩展现有Sling以最大限度地减少重复(例如常见客户端或基本URL)。

New()
const twitterApi = "https://api.twitter.com/1.1/"
base := sling.New().Base(twitterApi).Client(authClient)

// statuses/show.json Sling
tweetShowSling := base.New().Get("statuses/show.json").QueryStruct(params)
req, err := tweetShowSling.Request()

// statuses/update.json Sling
tweetPostSling := base.New().Post("statuses/update.json").BodyForm(params)
req, err := tweetPostSling.Request()
New()

修改Request

Sling提供raw http.Request ,因此可以使用标准的 net/http 功能进行修改。

例如,在Go 1.7+中,将HTTP跟踪添加到具有上下文的请求:

req, err := sling.New().Get("https://example.com").QueryStruct(params).Request()
// handle error

trace := &httptrace.ClientTrace{
   DNSDone: func(dnsInfo httptrace.DNSDoneInfo) {
      fmt.Printf("DNS Info: %+v\n", dnsInfo)
   },
   GotConn: func(connInfo httptrace.GotConnInfo) {
      fmt.Printf("Got Conn: %+v\n", connInfo)
   },
}

req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
client.Do(req)

post请求form-data demo

查看Sling 源码 得知,Sling 不直接支持 multipart/form-data

const (
	contentType     = "Content-Type"
	jsonContentType = "application/json"
	formContentType = "application/x-www-form-urlencoded"
)

提供的2个传内容的方法里面没有 处理 multipart/form-data 情况
如下,一个是json,一个是在body中 进行url编码。

// BodyJSON sets the Sling's bodyJSON. The value pointed to by the bodyJSON
// will be JSON encoded as the Body on new requests (see Request()).
// The bodyJSON argument should be a pointer to a JSON tagged struct. See
// https://golang.org/pkg/encoding/json/#MarshalIndent for details.
func (s *Sling) BodyJSON(bodyJSON interface{}) *Sling {
	if bodyJSON == nil {
		return s
	}
	return s.BodyProvider(jsonBodyProvider{payload: bodyJSON})
}

// BodyForm sets the Sling's bodyForm. The value pointed to by the bodyForm
// will be url encoded as the Body on new requests (see Request()).
// The bodyForm argument should be a pointer to a url tagged struct. See
// https://godoc.org/github.com/google/go-querystring/query for details.
func (s *Sling) BodyForm(bodyForm interface{}) *Sling {
	if bodyForm == nil {
		return s
	}
	return s.BodyProvider(formBodyProvider{payload: bodyForm})
}

解决思路:
Sling 本质还是使用标准库的http。Sling 封装的 不支持处理 multipart/form-data 情况。

亲测,尝试直接利用 Sling 失败,这里暂时使用标准http库的写法来实现。

读者如果直接利用Sling 可以实现,可以联系告诉我~!

用于构建一个API

APIs typically define an endpoint (also called a service) for each type of resource. For example, here is a tiny Github IssueService which lists repository issues.

APIs 通常为每种类型的资源定义端点(也称为服务)。例如,这是一个小型gihub IssueService,用于列出了github仓库issues。

const baseURL = "https://api.github.com/"

type IssueService struct {
    sling *sling.Sling
}

func NewIssueService(httpClient *http.Client) *IssueService {
    return &IssueService{
        sling: sling.New().Client(httpClient).Base(baseURL),
    }
}

func (s *IssueService) ListByRepo(owner, repo string, params *IssueListParams) ([]Issue, *http.Response, error) {
    issues := new([]Issue)
    githubError := new(GithubError)
    path := fmt.Sprintf("repos/%s/%s/issues", owner, repo)
    resp, err := s.sling.New().Get(path).QueryStruct(params).Receive(issues, githubError)
    if err == nil {
        err = githubError
    }
    return *issues, resp, err
}

使用Sling构建APIs示例

  • Digits dghubble/go-digits
  • GoSquared drinkin/go-gosquared
  • Kala ajvb/kala
  • Parse fergstar/go-parse
  • Swagger Generator swagger-api/swagger-codegen
  • Twitter dghubble/go-twitter
  • Stacksmith jesustinoco/go-smith

由上知,
twitter的client就是利用 Sling 封装的 go sdk,
项目参考 github.com/dghubble/go-twitter/twitter

在这里插入图片描述

四、参考

golang系列——实战http客户端
参考URL: https://zhuanlan.zhihu.com/p/134146738
【GoCN酷Go推荐】灵活的Go http client库-Sling
参考URL: https://jishuin.proginn.com/p/763bfbd5b75f