Skip to content

Commit

Permalink
[util] Add method http_get
Browse files Browse the repository at this point in the history
  • Loading branch information
dagurval committed Mar 31, 2019
1 parent f6d7453 commit 456c478
Show file tree
Hide file tree
Showing 6 changed files with 244 additions and 0 deletions.
3 changes: 3 additions & 0 deletions src/.formatted-files
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ unlimited.cpp
unlimited.h
util.cpp
util.h
utilhttp.cpp
utilhttp.h
utilmoneystr.cpp
utilmoneystr.h
utilstrencodings.cpp
Expand Down Expand Up @@ -351,6 +353,7 @@ test/uahf_test.cpp
test/uint256_tests.cpp
test/univalue_tests.cpp
test/util_tests.cpp
test/utilhttp_tests.cpp
test/versionbits_tests.cpp
wallet/crypter.cpp
wallet/crypter.h
Expand Down
2 changes: 2 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ BITCOIN_CORE_H = \
ui_interface.h \
undo.h \
unlimited.h \
utilhttp.h \
stat.h \
tweak.h \
requestManager.h \
Expand Down Expand Up @@ -291,6 +292,7 @@ libbitcoin_server_a_SOURCES = \
txorphanpool.cpp \
tweak.cpp \
unlimited.cpp \
utilhttp.cpp \
requestManager.cpp \
validation/forks.cpp \
validation/validation.cpp \
Expand Down
1 change: 1 addition & 0 deletions src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ BITCOIN_TESTS =\
test/uint256_tests.cpp \
test/univalue_tests.cpp \
test/util_tests.cpp \
test/utilhttp_tests.cpp \
test/xversionmessage_tests.cpp

if ENABLE_WALLET
Expand Down
69 changes: 69 additions & 0 deletions src/test/utilhttp_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (c) 2019 The Bitcoin Unlimited developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "utilhttp.h"
#include "test/test_bitcoin.h"
#include <boost/test/unit_test.hpp>
#include <event2/buffer.h>
#include <event2/event.h>
#include <event2/http.h>
#include <iostream>

template <typename T>
class RAII
{
public:
RAII(T *o, std::function<void(T *)> d) : obj(o), destroy(d) { assert(o != nullptr); }
~RAII() { destroy(obj); }
T *get() { return obj; }
private:
T *obj;
std::function<void(T *)> destroy;
};

BOOST_FIXTURE_TEST_SUITE(utilhttp_tests, BasicTestingSetup)

BOOST_AUTO_TEST_CASE(http_get_test)
{
const char HOST[] = "127.0.0.1";
const int PORT = 23456;
auto server_response = [](struct evhttp_request *req, void *) -> void {
if (evhttp_request_get_command(req) != EVHTTP_REQ_GET)
{
evhttp_send_error(req, 400, "not a GET request");
}
RAII<struct evbuffer> buffer(evbuffer_new(), evbuffer_free);
evbuffer_add(buffer.get(), "magic", std::string("magic").size());
evhttp_send_reply(req, 200, "OK", buffer.get());
};

// bind a http server to HOST:PORT
RAII<struct event_base> base(event_base_new(), event_base_free);
RAII<struct evhttp> server(evhttp_new(base.get()), evhttp_free);
evhttp_set_gencb(server.get(), server_response, nullptr);

if (evhttp_bind_socket(server.get(), HOST, PORT) != 0)
{
std::cerr << __func__ << ": could not bind to " << HOST << ":" << PORT << ", skipping test" << std::endl;
return;
}

std::atomic<bool> done(false);
std::thread dispatch_thread([&]() {
while (!done)
{
event_base_loop(base.get(), EVLOOP_NONBLOCK);
}
});

// test http_get
std::string result;
BOOST_CHECK_NO_THROW(result = http_get(HOST, PORT, "/").str());
BOOST_CHECK_EQUAL("magic", result);

done = true;
dispatch_thread.join();
}

BOOST_AUTO_TEST_SUITE_END()
158 changes: 158 additions & 0 deletions src/utilhttp.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// Copyright (c) 2019 The Bitcoin Unlimited developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "utilhttp.h"
#include "util.h"

#include <cstdlib>
#include <iostream>
#include <sstream>
#include <stdexcept>

#include <event2/buffer.h>
#include <event2/event.h>
#include <event2/http.h>
#include <event2/http_struct.h>

// RAII object to clean up after libevent
struct EventRAII
{
struct event_base *base = nullptr;
struct evhttp_connection *http_conn = nullptr;
struct evhttp_request *req = nullptr;

~EventRAII()
{
if (http_conn == nullptr && req != nullptr)
{
// http_conn takes ownership of req, so only release if http_conn
// failed to initialize.
evhttp_request_free(req);
req = nullptr;
}
if (http_conn != nullptr)
{
evhttp_connection_free(http_conn);
http_conn = nullptr;
}
if (base != nullptr)
{
event_base_free(base);
base = nullptr;
}
}
};

namespace
{
static const int CB_UNKNOWN_ERROR = -1;
}

// The callback function writes the response to this.
struct CallbackArgs
{
int &http_code;
std::stringstream &res;
};

// Callback for fetching the http response.
void response_cb(struct evhttp_request *req, void *args_)
{
CallbackArgs *args(static_cast<CallbackArgs *>(args_));
if (req == nullptr)
{
args->http_code = CB_UNKNOWN_ERROR;
return;
}
args->http_code = evhttp_request_get_response_code(req);
char buffer[1024];
int s = 0;
do
{
// evbuffer_remove should not have written more than the size
// of the buffer we gave it
DbgAssert(static_cast<size_t>(s) <= sizeof buffer, return );

args->res.write(buffer, s);
s = evbuffer_remove(req->input_buffer, &buffer, sizeof buffer);
} while (s > 0);
};


std::stringstream http_get(const std::string &host, const int port, const std::string &target)
{
EventRAII event;
std::stringstream res;
int http_code = CB_UNKNOWN_ERROR;
CallbackArgs cbargs{http_code, res};

// Initiate request
event.base = event_base_new();
if (event.base == nullptr)
{
throw std::runtime_error("event_base_new");
}

event.http_conn = evhttp_connection_base_new(event.base, NULL, host.c_str(), port);
if (event.http_conn == nullptr)
{
throw std::runtime_error("evhttp_connection_base_new");
}

event.req = evhttp_request_new(response_cb, static_cast<void *>(&cbargs));
if (event.req == nullptr)
{
throw std::runtime_error("evhttp_request_new");
}

struct evkeyvalq *output_headers = evhttp_request_get_output_headers(event.req);
if (output_headers == nullptr)
{
throw std::runtime_error("evhttp_request_get_output_headers");
}
if (evhttp_add_header(output_headers, "Host", host.c_str()) != 0)
{
throw std::runtime_error("evhttp_add_header failed");
}
if (evhttp_add_header(output_headers, "Connection", "close") != 0)
{
throw std::runtime_error("evhttp_add_header failed");
}

// Perform request
int rc = evhttp_make_request(event.http_conn, event.req, EVHTTP_REQ_GET, target.c_str());
if (rc != 0)
{
throw std::runtime_error("evhttp_make_request failed");
}
rc = event_base_dispatch(event.base);

// Check response
switch (rc)
{
case -1:
throw std::runtime_error("event_base_dispatch failed");
case 0: // OK
case 1: // No events pending -- OK
break;
default:
throw std::runtime_error("unknown rc from event_base_dispatch");
}
if (http_code == 0)
{
throw std::runtime_error("http_get failed (invalid host/port?)");
}
if (http_code == CB_UNKNOWN_ERROR)
{
throw std::runtime_error("http_get failed with unknown error");
}
if (http_code != 200)
{
std::stringstream err;
err << "http_get failed with error " << http_code;
throw std::runtime_error(err.str());
}

return res;
}
11 changes: 11 additions & 0 deletions src/utilhttp.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#ifndef UTILHTTP_H
#define UTILHTTP_H

#include <sstream>
#include <string>

//! Does a HTTP GET request for <host>:<port><target>. Throws on error. Throws
//! on http response code != 200.
std::stringstream http_get(const std::string &host, int port, const std::string &target /* ex: /index.html */);

#endif

0 comments on commit 456c478

Please sign in to comment.