diff --git a/.gitignore b/.gitignore index 057ec089..dad74e7a 100644 --- a/.gitignore +++ b/.gitignore @@ -78,6 +78,7 @@ libevent_openssl.pc /sample/hello-world /sample/http-server /sample/le-proxy +/sample/https-client /sample/signal-test /sample/time-test diff --git a/sample/https-client.c b/sample/https-client.c new file mode 100644 index 00000000..1ea6e271 --- /dev/null +++ b/sample/https-client.c @@ -0,0 +1,207 @@ +/* + This is an example of how to hook up evhttp with bufferevent_ssl + + It just GETs an https URL given on the command-line and prints the response + body to stdout. + + Actually, it also accepts plain http URLs to make it easy to compare http vs + https code paths. + + Loosely based on le-proxy.c. + */ + +#include +#include +#include +#include +#include + +#ifdef WIN32 +#include +#include +#else +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +static struct event_base *base; + +static void +http_request_done(struct evhttp_request *req, void *ctx) +{ + char buffer[256]; + int nread; + + if (req == NULL) { + fprintf(stderr, "some request failed - no idea which one though!\n"); + return; + } + + fprintf(stderr, "Response line: %d %s\n", + req->response_code, req->response_code_line); + + while ((nread = evbuffer_remove(req->input_buffer, buffer, sizeof(buffer))) + > 0) { + fwrite("> ", 2, 1, stdout); + fwrite(buffer, nread, 1, stdout); + fwrite("\n", 1, 1, stdout); + } +} + +static void +syntax(void) +{ + fputs("Syntax:\n", stderr); + fputs(" https-client \n", stderr); + fputs("Example:\n", stderr); + fputs(" https-client https://ip.appspot.com/\n", stderr); + + exit(1); +} + +static void +die(const char *msg) +{ + fputs(msg, stderr); + exit(1); +} + +int +main(int argc, char **argv) +{ + int r; + + struct evhttp_uri *http_uri; + const char *url, *scheme, *host, *path, *query; + char uri[256]; + int port; + + SSL_CTX *ssl_ctx; + SSL *ssl; + struct bufferevent *bev; + struct evhttp_connection *evcon; + struct evhttp_request *req; + + if (argc != 2) + syntax(); + + url = argv[1]; + http_uri = evhttp_uri_parse(url); + if (http_uri == NULL) { + die("malformed url"); + } + + scheme = evhttp_uri_get_scheme(http_uri); + if (scheme == NULL || (strcasecmp(scheme, "https") != 0 && + strcasecmp(scheme, "http") != 0)) { + die("url must be http or https"); + } + + host = evhttp_uri_get_host(http_uri); + if (host == NULL) { + die("url must have a host"); + } + + port = evhttp_uri_get_port(http_uri); + if (port == -1) { + port = (strcasecmp(scheme, "http") == 0) ? 80 : 443; + } + + path = evhttp_uri_get_path(http_uri); + if (path == NULL) { + path = "/"; + } + + query = evhttp_uri_get_query(http_uri); + if (query == NULL) { + snprintf(uri, sizeof(uri) - 1, "%s", path); + } else { + snprintf(uri, sizeof(uri) - 1, "%s?%s", path, query); + } + uri[sizeof(uri) - 1] = '\0'; + + // Initialize OpenSSL + SSL_library_init(); + ERR_load_crypto_strings(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); + r = RAND_poll(); + if (r == 0) { + fprintf(stderr, "RAND_poll() failed.\n"); + return 1; + } + ssl_ctx = SSL_CTX_new(SSLv23_method()); + + // Create event base + base = event_base_new(); + if (!base) { + perror("event_base_new()"); + return 1; + } + + // Create OpenSSL bufferevent and stack evhttp on top of it + ssl = SSL_new(ssl_ctx); + if (ssl == NULL) { + fprintf(stderr, "SSL_new() failed\n"); + return 1; + } + + if (strcasecmp(scheme, "http") == 0) { + bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE); + } else { + bev = bufferevent_openssl_socket_new(base, -1, ssl, + BUFFEREVENT_SSL_CONNECTING, + BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS); + } + + if (bev == NULL) { + fprintf(stderr, "bufferevent_openssl_socket_new() failed\n"); + return 1; + } + + bufferevent_openssl_set_allow_dirty_shutdown(bev, 1); + + // For simplicity, we let DNS resolution block. Everything else should be + // asynchronous though. + evcon = evhttp_connection_base_bufferevent_new(base, NULL, bev, + host, port); + if (evcon == NULL) { + fprintf(stderr, "evhttp_connection_base_bufferevent_new() failed\n"); + return 1; + } + + // Fire off the request + req = evhttp_request_new(http_request_done, NULL); + if (req == NULL) { + fprintf(stderr, "evhttp_request_new() failed\n"); + return 1; + } + + evhttp_add_header(req->output_headers, "Host", host); + evhttp_add_header(req->output_headers, "Connection", "close"); + + r = evhttp_make_request(evcon, req, EVHTTP_REQ_GET, uri); + if (r != 0) { + fprintf(stderr, "evhttp_make_request() failed\n"); + return 1; + } + + event_base_dispatch(base); + + evhttp_connection_free(evcon); + event_base_free(base); + + return 0; +} diff --git a/sample/include.am b/sample/include.am index 44510b77..6442a8b5 100644 --- a/sample/include.am +++ b/sample/include.am @@ -17,6 +17,11 @@ SAMPLES += sample/le-proxy sample_le_proxy_SOURCES = sample/le-proxy.c sample_le_proxy_LDADD = libevent.la libevent_openssl.la -lssl -lcrypto ${OPENSSL_LIBADD} sample_le_proxy_INCLUDES = $(OPENSSL_INCS) + +SAMPLES += sample/https-client +sample_https_client_SOURCES = sample/https-client.c +sample_https_client_LDADD = libevent.la libevent_openssl.la -lssl -lcrypto ${OPENSSL_LIBADD} +sample_https_client_INCLUDES = $(OPENSSL_INCS) endif noinst_PROGRAMS += $(SAMPLES)