Skip to content

Commit 99d5a6d

Browse files
authored
Add uploadLargeFormFile() for memory-efficient multipart uploads (#795)
1 parent 85c6c76 commit 99d5a6d

1 file changed

Lines changed: 108 additions & 18 deletions

File tree

http/client/requests.h

Lines changed: 108 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,32 @@ HV_INLINE Response uploadFormFile(const char* url, const char* name, const char*
119119
#endif
120120

121121
typedef std::function<void(size_t sended_bytes, size_t total_bytes)> upload_progress_cb;
122+
123+
// Internal helper for streaming file data through an established connection
124+
// Used by uploadLargeFile() and uploadLargeFormFile()
125+
namespace internal {
126+
HV_INLINE int streamFileToConnection(
127+
hv::HttpClient& cli,
128+
HFile& file,
129+
size_t file_size,
130+
upload_progress_cb progress_cb = NULL)
131+
{
132+
size_t sent_bytes = 0;
133+
char buf[40960]; // 40K
134+
while (sent_bytes < file_size) {
135+
size_t to_read = file_size - sent_bytes;
136+
if (to_read > sizeof(buf)) to_read = sizeof(buf);
137+
int nread = file.read(buf, to_read);
138+
if (nread <= 0) return -1;
139+
int nsend = cli.sendData(buf, nread);
140+
if (nsend != nread) return -1;
141+
sent_bytes += nsend;
142+
if (progress_cb) progress_cb(sent_bytes, file_size);
143+
}
144+
return 0;
145+
}
146+
} // namespace internal
147+
122148
HV_INLINE Response uploadLargeFile(const char* url, const char* filepath, upload_progress_cb progress_cb = NULL, http_method method = HTTP_POST, const http_headers& headers = DefaultHeaders) {
123149
// open file
124150
HFile file;
@@ -151,24 +177,9 @@ HV_INLINE Response uploadLargeFile(const char* url, const char* filepath, upload
151177
return NULL;
152178
}
153179

154-
// send file
155-
size_t sended_bytes = 0;
156-
char filebuf[40960]; // 40K
157-
int filebuflen = sizeof(filebuf);
158-
int nread = 0, nsend = 0;
159-
while (sended_bytes < total_bytes) {
160-
nread = file.read(filebuf, filebuflen);
161-
if (nread <= 0) {
162-
return NULL;
163-
}
164-
nsend = cli.sendData(filebuf, nread);
165-
if (nsend != nread) {
166-
return NULL;
167-
}
168-
sended_bytes += nsend;
169-
if (progress_cb) {
170-
progress_cb(sended_bytes, total_bytes);
171-
}
180+
// stream file using shared helper
181+
if (internal::streamFileToConnection(cli, file, total_bytes, progress_cb) != 0) {
182+
return NULL;
172183
}
173184

174185
// recv response
@@ -180,6 +191,85 @@ HV_INLINE Response uploadLargeFile(const char* url, const char* filepath, upload
180191
return resp;
181192
}
182193

194+
// Streaming multipart form file upload - memory efficient for large files
195+
// Overload with explicit upload_filename for when the filename differs from local path
196+
#ifndef WITHOUT_HTTP_CONTENT
197+
HV_INLINE Response uploadLargeFormFile(const char* url, const char* name, const char* filepath,
198+
const char* upload_filename,
199+
std::map<std::string, std::string>& params = hv::empty_map,
200+
upload_progress_cb progress_cb = NULL, http_method method = HTTP_POST,
201+
const http_headers& headers = DefaultHeaders)
202+
{
203+
HFile file;
204+
if (file.open(filepath, "rb") != 0) return NULL;
205+
206+
size_t file_size = file.size(filepath);
207+
const char* filename = upload_filename ? upload_filename : hv_basename(filepath);
208+
static const char* BOUNDARY = "----libhvFormBoundary7MA4YWxkTrZu0gW";
209+
210+
// Build preamble (form fields + file header)
211+
std::string preamble;
212+
for (auto& param : params) {
213+
preamble += "--"; preamble += BOUNDARY; preamble += "\r\n";
214+
preamble += "Content-Disposition: form-data; name=\"";
215+
preamble += param.first; preamble += "\"\r\n\r\n";
216+
preamble += param.second; preamble += "\r\n";
217+
}
218+
preamble += "--"; preamble += BOUNDARY; preamble += "\r\n";
219+
preamble += "Content-Disposition: form-data; name=\"";
220+
preamble += name; preamble += "\"; filename=\"";
221+
preamble += filename; preamble += "\"\r\n";
222+
preamble += "Content-Type: application/octet-stream\r\n\r\n";
223+
224+
std::string epilogue = "\r\n--";
225+
epilogue += BOUNDARY; epilogue += "--\r\n";
226+
227+
// Total = preamble + file + epilogue
228+
size_t total_length = preamble.size() + file_size + epilogue.size();
229+
230+
hv::HttpClient cli;
231+
auto req = std::make_shared<HttpRequest>();
232+
req->method = method;
233+
req->url = url;
234+
req->timeout = 3600;
235+
req->SetHeader("Content-Type", std::string("multipart/form-data; boundary=") + BOUNDARY);
236+
req->SetHeader("Content-Length", hv::to_string(total_length));
237+
if (&headers != &DefaultHeaders) {
238+
for (auto& h : headers) req->SetHeader(h.first.c_str(), h.second);
239+
}
240+
241+
req->ParseUrl();
242+
if (cli.connect(req->host.c_str(), req->port, req->IsHttps(), req->connect_timeout) < 0) {
243+
return NULL;
244+
}
245+
if (cli.sendHeader(req.get()) != 0) return NULL;
246+
247+
// Send preamble
248+
if (cli.sendData(preamble.data(), preamble.size()) != (int)preamble.size()) return NULL;
249+
250+
// Stream file using shared helper
251+
if (internal::streamFileToConnection(cli, file, file_size, progress_cb) != 0) {
252+
return NULL;
253+
}
254+
255+
// Send epilogue
256+
if (cli.sendData(epilogue.data(), epilogue.size()) != (int)epilogue.size()) return NULL;
257+
258+
auto resp = std::make_shared<HttpResponse>();
259+
if (cli.recvResponse(resp.get()) != 0) return NULL;
260+
return resp;
261+
}
262+
263+
// Convenience overload - uses basename of filepath as upload filename
264+
HV_INLINE Response uploadLargeFormFile(const char* url, const char* name, const char* filepath,
265+
std::map<std::string, std::string>& params = hv::empty_map,
266+
upload_progress_cb progress_cb = NULL, http_method method = HTTP_POST,
267+
const http_headers& headers = DefaultHeaders)
268+
{
269+
return uploadLargeFormFile(url, name, filepath, NULL, params, progress_cb, method, headers);
270+
}
271+
#endif
272+
183273
// see examples/wget.cpp
184274
typedef std::function<void(size_t received_bytes, size_t total_bytes)> download_progress_cb;
185275
HV_INLINE size_t downloadFile(const char* url, const char* filepath, download_progress_cb progress_cb = NULL) {

0 commit comments

Comments
 (0)