Go
package main
import (
"crypto/md5"
"encoding/hex"
"fmt"
"io"
"net/http"
"os"
"strings"
randomString "github.com/delphinus/random-string"
"golang.org/x/net/context"
)
type httpClientKey struct{}
var HTTPClientKey httpClientKey
func ContextWithClient(parent context.Context, client *http.Client) context.Context {
return context.WithValue(parent, HTTPClientKey, client)
}
func clientFromContext(ctx context.Context) *http.Client {
if client, ok := ctx.Value(HTTPClientKey).(*http.Client); ok {
return client
}
return http.DefaultClient
}
type DigestRequest struct {
context.Context
client *http.Client
username, password string
nonceCount nonceCount
}
type nonceCount int
func (nc nonceCount) String() string {
c := int(nc)
return fmt.Sprintf("%08x", c)
}
const authorization = "Authorization"
const contentType = "Content-Type"
const nonce = "nonce"
const opaque = "opaque"
const qop = "qop"
const realm = "realm"
const wwwAuthenticate = "Www-Authenticate"
var wanted = []string{nonce, qop, realm}
func NewDigestRequest(ctx context.Context, username, password string) *DigestRequest {
return &DigestRequest{
Context: ctx,
client: clientFromContext(ctx),
username: username,
password: password,
}
}
func (r *DigestRequest) Do(req *http.Request) (*http.Response, error) {
parts, err := r.makeParts(req)
if err != nil {
return nil, err
}
if parts != nil {
aut := r.makeAuthorization(req, parts)
fmt.Println("header:", aut)
req.Header.Set(authorization, aut)
}
return r.client.Do(req)
}
func (r *DigestRequest) makeParts(req *http.Request) (map[string]string, error) {
authReq, _ := http.NewRequest(req.Method, req.URL.String(), nil)
resp, err := r.client.Do(authReq)
if err != nil {
return nil, err
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusUnauthorized {
return nil, nil
}
for _, vv := range resp.Header {
fmt.Println(vv)
}
if len(resp.Header[wwwAuthenticate]) == 0 {
return nil, fmt.Errorf("headers do not have %s", wwwAuthenticate)
}
headers := strings.Split(resp.Header[wwwAuthenticate][0], ",")
parts := make(map[string]string, len(wanted))
for _, r := range headers {
for _, w := range wanted {
if strings.Contains(r, w) {
parts[w] = strings.Split(r, `"`)[1]
}
}
}
if len(parts) != len(wanted) {
return nil, fmt.Errorf("header is invalid: %+v", parts)
}
return parts, nil
}
func getMD5(texts []string) string {
h := md5.New()
_, _ = io.WriteString(h, strings.Join(texts, ":"))
return hex.EncodeToString(h.Sum(nil))
}
func (r *DigestRequest) getNonceCount() string {
r.nonceCount++
return r.nonceCount.String()
}
func (r *DigestRequest) makeAuthorization(req *http.Request, parts map[string]string) string {
uri := req.URL.RequestURI()
ha1 := getMD5([]string{r.username, parts[realm], r.password})
ha2 := getMD5([]string{req.Method, uri})
cnonce := randomString.Generate(16)
nc := r.getNonceCount()
response := getMD5([]string{
ha1,
parts[nonce],
nc,
cnonce,
parts[qop],
ha2,
})
return fmt.Sprintf(
`Digest username="%s", realm="%s", nonce="%s", uri="%s", qop=%s, nc=%s, cnonce="%s", response="%s"`,
r.username,
parts[realm],
parts[nonce],
uri,
parts[qop],
nc,
cnonce,
response,
)
}
func main() {
url := "http://172.31.2.164:80/ISAPI/Streaming/channels/33/picture"
username := "admin"
password := "root123456"
ctx := context.Background()
r := NewDigestRequest(ctx, username, password) // username & password
if r == nil {
fmt.Println("unk")
return
}
req, _ := http.NewRequest("GET", url, nil)
resp, err := r.Do(req)
if err != nil {
fmt.Println("unk1", err)
return
}
fmt.Println("Code:", resp.StatusCode)
// 保存图片
file, err := os.Create("1.jpeg")
if err != nil {
fmt.Println("Error creating file:", err)
return
}
defer file.Close()
_, err = io.Copy(file, resp.Body)
if err != nil {
fmt.Println("Error saving file:", err)
return
}
fmt.Println("图片保存成功。")
}
这段代码实现了一个带有 Digest 认证的 HTTP 客户端,用于从指定 URL 获取图片并保存到本地。代码通过上下文传递 HTTP 客户端,并在需要时从上下文中提取客户端。代码还处理了 Digest 认证的具体细节,包括生成认证头和管理 nonce 计数。