add request
This commit is contained in:
parent
a6f745a787
commit
64a9a3b59d
13
README.md
13
README.md
|
@ -54,15 +54,14 @@ Run in `shell`:
|
|||
|
||||
## Performance
|
||||
|
||||
This test is performed by Jmeter.
|
||||
This test is performed by Jmeter. The unit for throughput is Transaction per second.
|
||||
|
||||
The unit for throughput is Transaction per second.
|
||||
|URL|bserv|
|
||||
|:-:|:-:|
|
||||
|`/login`|139.55|
|
||||
|`/find/<user>`|958.77|
|
||||
|
||||
|
||||
|URL|bserv|Java Spring Boot|
|
||||
|:-:|:-:|:-:|
|
||||
|`/login`|139.55||
|
||||
|`/find/<user>`|958.77||
|
||||
For `/login`, we must slow down the attacker's speed. In Java, plain password is stored, which results in a higher performance.
|
||||
|
||||
|
||||
### Computer Hardware:
|
||||
|
|
13
bserv.cpp
13
bserv.cpp
|
@ -22,13 +22,12 @@
|
|||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
#include <pqxx/pqxx>
|
||||
|
||||
#include "config.hpp"
|
||||
#include "logging.hpp"
|
||||
#include "utils.hpp"
|
||||
#include "routing.hpp"
|
||||
#include "database.hpp"
|
||||
#include "client.hpp"
|
||||
|
||||
namespace bserv {
|
||||
|
||||
|
@ -38,10 +37,6 @@ namespace asio = boost::asio;
|
|||
namespace json = boost::json;
|
||||
using asio::ip::tcp;
|
||||
|
||||
void fail(const beast::error_code& ec, const char* what) {
|
||||
lgerror << what << ": " << ec.message() << std::endl;
|
||||
}
|
||||
|
||||
// this function produces an HTTP response for the given
|
||||
// request. The type of the response object depends on the
|
||||
// contents of the request, so the interface requires the
|
||||
|
@ -328,6 +323,9 @@ int main(int argc, char* argv[]) {
|
|||
init_logging();
|
||||
show_config();
|
||||
|
||||
// io_context for all I/O
|
||||
asio::io_context ioc{NUM_THREADS};
|
||||
|
||||
// some initializations must be done after parsing the arguments
|
||||
// e.g. database connection
|
||||
try {
|
||||
|
@ -339,8 +337,7 @@ int main(int argc, char* argv[]) {
|
|||
}
|
||||
session_mgr = std::make_shared<memory_session>();
|
||||
|
||||
// io_context for all I/O
|
||||
asio::io_context ioc{NUM_THREADS};
|
||||
client_ptr = std::make_shared<client>(ioc);
|
||||
|
||||
// creates and launches a listening port
|
||||
std::make_shared<listener>(
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
# Build
|
||||
|
||||
```
|
||||
cmake ..
|
||||
cmake --build .
|
||||
```
|
|
@ -0,0 +1,283 @@
|
|||
#ifndef _CLIENT_HPP
|
||||
#define _CLIENT_HPP
|
||||
|
||||
#include <boost/beast.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/json/src.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <cstddef>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
#include <exception>
|
||||
|
||||
#include "config.hpp"
|
||||
#include "logging.hpp"
|
||||
|
||||
namespace bserv {
|
||||
|
||||
namespace beast = boost::beast;
|
||||
namespace http = beast::http;
|
||||
namespace asio = boost::asio;
|
||||
namespace json = boost::json;
|
||||
using asio::ip::tcp;
|
||||
|
||||
class request_failed_exception
|
||||
: public std::exception {
|
||||
private:
|
||||
std::string msg_;
|
||||
public:
|
||||
request_failed_exception(const std::string& msg) : msg_{msg} {}
|
||||
const char* what() const noexcept { return msg_.c_str(); }
|
||||
};
|
||||
|
||||
// https://www.boost.org/doc/libs/1_75_0/libs/beast/example/http/client/async/http_client_async.cpp
|
||||
|
||||
// sends one async request to a remote server
|
||||
template <typename ResponseType>
|
||||
class client_session
|
||||
: public std::enable_shared_from_this<
|
||||
client_session<ResponseType>> {
|
||||
private:
|
||||
tcp::resolver resolver_;
|
||||
beast::tcp_stream stream_;
|
||||
// must persist between reads
|
||||
beast::flat_buffer buffer_;
|
||||
http::request<http::string_body> req_;
|
||||
http::response<http::string_body> res_;
|
||||
std::promise<ResponseType> promise_;
|
||||
void failed(const beast::error_code& ec, const std::string& what) {
|
||||
promise_.set_exception(
|
||||
std::make_exception_ptr(
|
||||
request_failed_exception{what + ": " + ec.message()}));
|
||||
}
|
||||
public:
|
||||
client_session(
|
||||
asio::io_context& ioc,
|
||||
const http::request<http::string_body>& req)
|
||||
: resolver_{asio::make_strand(ioc)},
|
||||
stream_{asio::make_strand(ioc)}, req_{req} {}
|
||||
std::future<ResponseType> send(
|
||||
const std::string& host,
|
||||
const std::string& port) {
|
||||
resolver_.async_resolve(
|
||||
host, port,
|
||||
beast::bind_front_handler(
|
||||
&client_session::on_resolve,
|
||||
client_session<ResponseType>::shared_from_this()));
|
||||
return promise_.get_future();
|
||||
}
|
||||
void on_resolve(
|
||||
beast::error_code ec,
|
||||
tcp::resolver::results_type results) {
|
||||
if (ec) {
|
||||
failed(ec, "client_session::resolver resolve");
|
||||
return;
|
||||
}
|
||||
// sets a timeout on the operation
|
||||
stream_.expires_after(std::chrono::seconds(30));
|
||||
// makes the connection on the IP address we get from a lookup
|
||||
stream_.async_connect(
|
||||
results,
|
||||
beast::bind_front_handler(
|
||||
&client_session::on_connect,
|
||||
client_session<ResponseType>::shared_from_this()));
|
||||
}
|
||||
void on_connect(
|
||||
beast::error_code ec,
|
||||
tcp::resolver::results_type::endpoint_type) {
|
||||
if (ec) {
|
||||
failed(ec, "client_session::stream connect");
|
||||
return;
|
||||
}
|
||||
// sets a timeout on the operation
|
||||
stream_.expires_after(std::chrono::seconds(30));
|
||||
// sends the HTTP request to the remote host
|
||||
http::async_write(
|
||||
stream_, req_,
|
||||
beast::bind_front_handler(
|
||||
&client_session::on_write,
|
||||
client_session<ResponseType>::shared_from_this()));
|
||||
}
|
||||
void on_write(
|
||||
beast::error_code ec,
|
||||
std::size_t bytes_transferred) {
|
||||
boost::ignore_unused(bytes_transferred);
|
||||
if (ec) {
|
||||
failed(ec, "client_session::stream write");
|
||||
return;
|
||||
}
|
||||
// receives the HTTP response
|
||||
http::async_read(
|
||||
stream_, buffer_, res_,
|
||||
beast::bind_front_handler(
|
||||
&client_session::on_read,
|
||||
client_session<ResponseType>::shared_from_this()));
|
||||
}
|
||||
static_assert(std::is_same<ResponseType, http::response<http::string_body>>::value
|
||||
|| std::is_same<ResponseType, boost::json::value>::value,
|
||||
"unsupported `ResponseType`");
|
||||
void on_read(
|
||||
beast::error_code ec,
|
||||
std::size_t bytes_transferred) {
|
||||
boost::ignore_unused(bytes_transferred);
|
||||
if (ec) {
|
||||
failed(ec, "client_session::stream read");
|
||||
return;
|
||||
}
|
||||
if constexpr (std::is_same<ResponseType, http::response<http::string_body>>::value) {
|
||||
promise_.set_value(std::move(res_));
|
||||
} else if constexpr (std::is_same<ResponseType, boost::json::value>::value) {
|
||||
promise_.set_value(boost::json::parse(res_.body()));
|
||||
} else { // this should never happen
|
||||
promise_.set_exception(
|
||||
std::make_exception_ptr(
|
||||
request_failed_exception{"unsupported `ResponseType`"}));
|
||||
}
|
||||
// gracefully close the socket
|
||||
stream_.socket().shutdown(tcp::socket::shutdown_both, ec);
|
||||
// `not_connected` happens sometimes so don't bother reporting it
|
||||
if (ec && ec != beast::errc::not_connected) {
|
||||
// reports the error to the log!
|
||||
fail(ec, "client_session::stream::socket shutdown");
|
||||
return;
|
||||
}
|
||||
// if we get here then the connection is closed gracefully
|
||||
}
|
||||
};
|
||||
|
||||
class client {
|
||||
private:
|
||||
asio::io_context& ioc_;
|
||||
public:
|
||||
client(asio::io_context& ioc)
|
||||
: ioc_{ioc} {}
|
||||
std::future<http::response<http::string_body>> request(
|
||||
const std::string& host,
|
||||
const std::string& port,
|
||||
const http::request<http::string_body>& req) {
|
||||
return std::make_shared<
|
||||
client_session<http::response<http::string_body>>
|
||||
>(ioc_, req)->send(host, port);
|
||||
}
|
||||
std::future<boost::json::value> request_for_object(
|
||||
const std::string& host,
|
||||
const std::string& port,
|
||||
const http::request<http::string_body>& req) {
|
||||
return std::make_shared<
|
||||
client_session<boost::json::value>
|
||||
>(ioc_, req)->send(host, port);
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<client> client_ptr;
|
||||
|
||||
namespace request {
|
||||
|
||||
request_type get_request(
|
||||
const std::string& host,
|
||||
const std::string& target,
|
||||
const http::verb& method,
|
||||
const boost::json::object& obj) {
|
||||
request_type req;
|
||||
req.method(method);
|
||||
req.target(target);
|
||||
req.set(http::field::host, host);
|
||||
req.set(http::field::user_agent, NAME);
|
||||
req.set(http::field::content_type, "application/json");
|
||||
req.body() = boost::json::serialize(obj);
|
||||
req.prepare_payload();
|
||||
return req;
|
||||
}
|
||||
|
||||
std::future<response_type> send(
|
||||
const std::string& host,
|
||||
const std::string& port,
|
||||
const std::string& target,
|
||||
const http::verb& method,
|
||||
const boost::json::object& obj) {
|
||||
request_type req = get_request(host, target, method, obj);
|
||||
return client_ptr->request(host, port, req);
|
||||
}
|
||||
|
||||
std::future<boost::json::value> send_for_object(
|
||||
const std::string& host,
|
||||
const std::string& port,
|
||||
const std::string& target,
|
||||
const http::verb& method,
|
||||
const boost::json::object& obj) {
|
||||
request_type req = get_request(host, target, method, obj);
|
||||
return client_ptr->request_for_object(host, port, req);
|
||||
}
|
||||
|
||||
std::future<response_type> get(
|
||||
const std::string& host,
|
||||
const std::string& port,
|
||||
const std::string& target,
|
||||
const boost::json::object& obj) {
|
||||
return send(host, port, target, http::verb::get, obj);
|
||||
}
|
||||
|
||||
std::future<boost::json::value> get_for_object(
|
||||
const std::string& host,
|
||||
const std::string& port,
|
||||
const std::string& target,
|
||||
const boost::json::object& obj) {
|
||||
return send_for_object(host, port, target, http::verb::get, obj);
|
||||
}
|
||||
|
||||
std::future<response_type> put(
|
||||
const std::string& host,
|
||||
const std::string& port,
|
||||
const std::string& target,
|
||||
const boost::json::object& obj) {
|
||||
return send(host, port, target, http::verb::put, obj);
|
||||
}
|
||||
|
||||
std::future<boost::json::value> put_for_object(
|
||||
const std::string& host,
|
||||
const std::string& port,
|
||||
const std::string& target,
|
||||
const boost::json::object& obj) {
|
||||
return send_for_object(host, port, target, http::verb::put, obj);
|
||||
}
|
||||
|
||||
std::future<response_type> post(
|
||||
const std::string& host,
|
||||
const std::string& port,
|
||||
const std::string& target,
|
||||
const boost::json::object& obj) {
|
||||
return send(host, port, target, http::verb::post, obj);
|
||||
}
|
||||
|
||||
std::future<boost::json::value> post_for_object(
|
||||
const std::string& host,
|
||||
const std::string& port,
|
||||
const std::string& target,
|
||||
const boost::json::object& obj) {
|
||||
return send_for_object(host, port, target, http::verb::post, obj);
|
||||
}
|
||||
|
||||
std::future<response_type> delete_(
|
||||
const std::string& host,
|
||||
const std::string& port,
|
||||
const std::string& target,
|
||||
const boost::json::object& obj) {
|
||||
return send(host, port, target, http::verb::delete_, obj);
|
||||
}
|
||||
|
||||
std::future<boost::json::value> delete_for_object(
|
||||
const std::string& host,
|
||||
const std::string& port,
|
||||
const std::string& target,
|
||||
const boost::json::object& obj) {
|
||||
return send_for_object(host, port, target, http::verb::delete_, obj);
|
||||
}
|
||||
|
||||
} // request
|
||||
|
||||
} // bserv
|
||||
|
||||
#endif // _CLIENT_HPP
|
|
@ -1,6 +1,7 @@
|
|||
#ifndef _COMMON_HPP
|
||||
#define _COMMON_HPP
|
||||
|
||||
#include "client.hpp"
|
||||
#include "database.hpp"
|
||||
#include "session.hpp"
|
||||
#include "router.hpp"
|
||||
|
|
18
handlers.hpp
18
handlers.hpp
|
@ -209,4 +209,22 @@ boost::json::object user_logout(
|
|||
};
|
||||
}
|
||||
|
||||
boost::json::object send_request() {
|
||||
// post for response:
|
||||
// auto res = bserv::request::post(
|
||||
// "localhost", "8081", "/test", {{"msg", "request"}}
|
||||
// ).get();
|
||||
// return {{"response", boost::json::parse(res.body())}};
|
||||
// -------------------------------------------------------
|
||||
// - if it takes longer than 30 seconds (by default) to
|
||||
// - get the response, this will raise a read timeout
|
||||
// -------------------------------------------------------
|
||||
// post for json response (json value, rather than json
|
||||
// object, is returned):
|
||||
auto obj = bserv::request::post_for_object(
|
||||
"localhost", "8081", "/test", {{"msg", "request"}}
|
||||
).get();
|
||||
return {{"response", obj}};
|
||||
}
|
||||
|
||||
#endif // _HANDLERS_HPP
|
|
@ -37,6 +37,10 @@ void init_logging() {
|
|||
#define lgerror BOOST_LOG_TRIVIAL(error)
|
||||
#define lgfatal BOOST_LOG_TRIVIAL(fatal)
|
||||
|
||||
void fail(const boost::system::error_code& ec, const char* what) {
|
||||
lgerror << what << ": " << ec.message() << std::endl;
|
||||
}
|
||||
|
||||
} // bserv
|
||||
|
||||
#endif // _LOGGING_HPP
|
|
@ -24,7 +24,8 @@ bserv::router routes{
|
|||
bserv::placeholders::session),
|
||||
bserv::make_path("/find/<str>", &find_user,
|
||||
bserv::placeholders::transaction,
|
||||
bserv::placeholders::_1)
|
||||
bserv::placeholders::_1),
|
||||
bserv::make_path("/send", &send_request)
|
||||
};
|
||||
|
||||
} // bserv
|
||||
|
|
20
utils.hpp
20
utils.hpp
|
@ -8,6 +8,8 @@
|
|||
#include <map>
|
||||
#include <random>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
#include <cryptopp/cryptlib.h>
|
||||
#include <cryptopp/pwdbased.h>
|
||||
|
@ -41,6 +43,11 @@ const std::string chars = "abcdefghijklmnopqrstuvwxyz"
|
|||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"1234567890";
|
||||
|
||||
|
||||
const std::string url_safe_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789-._~";
|
||||
|
||||
} // internal
|
||||
|
||||
// https://www.boost.org/doc/libs/1_75_0/libs/random/example/password.cpp
|
||||
|
@ -132,6 +139,19 @@ std::string decode_url(const std::string& s) {
|
|||
return r;
|
||||
}
|
||||
|
||||
std::string encode_url(const std::string& s) {
|
||||
std::ostringstream oss;
|
||||
for (auto& c : s) {
|
||||
if (internal::url_safe_characters.find(c) != std::string::npos) {
|
||||
oss << c;
|
||||
} else {
|
||||
oss << '%' << std::setfill('0') << std::setw(2) <<
|
||||
std::uppercase << std::hex << (0xff & c);
|
||||
}
|
||||
}
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
// this function parses param list in the form of k1=v1&k2=v2...,
|
||||
// where '&' can be any delimiter.
|
||||
// ki and vi will be converted if they are percent-encoded,
|
||||
|
|
Loading…
Reference in New Issue