diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4f8c0c6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea/ +*/.DS_Store +.vscode +go.sum \ No newline at end of file diff --git a/client.go b/client.go index 58b08db..79932bf 100644 --- a/client.go +++ b/client.go @@ -155,42 +155,57 @@ func (c *Client) Use(middlewares ...MiddlewareFunc) *Client { return c } +// Download download a file from the network address to the local. +func (c *Client) Download(url, dir string, filename ...string) (string, error) { + return NewDownload(c).Download(url, dir, filename...) +} + +// Request send an http request. func (c *Client) Request(method, url string, data ...interface{}) (*Response, error) { return NewRequest(c).request(method, url, data...) } +// Get send an http request use get method. func (c *Client) Get(url string, data ...interface{}) (*Response, error) { return c.Request(MethodGet, url, data...) } +// Post send an http request use post method. func (c *Client) Post(url string, data ...interface{}) (*Response, error) { return c.Request(MethodPost, url, data...) } +// Put send an http request use put method. func (c *Client) Put(url string, data ...interface{}) (*Response, error) { return c.Request(MethodPut, url, data...) } +// Patch send an http request use patch method. func (c *Client) Patch(url string, data ...interface{}) (*Response, error) { return c.Request(MethodPatch, url, data...) } +// Delete send an http request use patch method. func (c *Client) Delete(url string, data ...interface{}) (*Response, error) { return c.Request(MethodDelete, url, data...) } +// Head send an http request use head method. func (c *Client) Head(url string, data ...interface{}) (*Response, error) { return c.Request(MethodHead, url, data...) } +// Options send an http request use options method. func (c *Client) Options(url string, data ...interface{}) (*Response, error) { return c.Request(MethodOptions, url, data...) } +// Connect send an http request use connect method. func (c *Client) Connect(url string, data ...interface{}) (*Response, error) { return c.Request(MethodConnect, url, data...) } +// Trace send an http request use trace method. func (c *Client) Trace(url string, data ...interface{}) (*Response, error) { return c.Request(MethodTrace, url, data...) } diff --git a/client_test.go b/client_test.go index f1bb7e0..1b59a67 100644 --- a/client_test.go +++ b/client_test.go @@ -59,23 +59,26 @@ func TestClient_Post(t *testing.T) { Sort: 0, } - //data := map[string]interface{}{ - // "id": 1, - // "pid": 0, - // "code": "110000", - // "name": "北京市", - // "sort": 0, - //} - if resp, err := client.Put("/backend/region/update-region", data); err != nil { t.Error(err) return } else { t.Log(resp.Response.Status) t.Log(resp.Response.Header) - t.Log(resp.Bytes()) - t.Log(resp.String()) + t.Log(resp.ReadBytes()) + t.Log(resp.ReadString()) t.Log(resp.GetHeaders()) t.Log(resp.GetCookies()) } } + +func TestClient_Download(t *testing.T) { + url := "https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png" + + if path, err := http.NewClient().Download(url, "./"); err != nil { + t.Error(err) + return + } else { + t.Log(path) + } +} diff --git a/download.go b/download.go new file mode 100644 index 0000000..85e4f1a --- /dev/null +++ b/download.go @@ -0,0 +1,87 @@ +/** + * @Author: fuxiao + * @Email: 576101059@qq.com + * @Date: 2021/8/26 1:59 下午 + * @Desc: TODO + */ + +package http + +import ( + "os" + "strings" + + "github.com/dobyte/http/internal" +) + +var contentTypeToFileSuffix = map[string]string{ + "application/x-001": ".001", + "text/h323": ".323", + "drawing/907": ".907", + "audio/x-mei-aac": ".acp", + "audio/aiff": ".aif", + "text/asa": ".asa", + "text/asp": ".asp", + "audio/basic": ".au", + "application/vnd.adobe.workflow": ".awf", + "application/x-bmp": ".bmp", + "application/x-c4t": ".c4t", + "application/x-cals": ".cal", + "application/x-netcdf": ".cdf", + "application/x-cel": ".cel", + "application/x-g4": ".cg4", + "application/x-cit": ".cit", + "text/xml": ".cml", + "application/x-cmx": ".cmx", + "application/pkix-crl": ".crl", + "application/x-csi": ".csi", + "application/x-cut": ".cut", + "application/x-dbm": ".dbm", +} + +type Download struct { + request *Request +} + +func NewDownload(c *Client) *Download { + return &Download{ + request: NewRequest(c), + } +} + +// Download download a file from the network address to the local. +func (d *Download) Download(url, dir string, filename ...string) (string, error) { + resp, err := d.request.request(MethodGet, url) + if err != nil { + return "", err + } + + var path string + + if len(filename) > 0 { + path = strings.TrimRight(dir, string(os.PathSeparator)) + string(os.PathSeparator) + filename[0] + } else { + path = d.genFilePath(resp, dir) + } + + if err = internal.SaveToFile(path, resp.ReadBytes()); err != nil { + return "", err + } + + return path, nil +} + +// genFilePath generate file path based on response content type +func (d *Download) genFilePath(resp *Response, dir string) string { + path := strings.TrimRight(dir, string(os.PathSeparator)) + string(os.PathSeparator) + internal.RandStr(16) + + if suffix := internal.GetFileType(resp.ReadBytes()); suffix != "" { + path += "." + suffix + } + + if internal.Exists(path) { + return d.genFilePath(resp, dir) + } + + return path +} diff --git a/internal/conv.go b/internal/conv.go new file mode 100644 index 0000000..88c4236 --- /dev/null +++ b/internal/conv.go @@ -0,0 +1,234 @@ +/** + * @Author: fuxiao + * @Email: 576101059@qq.com + * @Date: 2021/8/26 5:00 下午 + * @Desc: TODO + */ + +package internal + +import ( + "encoding" + "encoding/json" + "fmt" + "reflect" + "strconv" + "time" + "unsafe" +) + +type stringInterface interface { + String() string +} + +type errorInterface interface { + Error() string +} + +func String(any interface{}) string { + switch v := any.(type) { + case nil: + return "" + case string: + return v + case int: + return strconv.Itoa(v) + case int8: + return strconv.Itoa(int(v)) + case int16: + return strconv.Itoa(int(v)) + case int32: + return strconv.Itoa(int(v)) + case int64: + return strconv.FormatInt(v, 10) + case uint: + return strconv.FormatUint(uint64(v), 10) + case uint8: + return strconv.FormatUint(uint64(v), 10) + case uint16: + return strconv.FormatUint(uint64(v), 10) + case uint64: + return strconv.FormatUint(v, 10) + case float32: + return strconv.FormatFloat(float64(v), 'f', -1, 32) + case float64: + return strconv.FormatFloat(v, 'f', -1, 64) + case bool: + return strconv.FormatBool(v) + case []byte: + return string(v) + case time.Time: + return v.String() + case *time.Time: + if v == nil { + return "" + } + return v.String() + default: + if v == nil { + return "" + } + + if i, ok := v.(stringInterface); ok { + return i.String() + } + + if i, ok := v.(errorInterface); ok { + return i.Error() + } + + var ( + rv = reflect.ValueOf(v) + kind = rv.Kind() + ) + + switch kind { + case reflect.Chan, + reflect.Map, + reflect.Slice, + reflect.Func, + reflect.Ptr, + reflect.Interface, + reflect.UnsafePointer: + if rv.IsNil() { + return "" + } + case reflect.String: + return rv.String() + } + + if kind == reflect.Ptr { + return String(rv.Elem().Interface()) + } + + if b, e := json.Marshal(v); e != nil { + return fmt.Sprint(v) + } else { + return string(b) + } + } +} + +func Scan(b []byte, any interface{}) error { + switch v := any.(type) { + case nil: + return fmt.Errorf("cache: Scan(nil)") + case *string: + *v = String(b) + return nil + case *[]byte: + *v = b + return nil + case *int: + var err error + *v, err = strconv.Atoi(String(b)) + return err + case *int8: + n, err := strconv.ParseInt(String(b), 10, 8) + if err != nil { + return err + } + *v = int8(n) + return nil + case *int16: + n, err := strconv.ParseInt(String(b), 10, 16) + if err != nil { + return err + } + *v = int16(n) + return nil + case *int32: + n, err := strconv.ParseInt(String(b), 10, 32) + if err != nil { + return err + } + *v = int32(n) + return nil + case *int64: + n, err := strconv.ParseInt(String(b), 10, 64) + if err != nil { + return err + } + *v = n + return nil + case *uint: + n, err := strconv.ParseUint(String(b), 10, 64) + if err != nil { + return err + } + *v = uint(n) + return nil + case *uint8: + n, err := strconv.ParseUint(String(b), 10, 8) + if err != nil { + return err + } + *v = uint8(n) + return nil + case *uint16: + n, err := strconv.ParseUint(String(b), 10, 16) + if err != nil { + return err + } + *v = uint16(n) + return nil + case *uint32: + n, err := strconv.ParseUint(String(b), 10, 32) + if err != nil { + return err + } + *v = uint32(n) + return nil + case *uint64: + n, err := strconv.ParseUint(String(b), 10, 64) + if err != nil { + return err + } + *v = n + return nil + case *float32: + n, err := strconv.ParseFloat(String(b), 32) + if err != nil { + return err + } + *v = float32(n) + return err + case *float64: + var err error + *v, err = strconv.ParseFloat(String(b), 64) + return err + case *bool: + *v = len(b) == 1 && b[0] == '1' + return nil + case *time.Time: + var err error + *v, err = time.Parse(time.RFC3339Nano, String(b)) + return err + case encoding.BinaryUnmarshaler: + return v.UnmarshalBinary(b) + default: + var ( + rv = reflect.ValueOf(v) + kind = rv.Kind() + ) + + if kind != reflect.Ptr { + return fmt.Errorf("can't unmarshal %T", v) + } + + switch kind = rv.Elem().Kind(); kind { + case reflect.Array, reflect.Slice, reflect.Map, reflect.Struct: + return json.Unmarshal(b, v) + } + + return fmt.Errorf("can't unmarshal %T", v) + } +} + +func UnsafeStringToBytes(s string) []byte { + return *(*[]byte)(unsafe.Pointer(&s)) +} + +func UnsafeBytesToString(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) +} \ No newline at end of file diff --git a/internal/file.go b/internal/file.go new file mode 100644 index 0000000..41a01e6 --- /dev/null +++ b/internal/file.go @@ -0,0 +1,66 @@ +/** + * @Author: fuxiao + * @Email: 576101059@qq.com + * @Date: 2021/8/26 4:59 下午 + * @Desc: TODO + */ + +package internal + +import ( + "io" + "os" + "path/filepath" +) + +// Exists check if the file or path exists. +func Exists(path string) bool { + if stat, err := os.Stat(path); stat != nil && !os.IsNotExist(err) { + return true + } + return false +} + +// SaveToFile save data to file. +func SaveToFile(path string, data []byte) error { + dir := filepath.Dir(path) + if !Exists(dir) { + if err := MakeDir(dir); err != nil { + return err + } + } + + f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(0666)) + if err != nil { + return err + } + defer f.Close() + + if n, err := f.Write(data); err != nil { + return err + } else if n < len(data) { + return io.ErrShortWrite + } + + return nil +} + +// MakeDir create directories recursively. +func MakeDir(dir string) error { + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + return err + } + return nil +} + +// RealPath get the real path. +func RealPath(path string) string { + p, err := filepath.Abs(path) + if err != nil { + return "" + } + if !Exists(p) { + return "" + } + return p +} diff --git a/internal/rand.go b/internal/rand.go new file mode 100644 index 0000000..6f96808 --- /dev/null +++ b/internal/rand.go @@ -0,0 +1,28 @@ +/** + * @Author: fuxiao + * @Email: 576101059@qq.com + * @Date: 2021/8/26 4:51 下午 + * @Desc: TODO + */ + +package internal + +import ( + "math/rand" + "time" +) + +var seedStr = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + +// RandStr generate a string of specified length. +func RandStr(length int) (lastStr string) { + rand.Seed(time.Now().UnixNano()) + + pos, seedLen := 0, len(seedStr) + for i := 0; i < length; i++ { + pos = rand.Intn(seedLen) + lastStr += seedStr[pos : pos+1] + } + + return lastStr +} diff --git a/internal/stream.go b/internal/stream.go new file mode 100644 index 0000000..16cd54d --- /dev/null +++ b/internal/stream.go @@ -0,0 +1,120 @@ +/** + * @Author: fuxiao + * @Email: 576101059@qq.com + * @Date: 2021/8/26 6:21 下午 + * @Desc: TODO + */ + +package internal + +import ( + "bytes" + "encoding/hex" + "strconv" + "strings" + "sync" +) + +var fileTypeMap sync.Map + +func init() { + fileTypeMap.Store("ffd8ffe000104a464946", "jpg") + fileTypeMap.Store("89504e470d0a1a0a0000", "png") + fileTypeMap.Store("47494638396126026f01", "gif") + fileTypeMap.Store("49492a00227105008037", "tif") + fileTypeMap.Store("424d228c010000000000", "bmp") + fileTypeMap.Store("424d8240090000000000", "bmp") + fileTypeMap.Store("424d8e1b030000000000", "bmp") + fileTypeMap.Store("41433130313500000000", "dwg") + fileTypeMap.Store("3c21444f435459504520", "html") + fileTypeMap.Store("3c68746d6c3e0", "html") + fileTypeMap.Store("3c21646f637479706520", "htm") + fileTypeMap.Store("48544d4c207b0d0a0942", "css") + fileTypeMap.Store("696b2e71623d696b2e71", "js") + fileTypeMap.Store("7b5c727466315c616e73", "rtf") + fileTypeMap.Store("38425053000100000000", "psd") + fileTypeMap.Store("46726f6d3a203d3f6762", "eml") + fileTypeMap.Store("d0cf11e0a1b11ae10000", "doc") + fileTypeMap.Store("d0cf11e0a1b11ae10000", "vsd") + fileTypeMap.Store("5374616E64617264204A", "mdb") + fileTypeMap.Store("252150532D41646F6265", "ps") + fileTypeMap.Store("255044462d312e350d0a", "pdf") + fileTypeMap.Store("2e524d46000000120001", "rmvb") + fileTypeMap.Store("464c5601050000000900", "flv") + fileTypeMap.Store("00000020667479706d70", "mp4") + fileTypeMap.Store("49443303000000002176", "mp3") + fileTypeMap.Store("000001ba210001000180", "mpg") + fileTypeMap.Store("3026b2758e66cf11a6d9", "wmv") + fileTypeMap.Store("52494646e27807005741", "wav") + fileTypeMap.Store("52494646d07d60074156", "avi") + fileTypeMap.Store("4d546864000000060001", "mid") + fileTypeMap.Store("504b0304140000000800", "zip") + fileTypeMap.Store("526172211a0700cf9073", "rar") + fileTypeMap.Store("235468697320636f6e66", "ini") + fileTypeMap.Store("504b03040a0000000000", "jar") + fileTypeMap.Store("4d5a9000030000000400", "exe") + fileTypeMap.Store("3c25402070616765206c", "jsp") + fileTypeMap.Store("4d616e69666573742d56", "mf") + fileTypeMap.Store("3c3f786d6c2076657273", "xml") + fileTypeMap.Store("494e5345525420494e54", "sql") + fileTypeMap.Store("7061636b616765207765", "java") + fileTypeMap.Store("406563686f206f66660d", "bat") + fileTypeMap.Store("1f8b0800000000000000", "gz") + fileTypeMap.Store("6c6f67346a2e726f6f74", "properties") + fileTypeMap.Store("cafebabe0000002e0041", "class") + fileTypeMap.Store("49545346030000006000", "chm") + fileTypeMap.Store("04000000010000001300", "mxp") + fileTypeMap.Store("504b0304140006000800", "docx") + fileTypeMap.Store("d0cf11e0a1b11ae10000", "wps") + fileTypeMap.Store("6431303a637265617465", "torrent") + fileTypeMap.Store("6D6F6F76", "mov") + fileTypeMap.Store("FF575043", "wpd") + fileTypeMap.Store("CFAD12FEC5FD746F", "dbx") + fileTypeMap.Store("2142444E", "pst") + fileTypeMap.Store("AC9EBD8F", "qdf") + fileTypeMap.Store("E3828596", "pwl") + fileTypeMap.Store("2E7261FD", "ram") +} + +// bytesToHexString get the binary of the previous result byte. +func bytesToHexString(stream []byte) string { + if stream == nil || len(stream) <= 0 { + return "" + } + + var ( + hv string + res = bytes.Buffer{} + temp = make([]byte, 0) + ) + + for _, v := range stream { + if hv = hex.EncodeToString(append(temp, v&0xFF)); len(hv) < 2 { + res.WriteString(strconv.FormatInt(int64(0), 10)) + } + + res.WriteString(hv) + } + + return res.String() +} + +// GetFileType judge the file type based on the binary byte stream. +func GetFileType(stream []byte) string { + var ( + fileType string + fileCode = bytesToHexString(stream) + ) + + fileTypeMap.Range(func(key, value interface{}) bool { + if strings.HasPrefix(fileCode, strings.ToLower(key.(string))) || + strings.HasPrefix(key.(string), strings.ToLower(fileCode)) { + fileType = value.(string) + return false + } + + return true + }) + + return fileType +} diff --git a/internal/utils.go b/internal/utils.go index affe7c7..75fdc63 100644 --- a/internal/utils.go +++ b/internal/utils.go @@ -8,27 +8,13 @@ package internal import ( - "encoding" "encoding/json" - "fmt" "net/url" - "os" - "reflect" - "strconv" "strings" - "time" - "unsafe" ) const fileUploadingKey = "@file:" -func Exists(path string) bool { - if stat, err := os.Stat(path); stat != nil && !os.IsNotExist(err) { - return true - } - return false -} - func BuildParams(params interface{}) string { switch v := params.(type) { case string: @@ -86,219 +72,3 @@ func BuildParams(params interface{}) string { return str } - -func String(any interface{}) string { - switch v := any.(type) { - case nil: - return "" - case string: - return v - case int: - return strconv.Itoa(v) - case int8: - return strconv.Itoa(int(v)) - case int16: - return strconv.Itoa(int(v)) - case int32: - return strconv.Itoa(int(v)) - case int64: - return strconv.FormatInt(v, 10) - case uint: - return strconv.FormatUint(uint64(v), 10) - case uint8: - return strconv.FormatUint(uint64(v), 10) - case uint16: - return strconv.FormatUint(uint64(v), 10) - case uint64: - return strconv.FormatUint(v, 10) - case float32: - return strconv.FormatFloat(float64(v), 'f', -1, 32) - case float64: - return strconv.FormatFloat(v, 'f', -1, 64) - case bool: - return strconv.FormatBool(v) - case []byte: - return string(v) - case time.Time: - return v.String() - case *time.Time: - if v == nil { - return "" - } - return v.String() - default: - if v == nil { - return "" - } - - if i, ok := v.(stringInterface); ok { - return i.String() - } - - if i, ok := v.(errorInterface); ok { - return i.Error() - } - - var ( - rv = reflect.ValueOf(v) - kind = rv.Kind() - ) - - switch kind { - case reflect.Chan, - reflect.Map, - reflect.Slice, - reflect.Func, - reflect.Ptr, - reflect.Interface, - reflect.UnsafePointer: - if rv.IsNil() { - return "" - } - case reflect.String: - return rv.String() - } - - if kind == reflect.Ptr { - return String(rv.Elem().Interface()) - } - - if b, e := json.Marshal(v); e != nil { - return fmt.Sprint(v) - } else { - return string(b) - } - } -} - -func Scan(b []byte, any interface{}) error { - switch v := any.(type) { - case nil: - return fmt.Errorf("cache: Scan(nil)") - case *string: - *v = String(b) - return nil - case *[]byte: - *v = b - return nil - case *int: - var err error - *v, err = strconv.Atoi(String(b)) - return err - case *int8: - n, err := strconv.ParseInt(String(b), 10, 8) - if err != nil { - return err - } - *v = int8(n) - return nil - case *int16: - n, err := strconv.ParseInt(String(b), 10, 16) - if err != nil { - return err - } - *v = int16(n) - return nil - case *int32: - n, err := strconv.ParseInt(String(b), 10, 32) - if err != nil { - return err - } - *v = int32(n) - return nil - case *int64: - n, err := strconv.ParseInt(String(b), 10, 64) - if err != nil { - return err - } - *v = n - return nil - case *uint: - n, err := strconv.ParseUint(String(b), 10, 64) - if err != nil { - return err - } - *v = uint(n) - return nil - case *uint8: - n, err := strconv.ParseUint(String(b), 10, 8) - if err != nil { - return err - } - *v = uint8(n) - return nil - case *uint16: - n, err := strconv.ParseUint(String(b), 10, 16) - if err != nil { - return err - } - *v = uint16(n) - return nil - case *uint32: - n, err := strconv.ParseUint(String(b), 10, 32) - if err != nil { - return err - } - *v = uint32(n) - return nil - case *uint64: - n, err := strconv.ParseUint(String(b), 10, 64) - if err != nil { - return err - } - *v = n - return nil - case *float32: - n, err := strconv.ParseFloat(String(b), 32) - if err != nil { - return err - } - *v = float32(n) - return err - case *float64: - var err error - *v, err = strconv.ParseFloat(String(b), 64) - return err - case *bool: - *v = len(b) == 1 && b[0] == '1' - return nil - case *time.Time: - var err error - *v, err = time.Parse(time.RFC3339Nano, String(b)) - return err - case encoding.BinaryUnmarshaler: - return v.UnmarshalBinary(b) - default: - var ( - rv = reflect.ValueOf(v) - kind = rv.Kind() - ) - - if kind != reflect.Ptr { - return fmt.Errorf("can't unmarshal %T", v) - } - - switch kind = rv.Elem().Kind(); kind { - case reflect.Array, reflect.Slice, reflect.Map, reflect.Struct: - return json.Unmarshal(b, v) - } - - return fmt.Errorf("can't unmarshal %T", v) - } -} - -type stringInterface interface { - String() string -} - -type errorInterface interface { - Error() string -} - -func UnsafeStringToBytes(s string) []byte { - return *(*[]byte)(unsafe.Pointer(&s)) -} - -func UnsafeBytesToString(b []byte) string { - return *(*string)(unsafe.Pointer(&b)) -} diff --git a/response.go b/response.go index 793072f..010ed05 100644 --- a/response.go +++ b/response.go @@ -10,6 +10,7 @@ package http import ( "io/ioutil" "net/http" + "sync" "github.com/dobyte/http/internal" ) @@ -19,16 +20,19 @@ type Response struct { Request *http.Request body []byte cookies map[string]string + mu sync.Mutex } -// Bytes retrieves and returns the response content as []byte. -func (r *Response) Bytes() []byte { +// ReadBytes retrieves and returns the response content as []byte. +func (r *Response) ReadBytes() []byte { if r == nil || r.Response == nil { return []byte{} } if r.body == nil { var err error + r.mu.Lock() + defer r.mu.Unlock() if r.body, err = ioutil.ReadAll(r.Response.Body); err != nil { return nil } @@ -37,14 +41,14 @@ func (r *Response) Bytes() []byte { return r.body } -// String retrieves and returns the response content as string. -func (r *Response) String() string { - return internal.UnsafeBytesToString(r.Bytes()) +// ReadString retrieves and returns the response content as string. +func (r *Response) ReadString() string { + return internal.UnsafeBytesToString(r.ReadBytes()) } // Scan convert the response into a complex data structure. func (r *Response) Scan(any interface{}) error { - return internal.Scan(r.Bytes(), any) + return internal.Scan(r.ReadBytes(), any) } // Close closes the response when it will never be used.