-
Notifications
You must be signed in to change notification settings - Fork 212
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1629 from dagurval/http-get
[util] Add method http_get
- Loading branch information
Showing
6 changed files
with
244 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |