331 lines
9.8 KiB
C++
331 lines
9.8 KiB
C++
|
/*
|
||
|
* @build: make examples
|
||
|
* @server bin/httpd -s restart -d
|
||
|
* @usage: bin/curl -v www.baidu.com
|
||
|
* bin/curl -v 127.0.0.1:8080
|
||
|
* bin/curl -v 127.0.0.1:8080/ping
|
||
|
* bin/curl -v 127.0.0.1:8080/echo -d 'hello,world!'
|
||
|
*/
|
||
|
|
||
|
#include "HttpClient.h"
|
||
|
#include "hurl.h"
|
||
|
|
||
|
#ifdef _MSC_VER
|
||
|
#include "misc/win32_getopt.h"
|
||
|
#else
|
||
|
#include <getopt.h>
|
||
|
#endif
|
||
|
|
||
|
static bool verbose = false;
|
||
|
static const char* method = NULL;
|
||
|
static const char* url = "/";
|
||
|
static int http_version = 1;
|
||
|
static int grpc = 0;
|
||
|
static int send_count = 1;
|
||
|
static int retry_count = 0;
|
||
|
static int retry_delay = 3;
|
||
|
static int timeout = 0;
|
||
|
|
||
|
static int lopt = 0;
|
||
|
static const char* http_proxy = NULL;
|
||
|
static const char* https_proxy = NULL;
|
||
|
static const char* no_proxy = NULL;
|
||
|
|
||
|
static const char* options = "hVvX:H:r:d:F:n:";
|
||
|
static const struct option long_options[] = {
|
||
|
{"help", no_argument, NULL, 'h'},
|
||
|
{"verion", no_argument, NULL, 'V'},
|
||
|
{"verbose", no_argument, NULL, 'v'},
|
||
|
{"method", required_argument, NULL, 'X'},
|
||
|
{"header", required_argument, NULL, 'H'},
|
||
|
{"range", required_argument, NULL, 'r'},
|
||
|
{"data", required_argument, NULL, 'd'},
|
||
|
{"form", required_argument, NULL, 'F'},
|
||
|
{"count", required_argument, NULL, 'n'},
|
||
|
{"http2", no_argument, &http_version, 2},
|
||
|
{"grpc", no_argument, &grpc, 1},
|
||
|
\
|
||
|
{"http-proxy", required_argument, &lopt, 1},
|
||
|
{"https-proxy", required_argument, &lopt, 2},
|
||
|
{"no-proxy", required_argument, &lopt, 3},
|
||
|
{"retry", required_argument, &lopt, 4},
|
||
|
{"delay", required_argument, &lopt, 5},
|
||
|
{"timeout", required_argument, &lopt, 6},
|
||
|
\
|
||
|
{NULL, 0, NULL, 0}
|
||
|
};
|
||
|
static const char* help = R"(Options:
|
||
|
-h|--help Print this message.
|
||
|
-V|--version Print version.
|
||
|
-v|--verbose Show verbose infomation.
|
||
|
-X|--method Set http method.
|
||
|
-H|--header Add http header, -H "Content-Type: application/json"
|
||
|
-r|--range Add http header Range:bytes=0-1023
|
||
|
-d|--data Set http body.
|
||
|
-F|--form Set http form, -F "name=value" -F "file=@filename"
|
||
|
-n|--count Send request count, used for test keep-alive
|
||
|
--http2 Use http2
|
||
|
--grpc Use grpc over http2
|
||
|
--http-proxy Set http proxy
|
||
|
--https-proxy Set https proxy
|
||
|
--no-proxy Set no proxy
|
||
|
--retry Set fail retry count
|
||
|
--timeout Set timeout, unit(s)
|
||
|
|
||
|
Examples:
|
||
|
curl -v GET httpbin.org/get
|
||
|
curl -v POST httpbin.org/post user=admin pswd=123456
|
||
|
curl -v PUT httpbin.org/put user=admin pswd=123456
|
||
|
curl -v localhost:8080
|
||
|
curl -v localhost:8080 -r 0-9
|
||
|
curl -v localhost:8080/ping
|
||
|
curl -v localhost:8080/query?page_no=1\&page_size=10
|
||
|
curl -v localhost:8080/echo hello,world!
|
||
|
curl -v localhost:8080/kv user=admin\&pswd=123456
|
||
|
curl -v localhost:8080/json user=admin pswd=123456
|
||
|
curl -v localhost:8080/form -F file=@filename
|
||
|
curl -v localhost:8080/upload @filename
|
||
|
)";
|
||
|
|
||
|
static void print_usage() {
|
||
|
fprintf(stderr, "Usage: curl [%s] [METHOD] url [header_field:header_value] [body_key=body_value]\n", options);
|
||
|
}
|
||
|
static void print_version() {
|
||
|
fprintf(stderr, "curl version 1.0.0\n");
|
||
|
}
|
||
|
static void print_help() {
|
||
|
print_usage();
|
||
|
puts(help);
|
||
|
print_version();
|
||
|
}
|
||
|
|
||
|
static bool is_upper_string(const char* str) {
|
||
|
const char* p = str;
|
||
|
while (*p >= 'A' && *p <= 'Z') ++p;
|
||
|
return *p == '\0';
|
||
|
}
|
||
|
|
||
|
static int parse_data(char* arg, HttpRequest* req) {
|
||
|
char* pos = NULL;
|
||
|
// @filename
|
||
|
if (arg[0] == '@') {
|
||
|
req->File(arg + 1);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// k1=v1&k2=v2
|
||
|
hv::KeyValue kvs = hv::splitKV(arg, '&', '=');
|
||
|
if (kvs.size() >= 2) {
|
||
|
if (req->ContentType() == CONTENT_TYPE_NONE) {
|
||
|
req->content_type = X_WWW_FORM_URLENCODED;
|
||
|
}
|
||
|
for (auto& kv : kvs) {
|
||
|
req->Set(kv.first.c_str(), kv.second);
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// k=v
|
||
|
if ((pos = strchr(arg, '=')) != NULL) {
|
||
|
*pos = '\0';
|
||
|
if (pos[1] == '@') {
|
||
|
// file=@filename
|
||
|
req->content_type = MULTIPART_FORM_DATA;
|
||
|
req->SetFormFile(optarg, pos + 2);
|
||
|
} else {
|
||
|
if (req->ContentType() == CONTENT_TYPE_NONE) {
|
||
|
req->content_type = APPLICATION_JSON;
|
||
|
}
|
||
|
req->Set(arg, pos + 1);
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (req->ContentType() == CONTENT_TYPE_NONE) {
|
||
|
req->content_type = TEXT_PLAIN;
|
||
|
}
|
||
|
req->body = arg;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int parse_cmdline(int argc, char* argv[], HttpRequest* req) {
|
||
|
int opt;
|
||
|
int opt_idx;
|
||
|
char* pos = NULL;
|
||
|
while ((opt = getopt_long(argc, argv, options, long_options, &opt_idx)) != EOF) {
|
||
|
switch(opt) {
|
||
|
case 'h': print_help(); exit(0);
|
||
|
case 'V': print_version(); exit(0);
|
||
|
case 'v': verbose = true; break;
|
||
|
case 'X': method = optarg; break;
|
||
|
case 'H':
|
||
|
// -H "Content-Type: application/json"
|
||
|
pos = strchr(optarg, ':');
|
||
|
if (pos) {
|
||
|
*pos = '\0';
|
||
|
req->headers[optarg] = hv::trim(pos + 1);
|
||
|
*pos = ':';
|
||
|
}
|
||
|
break;
|
||
|
case 'r':
|
||
|
req->headers["Range"] = std::string("bytes=").append(optarg);
|
||
|
break;
|
||
|
case 'd':
|
||
|
parse_data(optarg, req);
|
||
|
break;
|
||
|
case 'F':
|
||
|
pos = strchr(optarg, '=');
|
||
|
if (pos) {
|
||
|
req->content_type = MULTIPART_FORM_DATA;
|
||
|
*pos = '\0';
|
||
|
if (pos[1] == '@') {
|
||
|
// -F file=@filename
|
||
|
req->SetFormFile(optarg, pos + 2);
|
||
|
} else {
|
||
|
// -F name=value
|
||
|
req->SetFormData(optarg, pos + 1);
|
||
|
}
|
||
|
*pos = '=';
|
||
|
}
|
||
|
break;
|
||
|
case 'n': send_count = atoi(optarg); break;
|
||
|
case 0 :
|
||
|
{
|
||
|
switch (lopt) {
|
||
|
case 1: http_proxy = optarg; break;
|
||
|
case 2: https_proxy = optarg; break;
|
||
|
case 3: no_proxy = optarg; break;
|
||
|
case 4: retry_count = atoi(optarg);break;
|
||
|
case 5: retry_delay = atoi(optarg);break;
|
||
|
case 6: timeout = atoi(optarg);break;
|
||
|
default: break;
|
||
|
}
|
||
|
}
|
||
|
default: break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (optind == argc) {
|
||
|
fprintf(stderr, "Missing url\n");
|
||
|
print_usage();
|
||
|
exit(-1);
|
||
|
}
|
||
|
|
||
|
if (is_upper_string(argv[optind])) {
|
||
|
method = argv[optind++];
|
||
|
}
|
||
|
url = argv[optind++];
|
||
|
|
||
|
for (int d = optind; d < argc; ++d) {
|
||
|
char* arg = argv[d];
|
||
|
if ((pos = strchr(arg, ':')) != NULL) {
|
||
|
*pos = '\0';
|
||
|
req->headers[arg] = pos + 1;
|
||
|
} else {
|
||
|
parse_data(arg, req);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// --grpc
|
||
|
if (grpc) {
|
||
|
http_version = 2;
|
||
|
req->content_type = APPLICATION_GRPC;
|
||
|
}
|
||
|
// --http2
|
||
|
if (http_version == 2) {
|
||
|
req->http_major = 2;
|
||
|
req->http_minor = 0;
|
||
|
}
|
||
|
// --timeout
|
||
|
if (timeout > 0) {
|
||
|
req->timeout = timeout;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int main(int argc, char* argv[]) {
|
||
|
if (argc < 2) {
|
||
|
print_usage();
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int ret = 0;
|
||
|
HttpRequest req;
|
||
|
parse_cmdline(argc, argv, &req);
|
||
|
if (method) {
|
||
|
req.method = http_method_enum(method);
|
||
|
} else {
|
||
|
req.DumpBody();
|
||
|
if (req.body.empty()) {
|
||
|
req.method = HTTP_GET;
|
||
|
} else {
|
||
|
req.method = HTTP_POST;
|
||
|
}
|
||
|
}
|
||
|
req.url = hv::escapeURL(url);
|
||
|
req.http_cb = [](HttpMessage* res, http_parser_state state, const char* data, size_t size) {
|
||
|
if (state == HP_HEADERS_COMPLETE) {
|
||
|
if (verbose) {
|
||
|
fprintf(stderr, "%s", res->Dump(true, false).c_str());
|
||
|
}
|
||
|
} else if (state == HP_BODY) {
|
||
|
if (data && size) {
|
||
|
printf("%.*s", (int)size, data);
|
||
|
// This program no need to save data to body.
|
||
|
// res->body.append(data, size);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
hv::HttpClient cli;
|
||
|
// http_proxy
|
||
|
if (http_proxy) {
|
||
|
hv::StringList ss = hv::split(http_proxy, ':');
|
||
|
const char* host = ss[0].c_str();
|
||
|
int port = ss.size() == 2 ? hv::from_string<int>(ss[1]) : DEFAULT_HTTP_PORT;
|
||
|
fprintf(stderr, "* http_proxy=%s:%d\n", host, port);
|
||
|
cli.setHttpProxy(host, port);
|
||
|
}
|
||
|
// https_proxy
|
||
|
if (https_proxy) {
|
||
|
hv::StringList ss = hv::split(https_proxy, ':');
|
||
|
const char* host = ss[0].c_str();
|
||
|
int port = ss.size() == 2 ? hv::from_string<int>(ss[1]) : DEFAULT_HTTPS_PORT;
|
||
|
fprintf(stderr, "* https_proxy=%s:%d\n", host, port);
|
||
|
cli.setHttpsProxy(host, port);
|
||
|
}
|
||
|
// no_proxy
|
||
|
if (no_proxy) {
|
||
|
hv::StringList ss = hv::split(no_proxy, ',');
|
||
|
fprintf(stderr, "* no_proxy=");
|
||
|
for (const auto& s : ss) {
|
||
|
fprintf(stderr, "%s,", s.c_str());
|
||
|
cli.addNoProxy(s.c_str());
|
||
|
}
|
||
|
fprintf(stderr, "\n");
|
||
|
}
|
||
|
|
||
|
send:
|
||
|
if (verbose) {
|
||
|
fprintf(stderr, "%s\n", req.Dump(true, true).c_str());
|
||
|
}
|
||
|
HttpResponse res;
|
||
|
ret = cli.send(&req, &res);
|
||
|
if (ret != 0) {
|
||
|
fprintf(stderr, "* Failed:%s:%d\n", http_client_strerror(ret), ret);
|
||
|
if (retry_count > 0) {
|
||
|
fprintf(stderr, "\nretry again later...%d\n", retry_count);
|
||
|
--retry_count;
|
||
|
hv_sleep(retry_delay);
|
||
|
goto send;
|
||
|
}
|
||
|
}
|
||
|
if (--send_count > 0) {
|
||
|
fprintf(stderr, "\nsend again later...%d\n", send_count);
|
||
|
hv_sleep(retry_delay);
|
||
|
goto send;
|
||
|
}
|
||
|
return ret;
|
||
|
}
|