Cleanup async client APIs.
This commit is contained in:
@@ -15,8 +15,6 @@ include(GNUInstallDirs)
|
||||
|
||||
set(HEADERS
|
||||
globals.h
|
||||
# http_async_client_base.h
|
||||
# http_async_client.h
|
||||
http_client_base.h
|
||||
http_client.h
|
||||
http_client_session.h
|
||||
@@ -31,7 +29,6 @@ set(HEADERS
|
||||
http_response_parser.h
|
||||
http_server.h
|
||||
http_ssl_client.h
|
||||
# http_ssl_async_client.h
|
||||
queue.h
|
||||
url.h
|
||||
utility.h
|
||||
@@ -40,8 +37,6 @@ set(HEADERS
|
||||
|
||||
set(SOURCES
|
||||
globals.cc
|
||||
# http_async_client_base.cc
|
||||
# http_async_client.cc
|
||||
http_client_base.cc
|
||||
http_client.cc
|
||||
http_client_session.cc
|
||||
@@ -55,7 +50,6 @@ set(SOURCES
|
||||
http_response_parser.cc
|
||||
http_server.cc
|
||||
http_ssl_client.cc
|
||||
# http_ssl_async_client.cc
|
||||
logger.cc
|
||||
url.cc
|
||||
utility.cc
|
||||
@@ -63,16 +57,12 @@ set(SOURCES
|
||||
|
||||
if(WEBCC_ENABLE_REST)
|
||||
set(REST_HEADERS
|
||||
# rest_async_client.h
|
||||
# rest_client.h
|
||||
rest_request_handler.h
|
||||
rest_server.h
|
||||
rest_service.h
|
||||
rest_service_manager.h
|
||||
)
|
||||
set(REST_SOURCES
|
||||
# rest_async_client.cc
|
||||
# rest_client.cc
|
||||
rest_request_handler.cc
|
||||
rest_service_manager.cc
|
||||
rest_service.cc
|
||||
@@ -84,7 +74,6 @@ endif()
|
||||
|
||||
if(WEBCC_ENABLE_SOAP)
|
||||
set(SOAP_HEADERS
|
||||
# soap_async_client.h
|
||||
soap_client.h
|
||||
soap_globals.h
|
||||
soap_message.h
|
||||
@@ -98,7 +87,6 @@ if(WEBCC_ENABLE_SOAP)
|
||||
)
|
||||
|
||||
set(SOAP_SOURCES
|
||||
# soap_async_client.cc
|
||||
soap_client.cc
|
||||
soap_globals.cc
|
||||
soap_message.cc
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
#include "webcc/http_async_client.h"
|
||||
|
||||
#include "boost/asio/connect.hpp"
|
||||
#include "boost/asio/read.hpp"
|
||||
#include "boost/asio/write.hpp"
|
||||
|
||||
#include "webcc/logger.h"
|
||||
#include "webcc/utility.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
HttpAsyncClient::HttpAsyncClient(boost::asio::io_context& io_context,
|
||||
std::size_t buffer_size)
|
||||
: HttpAsyncClientBase(io_context, buffer_size),
|
||||
socket_(io_context) {
|
||||
}
|
||||
|
||||
void HttpAsyncClient::SocketAsyncConnect(const Endpoints& endpoints,
|
||||
ConnectHandler&& handler) {
|
||||
boost::asio::async_connect(socket_, endpoints, std::move(handler));
|
||||
}
|
||||
|
||||
void HttpAsyncClient::SocketAsyncWrite(WriteHandler&& handler) {
|
||||
boost::asio::async_write(socket_, request_->ToBuffers(), std::move(handler));
|
||||
}
|
||||
|
||||
void HttpAsyncClient::SocketAsyncReadSome(ReadHandler&& handler) {
|
||||
socket_.async_read_some(boost::asio::buffer(buffer_), std::move(handler));
|
||||
}
|
||||
|
||||
void HttpAsyncClient::SocketClose(boost::system::error_code* ec) {
|
||||
socket_.close(*ec);
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
@@ -1,51 +0,0 @@
|
||||
#ifndef WEBCC_HTTP_ASYNC_CLIENT_H_
|
||||
#define WEBCC_HTTP_ASYNC_CLIENT_H_
|
||||
|
||||
#include "webcc/http_async_client_base.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class HttpAsyncClient;
|
||||
typedef std::shared_ptr<HttpAsyncClient> HttpAsyncClientPtr;
|
||||
|
||||
// HTTP asynchronous client.
|
||||
class HttpAsyncClient : public HttpAsyncClientBase {
|
||||
public:
|
||||
~HttpAsyncClient() = default;
|
||||
|
||||
// Forbid to create HttpAsyncClient in stack since it's derived from
|
||||
// std::shared_from_this.
|
||||
static HttpAsyncClientPtr New(boost::asio::io_context& io_context,
|
||||
std::size_t buffer_size = 0) {
|
||||
return HttpAsyncClientPtr{
|
||||
new HttpAsyncClient(io_context, buffer_size)
|
||||
};
|
||||
}
|
||||
|
||||
private:
|
||||
explicit HttpAsyncClient(boost::asio::io_context& io_context,
|
||||
std::size_t buffer_size = 0);
|
||||
|
||||
void Resolve() final {
|
||||
DoResolve(kHttpPort);
|
||||
}
|
||||
|
||||
void OnConnected() final {
|
||||
DoWrite();
|
||||
}
|
||||
|
||||
void SocketAsyncConnect(const Endpoints& endpoints,
|
||||
ConnectHandler&& handler) final;
|
||||
|
||||
void SocketAsyncWrite(WriteHandler&& handler) final;
|
||||
|
||||
void SocketAsyncReadSome(ReadHandler&& handler) final;
|
||||
|
||||
void SocketClose(boost::system::error_code* ec) final;
|
||||
|
||||
tcp::socket socket_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_HTTP_ASYNC_CLIENT_H_
|
||||
@@ -1,236 +0,0 @@
|
||||
#include "webcc/http_async_client_base.h"
|
||||
|
||||
#include "boost/asio/connect.hpp"
|
||||
#include "boost/asio/read.hpp"
|
||||
#include "boost/asio/write.hpp"
|
||||
|
||||
#include "webcc/logger.h"
|
||||
#include "webcc/utility.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
HttpAsyncClientBase::HttpAsyncClientBase(boost::asio::io_context& io_context,
|
||||
std::size_t buffer_size)
|
||||
: resolver_(io_context),
|
||||
buffer_(buffer_size == 0 ? kBufferSize : buffer_size),
|
||||
deadline_(io_context),
|
||||
timeout_seconds_(kMaxReadSeconds),
|
||||
stopped_(false),
|
||||
timed_out_(false) {
|
||||
}
|
||||
|
||||
void HttpAsyncClientBase::SetTimeout(int seconds) {
|
||||
if (seconds > 0) {
|
||||
timeout_seconds_ = seconds;
|
||||
}
|
||||
}
|
||||
|
||||
void HttpAsyncClientBase::Request(std::shared_ptr<HttpRequest> request,
|
||||
HttpResponseCallback response_callback) {
|
||||
assert(request);
|
||||
assert(response_callback);
|
||||
|
||||
response_.reset(new HttpResponse());
|
||||
response_parser_.reset(new HttpResponseParser(response_.get()));
|
||||
|
||||
stopped_ = false;
|
||||
timed_out_ = false;
|
||||
|
||||
LOG_VERB("HTTP request:\n%s", request->Dump(4, "> ").c_str());
|
||||
|
||||
request_ = request;
|
||||
response_callback_ = response_callback;
|
||||
|
||||
DoResolve(kHttpSslPort);
|
||||
}
|
||||
|
||||
void HttpAsyncClientBase::Stop() {
|
||||
LOG_INFO("The user asks to cancel the request.");
|
||||
|
||||
DoStop();
|
||||
}
|
||||
|
||||
void HttpAsyncClientBase::DoResolve(const std::string& default_port) {
|
||||
auto handler = std::bind(&HttpAsyncClientBase::OnResolve, shared_from_this(),
|
||||
std::placeholders::_1, std::placeholders::_2);
|
||||
|
||||
resolver_.async_resolve(tcp::v4(), request_->host(),
|
||||
request_->port(default_port), handler);
|
||||
}
|
||||
|
||||
void HttpAsyncClientBase::OnResolve(boost::system::error_code ec,
|
||||
tcp::resolver::results_type endpoints) {
|
||||
if (ec) {
|
||||
LOG_ERRO("Host resolve error (%s): %s, %s.", ec.message().c_str(),
|
||||
request_->host().c_str(), request_->port().c_str());
|
||||
|
||||
response_callback_(response_, kHostResolveError, timed_out_);
|
||||
} else {
|
||||
LOG_VERB("Host resolved.");
|
||||
|
||||
DoConnect(endpoints);
|
||||
}
|
||||
}
|
||||
|
||||
void HttpAsyncClientBase::DoConnect(const Endpoints& endpoints) {
|
||||
auto handler = std::bind(&HttpAsyncClientBase::OnConnect,
|
||||
shared_from_this(),
|
||||
std::placeholders::_1, std::placeholders::_2);
|
||||
|
||||
SocketAsyncConnect(endpoints, std::move(handler));
|
||||
}
|
||||
|
||||
void HttpAsyncClientBase::OnConnect(boost::system::error_code ec,
|
||||
tcp::endpoint endpoint) {
|
||||
if (ec) {
|
||||
LOG_ERRO("Socket connect error (%s).", ec.message().c_str());
|
||||
DoStop();
|
||||
response_callback_(response_, kEndpointConnectError, timed_out_);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_VERB("Socket connected.");
|
||||
|
||||
// Even though the connect operation notionally succeeded, the user could
|
||||
// have stopped the operation by calling Stop(). And if we started the
|
||||
// deadline timer, it could also be stopped due to timeout.
|
||||
if (stopped_) {
|
||||
// TODO: Use some other error.
|
||||
response_callback_(response_, kEndpointConnectError, timed_out_);
|
||||
return;
|
||||
}
|
||||
|
||||
// Connection established.
|
||||
OnConnected();
|
||||
}
|
||||
|
||||
void HttpAsyncClientBase::DoWrite() {
|
||||
// NOTE:
|
||||
// It doesn't make much sense to set a timeout for socket write.
|
||||
// I find that it's almost impossible to simulate a situation in the server
|
||||
// side to test this timeout.
|
||||
|
||||
SocketAsyncWrite(std::bind(&HttpAsyncClientBase::OnWrite, shared_from_this(),
|
||||
std::placeholders::_1, std::placeholders::_2));
|
||||
}
|
||||
|
||||
void HttpAsyncClientBase::OnWrite(boost::system::error_code ec,
|
||||
std::size_t /*length*/) {
|
||||
if (stopped_) {
|
||||
// TODO: Use some other error.
|
||||
response_callback_(response_, kSocketWriteError, timed_out_);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ec) {
|
||||
LOG_ERRO("Socket write error (%s).", ec.message().c_str());
|
||||
DoStop();
|
||||
response_callback_(response_, kSocketWriteError, timed_out_);
|
||||
} else {
|
||||
LOG_INFO("Request sent.");
|
||||
LOG_VERB("Read response (timeout: %ds)...", timeout_seconds_);
|
||||
|
||||
deadline_.expires_from_now(boost::posix_time::seconds(timeout_seconds_));
|
||||
DoWaitDeadline();
|
||||
|
||||
DoRead();
|
||||
}
|
||||
}
|
||||
|
||||
void HttpAsyncClientBase::DoRead() {
|
||||
auto handler = std::bind(&HttpAsyncClientBase::OnRead, shared_from_this(),
|
||||
std::placeholders::_1, std::placeholders::_2);
|
||||
SocketAsyncReadSome(std::move(handler));
|
||||
}
|
||||
|
||||
void HttpAsyncClientBase::OnRead(boost::system::error_code ec,
|
||||
std::size_t length) {
|
||||
LOG_VERB("Socket async read handler.");
|
||||
|
||||
if (ec || length == 0) {
|
||||
DoStop();
|
||||
LOG_ERRO("Socket read error (%s).", ec.message().c_str());
|
||||
response_callback_(response_, kSocketReadError, timed_out_);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO("Read data, length: %u.", length);
|
||||
|
||||
// Parse the response piece just read.
|
||||
// If the content has been fully received, |finished()| will be true.
|
||||
if (!response_parser_->Parse(buffer_.data(), length)) {
|
||||
DoStop();
|
||||
LOG_ERRO("Failed to parse HTTP response.");
|
||||
response_callback_(response_, kHttpError, timed_out_);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response_parser_->finished()) {
|
||||
DoStop();
|
||||
|
||||
LOG_INFO("Finished to read and parse HTTP response.");
|
||||
LOG_VERB("HTTP response:\n%s", response_->Dump(4, "> ").c_str());
|
||||
|
||||
response_callback_(response_, kNoError, timed_out_);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!stopped_) {
|
||||
DoRead();
|
||||
}
|
||||
}
|
||||
|
||||
void HttpAsyncClientBase::DoWaitDeadline() {
|
||||
deadline_.async_wait(std::bind(&HttpAsyncClientBase::OnDeadline,
|
||||
shared_from_this(), std::placeholders::_1));
|
||||
}
|
||||
|
||||
void HttpAsyncClientBase::OnDeadline(boost::system::error_code ec) {
|
||||
if (stopped_) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_VERB("OnDeadline.");
|
||||
|
||||
// NOTE: Can't check this:
|
||||
// if (ec == boost::asio::error::operation_aborted) {
|
||||
// LOG_VERB("Deadline timer canceled.");
|
||||
// return;
|
||||
// }
|
||||
|
||||
if (deadline_.expires_at() <=
|
||||
boost::asio::deadline_timer::traits_type::now()) {
|
||||
// The deadline has passed.
|
||||
// The socket is closed so that any outstanding asynchronous operations
|
||||
// are canceled.
|
||||
LOG_WARN("HTTP client timed out.");
|
||||
timed_out_ = true;
|
||||
Stop();
|
||||
return;
|
||||
}
|
||||
|
||||
// Put the actor back to sleep.
|
||||
DoWaitDeadline();
|
||||
}
|
||||
|
||||
void HttpAsyncClientBase::DoStop() {
|
||||
if (stopped_) {
|
||||
return;
|
||||
}
|
||||
|
||||
stopped_ = true;
|
||||
|
||||
LOG_INFO("Close socket...");
|
||||
|
||||
boost::system::error_code ec;
|
||||
SocketClose(&ec);
|
||||
|
||||
if (ec) {
|
||||
LOG_ERRO("Socket close error (%s).", ec.message().c_str());
|
||||
}
|
||||
|
||||
LOG_INFO("Cancel deadline timer...");
|
||||
deadline_.cancel();
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
@@ -1,130 +0,0 @@
|
||||
#ifndef WEBCC_HTTP_ASYNC_CLIENT_BASE_H_
|
||||
#define WEBCC_HTTP_ASYNC_CLIENT_BASE_H_
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "boost/asio/deadline_timer.hpp"
|
||||
#include "boost/asio/io_context.hpp"
|
||||
#include "boost/asio/ip/tcp.hpp"
|
||||
|
||||
#include "webcc/globals.h"
|
||||
#include "webcc/http_request.h"
|
||||
#include "webcc/http_response.h"
|
||||
#include "webcc/http_response_parser.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
// Response callback.
|
||||
typedef std::function<void(HttpResponsePtr, Error, bool)> HttpResponseCallback;
|
||||
|
||||
// HTTP client session in asynchronous mode.
|
||||
// A request will return without waiting for the response, the callback handler
|
||||
// will be invoked when the response is received or timeout occurs.
|
||||
// Don't use the same HttpAsyncClient object in multiple threads.
|
||||
class HttpAsyncClientBase
|
||||
: public std::enable_shared_from_this<HttpAsyncClientBase> {
|
||||
public:
|
||||
// The |buffer_size| is the bytes of the buffer for reading response.
|
||||
// 0 means default value (e.g., 1024) will be used.
|
||||
explicit HttpAsyncClientBase(boost::asio::io_context& io_context,
|
||||
std::size_t buffer_size = 0);
|
||||
|
||||
virtual ~HttpAsyncClientBase() = default;
|
||||
|
||||
WEBCC_DELETE_COPY_ASSIGN(HttpAsyncClientBase);
|
||||
|
||||
// Set the timeout seconds for reading response.
|
||||
// The |seconds| is only effective when greater than 0.
|
||||
void SetTimeout(int seconds);
|
||||
|
||||
// Asynchronously connect to the server, send the request, read the response,
|
||||
// and call the |response_callback| when all these finish.
|
||||
void Request(HttpRequestPtr request, HttpResponseCallback response_callback);
|
||||
|
||||
// Called by the user to cancel the request.
|
||||
void Stop();
|
||||
|
||||
protected:
|
||||
using tcp = boost::asio::ip::tcp;
|
||||
|
||||
typedef tcp::resolver::results_type Endpoints;
|
||||
|
||||
typedef std::function<void(boost::system::error_code, std::size_t)>
|
||||
ReadHandler;
|
||||
|
||||
typedef std::function<void(boost::system::error_code, tcp::endpoint)>
|
||||
ConnectHandler;
|
||||
|
||||
typedef std::function<void(boost::system::error_code, std::size_t)>
|
||||
WriteHandler;
|
||||
|
||||
// To enable_shared_from_this for both parent and derived.
|
||||
// See https://stackoverflow.com/q/657155/6825348
|
||||
template <typename Derived>
|
||||
std::shared_ptr<Derived> shared_from_base() {
|
||||
return std::static_pointer_cast<Derived>(shared_from_this());
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void Resolve() = 0;
|
||||
|
||||
void DoResolve(const std::string& default_port);
|
||||
void OnResolve(boost::system::error_code ec,
|
||||
tcp::resolver::results_type results);
|
||||
|
||||
void DoConnect(const Endpoints& endpoints);
|
||||
void OnConnect(boost::system::error_code ec, tcp::endpoint endpoint);
|
||||
|
||||
virtual void OnConnected() = 0;
|
||||
|
||||
void DoWrite();
|
||||
void OnWrite(boost::system::error_code ec, std::size_t length);
|
||||
|
||||
void DoRead();
|
||||
void OnRead(boost::system::error_code ec, std::size_t length);
|
||||
|
||||
void DoWaitDeadline();
|
||||
void OnDeadline(boost::system::error_code ec);
|
||||
|
||||
// Terminate all the actors to shut down the connection.
|
||||
void DoStop();
|
||||
|
||||
virtual void SocketAsyncConnect(const Endpoints& endpoints,
|
||||
ConnectHandler&& handler) = 0;
|
||||
|
||||
virtual void SocketAsyncWrite(WriteHandler&& handler) = 0;
|
||||
|
||||
virtual void SocketAsyncReadSome(ReadHandler&& handler) = 0;
|
||||
|
||||
virtual void SocketClose(boost::system::error_code* ec) = 0;
|
||||
|
||||
tcp::resolver resolver_;
|
||||
|
||||
HttpRequestPtr request_;
|
||||
|
||||
std::vector<char> buffer_;
|
||||
|
||||
HttpResponsePtr response_;
|
||||
std::unique_ptr<HttpResponseParser> response_parser_;
|
||||
HttpResponseCallback response_callback_;
|
||||
|
||||
// Timer for the timeout control.
|
||||
boost::asio::deadline_timer deadline_;
|
||||
|
||||
// Maximum seconds to wait before the client cancels the operation.
|
||||
// Only for receiving response from server.
|
||||
int timeout_seconds_;
|
||||
|
||||
// Request stopped due to timeout or socket error.
|
||||
bool stopped_;
|
||||
|
||||
// If the error was caused by timeout or not.
|
||||
// Will be passed to the response handler/callback.
|
||||
bool timed_out_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_HTTP_ASYNC_CLIENT_BASE_H_
|
||||
+2
-2
@@ -7,12 +7,12 @@ namespace webcc {
|
||||
|
||||
// HTTP synchronous client.
|
||||
class HttpClient : public HttpClientBase {
|
||||
public:
|
||||
public:
|
||||
explicit HttpClient(std::size_t buffer_size = 0);
|
||||
|
||||
~HttpClient() = default;
|
||||
|
||||
private:
|
||||
private:
|
||||
Error Connect(const HttpRequest& request) final {
|
||||
return DoConnect(request, kPort80);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace webcc {
|
||||
// or timeout occurs.
|
||||
// Please don't use the same client object in multiple threads.
|
||||
class HttpClientBase {
|
||||
public:
|
||||
public:
|
||||
// The |buffer_size| is the bytes of the buffer for reading response.
|
||||
// 0 means default value (e.g., 1024) will be used.
|
||||
explicit HttpClientBase(std::size_t buffer_size = 0);
|
||||
@@ -56,7 +56,7 @@ class HttpClientBase {
|
||||
|
||||
Error error() const { return error_; }
|
||||
|
||||
protected:
|
||||
public:
|
||||
typedef boost::asio::ip::tcp::resolver::results_type Endpoints;
|
||||
|
||||
typedef std::function<void(boost::system::error_code, std::size_t)>
|
||||
|
||||
@@ -14,14 +14,20 @@ HttpResponsePtr HttpClientSession::Request(HttpRequestArgs&& args) {
|
||||
assert(args.parameters_.size() % 2 == 0);
|
||||
assert(args.headers_.size() % 2 == 0);
|
||||
|
||||
HttpRequest request{ args.method_, args.url_, args.parameters_ };
|
||||
HttpRequest request{ args.method_, args.url_ };
|
||||
|
||||
for (std::size_t i = 1; i < args.parameters_.size(); i += 2) {
|
||||
request.AddParameter(args.parameters_[i - 1], args.parameters_[i]);
|
||||
}
|
||||
|
||||
if (!args.data_.empty()) {
|
||||
request.SetContent(std::move(args.data_), true);
|
||||
|
||||
// TODO: charset/encoding
|
||||
// TODO: Request-level charset.
|
||||
if (args.json_) {
|
||||
request.SetContentType(http::media_types::kApplicationJson, "");
|
||||
request.SetContentType(http::media_types::kApplicationJson, charset_);
|
||||
} else if (!content_type_.empty()) {
|
||||
request.SetContentType(content_type_, charset_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,14 @@ public:
|
||||
|
||||
~HttpClientSession() = default;
|
||||
|
||||
void set_content_type(const std::string& content_type) {
|
||||
content_type_ = content_type;
|
||||
}
|
||||
|
||||
void set_charset(const std::string& charset) {
|
||||
charset_ = charset;
|
||||
}
|
||||
|
||||
void AddHeader(const std::string& key, const std::string& value) {
|
||||
headers_.Add(key, value);
|
||||
}
|
||||
@@ -34,7 +42,13 @@ public:
|
||||
private:
|
||||
void InitHeaders();
|
||||
|
||||
// Headers to be sent on each request sent from this session.
|
||||
// E.g., "application/json".
|
||||
std::string content_type_;
|
||||
|
||||
// E.g., "utf-8".
|
||||
std::string charset_;
|
||||
|
||||
// Headers for each request sent from this session.
|
||||
HttpHeaderDict headers_;
|
||||
};
|
||||
|
||||
|
||||
@@ -73,12 +73,6 @@ void HttpMessage::SetContent(std::string&& content, bool set_length) {
|
||||
}
|
||||
}
|
||||
|
||||
void HttpMessage::SetContentInAppJsonUtf8(std::string&& content,
|
||||
bool set_length) {
|
||||
SetContent(std::move(content), set_length);
|
||||
SetContentType(http::media_types::kApplicationJson, http::charsets::kUtf8);
|
||||
}
|
||||
|
||||
// ATTENTION: The buffers don't hold the memory!
|
||||
std::vector<boost::asio::const_buffer> HttpMessage::ToBuffers() const {
|
||||
assert(!start_line_.empty());
|
||||
|
||||
@@ -88,8 +88,6 @@ public:
|
||||
// TODO: Remove parameter |set_length|.
|
||||
void SetContent(std::string&& content, bool set_length);
|
||||
|
||||
void SetContentInAppJsonUtf8(std::string&& content, bool set_length);
|
||||
|
||||
// Make the message (e.g., update start line).
|
||||
// Must be called before ToBuffers()!
|
||||
virtual bool Prepare() = 0;
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@ class HttpMessage;
|
||||
|
||||
// HttpParser parses HTTP request and response.
|
||||
class HttpParser {
|
||||
public:
|
||||
public:
|
||||
explicit HttpParser(HttpMessage* message);
|
||||
|
||||
virtual ~HttpParser() = default;
|
||||
@@ -24,7 +24,7 @@ class HttpParser {
|
||||
|
||||
bool Parse(const char* data, std::size_t length);
|
||||
|
||||
protected:
|
||||
public:
|
||||
// Parse headers from pending data.
|
||||
// Return false only on syntax errors.
|
||||
bool ParseHeaders();
|
||||
|
||||
@@ -4,16 +4,6 @@
|
||||
|
||||
namespace webcc {
|
||||
|
||||
HttpRequest::HttpRequest(const std::string& method,
|
||||
const std::string& url,
|
||||
const std::vector<std::string>& parameters)
|
||||
: method_(method), url_(url) {
|
||||
assert(parameters.size() % 2 == 0);
|
||||
for (std::size_t i = 1; i < parameters.size(); i += 2) {
|
||||
url_.AddParameter(parameters[i - 1], parameters[i]);
|
||||
}
|
||||
}
|
||||
|
||||
bool HttpRequest::Prepare() {
|
||||
if (url_.host().empty()) {
|
||||
LOG_ERRO("Invalid request: host is missing.");
|
||||
@@ -41,17 +31,4 @@ bool HttpRequest::Prepare() {
|
||||
return true;
|
||||
}
|
||||
|
||||
HttpRequestPtr HttpRequest::New(const std::string& method,
|
||||
const std::string& url,
|
||||
const std::vector<std::string>& parameters,
|
||||
bool prepare) {
|
||||
HttpRequestPtr request{ new HttpRequest{ method, url, parameters } };
|
||||
|
||||
if (prepare) {
|
||||
request->Prepare();
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
+16
-30
@@ -11,7 +11,6 @@
|
||||
namespace webcc {
|
||||
|
||||
class HttpRequest;
|
||||
class HttpRequestParser;
|
||||
|
||||
typedef std::shared_ptr<HttpRequest> HttpRequestPtr;
|
||||
|
||||
@@ -19,13 +18,25 @@ class HttpRequest : public HttpMessage {
|
||||
public:
|
||||
HttpRequest() = default;
|
||||
|
||||
// TODO: Move parameters
|
||||
HttpRequest(const std::string& method,
|
||||
const std::string& url,
|
||||
const std::vector<std::string>& parameters = {});
|
||||
HttpRequest(const std::string& method, const std::string& url)
|
||||
: method_(method), url_(url) {
|
||||
}
|
||||
|
||||
~HttpRequest() override = default;
|
||||
|
||||
void set_method(const std::string& method) {
|
||||
method_ = method;
|
||||
}
|
||||
|
||||
void set_url(const std::string& url) {
|
||||
url_.Init(url);
|
||||
}
|
||||
|
||||
// Add URL query parameter.
|
||||
void AddParameter(const std::string& key, const std::string& value) {
|
||||
url_.AddParameter(key, value);
|
||||
}
|
||||
|
||||
const std::string& method() const {
|
||||
return method_;
|
||||
}
|
||||
@@ -46,36 +57,11 @@ public:
|
||||
return port().empty() ? default_port : port();
|
||||
}
|
||||
|
||||
// Shortcut to set `Accept` header.
|
||||
void Accept(const std::string& media_type) {
|
||||
SetHeader(http::headers::kAccept, media_type);
|
||||
}
|
||||
|
||||
// Shortcut to set `Accept` header.
|
||||
void AcceptAppJson() {
|
||||
SetHeader(http::headers::kAccept, http::media_types::kApplicationJson);
|
||||
}
|
||||
|
||||
// Prepare payload.
|
||||
// Compose start line, set Host header, etc.
|
||||
bool Prepare() override;
|
||||
|
||||
// TODO: Re-place
|
||||
static HttpRequestPtr New(const std::string& method,
|
||||
const std::string& url,
|
||||
const std::vector<std::string>& parameters = {},
|
||||
bool prepare = true);
|
||||
|
||||
private:
|
||||
friend class HttpRequestParser;
|
||||
|
||||
void set_method(const std::string& method) {
|
||||
method_ = method;
|
||||
}
|
||||
void set_url(const std::string& url) {
|
||||
url_.Init(url);
|
||||
}
|
||||
|
||||
std::string method_;
|
||||
Url url_;
|
||||
};
|
||||
|
||||
@@ -19,8 +19,8 @@ bool HttpRequestParser::ParseStartLine(const std::string& line) {
|
||||
return false;
|
||||
}
|
||||
|
||||
request_->set_method(strs[0]);
|
||||
request_->set_url(strs[1]);
|
||||
request_->set_method(std::move(strs[0]));
|
||||
request_->set_url(std::move(strs[1]));
|
||||
|
||||
// HTTP version is ignored.
|
||||
|
||||
|
||||
@@ -10,12 +10,12 @@ namespace webcc {
|
||||
class HttpRequest;
|
||||
|
||||
class HttpRequestParser : public HttpParser {
|
||||
public:
|
||||
public:
|
||||
explicit HttpRequestParser(HttpRequest* request);
|
||||
|
||||
~HttpRequestParser() override = default;
|
||||
|
||||
private:
|
||||
private:
|
||||
bool ParseStartLine(const std::string& line) override;
|
||||
|
||||
HttpRequest* request_;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
namespace webcc {
|
||||
|
||||
class HttpResponse : public HttpMessage {
|
||||
public:
|
||||
public:
|
||||
HttpResponse() : status_(http::Status::kOK) {}
|
||||
|
||||
~HttpResponse() override = default;
|
||||
@@ -25,7 +25,7 @@ class HttpResponse : public HttpMessage {
|
||||
// TODO: Avoid copy.
|
||||
static HttpResponse Fault(http::Status status);
|
||||
|
||||
private:
|
||||
private:
|
||||
int status_;
|
||||
};
|
||||
|
||||
|
||||
@@ -10,12 +10,12 @@ namespace webcc {
|
||||
class HttpResponse;
|
||||
|
||||
class HttpResponseParser : public HttpParser {
|
||||
public:
|
||||
public:
|
||||
explicit HttpResponseParser(HttpResponse* response);
|
||||
|
||||
~HttpResponseParser() override = default;
|
||||
|
||||
private:
|
||||
private:
|
||||
// Parse HTTP start line; E.g., "HTTP/1.1 200 OK".
|
||||
bool ParseStartLine(const std::string& line) override;
|
||||
|
||||
|
||||
+2
-2
@@ -17,7 +17,7 @@ class HttpRequestHandler;
|
||||
// HTTP server accepts TCP connections from TCP clients.
|
||||
// NOTE: Only support IPv4.
|
||||
class HttpServer {
|
||||
public:
|
||||
public:
|
||||
HttpServer(std::uint16_t port, std::size_t workers);
|
||||
|
||||
virtual ~HttpServer() = default;
|
||||
@@ -27,7 +27,7 @@ class HttpServer {
|
||||
// Run the server's io_service loop.
|
||||
void Run();
|
||||
|
||||
private:
|
||||
private:
|
||||
// Register to handle the signals that indicate when the server should exit.
|
||||
void RegisterSignals();
|
||||
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
#include "webcc/http_ssl_async_client.h"
|
||||
|
||||
#include "boost/asio/connect.hpp"
|
||||
|
||||
#include "webcc/logger.h"
|
||||
|
||||
using tcp = boost::asio::ip::tcp;
|
||||
namespace ssl = boost::asio::ssl;
|
||||
|
||||
namespace webcc {
|
||||
|
||||
HttpSslAsyncClient::HttpSslAsyncClient(boost::asio::io_context& io_context,
|
||||
std::size_t buffer_size,
|
||||
bool ssl_verify)
|
||||
: HttpAsyncClientBase(io_context, buffer_size),
|
||||
ssl_context_(ssl::context::sslv23),
|
||||
ssl_socket_(io_context, ssl_context_),
|
||||
ssl_verify_(ssl_verify) {
|
||||
// Use the default paths for finding CA certificates.
|
||||
ssl_context_.set_default_verify_paths();
|
||||
}
|
||||
|
||||
void HttpSslAsyncClient::Resolve() {
|
||||
DoResolve(kHttpSslPort);
|
||||
}
|
||||
|
||||
void HttpSslAsyncClient::DoHandshake() {
|
||||
if (ssl_verify_) {
|
||||
ssl_socket_.set_verify_mode(ssl::verify_peer);
|
||||
} else {
|
||||
ssl_socket_.set_verify_mode(ssl::verify_none);
|
||||
}
|
||||
|
||||
ssl_socket_.set_verify_callback(ssl::rfc2818_verification(request_->host()));
|
||||
|
||||
ssl_socket_.async_handshake(ssl::stream_base::client,
|
||||
std::bind(&HttpSslAsyncClient::OnHandshake,
|
||||
shared_from_this(),
|
||||
std::placeholders::_1));
|
||||
}
|
||||
|
||||
void HttpSslAsyncClient::OnHandshake(boost::system::error_code ec) {
|
||||
if (ec) {
|
||||
LOG_ERRO("Handshake error (%s).", ec.message().c_str());
|
||||
response_callback_(response_, kHandshakeError, false);
|
||||
return;
|
||||
}
|
||||
|
||||
DoWrite();
|
||||
}
|
||||
|
||||
void HttpSslAsyncClient::SocketAsyncConnect(const Endpoints& endpoints,
|
||||
ConnectHandler&& handler) {
|
||||
boost::asio::async_connect(ssl_socket_.lowest_layer(), endpoints,
|
||||
std::move(handler));
|
||||
}
|
||||
|
||||
void HttpSslAsyncClient::SocketAsyncWrite(WriteHandler&& handler) {
|
||||
boost::asio::async_write(ssl_socket_, request_->ToBuffers(),
|
||||
std::move(handler));
|
||||
}
|
||||
|
||||
void HttpSslAsyncClient::SocketAsyncReadSome(ReadHandler&& handler) {
|
||||
ssl_socket_.async_read_some(boost::asio::buffer(buffer_), std::move(handler));
|
||||
}
|
||||
|
||||
void HttpSslAsyncClient::SocketClose(boost::system::error_code* ec) {
|
||||
ssl_socket_.lowest_layer().close(*ec);
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
@@ -1,69 +0,0 @@
|
||||
#ifndef WEBCC_HTTP_SSL_ASYNC_CLIENT_H_
|
||||
#define WEBCC_HTTP_SSL_ASYNC_CLIENT_H_
|
||||
|
||||
#include "webcc/http_async_client_base.h"
|
||||
|
||||
#include "boost/asio/ssl.hpp"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class HttpSslAsyncClient;
|
||||
typedef std::shared_ptr<HttpSslAsyncClient> HttpSslAsyncClientPtr;
|
||||
|
||||
// HTTP SSL (a.k.a., HTTPS) asynchronous client.
|
||||
class HttpSslAsyncClient : public HttpAsyncClientBase {
|
||||
public:
|
||||
~HttpSslAsyncClient() = default;
|
||||
|
||||
// Forbid to create HttpSslAsyncClient in stack since it's derived from
|
||||
// std::shared_from_this.
|
||||
static HttpSslAsyncClientPtr New(boost::asio::io_context& io_context,
|
||||
std::size_t buffer_size = 0,
|
||||
bool ssl_verify = true) {
|
||||
return HttpSslAsyncClientPtr{
|
||||
new HttpSslAsyncClient(io_context, buffer_size, ssl_verify)
|
||||
};
|
||||
}
|
||||
|
||||
// See https://stackoverflow.com/q/657155/6825348
|
||||
std::shared_ptr<HttpSslAsyncClient> shared_from_this() {
|
||||
return shared_from_base<HttpSslAsyncClient>();
|
||||
}
|
||||
|
||||
private:
|
||||
// SSL verification (|ssl_verify|) needs CA certificates to be found
|
||||
// in the default verify paths of OpenSSL. On Windows, it means you need to
|
||||
// set environment variable SSL_CERT_FILE properly.
|
||||
explicit HttpSslAsyncClient(boost::asio::io_context& io_context,
|
||||
std::size_t buffer_size = 0,
|
||||
bool ssl_verify = true);
|
||||
|
||||
void Resolve() final;
|
||||
|
||||
// Override to do handshake after connected.
|
||||
void OnConnected() final {
|
||||
DoHandshake();
|
||||
}
|
||||
|
||||
void DoHandshake();
|
||||
void OnHandshake(boost::system::error_code ec);
|
||||
|
||||
void SocketAsyncConnect(const Endpoints& endpoints,
|
||||
ConnectHandler&& handler) final;
|
||||
|
||||
void SocketAsyncWrite(WriteHandler&& handler) final;
|
||||
|
||||
void SocketAsyncReadSome(ReadHandler&& handler) final;
|
||||
|
||||
void SocketClose(boost::system::error_code* ec) final;
|
||||
|
||||
boost::asio::ssl::context ssl_context_;
|
||||
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_socket_;
|
||||
|
||||
// Verify the certificate of the peer (remote server) or not.
|
||||
bool ssl_verify_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_HTTP_SSL_ASYNC_CLIENT_H_
|
||||
@@ -9,7 +9,7 @@ namespace webcc {
|
||||
|
||||
// HTTP SSL (a.k.a., HTTPS) synchronous client.
|
||||
class HttpSslClient : public HttpClientBase {
|
||||
public:
|
||||
public:
|
||||
// SSL verification (|ssl_verify|) needs CA certificates to be found
|
||||
// in the default verify paths of OpenSSL. On Windows, it means you need to
|
||||
// set environment variable SSL_CERT_FILE properly.
|
||||
@@ -17,7 +17,7 @@ class HttpSslClient : public HttpClientBase {
|
||||
|
||||
~HttpSslClient() = default;
|
||||
|
||||
private:
|
||||
private:
|
||||
Error Handshake(const std::string& host);
|
||||
|
||||
// Override to do handshake after connected.
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ namespace webcc {
|
||||
|
||||
template <typename T>
|
||||
class Queue {
|
||||
public:
|
||||
public:
|
||||
Queue() = default;
|
||||
|
||||
Queue(const Queue&) = delete;
|
||||
@@ -49,7 +49,7 @@ class Queue {
|
||||
not_empty_cv_.notify_one();
|
||||
}
|
||||
|
||||
private:
|
||||
private:
|
||||
std::list<T> message_list_;
|
||||
std::mutex mutex_;
|
||||
std::condition_variable not_empty_cv_;
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
#include "webcc/rest_async_client.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
RestAsyncClient::RestAsyncClient(boost::asio::io_context& io_context,
|
||||
const std::string& host,
|
||||
const std::string& port,
|
||||
std::size_t buffer_size)
|
||||
: io_context_(io_context),
|
||||
host_(host), port_(port),
|
||||
timeout_seconds_(0),
|
||||
buffer_size_(buffer_size) {
|
||||
}
|
||||
|
||||
void RestAsyncClient::Request(const std::string& method,
|
||||
const std::string& url,
|
||||
std::string&& content,
|
||||
HttpResponseCallback callback) {
|
||||
HttpRequestPtr http_request(new HttpRequest(method, url, host_, port_));
|
||||
|
||||
if (!content.empty()) {
|
||||
http_request->SetContent(std::move(content), true);
|
||||
http_request->SetContentType(http::media_types::kApplicationJson,
|
||||
http::charsets::kUtf8);
|
||||
}
|
||||
|
||||
http_request->Prepare();
|
||||
|
||||
auto http_async_client = HttpAsyncClient::New(io_context_, buffer_size_);
|
||||
|
||||
if (timeout_seconds_ > 0) {
|
||||
http_async_client->SetTimeout(timeout_seconds_);
|
||||
}
|
||||
|
||||
http_async_client->Request(http_request, callback);
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
@@ -1,61 +0,0 @@
|
||||
#ifndef WEBCC_REST_ASYNC_CLIENT_H_
|
||||
#define WEBCC_REST_ASYNC_CLIENT_H_
|
||||
|
||||
#include <string>
|
||||
#include <utility> // for move()
|
||||
|
||||
#include "webcc/http_async_client.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class RestAsyncClient {
|
||||
public:
|
||||
RestAsyncClient(boost::asio::io_context& io_context,
|
||||
const std::string& host, const std::string& port,
|
||||
std::size_t buffer_size = 0);
|
||||
|
||||
void SetTimeout(int seconds) {
|
||||
timeout_seconds_ = seconds;
|
||||
}
|
||||
|
||||
void Get(const std::string& url, HttpResponseCallback callback) {
|
||||
Request(kHttpGet, url, "", callback);
|
||||
}
|
||||
|
||||
void Post(const std::string& url, std::string&& content,
|
||||
HttpResponseCallback callback) {
|
||||
Request(kHttpPost, url, std::move(content), callback);
|
||||
}
|
||||
|
||||
void Put(const std::string& url, std::string&& content,
|
||||
HttpResponseCallback callback) {
|
||||
Request(kHttpPut, url, std::move(content), callback);
|
||||
}
|
||||
|
||||
void Patch(const std::string& url, std::string&& content,
|
||||
HttpResponseCallback callback) {
|
||||
Request(kHttpPatch, url, std::move(content), callback);
|
||||
}
|
||||
|
||||
void Delete(const std::string& url, HttpResponseCallback callback) {
|
||||
Request(kHttpDelete, url, "", callback);
|
||||
}
|
||||
|
||||
private:
|
||||
void Request(const std::string& method, const std::string& url,
|
||||
std::string&& content, HttpResponseCallback callback);
|
||||
|
||||
boost::asio::io_context& io_context_;
|
||||
|
||||
std::string host_;
|
||||
std::string port_;
|
||||
|
||||
// Timeout in seconds; only effective when > 0.
|
||||
int timeout_seconds_;
|
||||
|
||||
std::size_t buffer_size_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_REST_ASYNC_CLIENT_H_
|
||||
@@ -1,34 +0,0 @@
|
||||
#include "webcc/rest_client.h"
|
||||
#include "webcc/utility.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
RestClient::RestClient(const std::string& host, const std::string& port,
|
||||
std::size_t buffer_size)
|
||||
: host_(host), port_(port), http_client_(buffer_size) {
|
||||
AdjustHostPort(host_, port_);
|
||||
}
|
||||
|
||||
bool RestClient::Request(const std::string& method, const std::string& url,
|
||||
std::string&& content, std::size_t buffer_size) {
|
||||
HttpRequest http_request(method, url, host_, port_);
|
||||
|
||||
http_request.SetHeader(http::headers::kAccept,
|
||||
http::media_types::kApplicationJson);
|
||||
|
||||
if (!content.empty()) {
|
||||
http_request.SetContent(std::move(content), true);
|
||||
http_request.SetContentType(http::media_types::kApplicationJson,
|
||||
http::charsets::kUtf8);
|
||||
}
|
||||
|
||||
http_request.Prepare();
|
||||
|
||||
if (!http_client_.Request(http_request, buffer_size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
@@ -1,94 +0,0 @@
|
||||
#ifndef WEBCC_REST_CLIENT_H_
|
||||
#define WEBCC_REST_CLIENT_H_
|
||||
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <utility> // for move()
|
||||
|
||||
#include "webcc/globals.h"
|
||||
#include "webcc/http_client.h"
|
||||
#include "webcc/http_request.h"
|
||||
#include "webcc/http_response.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class RestClient {
|
||||
public:
|
||||
// If |port| is empty, |host| will be checked to see if it contains port or
|
||||
// not (separated by ':').
|
||||
explicit RestClient(const std::string& host,
|
||||
const std::string& port = "",
|
||||
std::size_t buffer_size = 0);
|
||||
|
||||
~RestClient() = default;
|
||||
|
||||
WEBCC_DELETE_COPY_ASSIGN(RestClient);
|
||||
|
||||
void SetTimeout(int seconds) {
|
||||
http_client_.SetTimeout(seconds);
|
||||
}
|
||||
|
||||
// NOTE:
|
||||
// The return value of the following methods (Get, Post, etc.) only indicates
|
||||
// if the socket communication is successful or not. Check error() and
|
||||
// timed_out() for more information if it's failed. Check response_status()
|
||||
// instead for the HTTP status code.
|
||||
|
||||
inline bool Get(const std::string& url, std::size_t buffer_size = 0) {
|
||||
return Request("GET", url, "", buffer_size);
|
||||
}
|
||||
|
||||
inline bool Post(const std::string& url, std::string&& content,
|
||||
std::size_t buffer_size = 0) {
|
||||
return Request("POST", url, std::move(content), buffer_size);
|
||||
}
|
||||
|
||||
inline bool Put(const std::string& url, std::string&& content,
|
||||
std::size_t buffer_size = 0) {
|
||||
return Request("PUT", url, std::move(content), buffer_size);
|
||||
}
|
||||
|
||||
inline bool Patch(const std::string& url, std::string&& content,
|
||||
std::size_t buffer_size = 0) {
|
||||
return Request("PATCH", url, std::move(content), buffer_size);
|
||||
}
|
||||
|
||||
inline bool Delete(const std::string& url, std::size_t buffer_size = 0) {
|
||||
return Request(kHttpDelete, url, "", buffer_size);
|
||||
}
|
||||
|
||||
HttpResponsePtr response() const {
|
||||
return http_client_.response();
|
||||
}
|
||||
|
||||
int response_status() const {
|
||||
assert(response());
|
||||
return response()->status();
|
||||
}
|
||||
|
||||
const std::string& response_content() const {
|
||||
assert(response());
|
||||
return response()->content();
|
||||
}
|
||||
|
||||
bool timed_out() const {
|
||||
return http_client_.timed_out();
|
||||
}
|
||||
|
||||
Error error() const {
|
||||
return http_client_.error();
|
||||
}
|
||||
|
||||
private:
|
||||
bool Request(const std::string& method, const std::string& url,
|
||||
std::string&& content, std::size_t buffer_size);
|
||||
|
||||
std::string host_;
|
||||
std::string port_;
|
||||
|
||||
HttpClient http_client_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_REST_CLIENT_H_
|
||||
+2
-2
@@ -12,7 +12,7 @@
|
||||
namespace webcc {
|
||||
|
||||
class RestServer : public HttpServer {
|
||||
public:
|
||||
public:
|
||||
RestServer(std::uint16_t port, std::size_t workers)
|
||||
: HttpServer(port, workers) {
|
||||
}
|
||||
@@ -31,7 +31,7 @@ class RestServer : public HttpServer {
|
||||
return request_handler_.Bind(service, url, is_regex);
|
||||
}
|
||||
|
||||
private:
|
||||
private:
|
||||
HttpRequestHandler* GetRequestHandler() override {
|
||||
return &request_handler_;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ struct RestResponse {
|
||||
|
||||
// Base class for your REST service.
|
||||
class RestService {
|
||||
public:
|
||||
public:
|
||||
virtual ~RestService() = default;
|
||||
|
||||
// Handle REST request, output response.
|
||||
@@ -58,10 +58,10 @@ typedef std::shared_ptr<RestService> RestServicePtr;
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
class RestListService : public RestService {
|
||||
public:
|
||||
public:
|
||||
void Handle(const RestRequest& request, RestResponse* response) final;
|
||||
|
||||
protected:
|
||||
public:
|
||||
RestListService() = default;
|
||||
|
||||
virtual void Get(const UrlQuery& query, RestResponse* response) {
|
||||
@@ -75,10 +75,10 @@ class RestListService : public RestService {
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
class RestDetailService : public RestService {
|
||||
public:
|
||||
public:
|
||||
void Handle(const RestRequest& request, RestResponse* response) final;
|
||||
|
||||
protected:
|
||||
public:
|
||||
virtual void Get(const UrlSubMatches& url_sub_matches,
|
||||
const UrlQuery& query,
|
||||
RestResponse* response) {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace webcc {
|
||||
|
||||
class RestServiceManager {
|
||||
public:
|
||||
public:
|
||||
RestServiceManager() = default;
|
||||
|
||||
WEBCC_DELETE_COPY_ASSIGN(RestServiceManager);
|
||||
@@ -30,9 +30,9 @@ class RestServiceManager {
|
||||
RestServicePtr GetService(const std::string& url,
|
||||
std::vector<std::string>* sub_matches);
|
||||
|
||||
private:
|
||||
private:
|
||||
class ServiceItem {
|
||||
public:
|
||||
public:
|
||||
ServiceItem(RestServicePtr _service, const std::string& _url,
|
||||
bool _is_regex)
|
||||
: service(_service), url(_url), is_regex(_is_regex) {
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
#include "webcc/rest_ssl_client.h"
|
||||
|
||||
#include "boost/algorithm/string.hpp"
|
||||
|
||||
#include "webcc/utility.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
RestClientBase::RestClientBase(HttpClientBase* http_client_base,
|
||||
const SSMap& headers)
|
||||
: http_client_base_(http_client_base),
|
||||
content_media_type_(http::media_types::kApplicationJson),
|
||||
content_charset_(http::charsets::kUtf8),
|
||||
headers_(headers) {
|
||||
}
|
||||
|
||||
|
||||
RestSslClient::RestSslClient(const std::string& host, const std::string& port,
|
||||
bool ssl_verify, const SSMap& headers,
|
||||
std::size_t buffer_size)
|
||||
: RestClientBase(&http_ssl_client_, headers),
|
||||
host_(host), port_(port),
|
||||
http_ssl_client_(ssl_verify, buffer_size) {
|
||||
AdjustHostPort(host_, port_);
|
||||
}
|
||||
|
||||
bool RestSslClient::Request(const std::string& method, const std::string& url,
|
||||
std::string&& content, const SSMap& headers,
|
||||
std::size_t buffer_size) {
|
||||
HttpRequest http_request(method, url, host_, port_);
|
||||
|
||||
if (!content.empty()) {
|
||||
http_request.SetContent(std::move(content), true);
|
||||
http_request.SetContentType(content_media_type_, content_charset_);
|
||||
}
|
||||
|
||||
for (auto& h : headers_) {
|
||||
http_request.SetHeader(h.first, h.second);
|
||||
}
|
||||
|
||||
for (auto& h : headers) {
|
||||
http_request.SetHeader(h.first, h.second);
|
||||
}
|
||||
|
||||
http_request.Prepare();
|
||||
|
||||
if (!http_ssl_client_.Request(http_request, buffer_size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
@@ -1,130 +0,0 @@
|
||||
#ifndef WEBCC_REST_SSL_CLIENT_H_
|
||||
#define WEBCC_REST_SSL_CLIENT_H_
|
||||
|
||||
#include <cassert>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <utility> // for move()
|
||||
|
||||
#include "webcc/globals.h"
|
||||
#include "webcc/http_request.h"
|
||||
#include "webcc/http_response.h"
|
||||
#include "webcc/http_ssl_client.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
typedef std::map<std::string, std::string> SSMap;
|
||||
|
||||
class RestClientBase {
|
||||
public:
|
||||
RestClientBase(HttpClientBase* http_client_base,
|
||||
const SSMap& headers = {});
|
||||
|
||||
virtual ~RestClientBase() = default;
|
||||
|
||||
WEBCC_DELETE_COPY_ASSIGN(RestClientBase);
|
||||
|
||||
void SetTimeout(int seconds) {
|
||||
http_client_base_->SetTimeout(seconds);
|
||||
}
|
||||
|
||||
// Overwrite the default content type (json & utf-8).
|
||||
void SetContentType(const std::string& media_type,
|
||||
const std::string& charset) {
|
||||
content_media_type_ = media_type;
|
||||
content_charset_ = charset;
|
||||
}
|
||||
|
||||
HttpResponsePtr response() const {
|
||||
return http_client_base_->response();
|
||||
}
|
||||
|
||||
int response_status() const {
|
||||
assert(response());
|
||||
return response()->status();
|
||||
}
|
||||
|
||||
const std::string& response_content() const {
|
||||
assert(response());
|
||||
return response()->content();
|
||||
}
|
||||
|
||||
bool timed_out() const {
|
||||
return http_client_base_->timed_out();
|
||||
}
|
||||
|
||||
Error error() const {
|
||||
return http_client_base_->error();
|
||||
}
|
||||
|
||||
protected:
|
||||
// Default: "application/json".
|
||||
std::string content_media_type_;
|
||||
// Default: "utf-8".
|
||||
std::string content_charset_;
|
||||
|
||||
// Default headers for each request sent from this session.
|
||||
std::map<std::string, std::string> headers_;
|
||||
|
||||
private:
|
||||
HttpClientBase* http_client_base_;
|
||||
};
|
||||
|
||||
|
||||
class RestSslClient : public RestClientBase {
|
||||
public:
|
||||
// If |port| is empty, |host| will be checked to see if it contains port or
|
||||
// not (separated by ':').
|
||||
explicit RestSslClient(const std::string& host,
|
||||
const std::string& port = "",
|
||||
bool ssl_verify = true,
|
||||
const SSMap& headers = {},
|
||||
std::size_t buffer_size = 0);
|
||||
|
||||
~RestSslClient() = default;
|
||||
|
||||
// NOTE:
|
||||
// The return value of the following methods (Get, Post, etc.) only indicates
|
||||
// if the socket communication is successful or not. Check error() and
|
||||
// timed_out() for more information if it's failed. Check response_status()
|
||||
// instead for the HTTP status code.
|
||||
|
||||
inline bool Get(const std::string& url, const SSMap& headers = {},
|
||||
std::size_t buffer_size = 0) {
|
||||
return Request(kHttpGet, url, "", headers, buffer_size);
|
||||
}
|
||||
|
||||
inline bool Post(const std::string& url, std::string&& content,
|
||||
const SSMap& headers = {}, std::size_t buffer_size = 0) {
|
||||
return Request(Post, url, std::move(content), headers, buffer_size);
|
||||
}
|
||||
|
||||
inline bool Put(const std::string& url, std::string&& content,
|
||||
const SSMap& headers = {}, std::size_t buffer_size = 0) {
|
||||
return Request(kHttpPut, url, std::move(content), headers, buffer_size);
|
||||
}
|
||||
|
||||
inline bool Patch(const std::string& url, std::string&& content,
|
||||
const SSMap& headers = {}, std::size_t buffer_size = 0) {
|
||||
return Request(kHttpPatch, url, std::move(content), headers, buffer_size);
|
||||
}
|
||||
|
||||
inline bool Delete(const std::string& url, const SSMap& headers = {},
|
||||
std::size_t buffer_size = 0) {
|
||||
return Request(kHttpDelete, url, "", headers, buffer_size);
|
||||
}
|
||||
|
||||
bool Request(const std::string& method, const std::string& url,
|
||||
std::string&& content, const SSMap& headers = {},
|
||||
std::size_t buffer_size = 0);
|
||||
|
||||
private:
|
||||
std::string host_;
|
||||
std::string port_;
|
||||
|
||||
HttpSslClient http_ssl_client_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_REST_SSL_CLIENT_H_
|
||||
@@ -1,102 +0,0 @@
|
||||
#include "webcc/soap_async_client.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <utility> // for move()
|
||||
|
||||
#include "webcc/soap_globals.h"
|
||||
#include "webcc/soap_request.h"
|
||||
#include "webcc/soap_response.h"
|
||||
#include "webcc/utility.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
SoapAsyncClient::SoapAsyncClient(boost::asio::io_context& io_context,
|
||||
const std::string& host,
|
||||
const std::string& port,
|
||||
SoapVersion soap_version,
|
||||
std::size_t buffer_size)
|
||||
: io_context_(io_context),
|
||||
host_(host), port_(port),
|
||||
soap_version_(soap_version),
|
||||
buffer_size_(buffer_size),
|
||||
format_raw_(true), timeout_seconds_(0) {
|
||||
AdjustHostPort(host_, port_);
|
||||
}
|
||||
|
||||
void SoapAsyncClient::Request(const std::string& operation,
|
||||
std::vector<SoapParameter>&& parameters,
|
||||
SoapResponseCallback soap_response_callback) {
|
||||
assert(service_ns_.IsValid());
|
||||
assert(!url_.empty() && !host_.empty());
|
||||
assert(!result_name_.empty());
|
||||
|
||||
SoapRequest soap_request;
|
||||
|
||||
// Set SOAP envelope namespace according to SOAP version.
|
||||
if (soap_version_ == kSoapV11) {
|
||||
soap_request.set_soapenv_ns(kSoapEnvNamespaceV11);
|
||||
} else {
|
||||
soap_request.set_soapenv_ns(kSoapEnvNamespaceV12);
|
||||
}
|
||||
|
||||
soap_request.set_service_ns(service_ns_);
|
||||
|
||||
soap_request.set_operation(operation);
|
||||
|
||||
for (SoapParameter& p : parameters) {
|
||||
soap_request.AddParameter(std::move(p));
|
||||
}
|
||||
|
||||
std::string http_content;
|
||||
soap_request.ToXml(format_raw_, indent_str_, &http_content);
|
||||
|
||||
HttpRequestPtr http_request(new HttpRequest(kHttpPost, url_, host_, port_));
|
||||
|
||||
http_request->SetContent(std::move(http_content), true);
|
||||
|
||||
if (soap_version_ == kSoapV11) {
|
||||
http_request->SetContentType(http::media_types::kTextXml,
|
||||
http::charsets::kUtf8);
|
||||
} else {
|
||||
http_request->SetContentType(http::media_types::kApplicationSoapXml,
|
||||
http::charsets::kUtf8);
|
||||
}
|
||||
|
||||
http_request->SetHeader(kSoapAction, operation);
|
||||
http_request->Prepare();
|
||||
|
||||
auto http_async_client = HttpAsyncClient::New(io_context_, buffer_size_);
|
||||
|
||||
if (timeout_seconds_ > 0) {
|
||||
http_async_client->SetTimeout(timeout_seconds_);
|
||||
}
|
||||
|
||||
auto http_response_callback = std::bind(&SoapAsyncClient::OnHttpResponse,
|
||||
this, soap_response_callback,
|
||||
std::placeholders::_1,
|
||||
std::placeholders::_2,
|
||||
std::placeholders::_3);
|
||||
|
||||
http_async_client->Request(http_request, http_response_callback);
|
||||
}
|
||||
|
||||
void SoapAsyncClient::OnHttpResponse(SoapResponseCallback soap_response_callback,
|
||||
HttpResponsePtr http_response,
|
||||
Error error, bool timed_out) {
|
||||
if (error != kNoError) {
|
||||
soap_response_callback("", error, timed_out);
|
||||
} else {
|
||||
SoapResponse soap_response;
|
||||
// TODO
|
||||
//soap_response.set_result_name(result_name_);
|
||||
|
||||
if (!soap_response.FromXml(http_response->content())) {
|
||||
soap_response_callback("", kXmlError, false);
|
||||
} else {
|
||||
// TODO
|
||||
//soap_response_callback(soap_response.result_moved(), kNoError, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
@@ -1,91 +0,0 @@
|
||||
#ifndef WEBCC_SOAP_ASYNC_CLIENT_H_
|
||||
#define WEBCC_SOAP_ASYNC_CLIENT_H_
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "webcc/http_async_client.h"
|
||||
#include "webcc/soap_message.h"
|
||||
#include "webcc/soap_parameter.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
// Response callback.
|
||||
typedef std::function<void(std::string, Error, bool)> SoapResponseCallback;
|
||||
|
||||
class SoapAsyncClient {
|
||||
public:
|
||||
// If |port| is empty, |host| will be checked to see if it contains port or
|
||||
// not (separated by ':').
|
||||
SoapAsyncClient(boost::asio::io_context& io_context, // NOLINT
|
||||
const std::string& host, const std::string& port = "",
|
||||
SoapVersion soap_version = kSoapV12,
|
||||
std::size_t buffer_size = 0);
|
||||
|
||||
~SoapAsyncClient() = default;
|
||||
|
||||
WEBCC_DELETE_COPY_ASSIGN(SoapAsyncClient);
|
||||
|
||||
void set_timeout_seconds(int timeout_seconds) {
|
||||
timeout_seconds_ = timeout_seconds;
|
||||
}
|
||||
|
||||
void set_url(const std::string& url) { url_ = url; }
|
||||
|
||||
void set_service_ns(const SoapNamespace& service_ns) {
|
||||
service_ns_ = service_ns;
|
||||
}
|
||||
|
||||
void set_result_name(const std::string& result_name) {
|
||||
result_name_ = result_name;
|
||||
}
|
||||
|
||||
void set_format_raw(bool format_raw) { format_raw_ = format_raw; }
|
||||
|
||||
void set_indent_str(const std::string& indent_str) {
|
||||
indent_str_ = indent_str;
|
||||
}
|
||||
|
||||
void Request(const std::string& operation,
|
||||
std::vector<SoapParameter>&& parameters,
|
||||
SoapResponseCallback soap_response_callback);
|
||||
|
||||
private:
|
||||
void OnHttpResponse(SoapResponseCallback soap_response_callback,
|
||||
HttpResponsePtr http_response,
|
||||
Error error, bool timed_out);
|
||||
|
||||
boost::asio::io_context& io_context_;
|
||||
|
||||
std::string host_;
|
||||
std::string port_; // Leave this empty to use default 80.
|
||||
|
||||
SoapVersion soap_version_;
|
||||
|
||||
std::size_t buffer_size_;
|
||||
|
||||
// Request URL.
|
||||
std::string url_;
|
||||
|
||||
// Namespace for your web service.
|
||||
SoapNamespace service_ns_;
|
||||
|
||||
// Response result XML node name.
|
||||
// E.g., "Result".
|
||||
std::string result_name_;
|
||||
|
||||
// Format request XML without any indentation or line breaks.
|
||||
bool format_raw_;
|
||||
|
||||
// Indent string for request XML.
|
||||
// Applicable when |format_raw_| is false.
|
||||
std::string indent_str_;
|
||||
|
||||
// Timeout in seconds; only effective when > 0.
|
||||
int timeout_seconds_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_SOAP_ASYNC_CLIENT_H_
|
||||
+2
-2
@@ -12,7 +12,7 @@
|
||||
namespace webcc {
|
||||
|
||||
class SoapClient {
|
||||
public:
|
||||
public:
|
||||
// If |port| is empty, |host| will be checked to see if it contains port or
|
||||
// not (separated by ':').
|
||||
explicit SoapClient(const std::string& host, const std::string& port = "",
|
||||
@@ -69,7 +69,7 @@ class SoapClient {
|
||||
|
||||
std::shared_ptr<SoapFault> fault() const { return fault_; }
|
||||
|
||||
private:
|
||||
private:
|
||||
std::string host_;
|
||||
std::string port_; // Leave this empty to use default 80.
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace webcc {
|
||||
|
||||
// Base class for SOAP request and response.
|
||||
class SoapMessage {
|
||||
public:
|
||||
public:
|
||||
virtual ~SoapMessage() = default;
|
||||
|
||||
// E.g., set as kSoapEnvNamespace.
|
||||
@@ -38,7 +38,7 @@ class SoapMessage {
|
||||
// Parse from SOAP XML.
|
||||
bool FromXml(const std::string& xml_string);
|
||||
|
||||
protected:
|
||||
public:
|
||||
// Convert to SOAP body XML.
|
||||
virtual void ToXmlBody(pugi::xml_node xbody) = 0;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace webcc {
|
||||
|
||||
// Key-value SOAP parameter.
|
||||
class SoapParameter {
|
||||
public:
|
||||
public:
|
||||
SoapParameter() : as_cdata_(false) {
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ class SoapParameter {
|
||||
|
||||
bool as_cdata() const { return as_cdata_; }
|
||||
|
||||
private:
|
||||
private:
|
||||
std::string key_;
|
||||
std::string value_;
|
||||
bool as_cdata_;
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace webcc {
|
||||
// Used to compose the SOAP request envelope XML which will be sent as the HTTP
|
||||
// request body.
|
||||
class SoapRequest : public SoapMessage {
|
||||
public:
|
||||
public:
|
||||
void AddParameter(const SoapParameter& parameter);
|
||||
|
||||
void AddParameter(SoapParameter&& parameter);
|
||||
@@ -21,11 +21,11 @@ class SoapRequest : public SoapMessage {
|
||||
// Get parameter value by key.
|
||||
const std::string& GetParameter(const std::string& key) const;
|
||||
|
||||
protected:
|
||||
public:
|
||||
void ToXmlBody(pugi::xml_node xbody) override;
|
||||
bool FromXmlBody(pugi::xml_node xbody) override;
|
||||
|
||||
private:
|
||||
private:
|
||||
std::vector<SoapParameter> parameters_;
|
||||
};
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
namespace webcc {
|
||||
|
||||
class SoapResponse : public SoapMessage {
|
||||
public:
|
||||
public:
|
||||
// Response result parser.
|
||||
// Called on each child of the response node.
|
||||
// Example:
|
||||
@@ -50,11 +50,11 @@ class SoapResponse : public SoapMessage {
|
||||
// - composer(xxxResponse);
|
||||
// The composer then add proper children to xxxResponse as the result.
|
||||
class Composer {
|
||||
public:
|
||||
public:
|
||||
void operator()(pugi::xml_node xresponse) {
|
||||
Compose(xresponse);
|
||||
}
|
||||
private:
|
||||
private:
|
||||
virtual void Compose(pugi::xml_node xresponse) = 0;
|
||||
};
|
||||
|
||||
@@ -127,12 +127,12 @@ class SoapResponse : public SoapMessage {
|
||||
|
||||
// TODO: Set fault from server.
|
||||
|
||||
protected:
|
||||
public:
|
||||
void ToXmlBody(pugi::xml_node xbody) override;
|
||||
|
||||
bool FromXmlBody(pugi::xml_node xbody) override;
|
||||
|
||||
private:
|
||||
private:
|
||||
// Fault element if any.
|
||||
std::shared_ptr<SoapFault> fault_;
|
||||
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@
|
||||
namespace webcc {
|
||||
|
||||
class SoapServer : public HttpServer {
|
||||
public:
|
||||
public:
|
||||
SoapServer(std::uint16_t port, std::size_t workers,
|
||||
SoapVersion soap_version = kSoapV12)
|
||||
: HttpServer(port, workers),
|
||||
@@ -36,7 +36,7 @@ class SoapServer : public HttpServer {
|
||||
return request_handler_.Bind(service, url);
|
||||
}
|
||||
|
||||
private:
|
||||
private:
|
||||
HttpRequestHandler* GetRequestHandler() override {
|
||||
return &request_handler_;
|
||||
}
|
||||
|
||||
@@ -12,14 +12,14 @@ class SoapResponse;
|
||||
|
||||
// Base class for your SOAP service.
|
||||
class SoapService {
|
||||
public:
|
||||
public:
|
||||
virtual ~SoapService() = default;
|
||||
|
||||
// Handle SOAP request, output the response.
|
||||
virtual bool Handle(const SoapRequest& soap_request,
|
||||
SoapResponse* soap_response) = 0;
|
||||
|
||||
protected:
|
||||
public:
|
||||
http::Status http_status_ = http::Status::kOK;
|
||||
};
|
||||
|
||||
|
||||
+2
-2
@@ -73,7 +73,7 @@ std::string GetNSAttr(const pugi::xml_node& xnode,
|
||||
// XmlStringWriter writer(&xml_string);
|
||||
// xdoc.save/print(writer);
|
||||
class XmlStringWriter : public pugi::xml_writer {
|
||||
public:
|
||||
public:
|
||||
explicit XmlStringWriter(std::string* result) : result_(result) {
|
||||
result_->clear();
|
||||
}
|
||||
@@ -82,7 +82,7 @@ class XmlStringWriter : public pugi::xml_writer {
|
||||
result_->append(static_cast<const char*>(data), size);
|
||||
}
|
||||
|
||||
private:
|
||||
private:
|
||||
std::string* result_;
|
||||
};
|
||||
|
||||
|
||||
+30
-1
@@ -7,6 +7,8 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "webcc/globals.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -57,9 +59,36 @@ class Url {
|
||||
public:
|
||||
Url() = default;
|
||||
|
||||
// TODO: decode/encode/encoded?
|
||||
explicit Url(const std::string& str, bool decode = true);
|
||||
|
||||
#if WEBCC_DEFAULT_MOVE_COPY_ASSIGN
|
||||
|
||||
Url(Url&&) = default;
|
||||
Url& operator=(Url&&) = default;
|
||||
|
||||
#else
|
||||
|
||||
Url(Url&& rhs)
|
||||
: scheme_(std::move(rhs.scheme_)),
|
||||
host_(std::move(rhs.host_)),
|
||||
port_(std::move(rhs.port_)),
|
||||
path_(std::move(rhs.path_)),
|
||||
query_(std::move(rhs.query_)) {
|
||||
}
|
||||
|
||||
Url& operator=(Url&& rhs) {
|
||||
if (&rhs != this) {
|
||||
scheme_ = std::move(rhs.scheme_);
|
||||
host_ = std::move(rhs.host_);
|
||||
port_ = std::move(rhs.port_);
|
||||
path_ = std::move(rhs.path_);
|
||||
query_ = std::move(rhs.query_);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
#endif // WEBCC_DEFAULT_MOVE_COPY_ASSIGN
|
||||
|
||||
void Init(const std::string& str, bool decode = true, bool clear = true);
|
||||
|
||||
const std::string& scheme() const {
|
||||
|
||||
Reference in New Issue
Block a user