add download method

This commit is contained in:
fuxiao 2021-08-26 18:50:14 +08:00
parent 9f9cd3fec1
commit 4744b3d3c0
10 changed files with 577 additions and 246 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.idea/
*/.DS_Store
.vscode
go.sum

View File

@ -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...)
}

View File

@ -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)
}
}

87
download.go Normal file
View File

@ -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
}

234
internal/conv.go Normal file
View File

@ -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))
}

66
internal/file.go Normal file
View File

@ -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
}

28
internal/rand.go Normal file
View File

@ -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
}

120
internal/stream.go Normal file
View File

@ -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
}

View File

@ -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))
}

View File

@ -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.