Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[util] Add method http_get #1629

Merged
merged 1 commit into from
Apr 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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};
gandrewstone marked this conversation as resolved.
Show resolved Hide resolved

// 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());
dagurval marked this conversation as resolved.
Show resolved Hide resolved
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