@@ -119,6 +119,32 @@ HV_INLINE Response uploadFormFile(const char* url, const char* name, const char*
119119#endif
120120
121121typedef 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+
122148HV_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
184274typedef std::function<void (size_t received_bytes, size_t total_bytes)> download_progress_cb;
185275HV_INLINE size_t downloadFile (const char * url, const char * filepath, download_progress_cb progress_cb = NULL ) {
0 commit comments