Skip to content

Commit efc9256

Browse files
author
nativebpm-bot
committed
sync: update from connectors monorepo
1 parent 25653ac commit efc9256

2 files changed

Lines changed: 32 additions & 0 deletions

File tree

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,16 @@ Ideal for large JSON bodies, multipart uploads, generated archives, or continuou
2727
Data is transmitted as it’s produced — the server can start processing immediately,
2828
without waiting for the full body to be buffered.
2929

30+
```mermaid
31+
flowchart TB
32+
subgraph P["io.Pipe()"]
33+
PW["PipeWriter"] --> PR["PipeReader"]
34+
end
35+
36+
W["Writer (goroutine)"] -->|"writes into"| P
37+
P -->|"provides data to"| R["Reader"]
38+
```
39+
3040
## Why Streaming Matters
3141

3242
Traditional HTTP clients buffer request bodies entirely before sending.
@@ -43,6 +53,7 @@ For large or dynamically generated payloads, this leads to:
4353
- [Streaming multipart upload](examples/multipart_streaming_example)
4454
- [Without fluent API (for comparison)](examples/multipart_streaming_example/multipart_streaming_without_fluent_api)
4555
- [Logger middleware](examples/logger_slog_example)
56+
- [Gotenbergo Client: Built on httpstream for efficient multipart uploads](https://github.com/nativebpm/gotenberg)
4657

4758
## License
4859

internal/httprequest/multipart.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package httprequest
22

33
import (
44
"context"
5+
"fmt"
56
"io"
67
"mime/multipart"
78
"net/http"
@@ -55,6 +56,23 @@ func (r *Multipart) Timeout(duration time.Duration) *Multipart {
5556
func (r *Multipart) Send() (*http.Response, error) {
5657
ctx := r.request.Context()
5758

59+
// Pre-validate files to ensure they are not empty
60+
for i, field := range r.fields {
61+
if field.contentType == applicationOctetStream {
62+
// Read first byte to check if file has content
63+
buf := make([]byte, 1)
64+
n, err := field.file.Read(buf)
65+
if err != nil && err != io.EOF {
66+
return nil, fmt.Errorf("failed to read file %s: %w", field.value, err)
67+
}
68+
if n == 0 {
69+
return nil, fmt.Errorf("empty file: %s", field.value)
70+
}
71+
// Wrap reader to return the read byte back to the stream
72+
r.fields[i].file = io.MultiReader(strings.NewReader(string(buf[:n])), field.file)
73+
}
74+
}
75+
5876
pr, pw := io.Pipe()
5977
mw := multipart.NewWriter(pw)
6078
r.request.Body = pr
@@ -72,18 +90,21 @@ func (r *Multipart) Send() (*http.Response, error) {
7290
return
7391
default:
7492
}
93+
7594
switch field.contentType {
7695
case multipartFormData:
7796
if err := mw.WriteField(field.key, field.value); err != nil {
7897
pw.CloseWithError(err)
7998
return
8099
}
100+
81101
case applicationOctetStream:
82102
part, err := mw.CreateFormFile(field.key, field.value)
83103
if err != nil {
84104
pw.CloseWithError(err)
85105
return
86106
}
107+
87108
if _, err := io.Copy(part, field.file); err != nil {
88109
pw.CloseWithError(err)
89110
return

0 commit comments

Comments
 (0)