feat(httpc): go-hua/httpurl 迁入并更名 httpc(HTTP 客户端)
- 原包名 httpurl 看不出是客户端, 更名 httpc; GET/POST/PUT/DELETE/代理/ Digest 认证, 函数签名不变, 附测试、例程与 README
This commit is contained in:
@@ -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]
|
||||
}
|
||||
Reference in New Issue
Block a user