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  }