Skip to content

Commit

Permalink
feat: mutipart form data (#11)
Browse files Browse the repository at this point in the history
* feat: mutipart form data

* fix typo
  • Loading branch information
卢旭泽 authored Mar 15, 2021
1 parent d520163 commit 8254484
Show file tree
Hide file tree
Showing 12 changed files with 194 additions and 66 deletions.
46 changes: 34 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@
[![Release](https://img.shields.io/github/release/monaco-io/request.svg?style=flat-square)](https://github.com/monaco-io/request/releases)
[![TODOs](https://badgen.net/https/api.tickgit.com/badgen/github.com/monaco-io/request)](https://www.tickgit.com/browse?repo=github.com/monaco-io/request)
[![License](https://img.shields.io/github/license/monaco-io/request?style=plastic)](https://github.com/monaco-io/request/blob/master/LICENSE)

<!-- [![Sourcegraph](https://sourcegraph.com/github.com/monaco-io/request/-/badge.svg)](https://sourcegraph.com/github.com/monaco-io/request?badge) -->
<!-- [![Open Source Helpers](https://www.codetriage.com/monaco-io/request/badges/users.svg)](https://www.codetriage.com/monaco-io/request) -->
<!-- [![Join the chat at https://gitter.im/monaco-io/request](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/monaco-io/request?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -->

HTTP client for golang, Inspired by [Javascript-axios](https://github.com/axios/axios) [Python-request](https://github.com/psf/requests).
HTTP Client for golang, Inspired by [Javascript-axios](https://github.com/axios/axios) [Python-request](https://github.com/psf/requests).
If you have experience about axios or requests, you will love it.
No 3rd dependency.

Expand Down Expand Up @@ -54,16 +55,16 @@ func main() {
var body = struct {
A string
B int
}{A: "A", B: 001}
}{A: "A", B: 1}
var result interface{}

client := request.Client{
c := request.Client{
URL: "https://google.com",
Method: "POST",
Query: map[string]string{"hello": "world"},
JSON: body,
}
resp := client.Send().Scan(&result)
resp := c.Send().Scan(&result)
if !resp.OK(){
// handle error
log.Println(resp.Error())
Expand All @@ -73,7 +74,27 @@ func main() {
// bytes := resp.Bytes()
```
### POST with empty request
### POST with local files
```go
package main

import (
"github.com/monaco-io/request"
)

func main() {
c := request.Client{
URL: "https://google.com",
Method: "POST",
Query: map[string]string{"hello": "world"},
JSON: body,
}
resp := c.Send().Scan(&result)
...
```
### POST step by step
```go
package main
Expand Down Expand Up @@ -106,14 +127,15 @@ import (
)

func main() {
client := request.Client{
c := request.Client{
URL: "https://google.com",
Method: "POST",
BasicAuth: request.BasicAuth{
Username:"user_xxx",
Password:"pwd_xxx",
MultipartForm: MultipartForm{
Fields: map[string]string{"a": "1"},
Files: []string{"doc.txt"},
},
}
resp := c.Send()
}
```
Expand All @@ -127,7 +149,7 @@ import (
)

func main() {
client := request.Client{
c := request.Client{
URL: "https://google.com",
Method: "POST",
Timeout: time.Second*10,
Expand All @@ -145,7 +167,7 @@ import (
)

func main() {
client := request.Client{
c := request.Client{
URL: "https://google.com",
CookiesMap: map[string]string{
"cookie_name": "cookie_value",
Expand All @@ -165,7 +187,7 @@ import (
)

func main() {
client := request.Client{
c := request.Client{
URL: "https://google.com",
TLSConfig: &tls.Config{InsecureSkipVerify: true},
}
Expand Down
4 changes: 3 additions & 1 deletion context/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ func TestNew(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := New(); !reflect.DeepEqual(got, tt.want) {
if got := New(); got == nil ||
!reflect.DeepEqual(got.Client, tt.want.Client) ||
!reflect.DeepEqual(got.Request, tt.want.Request) {
t.Errorf("New() = %v, want %v", got, tt.want)
}
})
Expand Down
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
module github.com/monaco-io/request

go 1.15
go 1.16

require (
github.com/kr/text v0.2.0 // indirect
golang.org/x/net v0.0.0-20210119194325-5f4716e94777
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
golang.org/x/text v0.3.5 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v2 v2.4.0
)
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
Expand Down
12 changes: 9 additions & 3 deletions model.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type Client struct {
// Header http header
Header map[string]string

// SortedHeader http sorted header, example: [][2]string{{"h1": "v1"}, {"h2": "v2"}}
// SortedHeader http sorted header, example: [][2]string{{"h1", "v1"}, {"h2", "v2"}}
SortedHeader [][2]string

// Query params on http url
Expand All @@ -51,8 +51,8 @@ type Client struct {
// URLEncodedForm string/bytes/map[string][]string
URLEncodedForm interface{}

// FormFields TODO
FormFields map[string]string
// MultipartForm key value pairs
MultipartForm MultipartForm

// BasicAuth http basic auth with username and password
BasicAuth BasicAuth
Expand Down Expand Up @@ -131,3 +131,9 @@ type A []interface{}

// H alias of map[string]interface{}
type H map[string]interface{}

// MultipartForm Fields is key value pairs, Files is a list of local files
type MultipartForm struct {
Fields map[string]string
Files []string
}
2 changes: 1 addition & 1 deletion request.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func (c *Client) Send() *response.Sugar {
request.BodyString{Data: c.String},
request.BodyXML{Data: c.XML},
request.BodyYAML{Data: c.YAML},
request.BodyForm{Fields: c.FormFields},
request.BodyForm{Fields: c.MultipartForm.Fields, Files: c.MultipartForm.Files},
request.BodyURLEncodedForm{Data: c.URLEncodedForm},
request.TLSConfig{Config: c.TLSConfig},
request.Transport{RoundTripper: c.Transport},
Expand Down
76 changes: 34 additions & 42 deletions request/body.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@ import (
"bytes"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/url"
"strconv"
"strings"
"path"

"github.com/monaco-io/request/context"
"gopkg.in/yaml.v2"
Expand Down Expand Up @@ -194,64 +192,58 @@ type FormFile struct {
// BodyForm represents the supported form fields by file and string data.
type BodyForm struct {
Fields map[string]string
Files []FormFile
Files []string
}

// Apply TODO
// Apply Form Data
func (fd BodyForm) Apply(ctx *context.Context) {
buf := &bytes.Buffer{}
multipartWriter := multipart.NewWriter(buf)

for index, file := range fd.Files {
if err := writeFile(multipartWriter, fd, file, index); err != nil {
return
var (
err error
buf bytes.Buffer
)

multipartWriter := multipart.NewWriter(&buf)

for _, filePath := range fd.Files {
var (
w io.Writer
data []byte
)

w, err = multipartWriter.CreateFormFile(path.Base(filePath), path.Base(filePath))
if err != nil {
err = fmt.Errorf("cread form file failed: %s", err)
goto ErrorHandler
}
data, err = ioutil.ReadFile(filePath)
if err != nil {
err = fmt.Errorf("read local file failed: %s", err)
goto ErrorHandler
}
_, err = w.Write(data)
if err != nil {
err = fmt.Errorf("write byte to writer failed: %s", err)
goto ErrorHandler
}
}

// Populate the other parts of the form (if there are any)
for k, v := range fd.Fields {
multipartWriter.WriteField(k, v)
}
if err := multipartWriter.Close(); err != nil {
if err = multipartWriter.Close(); err != nil {
return
}
if buf.Len() == 0 {
return
}

ctx.Request.Body = ioutil.NopCloser(buf)
ctx.Request.Body = ioutil.NopCloser(&buf)
ctx.Request.Header.Add("Content-Type", multipartWriter.FormDataContentType())
return
}

func writeFile(multipartWriter *multipart.Writer, fd BodyForm, ff FormFile, index int) error {
if ff.Reader == nil {
return errors.New("github/monaco-io/request: file reader cannot be nil")
}

rc, ok := ff.Reader.(io.ReadCloser)
if !ok && ff.Reader != nil {
rc = ioutil.NopCloser(ff.Reader)
}

fileName := "file"
if len(fd.Files) > 1 {
fileName = strings.Join([]string{fileName, strconv.Itoa(index + 1)}, "")
}
if ff.Name != "" {
fileName = ff.Name
}

writer, err := multipartWriter.CreateFormFile(fileName, ff.Name)
if err != nil {
return err
}
if _, err = io.Copy(writer, rc); err != nil && err != io.EOF {
return err
}
rc.Close()

return nil
ErrorHandler:
ctx.SetError(err)
}

// Valid form body valid?
Expand Down
6 changes: 6 additions & 0 deletions request/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,12 @@ func (r *Request) AddTransform(data http.RoundTripper) *Request {
use(Transport{RoundTripper: data})
}

// AddMultipartForm ...
func (r *Request) AddMultipartForm(fields map[string]string, files []string) *Request {
return r.
use(BodyForm{Fields: fields, Files: files})
}

// Send ...
func (r *Request) Send() *response.Sugar {
return response.New(r.ctx).Do()
Expand Down
15 changes: 14 additions & 1 deletion request/request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"testing"
)

func TestRequest_Send(t *testing.T) {
func TestRequest_URLEncodedForm(t *testing.T) {
var data map[string]interface{}
resp := New().
POST("http://httpbin.org/post").
Expand All @@ -26,3 +26,16 @@ func TestRequest_Send(t *testing.T) {
t.Error("form")
}
}

func TestRequest_Form(t *testing.T) {
resp := New().
POST("http://httpbin.org/post").
AddHeader(map[string]string{"Google": "google"}).
AddBasicAuth("google", "google").
AddMultipartForm(map[string]string{"field": "value"}, []string{"no_exist.txt"}).
Send()

if resp.Error().Error() != "read local file failed: open no_exist.txt: no such file or directory" {
t.Error(resp.Error())
}
}
Loading

0 comments on commit 8254484

Please # to comment.