/* * @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 #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(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(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; }