feat(httpc): go-hua/httpurl 迁入并更名 httpc(HTTP 客户端)

- 原包名 httpurl 看不出是客户端, 更名 httpc; GET/POST/PUT/DELETE/代理/
  Digest 认证, 函数签名不变, 附测试、例程与 README
This commit is contained in:
2026-09-20 12:25:36 +08:00
parent caa65209ac
commit a8577b7b3b
5 changed files with 547 additions and 0 deletions
+137
View File
@@ -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)
}
}