Skip to content

Commit

Permalink
multipart upload support
Browse files Browse the repository at this point in the history
  • Loading branch information
speier committed Nov 25, 2020
1 parent fe14f86 commit b5d6394
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 44 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
VERSION=v0.1.4
VERSION=v0.1.5

release:
@git tag -a ${VERSION} -m "Release ${VERSION}" && git push origin ${VERSION}
Expand Down
4 changes: 4 additions & 0 deletions examples/upload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const host = 'https://httpbin.org'

let data = { name: 'Foo Logo', cv: '@/tmp/foo.png' }
POST(`${host}/post`, 'Content-Type: multipart/form-data', 'Accept: multipart/form-data', data)
156 changes: 113 additions & 43 deletions pkg/req/req.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"os"
"strings"
)

Expand All @@ -14,74 +17,50 @@ var (
cookies = []*http.Cookie{}

Methods = map[string]interface{}{
"HEADER": setHeader,
"header": setHeader,
"GET": httpGet,
"POST": httpPost,
}
)

func init() {
// default headers
// default headers (can be override from script)
header.Set("User-Agent", "hrun/0.1")
header.Set("Accept", "application/json")
header.Set("Content-Type", "application/json")
}

func printres(host string, status int, body string, err error) {
func printres(res *Response, err error) {
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Printf("\n[%d] %s\n%s\n", status, host, body)
fmt.Println(res)
}

func httpGet(host string, args ...interface{}) int {
status, body, err := httpreq(http.MethodGet, host, args...)
printres(host, status, body, err)
return status
res, err := httpreq(http.MethodGet, host, args...)
printres(res, err)
return res.StatusCode
}

func httpPost(host string, args ...interface{}) int {
status, body, err := httpreq(http.MethodPost, host, args...)
printres(host, status, body, err)
return status
res, err := httpreq(http.MethodPost, host, args...)
printres(res, err)
return res.StatusCode
}

func setHeader(args ...interface{}) {
for _, a := range args {
s, ok := a.(string)
if ok {
h := strings.Split(s, ":")
if len(h) == 2 {
k, v := h[0], h[1]
header.Set(k, v)
}
}
}
}

func httpreq(method string, host string, args ...interface{}) (statusCode int, body string, err error) {
func httpreq(method string, host string, args ...interface{}) (*Response, error) {
setHeader(args...)

payload := &bytes.Buffer{}
for _, a := range args {
// payload
m, ok := a.(map[string]interface{})
if ok {
var b []byte
b, err = json.Marshal(m)
if err != nil {
return
}
_, err = payload.Write(b)
if err != nil {
return
}
}
payload, err := getPayload(args...)
if err != nil {
return nil, err
}

req, err := http.NewRequest(method, host, payload)
if err != nil {
return
return nil, err
}

// set request headers and cookies
Expand All @@ -93,20 +72,111 @@ func httpreq(method string, host string, args ...interface{}) (statusCode int, b
client := &http.Client{}
res, err := client.Do(req)
if err != nil {
return
return nil, err
}
defer res.Body.Close()

var bodyBytes []byte
bodyBytes, err = ioutil.ReadAll(res.Body)
if err != nil {
return
return nil, err
}

// save response cookies
for _, c := range res.Cookies() {
cookies = append(cookies, c)
}

return res.StatusCode, string(bodyBytes), nil
return &Response{
Host: host,
Status: res.Status,
StatusCode: res.StatusCode,
Proto: res.Proto,
ProtoMajor: res.ProtoMajor,
ProtoMinor: res.ProtoMinor,
Header: res.Header,
Body: string(bodyBytes),
ContentLength: res.ContentLength,
TransferEncoding: res.TransferEncoding,
Uncompressed: res.Uncompressed,
Trailer: res.Trailer,
}, nil
}

func setHeader(args ...interface{}) {
for _, a := range args {
s, ok := a.(string)
if ok {
h := strings.Split(s, ":")
if len(h) == 2 {
k, v := h[0], h[1]
header.Set(strings.TrimSpace(k), strings.TrimSpace(v))
}
}
}
}

func getPayload(args ...interface{}) (io.Reader, error) {
payload := &bytes.Buffer{}

for _, a := range args {
m, ok := a.(map[string]interface{})
if ok {
if strings.Contains(header.Get("Content-Type"), "multipart/form-data") {
// multipart
w := multipart.NewWriter(payload)
err := writeMultipartPayload(m, w)
if err != nil {
return nil, err
}
} else {
// json (default)
err := writeJsonPayload(m, payload)
if err != nil {
return nil, err
}
}
}
}

return payload, nil
}

func writeMultipartPayload(m map[string]interface{}, w *multipart.Writer) error {
for k, v := range m {
s := fmt.Sprintf("%v", v)
// file
if strings.HasPrefix(s, "@") {
s = s[1:]
// read file
f, err := os.Open(s)
if err != nil {
return err
}
defer f.Close()
// copy file to form field part
fp, err := w.CreateFormFile(k, f.Name())
_, err = io.Copy(fp, f)
if err != nil {
return err
}
} else {
err := w.WriteField(k, s)
if err != nil {
return err
}
}
}
header.Set("Content-Type", w.FormDataContentType())
return w.Close()
}

func writeJsonPayload(m map[string]interface{}, w io.Writer) error {
var b []byte
b, err := json.Marshal(m)
if err != nil {
return err
}
_, err = w.Write(b)
return err
}
30 changes: 30 additions & 0 deletions pkg/req/res.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package req

import (
"fmt"
"net/http"
"strings"
)

// minimal http.Response
type Response struct {
Host string
Status string // e.g. "200 OK"
StatusCode int // e.g. 200
Proto string // e.g. "HTTP/1.0"
ProtoMajor int // e.g. 1
ProtoMinor int // e.g. 0
Header http.Header
Body string
ContentLength int64
TransferEncoding []string
Uncompressed bool
Trailer http.Header
}

func (r *Response) String() string {
sb := &strings.Builder{}
fmt.Fprintf(sb, "\n[%d] %s\n", r.StatusCode, r.Host)
fmt.Fprintf(sb, "%s\n", r.Body)
return sb.String()
}

0 comments on commit b5d6394

Please sign in to comment.