Last active
October 25, 2022 21:40
-
-
Save jay/daac01682f17c2dc213e to your computer and use it in GitHub Desktop.
Use libcurl to upload POST data.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Use libcurl to upload POST data. | |
Usage: PostUpload <post_data_filename> <url> | |
Used this as a speed test in Win7 x64, see | |
https://github.com/curl/curl/issues/708 | |
First test the speed in a web browser: | |
Go to https://testmy.net/mirror and choose the geographically closest mirror. | |
After the mirror is set click on 'Upload Test'. | |
Select 'Manual Test Size' 50MB. The browser will download 50MB then upload it. | |
Note the server as it's uploading. (eg: ny2.testmy.net) | |
After the upload completes it gives upload speed in kB/s. (eg: 633 kB/s) | |
2018-08-07: testmy no longer supports plain HTTP so now results may vary | |
depending on the SSL backend used by libcurl. | |
From the command line make 50MB of POST data and upload it to that server: | |
perl -e "print 'size_data=52428800&test_type=upload&svrPort=80'" > ulspeedtest | |
perl -e "print '&svrPort=80&start=9999999999999&data='" >> ulspeedtest | |
perl -e "print '0' x 52428800" >> ulspeedtest | |
PostUpload ulspeedtest https://ny2.testmy.net/uploader | |
The upload speed will be printed and should be the same as in the browser: | |
------------------------------------------------------------------------------- | |
Transfer rate: 625 KB/sec (52428883 bytes in 82 seconds) | |
Other data rate units: | |
5.12 Mbps | |
5122.19 kbps | |
0.61 MiB/s | |
625.27 KiB/s | |
640.27 kB/s <-------- Compare against this if it shows kB/s | |
640274.00 B/s | |
------------------------------------------------------------------------------- | |
Copyright (C) 2016 Jay Satiro <[email protected]> | |
https://curl.haxx.se/docs/copyright.html | |
https://gist.github.com/jay/daac01682f17c2dc213e | |
*/ | |
#define _CRT_NONSTDC_NO_DEPRECATE | |
#define _FILE_OFFSET_BITS 64 | |
#include <assert.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <sys/stat.h> | |
#include <time.h> | |
#ifdef _WIN32 | |
#include <io.h> | |
#else | |
#include <unistd.h> | |
#endif | |
/* https://curl.haxx.se/download.html */ | |
#include <curl/curl.h> | |
#undef FALSE | |
#define FALSE 0 | |
#undef TRUE | |
#define TRUE 1 | |
#ifdef _WIN32 | |
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) | |
#define fstat _fstati64 | |
#define lseek _lseeki64 | |
#define stat _stati64 | |
#define strncasecmp strnicmp | |
#endif | |
#ifndef CURL_OFF_T_MAX | |
/* https://github.com/curl/curl/pull/6704#issuecomment-796566199 */ | |
#define CURL_OFF_T_MAX ((curl_off_t)(((unsigned CURL_TYPEOF_CURL_OFF_T)-1)/2)) | |
#define CURL_OFF_T_MIN ((curl_off_t)(-CURL_OFF_T_MAX - 1)) | |
#endif | |
enum progress_type { | |
PROGRESS_NONE = 0, /* NONE must stay at 0 */ | |
PROGRESS_STARTED, | |
PROGRESS_DONE | |
}; | |
/* to initialize zero out the struct then set session */ | |
struct progress_data { | |
CURL *session; /* curl easy_handle to the calling session */ | |
int percent; /* last percent output to stderr */ | |
time_t time; /* last time progress was output to stderr */ | |
enum progress_type type; | |
}; | |
int progress_callback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, | |
curl_off_t ultotal, curl_off_t ulnow) | |
{ | |
int percent; | |
time_t timenow; | |
struct progress_data *d = (struct progress_data *)clientp; | |
(void)dltotal; | |
(void)dlnow; | |
#if 0 | |
fprintf(stderr, "\ndltotal: %" CURL_FORMAT_CURL_OFF_T | |
", dlnow: %" CURL_FORMAT_CURL_OFF_T | |
", ultotal: %" CURL_FORMAT_CURL_OFF_T | |
", ulnow: %" CURL_FORMAT_CURL_OFF_T | |
", time: %" CURL_FORMAT_CURL_OFF_T "\n", | |
dltotal, dlnow, ultotal, ulnow, (curl_off_t)time(NULL)); | |
#endif | |
if(d->type == PROGRESS_DONE || ultotal <= 0 || ulnow < 0) | |
return 0; | |
else if(!ulnow) | |
percent = 0; | |
else if(ulnow >= ultotal) | |
percent = 100; | |
else if(ultotal < 10000) | |
percent = (int)(ulnow * 100 / ultotal); | |
else | |
percent = (int)(ulnow / (ultotal / 100)); | |
if(d->type == PROGRESS_STARTED && d->percent == percent) | |
return 0; | |
timenow = time(NULL); | |
if(timenow == d->time && percent != 100) | |
return 0; | |
fprintf(stderr, "\r%3d%% uploaded...", percent); | |
d->percent = percent; | |
d->time = timenow; | |
if(percent == 100) { | |
fprintf(stderr, "\n\n"); | |
d->type = PROGRESS_DONE; | |
} | |
else | |
d->type = PROGRESS_STARTED; | |
return 0; | |
} | |
size_t read_callback(char *buffer, size_t size, size_t nitems, void *instream) | |
{ | |
size_t bytes_read; | |
/* I'm doing it this way to get closer to what the reporter is doing. | |
Technically we don't need to do this, we could just use the default read | |
callback which is fread. Also, 'size' param is always set to 1 by libcurl | |
so it's fine to pass as buffer, size, nitems, instream. */ | |
bytes_read = fread(buffer, 1, (size * nitems), (FILE *)instream); | |
return bytes_read; | |
} | |
/* Show some other rate units like Mbps, kB/s, etc. | |
https://en.wikipedia.org/wiki/Data_rate_units | |
*/ | |
void show_other_rate_units(double speed_in_Bps) | |
{ | |
size_t i; | |
struct rate { | |
char *symbol; | |
int Bps; /* bytes per second */ | |
}; | |
struct rate rate[] = { | |
{ "Mbps", 1000000 / 8 }, | |
{ "kbps", 1000 / 8 }, | |
{ "MiB/s", 1024 * 1024 }, | |
{ "KiB/s", 1024 }, | |
{ "kB/s", 1000 }, | |
{ "B/s", 1 } | |
}; | |
fprintf(stderr, "\nOther data rate units:\n\n"); | |
for(i = 0; i < sizeof rate / sizeof rate[0]; ++i) | |
fprintf(stderr, "%22.2f %s\n", speed_in_Bps / rate[i].Bps, rate[i].symbol); | |
fprintf(stderr, "\n"); | |
} | |
int PostUpload(const char *post_data_filename, const char *url) | |
{ | |
int retcode = FALSE; | |
int delayed_error = FALSE; | |
CURL *curl = NULL; | |
CURLcode res = CURLE_FAILED_INIT; | |
char errbuf[CURL_ERROR_SIZE] = { 0, }; | |
char *filebuf = NULL; | |
FILE *fp = NULL; | |
struct progress_data progress_data = { 0, }; | |
double average_speed = 0; | |
double bytes_uploaded = 0; | |
double total_upload_time = 0; | |
long response_code = 0; | |
struct curl_slist *header_list = NULL; | |
struct stat stbuf = { 0, }; | |
if(!post_data_filename || !*post_data_filename) { | |
fprintf(stderr, "Error: post_data_filename parameter is missing.\n"); | |
goto cleanup; | |
} | |
if(!url || !*url) { | |
fprintf(stderr, "Error: url parameter is missing.\n"); | |
goto cleanup; | |
} | |
fp = fopen(post_data_filename, "rb"); | |
if(!fp) { | |
fprintf(stderr, "Error: failed to open file \"%s\"\n", post_data_filename); | |
goto cleanup; | |
} | |
if(fstat(fileno(fp), &stbuf) || !S_ISREG(stbuf.st_mode)) { | |
fprintf(stderr, "Error: unknown file size \"%s\"\n", post_data_filename); | |
goto cleanup; | |
} | |
if(!(0 <= stbuf.st_size && stbuf.st_size <= CURL_OFF_T_MAX)) { | |
fprintf(stderr, "Error: file size too large \"%s\"\n", post_data_filename); | |
goto cleanup; | |
} | |
curl = curl_easy_init(); | |
if(!curl) { | |
fprintf(stderr, "Error: curl_easy_init failed.\n"); | |
goto cleanup; | |
} | |
/* CURLOPT_CAINFO | |
To verify SSL sites you may need to load a bundle of certificates. | |
You can download the default bundle here: | |
https://raw.githubusercontent.com/bagder/ca-bundle/master/ca-bundle.crt | |
However your SSL backend might use a database in addition to or instead of | |
the bundle. | |
https://curl.haxx.se/docs/ssl-compared.html | |
*/ | |
curl_easy_setopt(curl, CURLOPT_CAINFO, "curl-ca-bundle.crt"); | |
curl_easy_setopt(curl, CURLOPT_POST, 1L); | |
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, | |
(curl_off_t)stbuf.st_size); | |
#if 1 | |
curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback); | |
curl_easy_setopt(curl, CURLOPT_READDATA, (void *)fp); | |
#else | |
/* Test what happens when we read the file in advance and load it in memory. | |
Should be the same upload speed as when READFUNCTION is used. */ | |
if(stbuf.st_size > (size_t)-1) { | |
fprintf(stderr, "Error: file size too large \"%s\"\n", post_data_filename); | |
goto cleanup; | |
} | |
filebuf = malloc((size_t)stbuf.st_size); | |
if(!filebuf) { | |
fprintf(stderr, "Error: couldn't allocate %" CURL_FORMAT_CURL_OFF_T | |
" bytes.\n", (curl_off_t)stbuf.st_size); | |
goto cleanup; | |
} | |
if((size_t)stbuf.st_size != fread(filebuf, 1, (size_t)stbuf.st_size, fp)) { | |
fprintf(stderr, "Error: couldn't read file \"%s\"\n", post_data_filename); | |
goto cleanup; | |
} | |
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, filebuf); | |
#endif | |
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); | |
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback); | |
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &progress_data); | |
progress_data.session = curl; | |
header_list = curl_slist_append(header_list, "Expect:"); | |
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_list); | |
/* Include server headers in the output */ | |
curl_easy_setopt(curl, CURLOPT_HEADER, 1L); | |
curl_easy_setopt(curl, CURLOPT_URL, url); | |
curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); | |
/* curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); */ | |
#ifdef DISABLE_LIBCURL_SSL_VERIFY | |
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); | |
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); | |
#endif | |
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf); | |
res = curl_easy_perform(curl); | |
if(res != CURLE_OK) { | |
size_t len = strlen(errbuf); | |
fprintf(stderr, "\nError: libcurl: (%d) ", res); | |
if(len) | |
fprintf(stderr, "%s%s", errbuf, ((errbuf[len - 1] != '\n') ? "\n" : "")); | |
fprintf(stderr, "%s\n\n", curl_easy_strerror(res)); | |
goto cleanup; | |
} | |
curl_easy_getinfo(curl, CURLINFO_SPEED_UPLOAD, &average_speed); | |
curl_easy_getinfo(curl, CURLINFO_SIZE_UPLOAD, &bytes_uploaded); | |
curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &total_upload_time); | |
fprintf(stderr, "\nTransfer rate: %.0f KB/sec" | |
" (%.0f bytes in %.0f seconds)\n", | |
average_speed / 1024, bytes_uploaded, total_upload_time); | |
show_other_rate_units(average_speed); | |
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); | |
if(response_code != 200) { | |
fprintf(stderr, "Error: HTTP response code is %ld.\n", response_code); | |
delayed_error = TRUE; | |
} | |
if(progress_data.type != PROGRESS_DONE) { | |
fprintf(stderr, "Error: Only %d%% was uploaded.\n", progress_data.percent); | |
delayed_error = TRUE; | |
} | |
retcode = !delayed_error; | |
cleanup: | |
curl_easy_cleanup(curl); | |
curl_slist_free_all(header_list); | |
free(filebuf); | |
if(fp) | |
fclose(fp); | |
return retcode; | |
} | |
int main(int argc, char *argv[]) | |
{ | |
if(argc != 3) { | |
fprintf(stderr, | |
"Usage: PostUpload <post_data_filename> <url>\n" | |
"\n" | |
"HTTP POST to url of the data contained in post_data_filename.\n" | |
"\n" | |
"This program will exit 0 on success. Success is defined as all the " | |
"data was uploaded AND the server returned a response code of 200.\n"); | |
return EXIT_FAILURE; | |
} | |
if(curl_global_init(CURL_GLOBAL_ALL)) { | |
fprintf(stderr, "Fatal: The initialization of libcurl has failed.\n"); | |
return EXIT_FAILURE; | |
} | |
if(atexit(curl_global_cleanup)) { | |
fprintf(stderr, "Fatal: atexit failed to register curl_global_cleanup.\n"); | |
curl_global_cleanup(); | |
return EXIT_FAILURE; | |
} | |
if(!PostUpload(argv[1], argv[2])) { | |
fprintf(stderr, "Fatal: PostUpload failed.\n"); | |
return EXIT_FAILURE; | |
} | |
return EXIT_SUCCESS; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I tried this program. But I had problem with setting the
curl-ca-bundle.crt
. So I did this:Work fine now.