github.com/fumiama/NanoBot@v0.0.0-20231122134259-c22d8183efca/http.go (about) 1 package nano 2 3 import ( 4 "bytes" 5 "encoding/base64" 6 "encoding/json" 7 "fmt" 8 "io" 9 "mime/multipart" 10 "net/http" 11 "net/textproto" 12 "net/url" 13 "os" 14 "reflect" 15 "strings" 16 _ "unsafe" 17 18 base14 "github.com/fumiama/go-base16384" 19 ) 20 21 // HTTPRequsetConstructer ... 22 type HTTPRequsetConstructer func(ep string, contenttype string, auth, appid string, body io.Reader) (*http.Request, error) 23 24 func newHTTPEndpointRequestWithAuth(method, contenttype, ep string, auth, appid string, body io.Reader) (req *http.Request, err error) { 25 req, err = http.NewRequest(method, ep, body) 26 if err != nil { 27 return 28 } 29 if auth != "" { 30 req.Header.Add("Authorization", auth) 31 } 32 if appid != "" { 33 req.Header.Add("X-Union-Appid", appid) 34 } 35 if contenttype == "" { 36 contenttype = "application/json" 37 } 38 req.Header.Add("Content-Type", contenttype) 39 return 40 } 41 42 // NewHTTPEndpointGetRequestWithAuth 新建带鉴权头的 HTTP GET 请求 43 func NewHTTPEndpointGetRequestWithAuth(ep string, contenttype string, auth, appid string, body io.Reader) (*http.Request, error) { 44 return newHTTPEndpointRequestWithAuth("GET", contenttype, OpenAPI+ep, auth, appid, body) 45 } 46 47 // NewHTTPEndpointPutRequestWithAuth 新建带鉴权头的 HTTP PUT 请求 48 func NewHTTPEndpointPutRequestWithAuth(ep string, contenttype string, auth, appid string, body io.Reader) (*http.Request, error) { 49 return newHTTPEndpointRequestWithAuth("PUT", contenttype, OpenAPI+ep, auth, appid, body) 50 } 51 52 // NewHTTPEndpointDeleteRequestWithAuth 新建带鉴权头的 HTTP DELETE 请求 53 func NewHTTPEndpointDeleteRequestWithAuth(ep string, contenttype string, auth, appid string, body io.Reader) (*http.Request, error) { 54 return newHTTPEndpointRequestWithAuth("DELETE", contenttype, OpenAPI+ep, auth, appid, body) 55 } 56 57 // NewHTTPEndpointPostRequestWithAuth 新建带鉴权头的 HTTP POST 请求 58 func NewHTTPEndpointPostRequestWithAuth(ep string, contenttype string, auth, appid string, body io.Reader) (*http.Request, error) { 59 return newHTTPEndpointRequestWithAuth("POST", contenttype, OpenAPI+ep, auth, appid, body) 60 } 61 62 // NewHTTPEndpointPatchRequestWithAuth 新建带鉴权头的 HTTP PATCH 请求 63 func NewHTTPEndpointPatchRequestWithAuth(ep string, contenttype string, auth, appid string, body io.Reader) (*http.Request, error) { 64 return newHTTPEndpointRequestWithAuth("PATCH", contenttype, OpenAPI+ep, auth, appid, body) 65 } 66 67 // WriteHTTPQueryIfNotNil 如果非空则将请求添加到 baseurl 后 68 // 69 // ex. WriteHTTPQueryIfNotNil("http://a.com/api", "a", 0, "b", 1, "c", 2) is http://a.com/api?b=1&c=2 70 func WriteHTTPQueryIfNotNil(baseurl string, queries ...any) string { 71 if len(queries) < 2 { 72 return baseurl 73 } 74 hasstart := false 75 queryname := "" 76 sb := strings.Builder{} 77 for i, q := range queries { 78 if i%2 == 0 { 79 queryname = q.(string) 80 continue 81 } 82 if reflect.ValueOf(q).IsZero() { 83 continue 84 } 85 if !hasstart { 86 sb.WriteString(baseurl) 87 sb.WriteByte('?') 88 hasstart = true 89 } 90 sb.WriteString(queryname) 91 sb.WriteByte('=') 92 sb.WriteString(url.QueryEscape(fmt.Sprint(q))) 93 sb.WriteByte('&') 94 } 95 if sb.Len() <= 4 { 96 return baseurl 97 } 98 return sb.String()[:sb.Len()-1] 99 } 100 101 // WriteBodyFromJSON 从 json 结构体 ptr 写入 bytes.Buffer, 忽略 error (内部使用不会出错) 102 func WriteBodyFromJSON(ptr any) *bytes.Buffer { 103 buf := bytes.NewBuffer(make([]byte, 0, 1024)) 104 _ = json.NewEncoder(buf).Encode(ptr) 105 return buf 106 } 107 108 //go:linkname escapeQuotes mime/multipart.escapeQuotes 109 func escapeQuotes(s string) string 110 111 // WriteBodyByMultipartFormData 使用 multipart/form-data 上传 112 func WriteBodyByMultipartFormData(params ...any) (*bytes.Buffer, string, error) { 113 if len(params)%2 != 0 { 114 panic("invalid params to " + getThisFuncName()) 115 } 116 fieldname := "" 117 buf := bytes.NewBuffer(make([]byte, 0, 65536)) 118 w := multipart.NewWriter(buf) 119 defer w.Close() 120 for i, x := range params { 121 if i%2 == 0 { // 参数 122 fieldname = x.(string) 123 continue 124 } 125 rx := reflect.ValueOf(x) 126 if rx.IsZero() { 127 continue 128 } 129 h := make(textproto.MIMEHeader) 130 if rx.Kind() == reflect.Pointer && rx.Elem().Kind() == reflect.Struct { // 使用 json 编码 131 h.Set("Content-Disposition", 132 fmt.Sprintf(`form-data; name="%s"`, escapeQuotes(fieldname))) 133 h.Set("Content-Type", "application/json") 134 r, err := w.CreatePart(h) 135 if err != nil { 136 return nil, "", err 137 } 138 err = json.NewEncoder(r).Encode(x) 139 if err != nil { 140 return nil, "", err 141 } 142 continue 143 } 144 switch o := x.(type) { 145 case string: 146 if strings.HasPrefix(o, "file:///") { // 是文件路径 147 h.Set("Content-Disposition", 148 fmt.Sprintf(`form-data; name="%s"; filename="%s"`, 149 escapeQuotes(fieldname), escapeQuotes(fieldname))) 150 h.Set("Content-Type", "application/octet-stream") 151 r, err := w.CreatePart(h) 152 if err != nil { 153 return nil, "", err 154 } 155 f, err := os.Open(o[8:]) 156 if err != nil { 157 return nil, "", err 158 } 159 defer f.Close() 160 _, err = io.Copy(r, f) 161 if err != nil { 162 return nil, "", err 163 } 164 continue 165 } 166 if strings.HasPrefix(o, "base64://") { // 是 base64 167 h.Set("Content-Disposition", 168 fmt.Sprintf(`form-data; name="%s"; filename="%s"`, 169 escapeQuotes(fieldname), escapeQuotes(fieldname))) 170 h.Set("Content-Type", "application/octet-stream") 171 r, err := w.CreatePart(h) 172 if err != nil { 173 return nil, "", err 174 } 175 _, err = io.Copy(r, base64.NewDecoder(base64.StdEncoding, bytes.NewBufferString(o[9:]))) 176 if err != nil { 177 return nil, "", err 178 } 179 continue 180 } 181 if strings.HasPrefix(o, "base16384://") { // 是 base16384 182 h.Set("Content-Disposition", 183 fmt.Sprintf(`form-data; name="%s"; filename="%s"`, 184 escapeQuotes(fieldname), escapeQuotes(fieldname))) 185 h.Set("Content-Type", "application/octet-stream") 186 r, err := w.CreatePart(h) 187 if err != nil { 188 return nil, "", err 189 } 190 _, err = io.Copy(r, base14.NewDecoder(bytes.NewBufferString(o[12:]))) 191 if err != nil { 192 return nil, "", err 193 } 194 continue 195 } 196 h.Set("Content-Disposition", 197 fmt.Sprintf(`form-data; name="%s"`, escapeQuotes(fieldname))) 198 h.Set("Content-Type", "application/json") 199 r, err := w.CreatePart(h) 200 if err != nil { 201 return nil, "", err 202 } 203 _, err = io.WriteString(r, o) 204 if err != nil { 205 return nil, "", err 206 } 207 continue 208 case []byte: 209 h.Set("Content-Disposition", 210 fmt.Sprintf(`form-data; name="%s"; filename="%s"`, 211 escapeQuotes(fieldname), escapeQuotes(fieldname))) 212 h.Set("Content-Type", "application/octet-stream") 213 r, err := w.CreatePart(h) 214 if err != nil { 215 return nil, "", err 216 } 217 _, err = r.Write(o) 218 if err != nil { 219 return nil, "", err 220 } 221 continue 222 default: 223 h.Set("Content-Disposition", 224 fmt.Sprintf(`form-data; name="%s"`, escapeQuotes(fieldname))) 225 h.Set("Content-Type", "application/json") 226 r, err := w.CreatePart(h) 227 if err != nil { 228 return nil, "", err 229 } 230 _, err = io.WriteString(r, fmt.Sprint(o)) 231 if err != nil { 232 return nil, "", err 233 } 234 continue 235 } 236 } 237 return buf, w.FormDataContentType(), nil 238 }