Skip to content

Commit

Permalink
Add BodyMultipart (#119)
Browse files Browse the repository at this point in the history
* Add BodyMultipart

* Test: Make CR visible
  • Loading branch information
earthboundkid authored Nov 21, 2024
1 parent 34081e9 commit ed53f99
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 0 deletions.
32 changes: 32 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package requests

import (
"compress/gzip"
"fmt"
"io"
"mime/multipart"
"net/http/httptest"
)

Expand Down Expand Up @@ -43,3 +45,33 @@ func TestServerConfig(s *httptest.Server) Config {
Client(s.Client())
}
}

// BodyMultipart returns a Config
// that uses a multipart.Writer for the request body.
// If boundary is "", a multipart boundary is chosen at random.
// The content type of the request is set to multipart/form-data
// with the correct boundary.
// The multipart.Writer is automatically closed if the callback succeeds.
func BodyMultipart(boundary string, h func(multi *multipart.Writer) error) Config {
return func(rb *Builder) {
if boundary == "" {
multi := multipart.NewWriter(nil)
boundary = multi.Boundary()
}
rb.
ContentType("multipart/form-data; boundary=" + boundary).
BodyWriter(func(w io.Writer) error {
multi := multipart.NewWriter(w)
if err := multi.SetBoundary(boundary); err != nil {
return fmt.Errorf("setting boundary: %w", err)
}
if err := h(multi); err != nil {
return err
}
if err := multi.Close(); err != nil {
return fmt.Errorf("closing multipart writer: %w", err)
}
return nil
})
}
}
60 changes: 60 additions & 0 deletions config_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ import (
"compress/gzip"
"context"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/http/httptest"
"net/http/httputil"
"net/textproto"
"strings"

"github.com/carlmjohnson/requests"
Expand Down Expand Up @@ -100,3 +104,59 @@ func ExampleTestServerConfig() {
// Hello, world!
// Howdy, planet!
}

func ExampleBodyMultipart() {
req, err := requests.
URL("http://example.com").
Config(requests.BodyMultipart("abc", func(multi *multipart.Writer) error {
// CreateFormFile hardcodes the Content-Type as application/octet-stream
w, err := multi.CreateFormFile("file", "en.txt")
if err != nil {
return err
}
_, err = io.WriteString(w, "Hello, World!")
if err != nil {
return err
}
// CreatePart is more flexible and lets you add headers
h := make(textproto.MIMEHeader)
h.Set("Content-Disposition", `form-data; name="file"; filename="jp.txt"`)
h.Set("Content-Type", "text/plain; charset=utf-8")
w, err = multi.CreatePart(h)
if err != nil {
panic(err)
}
_, err = io.WriteString(w, "こんにちは世界!")
if err != nil {
return err
}
return nil
})).
Request(context.Background())
if err != nil {
panic(err)
}
b, err := httputil.DumpRequest(req, true)
if err != nil {
panic(err)
}

// Make carriage return visible
fmt.Println(strings.ReplaceAll(string(b), "\r", "↵"))
// Output:
// POST / HTTP/1.1↵
// Host: example.com↵
// Content-Type: multipart/form-data; boundary=abc↵
// ↵
// --abc↵
// Content-Disposition: form-data; name="file"; filename="en.txt"↵
// Content-Type: application/octet-stream↵
// ↵
// Hello, World!↵
// --abc↵
// Content-Disposition: form-data; name="file"; filename="jp.txt"↵
// Content-Type: text/plain; charset=utf-8↵
// ↵
// こんにちは世界!↵
// --abc--↵
}

0 comments on commit ed53f99

Please sign in to comment.