feat(httpc): go-hua/httpurl 迁入并更名 httpc(HTTP 客户端)
- 原包名 httpurl 看不出是客户端, 更名 httpc; GET/POST/PUT/DELETE/代理/ Digest 认证, 函数签名不变, 附测试、例程与 README
This commit is contained in:
@@ -0,0 +1,47 @@
|
|||||||
|
// httpc 包示例:HTTP 客户端封装(Get/Post/Put/Delete/GetProxy/DigestAuthRequest)
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.zeroonesoft.cn/golib/zogo/httpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// 启动一个本地测试服务
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/get", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Fprintf(w, `{"method":"%s","query":"%s"}`, r.Method, r.URL.RawQuery)
|
||||||
|
})
|
||||||
|
mux.HandleFunc("/post", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
body, _ := io.ReadAll(r.Body)
|
||||||
|
fmt.Fprintf(w, `{"method":"%s","body":%s}`, r.Method, string(body))
|
||||||
|
})
|
||||||
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
go http.Serve(l, mux)
|
||||||
|
base := "http://" + l.Addr().String()
|
||||||
|
|
||||||
|
// GET
|
||||||
|
body, err := httpc.Get(base+"/get?name=go-hua", nil)
|
||||||
|
fmt.Println("GET:", string(body), "err:", err)
|
||||||
|
|
||||||
|
// POST(默认 Content-Type: application/json)
|
||||||
|
body, err = httpc.Post(base+"/post", []byte(`{"id":1}`), nil)
|
||||||
|
fmt.Println("POST:", string(body), "err:", err)
|
||||||
|
|
||||||
|
// 带自定义请求头
|
||||||
|
body, err = httpc.Get(base+"/get", map[string]string{"X-Token": "abc123"})
|
||||||
|
fmt.Println("GET(带Header):", string(body), "err:", err)
|
||||||
|
|
||||||
|
// 其他方法用法相同:
|
||||||
|
// httpc.Put(url, param, header)
|
||||||
|
// httpc.Delete(url, param, header)
|
||||||
|
// httpc.GetProxy(url, header, "http://127.0.0.1:7890")
|
||||||
|
// httpc.DigestAuthRequest(&httpc.RequestArg{...})
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# httpc
|
||||||
|
|
||||||
|
轻量 HTTP 客户端:GET/POST/PUT/DELETE 快捷方法(自定义 Header、可选代理)、
|
||||||
|
Digest 认证请求。
|
||||||
|
|
||||||
|
> 迁移自 go-hua/httpurl 并更名:原包名 `httpurl` 看不出是客户端,更名 `httpc`
|
||||||
|
> (http client)。函数签名不变。
|
||||||
|
|
||||||
|
## 用法
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "git.zeroonesoft.cn/golib/zogo/httpc"
|
||||||
|
|
||||||
|
body, err := httpc.Get("https://api.example.com/v1/info", nil)
|
||||||
|
body, err := httpc.Post("https://api.example.com/v1/add", []byte(`{"id":1}`),
|
||||||
|
map[string]string{"Content-Type": "application/json"})
|
||||||
|
body, err := httpc.GetProxy(url, nil, "http://127.0.0.1:8080") // 走代理
|
||||||
|
body, err := httpc.Put(url, payload, nil)
|
||||||
|
body, err := httpc.Delete(url, nil, nil)
|
||||||
|
|
||||||
|
// Digest 认证
|
||||||
|
resp, err := httpc.DigestAuthRequest(&httpc.RequestArg{
|
||||||
|
Url: url,
|
||||||
|
Method: "GET",
|
||||||
|
Username: "admin",
|
||||||
|
Password: "pass",
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
完整可运行例程:[examples/httpc/main.go](../examples/httpc/main.go)
|
||||||
|
|
||||||
|
## 返回与错误
|
||||||
|
|
||||||
|
- 2xx 返回响应体字节集;非 2xx 返回包含状态码的错误。
|
||||||
|
- `Get/Post/Put/Delete` 的 `header` 参数传 `nil` 表示不附加自定义头。
|
||||||
|
|
||||||
|
## 注意
|
||||||
|
|
||||||
|
- `DigestAuthRequest` 只处理 RFC 2617 Digest 流程(qop=auth);Basic/Bearer
|
||||||
|
请直接在 header 里带 Authorization。
|
||||||
|
- 需要 JSON 自动绑定/泛型封装时配合服务端包 `zogo/httpx` 之外的客户端封装自行扩展。
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
package httpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RequestArg Digest 认证请求参数
|
||||||
|
type RequestArg struct {
|
||||||
|
Host string //http://localhost
|
||||||
|
Uri string
|
||||||
|
Method string
|
||||||
|
Header map[string]string //请求头
|
||||||
|
PostBody []byte
|
||||||
|
Username string //用户名
|
||||||
|
Password string //密码
|
||||||
|
}
|
||||||
|
|
||||||
|
// DigestAuthRequest digestAuth 请求
|
||||||
|
func DigestAuthRequest(arg *RequestArg) ([]byte, error) {
|
||||||
|
url := arg.Host + arg.Uri
|
||||||
|
req, err := http.NewRequest(arg.Method, url, nil)
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusUnauthorized {
|
||||||
|
resStr, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return resStr, nil
|
||||||
|
}
|
||||||
|
parts := digestParts(resp)
|
||||||
|
parts["uri"] = arg.Uri
|
||||||
|
parts["method"] = arg.Method
|
||||||
|
parts["username"] = arg.Username
|
||||||
|
parts["password"] = arg.Password
|
||||||
|
parts["algorithm"] = "MD5"
|
||||||
|
req, err = http.NewRequest(arg.Method, url, bytes.NewBuffer(arg.PostBody))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Authorization", getDigestAuthorization(parts))
|
||||||
|
for k, v := range arg.Header {
|
||||||
|
req.Header.Set(k, v)
|
||||||
|
}
|
||||||
|
resp, err = client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return body, err
|
||||||
|
}
|
||||||
|
all, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return all, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第一次请求的响应header 获取第二次请求所需要的信息 Www-Authenticate
|
||||||
|
func digestParts(resp *http.Response) map[string]string {
|
||||||
|
result := map[string]string{}
|
||||||
|
if len(resp.Header["Www-Authenticate"]) > 0 {
|
||||||
|
wantedHeaders := []string{"nonce", "realm", "qop", "opaque"}
|
||||||
|
responseHeaders := strings.Split(resp.Header["Www-Authenticate"][0], ",")
|
||||||
|
for _, r := range responseHeaders {
|
||||||
|
for _, w := range wantedHeaders {
|
||||||
|
if strings.Contains(r, w) {
|
||||||
|
result[w] = strings.Split(r, `"`)[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第二次请求的请求header Authorization 值
|
||||||
|
func getDigestAuthorization(digestParts map[string]string) string {
|
||||||
|
d := digestParts
|
||||||
|
//ha1=md5(username:realm:password)
|
||||||
|
ha1 := getMD5(d["username"] + ":" + d["realm"] + ":" + d["password"])
|
||||||
|
//ha2=md5(method:uri)
|
||||||
|
ha2 := getMD5(d["method"] + ":" + d["uri"])
|
||||||
|
nonceCount := "00000001"
|
||||||
|
cnonce := getCnonce()
|
||||||
|
|
||||||
|
//response=md5(ha1:nonce:nc:cnonce:qop:ha2)
|
||||||
|
response := getMD5(fmt.Sprintf("%s:%s:%s:%s:%s:%s", ha1, d["nonce"], nonceCount, cnonce, d["qop"], ha2))
|
||||||
|
|
||||||
|
//qop和nc的值不能加引号
|
||||||
|
authorization := fmt.Sprintf(
|
||||||
|
`Digest username="%s", realm="%s", nonce="%s", uri="%s", response="%s", opaque="%s", algorithm=MD5", qop=%s, nc=%s, cnonce="%s"`,
|
||||||
|
d["username"], d["realm"], d["nonce"], d["uri"], response, d["opaque"], d["qop"], nonceCount, cnonce)
|
||||||
|
|
||||||
|
return authorization
|
||||||
|
}
|
||||||
|
|
||||||
|
// 字符串MD5加密
|
||||||
|
func getMD5(text string) string {
|
||||||
|
hash := md5.New()
|
||||||
|
hash.Write([]byte(text))
|
||||||
|
return hex.EncodeToString(hash.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 cnonce
|
||||||
|
// 客户端提供的不透明带引号的字符串值,客户端和服务器都使用它来避免选定的明文攻击、提供相互身份验证以及提供一些消息完整性保护
|
||||||
|
func getCnonce() string {
|
||||||
|
b := make([]byte, 8)
|
||||||
|
io.ReadFull(rand.Reader, b)
|
||||||
|
return fmt.Sprintf("%x", b)[:16]
|
||||||
|
}
|
||||||
+196
@@ -0,0 +1,196 @@
|
|||||||
|
// Package httpc 封装常用 HTTP 客户端请求(Get/Post/Put/Delete/代理/Digest 认证)。
|
||||||
|
package httpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 复用的 http.Transport 实例
|
||||||
|
var transport = &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复用的 http.Client 实例
|
||||||
|
var client = &http.Client{
|
||||||
|
Transport: transport,
|
||||||
|
Timeout: time.Second * 15,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post 发送 POST 请求并返回响应体
|
||||||
|
// header 为空或未指定 Content-Type 时默认使用 application/json
|
||||||
|
func Post(url string, param []byte, header map[string]string) ([]byte, error) {
|
||||||
|
payload := bytes.NewReader(param)
|
||||||
|
// 创建新的 HTTP POST 请求
|
||||||
|
req, err := http.NewRequest("POST", url, payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 添加请求头
|
||||||
|
for k, v := range header {
|
||||||
|
req.Header.Add(k, v)
|
||||||
|
}
|
||||||
|
if header == nil || header["Content-Type"] == "" {
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
|
}
|
||||||
|
// 发送请求
|
||||||
|
res, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 确保响应体在函数结束时关闭
|
||||||
|
defer res.Body.Close()
|
||||||
|
// 读取响应体
|
||||||
|
body, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 检查响应状态码
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
return nil, fmt.Errorf("status code: %d", res.StatusCode)
|
||||||
|
}
|
||||||
|
return body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get 发送 GET 请求并返回响应体
|
||||||
|
func Get(httpUrl string, header map[string]string) ([]byte, error) {
|
||||||
|
// 创建新的 HTTP GET 请求
|
||||||
|
req, err := http.NewRequest("GET", httpUrl, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 添加请求头
|
||||||
|
for k, v := range header {
|
||||||
|
req.Header.Add(k, v)
|
||||||
|
}
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
|
// 发送请求
|
||||||
|
res, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 确保响应体在函数结束时关闭
|
||||||
|
defer res.Body.Close()
|
||||||
|
// 读取响应体
|
||||||
|
body, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 检查响应状态码
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
return nil, fmt.Errorf("status code: %d", res.StatusCode)
|
||||||
|
}
|
||||||
|
return body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProxy 通过指定代理发送 GET 请求并返回响应体
|
||||||
|
// proxyAddr 如 "http://127.0.0.1:7890",为空则直连
|
||||||
|
func GetProxy(httpUrl string, header map[string]string, proxyAddr string) ([]byte, error) {
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", httpUrl, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for k, v := range header {
|
||||||
|
req.Header.Add(k, v)
|
||||||
|
}
|
||||||
|
// req.Header.Add("Content-Type", "application/json")
|
||||||
|
/*req.Header.Add("Authorization", "BNbmgAAGI155F6MJ3N2Tk9ruL_6XQpx-uxkkg:tGCY3xCsgybHd5IjcDMi9yZXBvcy93aF9mbG93RGF0YVNvdXJjZTEiLCJleHBpcmVzIjoxNTM2NzU4NjQ3LCJjb250ZW5VudFR5cGUiOiIiLCJoZWFkZXJzIjoiIiwibWV0aG9kIjoiR0VUIn0=")*/
|
||||||
|
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
if proxyAddr != "" {
|
||||||
|
proxyUrl, err := url.Parse(proxyAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tr.Proxy = http.ProxyURL(proxyUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &http.Client{Transport: tr}
|
||||||
|
client.Timeout = time.Second * 15
|
||||||
|
defer client.CloseIdleConnections()
|
||||||
|
res, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
body, _ := io.ReadAll(res.Body)
|
||||||
|
|
||||||
|
//fmt.Println(res)
|
||||||
|
//fmt.Println(string(body))
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
return body, fmt.Errorf("status code: %d", res.StatusCode)
|
||||||
|
}
|
||||||
|
return body, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put 发送 PUT 请求并返回响应体
|
||||||
|
func Put(url string, param []byte, header map[string]string) ([]byte, error) {
|
||||||
|
payload := bytes.NewReader(param)
|
||||||
|
// 创建新的 HTTP GET 请求
|
||||||
|
req, err := http.NewRequest("PUT", url, payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 添加请求头
|
||||||
|
for k, v := range header {
|
||||||
|
req.Header.Add(k, v)
|
||||||
|
}
|
||||||
|
// 发送请求
|
||||||
|
res, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 确保响应体在函数结束时关闭
|
||||||
|
defer res.Body.Close()
|
||||||
|
// 读取响应体
|
||||||
|
body, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 检查响应状态码
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
return nil, fmt.Errorf("status code: %d", res.StatusCode)
|
||||||
|
}
|
||||||
|
return body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete 发送 DELETE 请求并返回响应体
|
||||||
|
func Delete(url string, param []byte, header map[string]string) ([]byte, error) {
|
||||||
|
payload := bytes.NewReader(param)
|
||||||
|
// 创建新的 HTTP DELETE 请求
|
||||||
|
req, err := http.NewRequest("DELETE", url, payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 添加请求头
|
||||||
|
for k, v := range header {
|
||||||
|
req.Header.Add(k, v)
|
||||||
|
}
|
||||||
|
// 发送请求
|
||||||
|
res, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 确保响应体在函数结束时关闭
|
||||||
|
defer res.Body.Close()
|
||||||
|
// 读取响应体
|
||||||
|
body, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 检查响应状态码
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
return nil, fmt.Errorf("status code: %d", res.StatusCode)
|
||||||
|
}
|
||||||
|
return body, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
package httpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGet(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodGet {
|
||||||
|
t.Errorf("Method = %s, want GET", r.Method)
|
||||||
|
}
|
||||||
|
if r.URL.Query().Get("name") != "zogo" {
|
||||||
|
t.Errorf("query name = %q", r.URL.Query().Get("name"))
|
||||||
|
}
|
||||||
|
w.Write([]byte(`{"ok":true}`))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
body, err := Get(ts.URL+"/get?name=zogo", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Get: %v", err)
|
||||||
|
}
|
||||||
|
if string(body) != `{"ok":true}` {
|
||||||
|
t.Errorf("body = %q", string(body))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetWithHeader(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Header.Get("X-Token") != "abc" {
|
||||||
|
t.Errorf("X-Token = %q", r.Header.Get("X-Token"))
|
||||||
|
}
|
||||||
|
w.Write([]byte("ok"))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
if _, err := Get(ts.URL, map[string]string{"X-Token": "abc"}); err != nil {
|
||||||
|
t.Fatalf("Get: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPost(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
t.Errorf("Method = %s, want POST", r.Method)
|
||||||
|
}
|
||||||
|
if r.Header.Get("Content-Type") != "application/json" {
|
||||||
|
t.Errorf("Content-Type = %q, want application/json", r.Header.Get("Content-Type"))
|
||||||
|
}
|
||||||
|
b, _ := io.ReadAll(r.Body)
|
||||||
|
if string(b) != `{"id":1}` {
|
||||||
|
t.Errorf("body = %q", string(b))
|
||||||
|
}
|
||||||
|
w.Write([]byte("posted"))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
body, err := Post(ts.URL, []byte(`{"id":1}`), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Post: %v", err)
|
||||||
|
}
|
||||||
|
if string(body) != "posted" {
|
||||||
|
t.Errorf("body = %q", string(body))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPostCustomContentType(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !strings.HasPrefix(r.Header.Get("Content-Type"), "text/plain") {
|
||||||
|
t.Errorf("Content-Type = %q, want text/plain", r.Header.Get("Content-Type"))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
if _, err := Post(ts.URL, []byte("text"), map[string]string{"Content-Type": "text/plain"}); err != nil {
|
||||||
|
t.Fatalf("Post: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPut(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPut {
|
||||||
|
t.Errorf("Method = %s, want PUT", r.Method)
|
||||||
|
}
|
||||||
|
w.Write([]byte("putted"))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
body, err := Put(ts.URL, []byte(`{}`), nil)
|
||||||
|
if err != nil || string(body) != "putted" {
|
||||||
|
t.Errorf("Put = %q, %v", string(body), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDelete(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodDelete {
|
||||||
|
t.Errorf("Method = %s, want DELETE", r.Method)
|
||||||
|
}
|
||||||
|
w.Write([]byte("deleted"))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
body, err := Delete(ts.URL, []byte(`{}`), nil)
|
||||||
|
if err != nil || string(body) != "deleted" {
|
||||||
|
t.Errorf("Delete = %q, %v", string(body), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetProxyEmptyProxy(t *testing.T) {
|
||||||
|
// 代理地址为空时应直连
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte("direct"))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
body, err := GetProxy(ts.URL, nil, "")
|
||||||
|
if err != nil || string(body) != "direct" {
|
||||||
|
t.Errorf("GetProxy 空代理 = %q, %v", string(body), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorStatusCode(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
_, err := Get(ts.URL, nil)
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "500") {
|
||||||
|
t.Errorf("非 2xx 响应应返回错误,got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user