upload
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
#ifndef WEBCC_BASE64_H_
|
||||
#define WEBCC_BASE64_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace webcc {
|
||||
|
||||
std::string Base64Encode(const std::uint8_t* data, std::size_t length);
|
||||
|
||||
std::string Base64Encode(const std::string& input);
|
||||
|
||||
std::string Base64Decode(const std::string& input);
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_BASE64_H_
|
||||
@@ -0,0 +1,204 @@
|
||||
#ifndef WEBCC_BODY_H_
|
||||
#define WEBCC_BODY_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "webcc/common.h"
|
||||
#include "webcc/fs.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
class Body {
|
||||
public:
|
||||
Body() = default;
|
||||
virtual ~Body() = default;
|
||||
|
||||
// Get the size in bytes of the body.
|
||||
virtual std::size_t GetSize() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool IsEmpty() const {
|
||||
return GetSize() == 0;
|
||||
}
|
||||
|
||||
#if WEBCC_ENABLE_GZIP
|
||||
|
||||
// Compress the data with Gzip.
|
||||
// If data size <= threshold (1400 bytes), no compression will be taken
|
||||
// and false will be simply returned.
|
||||
virtual bool Compress() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decompress the data.
|
||||
virtual bool Decompress() {
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif // WEBCC_ENABLE_GZIP
|
||||
|
||||
// Initialize the payload for iteration.
|
||||
// Usage:
|
||||
// InitPayload();
|
||||
// for (auto p = NextPayload(); !p.empty(); p = NextPayload()) {
|
||||
// }
|
||||
virtual void InitPayload() {
|
||||
}
|
||||
|
||||
// Get the next payload.
|
||||
// An empty payload returned indicates the end.
|
||||
virtual Payload NextPayload(bool free_previous = false) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Dump to output stream for logging purpose.
|
||||
virtual void Dump(std::ostream& os, const std::string& prefix) const {
|
||||
}
|
||||
};
|
||||
|
||||
using BodyPtr = std::shared_ptr<Body>;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
class StringBody : public Body {
|
||||
public:
|
||||
explicit StringBody(const std::string& data, bool compressed)
|
||||
: data_(data), compressed_(compressed) {
|
||||
}
|
||||
|
||||
explicit StringBody(std::string&& data, bool compressed)
|
||||
: data_(std::move(data)), compressed_(compressed) {
|
||||
}
|
||||
|
||||
std::size_t GetSize() const override {
|
||||
return data_.size();
|
||||
}
|
||||
|
||||
const std::string& data() const {
|
||||
return data_;
|
||||
}
|
||||
|
||||
bool compressed() const {
|
||||
return compressed_;
|
||||
}
|
||||
|
||||
#if WEBCC_ENABLE_GZIP
|
||||
|
||||
bool Compress() override;
|
||||
|
||||
bool Decompress() override;
|
||||
|
||||
#endif // WEBCC_ENABLE_GZIP
|
||||
|
||||
void InitPayload() override;
|
||||
|
||||
Payload NextPayload(bool free_previous = false) override;
|
||||
|
||||
void Dump(std::ostream& os, const std::string& prefix) const override;
|
||||
|
||||
private:
|
||||
std::string data_;
|
||||
|
||||
// Is the data compressed?
|
||||
bool compressed_;
|
||||
|
||||
// Index for (not really) iterating the payload.
|
||||
std::size_t index_ = 0;
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Multi-part form body for request.
|
||||
class FormBody : public Body {
|
||||
public:
|
||||
FormBody(const std::vector<FormPartPtr>& parts, const std::string& boundary);
|
||||
|
||||
std::size_t GetSize() const override;
|
||||
|
||||
const std::vector<FormPartPtr>& parts() const {
|
||||
return parts_;
|
||||
}
|
||||
|
||||
void InitPayload() override;
|
||||
|
||||
Payload NextPayload(bool free_previous = false) override;
|
||||
|
||||
void Dump(std::ostream& os, const std::string& prefix) const override;
|
||||
|
||||
private:
|
||||
void AddBoundary(Payload* payload);
|
||||
void AddBoundaryEnd(Payload* payload);
|
||||
|
||||
void Free(std::size_t index);
|
||||
|
||||
private:
|
||||
std::vector<FormPartPtr> parts_;
|
||||
std::string boundary_;
|
||||
|
||||
// Index for iterating the payload.
|
||||
std::size_t index_ = 0;
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// File body for server to serve a file without loading the whole of it into
|
||||
// the memory.
|
||||
class FileBody : public Body {
|
||||
public:
|
||||
// For message to be sent out.
|
||||
FileBody(const fs::path& path, std::size_t chunk_size);
|
||||
|
||||
// For message received.
|
||||
// No |chunk_size| is needed since you don't iterate the payload of a
|
||||
// received message.
|
||||
// If |auto_delete| is true, the file will be deleted on destructor unless it
|
||||
// is moved to another path (see Move()).
|
||||
FileBody(const fs::path& path, bool auto_delete = false);
|
||||
|
||||
~FileBody() override;
|
||||
|
||||
std::size_t GetSize() const override {
|
||||
return size_;
|
||||
}
|
||||
|
||||
void InitPayload() override;
|
||||
|
||||
Payload NextPayload(bool free_previous = false) override;
|
||||
|
||||
void Dump(std::ostream& os, const std::string& prefix) const override;
|
||||
|
||||
const fs::path& path() const {
|
||||
return path_;
|
||||
}
|
||||
|
||||
// Move (or rename) the file.
|
||||
// Used to move the streamed file of the received message to a new place.
|
||||
// Applicable to both client and server.
|
||||
// After move, the original path will be reset to empty.
|
||||
// If |new_path| and |path_| resolve to the same file, do nothing and just
|
||||
// return false.
|
||||
// If |new_path| resolves to an existing non-directory file, it is removed.
|
||||
// If |new_path| resolves to an existing directory, it is removed if empty
|
||||
// on ISO/IEC 9945 but is an error on Windows.
|
||||
// See fs::rename() for more details.
|
||||
bool Move(const fs::path& new_path);
|
||||
|
||||
private:
|
||||
fs::path path_;
|
||||
std::size_t chunk_size_;
|
||||
bool auto_delete_;
|
||||
|
||||
std::size_t size_; // File size in bytes
|
||||
|
||||
fs::ifstream ifstream_;
|
||||
std::string chunk_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_BODY_H_
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef WEBCC_CLIENT_H_
|
||||
#define WEBCC_CLIENT_H_
|
||||
|
||||
#include "webcc/client_base.h"
|
||||
#include "webcc/socket.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class Client final : public ClientBase {
|
||||
public:
|
||||
explicit Client(boost::asio::io_context& io_context)
|
||||
: ClientBase(io_context) {
|
||||
}
|
||||
|
||||
~Client() = default;
|
||||
|
||||
protected:
|
||||
void CreateSocket() override {
|
||||
socket_.reset(new Socket{ io_context_ });
|
||||
}
|
||||
|
||||
void Resolve() override {
|
||||
AsyncResolve("80");
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_CLIENT_H_
|
||||
@@ -0,0 +1,165 @@
|
||||
#ifndef WEBCC_CLIENT_BASE_H_
|
||||
#define WEBCC_CLIENT_BASE_H_
|
||||
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "boost/asio/io_context.hpp"
|
||||
#include "boost/asio/ip/tcp.hpp"
|
||||
#include "boost/asio/steady_timer.hpp"
|
||||
|
||||
#include "webcc/globals.h"
|
||||
#include "webcc/request.h"
|
||||
#include "webcc/response.h"
|
||||
#include "webcc/response_parser.h"
|
||||
#include "webcc/socket_base.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class ClientBase {
|
||||
public:
|
||||
explicit ClientBase(boost::asio::io_context& io_context);
|
||||
|
||||
ClientBase(const ClientBase&) = delete;
|
||||
ClientBase& operator=(const ClientBase&) = delete;
|
||||
|
||||
~ClientBase() = default;
|
||||
|
||||
void set_buffer_size(std::size_t buffer_size) {
|
||||
if (buffer_size > 0) {
|
||||
buffer_size_ = buffer_size;
|
||||
}
|
||||
}
|
||||
|
||||
void set_connect_timeout(int timeout) {
|
||||
if (timeout > 0) {
|
||||
connect_timeout_ = timeout;
|
||||
}
|
||||
}
|
||||
|
||||
void set_read_timeout(int timeout) {
|
||||
if (timeout > 0) {
|
||||
read_timeout_ = timeout;
|
||||
}
|
||||
}
|
||||
|
||||
// Set progress callback to be informed about the read progress.
|
||||
// NOTE: Don't use move semantics because in practice, there is no difference
|
||||
// between copying and moving an object of a closure type.
|
||||
// TODO: Support write progress
|
||||
void set_progress_callback(ProgressCallback callback) {
|
||||
progress_callback_ = callback;
|
||||
}
|
||||
|
||||
// Connect, send request, wait until response is received.
|
||||
Error Request(RequestPtr request, bool stream = false);
|
||||
|
||||
// Close the connection.
|
||||
// The async operation on the socket will be canceled.
|
||||
void Close();
|
||||
|
||||
bool connected() const {
|
||||
return connected_;
|
||||
}
|
||||
|
||||
ResponsePtr response() const {
|
||||
return response_;
|
||||
}
|
||||
|
||||
// Reset response object.
|
||||
// Used to make sure the response object will released even the client object
|
||||
// itself will be cached for keep-alive purpose.
|
||||
void Reset() {
|
||||
response_.reset();
|
||||
response_parser_.Init(nullptr, false);
|
||||
}
|
||||
|
||||
protected:
|
||||
// Create Socket or SslSocket.
|
||||
virtual void CreateSocket() = 0;
|
||||
|
||||
// Resolve host.
|
||||
virtual void Resolve() = 0;
|
||||
|
||||
void CloseSocket();
|
||||
|
||||
void AsyncResolve(string_view default_port);
|
||||
|
||||
void OnResolve(boost::system::error_code ec,
|
||||
boost::asio::ip::tcp::resolver::results_type endpoints);
|
||||
|
||||
void OnConnect(boost::system::error_code ec,
|
||||
boost::asio::ip::tcp::endpoint endpoint);
|
||||
|
||||
void AsyncWrite();
|
||||
void OnWrite(boost::system::error_code ec, std::size_t length);
|
||||
|
||||
void AsyncWriteBody();
|
||||
void OnWriteBody(boost::system::error_code ec, std::size_t length);
|
||||
|
||||
void HandleWriteError(boost::system::error_code ec);
|
||||
|
||||
void AsyncRead();
|
||||
void OnRead(boost::system::error_code ec, std::size_t length);
|
||||
|
||||
void AsyncWaitDeadlineTimer(int seconds);
|
||||
void OnDeadlineTimer(boost::system::error_code ec);
|
||||
void StopDeadlineTimer();
|
||||
|
||||
void FinishRequest();
|
||||
|
||||
protected:
|
||||
boost::asio::io_context& io_context_;
|
||||
|
||||
std::unique_ptr<SocketBase> socket_;
|
||||
|
||||
boost::asio::ip::tcp::resolver resolver_;
|
||||
|
||||
bool request_finished_ = true;
|
||||
std::condition_variable request_cv_;
|
||||
std::mutex request_mutex_;
|
||||
|
||||
RequestPtr request_;
|
||||
|
||||
ResponsePtr response_;
|
||||
ResponseParser response_parser_;
|
||||
|
||||
// The length already read.
|
||||
std::size_t length_read_ = 0;
|
||||
|
||||
// The buffer for reading response.
|
||||
std::vector<char> buffer_;
|
||||
|
||||
// The size of the buffer for reading response.
|
||||
// 0 means default value will be used.
|
||||
std::size_t buffer_size_ = kBufferSize;
|
||||
|
||||
// Timeout (seconds) for connecting to server.
|
||||
// Default as 0 to disable our own control (i.e., deadline_timer_).
|
||||
int connect_timeout_ = 0;
|
||||
|
||||
// Timeout (seconds) for reading response.
|
||||
int read_timeout_ = kMaxReadSeconds;
|
||||
|
||||
// Deadline timer for connecting to server.
|
||||
boost::asio::steady_timer deadline_timer_;
|
||||
bool deadline_timer_stopped_ = true;
|
||||
|
||||
// Socket connected or not.
|
||||
bool connected_ = false;
|
||||
|
||||
// Progress callback (optional).
|
||||
ProgressCallback progress_callback_;
|
||||
|
||||
// Current error.
|
||||
Error error_;
|
||||
};
|
||||
|
||||
using ClientPtr = std::shared_ptr<ClientBase>;
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_CLIENT_BASE_H_
|
||||
@@ -0,0 +1,64 @@
|
||||
#ifndef WEBCC_CLIENT_POOL_H_
|
||||
#define WEBCC_CLIENT_POOL_H_
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "webcc/client.h"
|
||||
#include "webcc/url.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
// Connection pool for keep-alive connections.
|
||||
class ClientPool {
|
||||
public:
|
||||
struct Key {
|
||||
std::string scheme;
|
||||
std::string host;
|
||||
std::string port;
|
||||
|
||||
Key() = default;
|
||||
|
||||
explicit Key(const Url& url)
|
||||
: scheme(url.scheme()), host(url.host()), port(url.port()) {
|
||||
}
|
||||
|
||||
bool operator==(const Key& rhs) const {
|
||||
return scheme == rhs.scheme && host == rhs.host && port == rhs.port;
|
||||
}
|
||||
|
||||
bool operator<(const Key& rhs) const {
|
||||
if (scheme < rhs.scheme) {
|
||||
return true;
|
||||
}
|
||||
if (host < rhs.host) {
|
||||
return true;
|
||||
}
|
||||
if (port < rhs.port) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
ClientPool() = default;
|
||||
|
||||
ClientPool(const ClientPool&) = delete;
|
||||
ClientPool& operator=(const ClientPool&) = delete;
|
||||
|
||||
~ClientPool();
|
||||
|
||||
ClientPtr Get(const Key& key) const;
|
||||
|
||||
void Add(const Key& key, ClientPtr client);
|
||||
|
||||
void Remove(const Key& key);
|
||||
|
||||
private:
|
||||
std::map<Key, ClientPtr> clients_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_CLIENT_POOL_H_
|
||||
@@ -0,0 +1,169 @@
|
||||
#ifndef WEBCC_CLIENT_SESSION_H_
|
||||
#define WEBCC_CLIENT_SESSION_H_
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "boost/asio/io_context.hpp"
|
||||
|
||||
#include "webcc/client_pool.h"
|
||||
#include "webcc/request_builder.h"
|
||||
#include "webcc/response.h"
|
||||
|
||||
#if WEBCC_ENABLE_SSL
|
||||
#include "boost/asio/ssl/context.hpp"
|
||||
#endif
|
||||
|
||||
namespace webcc {
|
||||
|
||||
// Client session provides connection-pooling, configuration and more.
|
||||
// If a client session is shared by multiple threads, the requests sent through
|
||||
// it will be serialized by using a mutex.
|
||||
class ClientSession {
|
||||
public:
|
||||
explicit ClientSession(std::size_t buffer_size = 0);
|
||||
|
||||
ClientSession(const ClientSession&) = delete;
|
||||
ClientSession& operator=(const ClientSession&) = delete;
|
||||
|
||||
~ClientSession();
|
||||
|
||||
// Start Asio loop in a thread.
|
||||
// You don't have to call Start() manually because it's called by the
|
||||
// constructor.
|
||||
void Start();
|
||||
|
||||
// Stop Asio loop.
|
||||
// You can call Start() to resume the loop.
|
||||
void Stop();
|
||||
|
||||
void set_connect_timeout(int timeout) {
|
||||
if (timeout > 0) {
|
||||
connect_timeout_ = timeout;
|
||||
}
|
||||
}
|
||||
|
||||
void set_read_timeout(int timeout) {
|
||||
if (timeout > 0) {
|
||||
read_timeout_ = timeout;
|
||||
}
|
||||
}
|
||||
|
||||
void set_buffer_size(std::size_t buffer_size) {
|
||||
buffer_size_ = buffer_size;
|
||||
}
|
||||
|
||||
void SetHeader(string_view key, string_view value) {
|
||||
headers_.Set(key, value);
|
||||
}
|
||||
|
||||
// Set `Content-Type` header, e.g., ("application/json", "utf-8").
|
||||
// Only applied when:
|
||||
// - the request to send has no `Content-Type` header, and
|
||||
// - the request has a body.
|
||||
void SetContentType(string_view media_type, string_view charset = "") {
|
||||
media_type_ = ToString(media_type);
|
||||
charset_ = ToString(charset);
|
||||
}
|
||||
|
||||
// Set content types to accept.
|
||||
void Accept(string_view content_types);
|
||||
|
||||
#if WEBCC_ENABLE_GZIP
|
||||
|
||||
// Accept Gzip compressed response data or not.
|
||||
void AcceptGzip(bool gzip = true);
|
||||
|
||||
#endif // WEBCC_ENABLE_GZIP
|
||||
|
||||
// Set authorization.
|
||||
void Auth(string_view type, string_view credentials);
|
||||
|
||||
// Set Basic authorization.
|
||||
void AuthBasic(string_view login, string_view password);
|
||||
|
||||
// Set Token authorization.
|
||||
void AuthToken(string_view token);
|
||||
|
||||
// Send a request.
|
||||
// Please use RequestBuilder to build the request.
|
||||
// If |stream| is true, the response data will be written into a temp file,
|
||||
// the response body will be FileBody, and you can easily move the temp file
|
||||
// to another path with FileBody::Move(). So, |stream| is really useful for
|
||||
// downloading files (JPEG, etc.) or saving memory for huge data responses.
|
||||
ResponsePtr Send(RequestPtr request, bool stream = false,
|
||||
ProgressCallback callback = {});
|
||||
|
||||
// Cancel any in-progress connecting, writing or reading.
|
||||
// Return if any client object has been closed.
|
||||
// It could be used to exit the program as soon as possible.
|
||||
bool Cancel();
|
||||
|
||||
private:
|
||||
void InitHeaders();
|
||||
|
||||
// Create a client object according to the URL scheme.
|
||||
ClientPtr CreateClient(const std::string& url_scheme);
|
||||
|
||||
#if WEBCC_ENABLE_SSL
|
||||
// Create SSL context if it's not created.
|
||||
void CreateSslContext();
|
||||
#endif // WEBCC_ENABLE_SSL
|
||||
|
||||
ResponsePtr DoSend(RequestPtr request, bool stream,
|
||||
ProgressCallback callback);
|
||||
|
||||
private:
|
||||
boost::asio::io_context io_context_;
|
||||
|
||||
// The thread to run Asio loop.
|
||||
std::unique_ptr<std::thread> io_thread_;
|
||||
|
||||
using ExecutorType = boost::asio::io_context::executor_type;
|
||||
boost::asio::executor_work_guard<ExecutorType> work_guard_;
|
||||
|
||||
#if WEBCC_ENABLE_SSL
|
||||
// SSL context is lazily created on the first HTTPS request.
|
||||
boost::asio::ssl::context* ssl_context_ = nullptr;
|
||||
#endif
|
||||
|
||||
// Is Asio loop running?
|
||||
bool started_ = false;
|
||||
|
||||
// The media (or MIME) type of `Content-Type` header.
|
||||
// E.g., "application/json".
|
||||
std::string media_type_;
|
||||
|
||||
// The charset of `Content-Type` header.
|
||||
// E.g., "utf-8".
|
||||
std::string charset_;
|
||||
|
||||
// Additional headers for each request.
|
||||
Headers headers_;
|
||||
|
||||
// Timeout (seconds) for connecting to server.
|
||||
int connect_timeout_ = 0;
|
||||
|
||||
// Timeout (seconds) for reading response.
|
||||
int read_timeout_ = 0;
|
||||
|
||||
// The size of the buffer for reading response.
|
||||
// 0 means default value will be used.
|
||||
std::size_t buffer_size_;
|
||||
|
||||
// Persistent (keep-alive) client connections.
|
||||
ClientPool pool_;
|
||||
|
||||
// Current requested client.
|
||||
ClientPtr client_;
|
||||
|
||||
// The mutex to serialize the requests.
|
||||
std::mutex mutex_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_CLIENT_SESSION_H_
|
||||
@@ -0,0 +1,250 @@
|
||||
#ifndef WEBCC_COMMON_H_
|
||||
#define WEBCC_COMMON_H_
|
||||
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "webcc/fs.h"
|
||||
#include "webcc/globals.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using Header = std::pair<std::string, std::string>;
|
||||
|
||||
class Headers {
|
||||
public:
|
||||
std::size_t size() const {
|
||||
return headers_.size();
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
return headers_.empty();
|
||||
}
|
||||
|
||||
const std::vector<Header>& data() const {
|
||||
return headers_;
|
||||
}
|
||||
|
||||
bool Set(string_view key, string_view value);
|
||||
|
||||
bool Has(string_view key) const;
|
||||
|
||||
// Get header by index.
|
||||
const Header& Get(std::size_t index) const {
|
||||
assert(index < size());
|
||||
return headers_[index];
|
||||
}
|
||||
|
||||
// Get header value by key.
|
||||
// If there's no such header with the given key, besides return empty, the
|
||||
// optional |existed| parameter will be set to false.
|
||||
const std::string& Get(string_view key, bool* existed = nullptr) const;
|
||||
|
||||
void Clear() {
|
||||
headers_.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Header>::iterator Find(string_view key);
|
||||
|
||||
std::vector<Header> headers_;
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Content-Type header.
|
||||
// Syntax:
|
||||
// Content-Type: text/html; charset=utf-8
|
||||
// Content-Type: multipart/form-data; boundary=something
|
||||
class ContentType {
|
||||
public:
|
||||
explicit ContentType(string_view str = "");
|
||||
|
||||
void Parse(string_view str);
|
||||
|
||||
void Reset();
|
||||
|
||||
bool Valid() const;
|
||||
|
||||
bool multipart() const {
|
||||
return multipart_;
|
||||
}
|
||||
|
||||
const std::string& media_type() const {
|
||||
return media_type_;
|
||||
}
|
||||
|
||||
const std::string& charset() const {
|
||||
assert(!multipart_);
|
||||
return additional_;
|
||||
}
|
||||
|
||||
const std::string& boundary() const {
|
||||
assert(multipart_);
|
||||
return additional_;
|
||||
}
|
||||
|
||||
private:
|
||||
void Init(string_view str);
|
||||
|
||||
private:
|
||||
std::string media_type_;
|
||||
std::string additional_;
|
||||
bool multipart_ = false;
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Content-Disposition header.
|
||||
// Syntax:
|
||||
// Content-Disposition: form-data
|
||||
// Content-Disposition: form-data; name="fieldName"
|
||||
// Content-Disposition: form-data; name="fieldName"; filename="filename.jpg"
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition
|
||||
class ContentDisposition {
|
||||
public:
|
||||
explicit ContentDisposition(string_view str) {
|
||||
valid_ = Init(str);
|
||||
}
|
||||
|
||||
bool valid() const {
|
||||
return valid_;
|
||||
}
|
||||
|
||||
const std::string& name() const {
|
||||
return name_;
|
||||
}
|
||||
|
||||
const std::string& file_name() const {
|
||||
return file_name_;
|
||||
}
|
||||
|
||||
private:
|
||||
bool Init(string_view str);
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
std::string file_name_;
|
||||
bool valid_ = false;
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
class FormPart;
|
||||
using FormPartPtr = std::shared_ptr<FormPart>;
|
||||
|
||||
// A part of the multipart form data.
|
||||
class FormPart {
|
||||
public:
|
||||
FormPart() = default;
|
||||
|
||||
FormPart(const FormPart&) = delete;
|
||||
FormPart& operator=(const FormPart&) = delete;
|
||||
|
||||
// Construct a non-file part.
|
||||
// The data will be moved, no file name is needed.
|
||||
// The media type is optional. If the data is a JSON string, you can specify
|
||||
// media type as "application/json".
|
||||
static FormPartPtr New(string_view name, std::string&& data,
|
||||
string_view media_type = "");
|
||||
|
||||
// Construct a file part.
|
||||
// The file name will be extracted from path.
|
||||
// The media type, if not provided, will be inferred from file extension.
|
||||
static FormPartPtr NewFile(string_view name, const fs::path& path,
|
||||
string_view media_type = "");
|
||||
|
||||
// API: SERVER
|
||||
const std::string& name() const {
|
||||
return name_;
|
||||
}
|
||||
|
||||
// API: SERVER/PARSER
|
||||
void set_name(const std::string& name) {
|
||||
name_ = name;
|
||||
}
|
||||
|
||||
// API: SERVER
|
||||
const std::string& file_name() const {
|
||||
return file_name_;
|
||||
}
|
||||
|
||||
// API: SERVER/PARSER
|
||||
void set_file_name(const std::string& file_name) {
|
||||
file_name_ = file_name;
|
||||
}
|
||||
|
||||
// API: SERVER
|
||||
const std::string& media_type() const {
|
||||
return media_type_;
|
||||
}
|
||||
|
||||
// API: SERVER
|
||||
const std::string& data() const {
|
||||
return data_;
|
||||
}
|
||||
|
||||
// API: SERVER/PARSER
|
||||
void AppendData(const std::string& data) {
|
||||
data_.append(data);
|
||||
}
|
||||
|
||||
// API: SERVER/PARSER
|
||||
void AppendData(const char* data, std::size_t count) {
|
||||
data_.append(data, count);
|
||||
}
|
||||
|
||||
// API: CLIENT
|
||||
void Prepare(Payload* payload);
|
||||
|
||||
// Free the memory of the data.
|
||||
void Free();
|
||||
|
||||
// Get the size of the whole payload.
|
||||
// Used by the request to calculate content length.
|
||||
std::size_t GetSize();
|
||||
|
||||
// Get the size of the data.
|
||||
std::size_t GetDataSize();
|
||||
|
||||
// Dump to output stream for logging purpose.
|
||||
void Dump(std::ostream& os, string_view prefix) const;
|
||||
|
||||
private:
|
||||
// Generate headers from properties.
|
||||
void SetHeaders();
|
||||
|
||||
private:
|
||||
// The <input> name within the original HTML form.
|
||||
// E.g., given HTML form:
|
||||
// <input name="file1" type="file">
|
||||
// the name will be "file1".
|
||||
std::string name_;
|
||||
|
||||
// The path of the file to post.
|
||||
fs::path path_;
|
||||
|
||||
// The original local file name.
|
||||
// E.g., "baby.jpg".
|
||||
std::string file_name_;
|
||||
|
||||
// The content-type if the media type is known (e.g., inferred from the file
|
||||
// extension or operating system typing information) or as
|
||||
// application/octet-stream.
|
||||
// E.g., "image/jpeg".
|
||||
std::string media_type_;
|
||||
|
||||
// Headers generated from the above properties.
|
||||
// Only Used to prepare payload.
|
||||
Headers headers_;
|
||||
|
||||
std::string data_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_COMMON_H_
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef WEBCC_CONFIG_H_
|
||||
#define WEBCC_CONFIG_H_
|
||||
|
||||
// Compile configurations.
|
||||
// This file is auto-generated by CMake during configuration
|
||||
// from config.h.in.
|
||||
|
||||
// Set 1/0 to enable/disable logging.
|
||||
#define WEBCC_ENABLE_LOG 1
|
||||
|
||||
#if WEBCC_ENABLE_LOG
|
||||
// 0:VERB, 1:INFO, 2:USER, 3:WARN or 4:ERRO
|
||||
#define WEBCC_LOG_LEVEL 0
|
||||
#endif // WEBCC_ENABLE_LOG
|
||||
|
||||
// Set 1/0 to enable/disable SSL/HTTPS.
|
||||
#define WEBCC_ENABLE_SSL 1
|
||||
|
||||
// Set 1/0 to enable/disable GZIP compression.
|
||||
#define WEBCC_ENABLE_GZIP 1
|
||||
|
||||
// Set 1 to use std::filesystem or 0 to use boost::filesystem.
|
||||
#define WEBCC_USE_STD_FILESYSTEM 1
|
||||
|
||||
// Set 1 to use std::string_view or 0 to use boost::string_view.
|
||||
#define WEBCC_USE_STD_STRING_VIEW 1
|
||||
|
||||
#endif // WEBCC_CONFIG_H_
|
||||
@@ -0,0 +1,108 @@
|
||||
#ifndef WEBCC_CONNECTION_H_
|
||||
#define WEBCC_CONNECTION_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "boost/asio/ip/tcp.hpp"
|
||||
|
||||
#include "webcc/globals.h"
|
||||
#include "webcc/queue.h"
|
||||
#include "webcc/request.h"
|
||||
#include "webcc/request_parser.h"
|
||||
#include "webcc/response.h"
|
||||
|
||||
// Set 1 to enable the log for the study of server thread model.
|
||||
// Need to use multiple workers and loops for Server::Run().
|
||||
// Suggest to configure the log level to USER.
|
||||
#define WEBCC_STUDY_SERVER_THREADING 0
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class Connection;
|
||||
class ConnectionPool;
|
||||
class Server;
|
||||
|
||||
using ConnectionPtr = std::shared_ptr<Connection>;
|
||||
|
||||
class Connection : public std::enable_shared_from_this<Connection> {
|
||||
public:
|
||||
Connection(boost::asio::ip::tcp::socket socket, ConnectionPool* pool,
|
||||
Queue<ConnectionPtr>* queue, ViewMatcher&& view_matcher,
|
||||
std::size_t buffer_size);
|
||||
|
||||
Connection(const Connection&) = delete;
|
||||
Connection& operator=(const Connection&) = delete;
|
||||
|
||||
~Connection() = default;
|
||||
|
||||
RequestPtr request() const {
|
||||
return request_;
|
||||
}
|
||||
|
||||
// Start to read and process the client request.
|
||||
void Start();
|
||||
|
||||
// Close the socket.
|
||||
void Close();
|
||||
|
||||
// Send a response to the client.
|
||||
// `Connection` header will be set to "Close" if |no_keep_alive| is true no
|
||||
// matter whether the client asked for Keep-Alive or not.
|
||||
void SendResponse(ResponsePtr response, bool no_keep_alive = false);
|
||||
|
||||
// Send a response with the given status and an empty body to the client.
|
||||
// `Connection` header will be set to "Close" if |no_keep_alive| is true no
|
||||
// matter whether the client asked for Keep-Alive or not.
|
||||
void SendResponse(Status status, bool no_keep_alive = false);
|
||||
|
||||
|
||||
// Send a response with the given status and an empty body to the client,with a custom 'Server' header
|
||||
// `Connection` header will be set to "Close" if |no_keep_alive| is true no
|
||||
// matter whether the client asked for Keep-Alive or not.
|
||||
void SendResponse(Status status, std::string server_name, bool no_keep_alive = false);
|
||||
|
||||
private:
|
||||
void AsyncRead();
|
||||
void OnRead(boost::system::error_code ec, std::size_t length);
|
||||
|
||||
void AsyncWrite();
|
||||
void OnWriteHeaders(boost::system::error_code ec, std::size_t length);
|
||||
|
||||
void AsyncWriteBody();
|
||||
void OnWriteBody(boost::system::error_code ec, std::size_t length);
|
||||
|
||||
void HandleWriteOK();
|
||||
void HandleWriteError(boost::system::error_code ec);
|
||||
|
||||
private:
|
||||
// The socket for the connection.
|
||||
boost::asio::ip::tcp::socket socket_;
|
||||
|
||||
// The connection pool.
|
||||
ConnectionPool* pool_;
|
||||
|
||||
// The connection queue.
|
||||
Queue<ConnectionPtr>* queue_;
|
||||
|
||||
// A function for matching view once the headers of a request has been
|
||||
// received.
|
||||
ViewMatcher view_matcher_;
|
||||
|
||||
// The buffer for incoming data.
|
||||
std::vector<char> buffer_;
|
||||
|
||||
// The incoming request.
|
||||
RequestPtr request_;
|
||||
|
||||
// The parser for the incoming request.
|
||||
RequestParser request_parser_;
|
||||
|
||||
// The response to be sent back to the client.
|
||||
ResponsePtr response_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_CONNECTION_H_
|
||||
@@ -0,0 +1,40 @@
|
||||
#ifndef WEBCC_CONNECTION_POOL_H_
|
||||
#define WEBCC_CONNECTION_POOL_H_
|
||||
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
|
||||
#include "webcc/connection.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class ConnectionPool {
|
||||
public:
|
||||
ConnectionPool() = default;
|
||||
|
||||
ConnectionPool(const ConnectionPool&) = delete;
|
||||
ConnectionPool& operator=(const ConnectionPool&) = delete;
|
||||
|
||||
// Add the connection and start to read the request from it.
|
||||
// Called when a new connection has just been accepted.
|
||||
void Start(ConnectionPtr c);
|
||||
|
||||
// Close the connection.
|
||||
// Called when the response of the connection has been sent back.
|
||||
void Close(ConnectionPtr c);
|
||||
|
||||
// Close all pending connections.
|
||||
// Called when the server is about to stop.
|
||||
void Clear();
|
||||
|
||||
private:
|
||||
std::set<ConnectionPtr> connections_;
|
||||
|
||||
// Mutex is necessary if the loop is running in multiple threads.
|
||||
// See Server::Run().
|
||||
std::mutex mutex_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_CONNECTION_POOL_H_
|
||||
@@ -0,0 +1,65 @@
|
||||
#ifndef WEBCC_FS_H_
|
||||
#define WEBCC_FS_H_
|
||||
|
||||
// Use std or boost filesystem according to config.
|
||||
|
||||
#include "webcc/config.h" // for WEBCC_USE_STD_FILESYSTEM
|
||||
|
||||
#if WEBCC_USE_STD_FILESYSTEM
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#else
|
||||
#include "boost/filesystem/fstream.hpp"
|
||||
#include "boost/filesystem/operations.hpp"
|
||||
#include "boost/filesystem/path.hpp"
|
||||
#endif // WEBCC_USE_STD_FILESYSTEM
|
||||
|
||||
namespace webcc {
|
||||
namespace fs {
|
||||
|
||||
#if WEBCC_USE_STD_FILESYSTEM
|
||||
|
||||
// types
|
||||
using std::error_code;
|
||||
using std::ifstream;
|
||||
using std::ofstream;
|
||||
using std::filesystem::path;
|
||||
using std::filesystem::filesystem_error;
|
||||
|
||||
// functions
|
||||
using std::filesystem::rename;
|
||||
using std::filesystem::remove;
|
||||
using std::filesystem::exists;
|
||||
using std::filesystem::is_directory;
|
||||
using std::filesystem::is_regular_file;
|
||||
using std::filesystem::create_directory;
|
||||
using std::filesystem::create_directories;
|
||||
using std::filesystem::current_path;
|
||||
using std::filesystem::temp_directory_path;
|
||||
|
||||
#else
|
||||
|
||||
// types
|
||||
using boost::system::error_code;
|
||||
using boost::filesystem::ifstream;
|
||||
using boost::filesystem::ofstream;
|
||||
using boost::filesystem::path;
|
||||
using boost::filesystem::filesystem_error;
|
||||
|
||||
// functions
|
||||
using boost::filesystem::rename;
|
||||
using boost::filesystem::remove;
|
||||
using boost::filesystem::exists;
|
||||
using boost::filesystem::is_directory;
|
||||
using boost::filesystem::is_regular_file;
|
||||
using boost::filesystem::create_directory;
|
||||
using boost::filesystem::create_directories;
|
||||
using boost::filesystem::current_path;
|
||||
using boost::filesystem::temp_directory_path;
|
||||
|
||||
#endif // WEBCC_USE_STD_FILESYSTEM
|
||||
|
||||
} // namespace fs
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_FS_H_
|
||||
@@ -0,0 +1,392 @@
|
||||
#ifndef WEBCC_GLOBALS_H_
|
||||
#define WEBCC_GLOBALS_H_
|
||||
|
||||
#include <cassert>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <iosfwd>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "boost/asio/buffer.hpp" // for const_buffer
|
||||
|
||||
#include "webcc/config.h"
|
||||
|
||||
#if WEBCC_USE_STD_STRING_VIEW
|
||||
#include <string_view>
|
||||
#else
|
||||
#include "boost/utility/string_view.hpp"
|
||||
#endif // WEBCC_USE_STD_STRING_VIEW
|
||||
|
||||
namespace webcc {
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#if WEBCC_USE_STD_STRING_VIEW
|
||||
using string_view = std::string_view;
|
||||
#else
|
||||
using string_view = boost::string_view;
|
||||
#endif // WEBCC_USE_STD_STRING_VIEW
|
||||
|
||||
inline std::string ToString(string_view sv) {
|
||||
#if WEBCC_USE_STD_STRING_VIEW
|
||||
return std::string{ sv.begin(), sv.end() };
|
||||
#else
|
||||
return sv.to_string();
|
||||
#endif // WEBCC_USE_STD_STRING_VIEW
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using Strings = std::vector<std::string>;
|
||||
|
||||
// Regex sub-matches of the URL (usually resource ID's).
|
||||
// Could also be considered as arguments, so named as UrlArgs.
|
||||
using UrlArgs = std::vector<std::string>;
|
||||
|
||||
using Payload = std::vector<boost::asio::const_buffer>;
|
||||
|
||||
using ProgressCallback =
|
||||
std::function<void(std::size_t length, std::size_t total_length)>;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const char* const kCRLF = "\r\n";
|
||||
|
||||
const std::size_t kInvalidLength = -1;
|
||||
|
||||
// Default timeout for reading response.
|
||||
const int kMaxReadSeconds = 30;
|
||||
|
||||
// Max size of the HTTP body to dump/log.
|
||||
// If the HTTP, e.g., response, has a very large content, it will be truncated
|
||||
// when dumped/logged.
|
||||
const std::size_t kMaxDumpSize = 2048;
|
||||
|
||||
// Default buffer size for socket reading.
|
||||
const std::size_t kBufferSize = 1024;
|
||||
|
||||
// Why 1400? See the following page:
|
||||
// https://www.itworld.com/article/2693941/why-it-doesn-t-make-sense-to-
|
||||
// gzip-all-content-from-your-web-server.html
|
||||
const std::size_t kGzipThreshold = 1400;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
namespace literal_buffers {
|
||||
|
||||
// Buffers for composing payload.
|
||||
// Literal strings can't be used because they have an extra '\0'.
|
||||
|
||||
extern const char HEADER_SEPARATOR[2];
|
||||
extern const char CRLF[2];
|
||||
extern const char DOUBLE_DASHES[2];
|
||||
|
||||
} // namespace literal_buffers
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
namespace methods {
|
||||
|
||||
// HTTP methods (verbs) in string.
|
||||
// Don't use enum to avoid converting back and forth.
|
||||
|
||||
const char* const kGet = "GET";
|
||||
const char* const kHead = "HEAD";
|
||||
const char* const kPost = "POST";
|
||||
const char* const kPut = "PUT";
|
||||
const char* const kDelete = "DELETE";
|
||||
const char* const kConnect = "CONNECT";
|
||||
const char* const kOptions = "OPTIONS";
|
||||
const char* const kTrace = "TRACE";
|
||||
const char* const kPatch = "PATCH";
|
||||
|
||||
} // namespace methods
|
||||
|
||||
// HTTP status codes.
|
||||
// Don't use "enum class" for converting to/from int easily.
|
||||
// The full list is available here:
|
||||
// https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
|
||||
enum Status {
|
||||
// 此临时响应表明客户端应继续请求,或者如果请求已完成,则忽略此响应。
|
||||
kContinue = 100,
|
||||
// 此代码是在响应客户端的 Upgrade 请求标头时发送的,用于指示服务器即将切换到的协议。
|
||||
kSwitchingProtocols = 101,
|
||||
// 此代码曾在 WebDAV 上下文中使用,表示服务器已收到请求,但在响应时无法提供状态。
|
||||
kProcessing = 102,
|
||||
// 此状态码主要与 Link 标头一起使用,允许用户代理在服务器准备响应时开始预加载资源,或预连接到页面需要资源的源站。
|
||||
kEarlyHints = 103,
|
||||
|
||||
// 请求成功。
|
||||
kOK = 200,
|
||||
// 请求成功,并因此创建了一个新资源。
|
||||
kCreated = 201,
|
||||
// 请求已被接收但尚未处理。
|
||||
kAccepted = 202,
|
||||
// 此响应代码表示返回的元数据与原始服务器上可用的不完全相同,而是从本地或第三方副本收集的。这主要用于另一个资源的镜像或备份。
|
||||
kNonAuthoritativeInformation = 203,
|
||||
// 对于此请求,没有内容可发送,但响应头可能有用。
|
||||
kNoContent = 204,
|
||||
// 告知用户代理重置发送此请求的文档。
|
||||
kResetContent = 205,
|
||||
// 当客户端请求了资源的一部分时,使用此响应代码进行响应。
|
||||
kPartialContent = 206,
|
||||
// 在可能需要多个状态码的情况下,传递关于多个资源的信息。
|
||||
kMultiStatus = 207,
|
||||
// 在 <dav:propstat> 响应元素内部使用,以避免重复枚举同一集合的多个绑定的内部成员。
|
||||
kAlreadyReported = 208,
|
||||
// 服务器已完成了对资源的 GET 请求,并且响应是对当前实例应用了一个或多个实例操作后的结果表示。
|
||||
kIMUsed = 226,
|
||||
|
||||
// 在代理驱动(agent-driven)的内容协商中,请求有多个可能的响应,用户代理或用户应选择其中之一。
|
||||
kMultipleChoices = 300,
|
||||
// 请求资源的 URL 已永久更改。新 URL 在响应中给出。
|
||||
kMovedPermanently = 301,
|
||||
// 此响应代码意味着请求资源的 URI 已暂时更改。未来可能还会对 URI 进行进一步更改,因此客户端在未来的请求中应使用相同的 URI。
|
||||
kFound = 302,
|
||||
// 服务器发送此响应以指示客户端使用 GET 请求在另一个 URI 获取请求的资源。
|
||||
kSeeOther = 303,
|
||||
// 用于缓存目的。它告知客户端响应未被修改,因此客户端可以继续使用相同的缓存响应版本。
|
||||
kNotModified = 304,
|
||||
// 在 HTTP 规范的前一版本中定义,表示请求的响应必须通过代理访问。由于涉及代理带内配置的安全问题,此状态码已被弃用。
|
||||
kUseProxy = 305,
|
||||
// 此响应代码不再使用,但被保留。它曾在 HTTP/1.1 规范的先前版本中使用。
|
||||
k__Unused = 306,
|
||||
// 服务器发送此响应以指示客户端使用与先前请求相同的方法在另一个 URI 获取请求的资源。其语义与 302 Found 响应代码相同,但用户代理不得更改使用的 HTTP 方法:如果在第一个请求中使用了 POST,则在重定向请求中也必须使用 POST。
|
||||
kTemporaryRedirect = 307,
|
||||
// 表示资源现在永久位于另一个 URI,由 Location 响应头指定。其语义与 301 Moved Permanently HTTP 响应代码相同,但用户代理不得更改使用的 HTTP 方法:如果在第一个请求中使用了 POST,则在第二个请求中也必须使用 POST。
|
||||
kPermanentRedirect = 308,
|
||||
|
||||
// 由于被认为是客户端错误的原因(例如,格式错误的请求语法、无效的请求消息结构或欺骗性的请求路由),服务器无法或不会处理该请求。
|
||||
kBadRequest = 400,
|
||||
// 尽管 HTTP 标准指定为 "unauthorized",但从语义上讲,此响应的意思是 "unauthenticated"。即,客户端必须进行身份验证才能获得请求的响应。
|
||||
kUnauthorized = 401,
|
||||
// 此代码最初用于数字支付系统,但此状态码很少使用,且不存在标准约定。
|
||||
kPaymentRequired = 402,
|
||||
// 客户端没有访问内容的权利;也就是说,它是未授权的,因此服务器拒绝提供请求的资源。与 401 Unauthorized 不同,服务器知道客户端的身份。
|
||||
kForbidden = 403,
|
||||
// 服务器找不到请求的资源。
|
||||
kNotFound = 404,
|
||||
// 服务器知道请求方法,但目标资源不支持该方法。
|
||||
kMethodNotAllowed = 405,
|
||||
// 当 Web 服务器执行服务器驱动的内容协商后,找不到任何符合用户代理给定条件的内容时,会发送此响应。
|
||||
kNotAcceptable = 406,
|
||||
// 类似于 401 Unauthorized,但需要通过代理进行身份验证。
|
||||
kProxyAuthenticationRequired = 407,
|
||||
// 某些服务器会在空闲连接上发送此响应,即使客户端之前没有任何请求。这意味着服务器希望关闭此未使用的连接。
|
||||
kRequestTimeout = 408,
|
||||
// 当请求与服务器的当前状态冲突时,发送此响应。
|
||||
kConflict = 409,
|
||||
// 当请求的内容已从服务器永久删除,且没有转发地址时,发送此响应。
|
||||
kGone = 410,
|
||||
// 服务器拒绝了请求,因为未定义 Content-Length 标头字段,而服务器需要它。
|
||||
kLengthRequired = 411,
|
||||
// 在条件请求中,客户端在其标头中指明了服务器不满足的前提条件。
|
||||
kPreconditionFailed = 412,
|
||||
// 请求体大于服务器定义的限制。
|
||||
kContentTooLarge = 413,
|
||||
// 客户端请求的 URI 长度超过了服务器愿意解释的长度。
|
||||
kURITooLong = 414,
|
||||
// 服务器不支持请求数据的媒体格式,因此服务器拒绝该请求。
|
||||
kUnsupportedMediaType = 415,
|
||||
// 无法满足请求中 Range 标头字段指定的范围。可能范围超出了目标资源数据的大小。
|
||||
kRangeNotSatisfiable = 416,
|
||||
// 此响应代码表示服务器无法满足 Expect 请求标头字段指示的期望。
|
||||
kExpectationFailed = 417,
|
||||
// 服务器拒绝尝试用茶壶煮咖啡。
|
||||
kIamATeapot = 418,
|
||||
// 请求被发送到了一个无法产生响应的服务器。
|
||||
kMisdirectedRequest = 421,
|
||||
// 请求格式正确,但由于语义错误而无法被遵循。
|
||||
kUnprocessableContent = 422,
|
||||
// 正在访问的资源已被锁定。
|
||||
kLocked = 423,
|
||||
// 由于先前的请求失败,导致当前请求失败。
|
||||
kFailedDependency = 424,
|
||||
// 表示服务器不愿意冒险处理一个可能被重放的请求。
|
||||
kTooEarly = 425,
|
||||
// 服务器拒绝使用当前协议执行请求,但可能在客户端升级到其他协议后愿意执行。服务器在 426 响应中发送 Upgrade 标头以指示所需的协议。
|
||||
kUpgradeRequired = 426,
|
||||
// 原始服务器要求请求是有条件的。此响应旨在防止"丢失更新"问题,即客户端 GET 资源状态,修改后 PUT 回服务器,而同时第三方已修改了服务器上的状态,导致冲突。
|
||||
kPreconditionRequired = 428,
|
||||
// 用户在给定的时间内发送了太多请求(速率限制)。
|
||||
kTooManyRequests = 429,
|
||||
// 服务器因请求头字段太大而不愿意处理该请求。
|
||||
kRequestHeaderFieldsTooLarge = 431,
|
||||
// 用户代理请求了一个无法合法提供的资源,例如被政府审查的网页。
|
||||
kUnavailableForLegalReasons = 451,
|
||||
|
||||
// 服务器遇到了不知道如何处理的情况。此错误是通用性的,表示服务器找不到更合适的 5XX 状态码来响应。
|
||||
kInternalServerError = 500,
|
||||
// 服务器不支持请求方法,无法处理。
|
||||
kNotImplemented = 501,
|
||||
// 此错误响应意味着服务器作为网关或代理时,收到了一个无效的响应。
|
||||
kBadGateway = 502,
|
||||
// 服务器尚未准备好处理请求。
|
||||
kServiceUnavailable = 503,
|
||||
// 当服务器作为网关或代理,无法及时获得响应时,会给出此错误响应。
|
||||
kGatewayTimeout = 504,
|
||||
// 服务器不支持请求中使用的 HTTP 版本。
|
||||
kHTTPVersionNotSupported = 505,
|
||||
// 服务器存在内部配置错误:在内容协商过程中,被选中的变体被配置为自身参与内容协商,这导致在创建响应时出现循环引用。
|
||||
kVariantAlsoNegotiates = 506,
|
||||
// 由于服务器无法存储成功完成请求所需的表示,因此无法对资源执行该方法。
|
||||
kInsufficientStorage = 507,
|
||||
// 服务器在处理请求时检测到无限循环。
|
||||
kLoopDetected = 508,
|
||||
// 客户端请求声明了一个应使用 HTTP 扩展(RFC 2774)来处理请求,但该扩展不受支持。
|
||||
kNotExtended = 510,
|
||||
// 表示客户端需要进行身份验证才能获得网络访问权限。
|
||||
kNetworkAuthenticationRequired = 511,
|
||||
|
||||
//Not Standard Code By UnknownObject
|
||||
|
||||
// 请求载体的格式错误,如:无法解析的JSON等。
|
||||
k_uRequestFormatError = 489,
|
||||
// 请求无效。可能是由于未正确携带数据等必要信息。
|
||||
k_uRequestInvalid = 490,
|
||||
// 请求URL超范围。此响应表示请求的URL是错误的。
|
||||
k_uURLOutOfRange = 492,
|
||||
// 无效的请求主机。指示请求时使用了错误的域名/IP。
|
||||
k_uInvalidRequestHost = 493,
|
||||
// IP地址被封禁。
|
||||
k_uIPBlocked = 494,
|
||||
// 非法上传请求。指示本次上传请求不符合服务器规定。
|
||||
k_uIllegalUpload = 495,
|
||||
// 文件格式错误。指示上传的文件格式不符合服务器规定。
|
||||
k_uFileFormatError = 496,
|
||||
// 无效文件。处理请求所需的文件已过期/无法访问。
|
||||
k_uInvalidFile = 497,
|
||||
// 上传的文件过大。非文件上传时应使用 413 Content Too Large。
|
||||
k_uFileTooLarge = 498,
|
||||
// 每秒请求数过多。仅在一些特殊API中使用,常规情况需使用 429 Too Many Requests。
|
||||
k_uRPSLimited = 499,
|
||||
|
||||
// 子过程失败。服务器在处理请求的某个步骤中遇到无法恢复的错误。
|
||||
k_uSubProcessFalied = 533,
|
||||
// 服务器检测到漏洞利用/可执行文件上传等网络攻击行为。
|
||||
k_uServerHateYou = 540,
|
||||
// 检测到拒绝服务漏洞攻击。
|
||||
k_uDoSFound = 550,
|
||||
// 检测到分布式拒绝服务漏洞攻击。
|
||||
k_uDDoSFound = 551,
|
||||
// 未知的服务器错误。当服务器无法定位错误来源时返回。否则应使用 500 Internal Server Error。
|
||||
k_uUnknownServerError = 560
|
||||
};
|
||||
|
||||
namespace headers {
|
||||
|
||||
// NOTE: Field names are case-insensitive.
|
||||
// See https://stackoverflow.com/a/5259004 for more details.
|
||||
|
||||
const char* const kHost = "Host";
|
||||
const char* const kDate = "Date";
|
||||
const char* const kAuthorization = "Authorization";
|
||||
const char* const kContentType = "Content-Type";
|
||||
const char* const kContentLength = "Content-Length";
|
||||
const char* const kContentEncoding = "Content-Encoding";
|
||||
const char* const kContentMD5 = "Content-MD5";
|
||||
const char* const kContentDisposition = "Content-Disposition";
|
||||
const char* const kConnection = "Connection";
|
||||
const char* const kTransferEncoding = "Transfer-Encoding";
|
||||
const char* const kAccept = "Accept";
|
||||
const char* const kAcceptEncoding = "Accept-Encoding";
|
||||
const char* const kUserAgent = "User-Agent";
|
||||
const char* const kServer = "Server";
|
||||
|
||||
} // namespace headers
|
||||
|
||||
namespace media_types {
|
||||
|
||||
// See the following link for the full list of media types:
|
||||
// https://www.iana.org/assignments/media-types/media-types.xhtml
|
||||
|
||||
const char* const kApplicationJson = "application/json";
|
||||
const char* const kApplicationSoapXml = "application/soap+xml";
|
||||
const char* const kApplicationFormUrlEncoded =
|
||||
"application/x-www-form-urlencoded";
|
||||
const char* const kTextPlain = "text/plain";
|
||||
const char* const kTextXml = "text/xml";
|
||||
|
||||
// Get media type from file extension.
|
||||
std::string FromExtension(const std::string& ext);
|
||||
|
||||
} // namespace media_types
|
||||
|
||||
namespace charsets {
|
||||
|
||||
const char* const kUtf8 = "utf-8";
|
||||
|
||||
} // namespace charsets
|
||||
|
||||
enum class ContentEncoding {
|
||||
kUnknown,
|
||||
kGzip,
|
||||
kDeflate,
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Error or exception (for client only).
|
||||
class Error : public std::exception {
|
||||
public:
|
||||
enum Code {
|
||||
kUnknownError = -1,
|
||||
kOK = 0,
|
||||
kStateError,
|
||||
kSyntaxError,
|
||||
kResolveError,
|
||||
kConnectError,
|
||||
kSocketReadError,
|
||||
kSocketWriteError,
|
||||
kParseError,
|
||||
kFileError,
|
||||
kDataError,
|
||||
};
|
||||
|
||||
public:
|
||||
explicit Error(Code code = kOK, string_view message = "")
|
||||
: code_(code), message_(message) {
|
||||
}
|
||||
|
||||
// Note that `noexcept` is required by GCC.
|
||||
const char* what() const noexcept override {
|
||||
return message_.c_str();
|
||||
}
|
||||
|
||||
Code code() const {
|
||||
return code_;
|
||||
}
|
||||
|
||||
const std::string& message() const {
|
||||
return message_;
|
||||
}
|
||||
|
||||
void Set(Code code, string_view message) {
|
||||
code_ = code;
|
||||
message_ = ToString(message);
|
||||
}
|
||||
|
||||
bool timeout() const {
|
||||
return timeout_;
|
||||
}
|
||||
|
||||
void set_timeout(bool timeout) {
|
||||
timeout_ = timeout;
|
||||
}
|
||||
|
||||
operator bool() const {
|
||||
return code_ != kOK;
|
||||
}
|
||||
|
||||
private:
|
||||
Code code_;
|
||||
std::string message_;
|
||||
bool timeout_ = false;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Error& error);
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_GLOBALS_H_
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef WEBCC_GZIP_H_
|
||||
#define WEBCC_GZIP_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace webcc {
|
||||
namespace gzip {
|
||||
|
||||
// Compress the input string to gzip format output.
|
||||
bool Compress(const std::string& input, std::string* output);
|
||||
|
||||
// Decompress the input string with auto detecting both gzip and zlib (deflate)
|
||||
// formats.
|
||||
bool Decompress(const std::string& input, std::string* output);
|
||||
|
||||
} // namespace gzip
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_GZIP_H_
|
||||
@@ -0,0 +1,124 @@
|
||||
#ifndef WEBCC_LOGGER_H_
|
||||
#define WEBCC_LOGGER_H_
|
||||
|
||||
// This file was generated from "config.h.in" by CMake.
|
||||
#include "webcc/config.h"
|
||||
|
||||
#if WEBCC_ENABLE_LOG
|
||||
|
||||
#include <cstring> // for strrchr()
|
||||
#include <string>
|
||||
|
||||
#include "webcc/fs.h"
|
||||
|
||||
// Log levels.
|
||||
// VERB is similar to DEBUG commonly used by other projects.
|
||||
// USER is for the users who want to log their own logs but don't want any
|
||||
// VERB or INFO.
|
||||
#define WEBCC_VERB 0
|
||||
#define WEBCC_INFO 1
|
||||
#define WEBCC_USER 2
|
||||
#define WEBCC_WARN 3
|
||||
#define WEBCC_ERRO 4
|
||||
|
||||
// Default log level.
|
||||
#ifndef WEBCC_LOG_LEVEL
|
||||
#define WEBCC_LOG_LEVEL WEBCC_VERB
|
||||
#endif
|
||||
|
||||
#define WEBCC_LOG_FILE_NAME "webcc.log"
|
||||
|
||||
namespace webcc
|
||||
{
|
||||
|
||||
enum LogMode
|
||||
{
|
||||
LOG_FILE = 1, // Log to file.
|
||||
LOG_CONSOLE = 2, // Log to console.
|
||||
LOG_FLUSH = 4, // Flush on each log.
|
||||
LOG_OVERWRITE = 8, // Overwrite any existing log file.
|
||||
};
|
||||
|
||||
// Commonly used modes.
|
||||
const int LOG_CONSOLE_FILE_APPEND = LOG_CONSOLE | LOG_FILE;
|
||||
const int LOG_CONSOLE_FILE_OVERWRITE = LOG_CONSOLE | LOG_FILE | LOG_OVERWRITE;
|
||||
const int LOG_FILE_OVERWRITE = LOG_FILE | LOG_OVERWRITE;
|
||||
|
||||
// Initialize logger.
|
||||
// If |dir| is empty, log file will be generated in current directory.
|
||||
void LogInit(const fs::path& dir, int modes);
|
||||
|
||||
void LogInit(const fs::path& dir, int modes, int log_level);
|
||||
|
||||
void Log(int level, const char* file, int line, const char* format, ...);
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
// Initialize the logger with a level.
|
||||
#define WEBCC_LOG_INIT(dir, modes) webcc::LogInit(dir, modes);
|
||||
#define WEBCC_LOG_INIT_2(dir, modes, level) webcc::LogInit(dir, modes, level);
|
||||
|
||||
// Definition of _WIN32 & _WIN64:
|
||||
// https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros?view=vs-2015
|
||||
#if (defined(_WIN32) || defined(_WIN64))
|
||||
|
||||
// See: https://stackoverflow.com/a/8488201
|
||||
// ISSUE: The last path separator of __FILE__ in a header file becomes "/"
|
||||
// instead of "\". The result is that __FILENAME__ will contain a
|
||||
// prefix of "webcc/". So don't log from a header file!
|
||||
#define __FILENAME__ std::strrchr("\\" __FILE__, '\\') + 1
|
||||
|
||||
#else
|
||||
|
||||
#define __FILENAME__ std::strrchr("/" __FILE__, '/') + 1
|
||||
|
||||
#endif // defined(_WIN32) || defined(_WIN64)
|
||||
|
||||
#if WEBCC_LOG_LEVEL <= WEBCC_VERB
|
||||
#define LOG_VERB(format, ...) \
|
||||
webcc::Log(WEBCC_VERB, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
|
||||
#else
|
||||
#define LOG_VERB(format, ...)
|
||||
#endif
|
||||
|
||||
#if WEBCC_LOG_LEVEL <= WEBCC_INFO
|
||||
#define LOG_INFO(format, ...) \
|
||||
webcc::Log(WEBCC_INFO, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
|
||||
#else
|
||||
#define LOG_INFO(format, ...)
|
||||
#endif
|
||||
|
||||
#if WEBCC_LOG_LEVEL <= WEBCC_USER
|
||||
#define LOG_USER(format, ...) \
|
||||
webcc::Log(WEBCC_USER, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
|
||||
#else
|
||||
#define LOG_INFO(format, ...)
|
||||
#endif
|
||||
|
||||
#if WEBCC_LOG_LEVEL <= WEBCC_WARN
|
||||
#define LOG_WARN(format, ...) \
|
||||
webcc::Log(WEBCC_WARN, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
|
||||
#else
|
||||
#define LOG_WARN(format, ...)
|
||||
#endif
|
||||
|
||||
#if WEBCC_LOG_LEVEL <= WEBCC_ERRO
|
||||
#define LOG_ERRO(format, ...) \
|
||||
webcc::Log(WEBCC_ERRO, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
|
||||
#else
|
||||
#define LOG_ERRO(format, ...)
|
||||
#endif
|
||||
|
||||
#else // WEBCC_ENABLE_LOG == 0
|
||||
|
||||
#define WEBCC_LOG_INIT(dir, modes)
|
||||
|
||||
#define LOG_VERB(format, ...)
|
||||
#define LOG_INFO(format, ...)
|
||||
#define LOG_USER(format, ...)
|
||||
#define LOG_WARN(format, ...)
|
||||
#define LOG_ERRO(format, ...)
|
||||
|
||||
#endif // WEBCC_ENABLE_LOG
|
||||
|
||||
#endif // WEBCC_LOGGER_H_
|
||||
@@ -0,0 +1,122 @@
|
||||
#ifndef WEBCC_MESSAGE_H_
|
||||
#define WEBCC_MESSAGE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "webcc/body.h"
|
||||
#include "webcc/common.h"
|
||||
#include "webcc/globals.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class Message {
|
||||
public:
|
||||
Message();
|
||||
|
||||
Message(const Message&) = delete;
|
||||
Message& operator=(const Message&) = delete;
|
||||
|
||||
virtual ~Message() = default;
|
||||
|
||||
const std::string& start_line() const {
|
||||
return start_line_;
|
||||
}
|
||||
|
||||
void set_start_line(string_view start_line) {
|
||||
start_line_ = ToString(start_line);
|
||||
}
|
||||
|
||||
void SetHeader(Header&& header) {
|
||||
headers_.Set(std::move(header.first), std::move(header.second));
|
||||
}
|
||||
|
||||
void SetHeader(string_view key, string_view value) {
|
||||
headers_.Set(key, value);
|
||||
}
|
||||
|
||||
const std::string& GetHeader(string_view key, bool* existed = nullptr) const {
|
||||
return headers_.Get(key, existed);
|
||||
}
|
||||
|
||||
bool HasHeader(string_view key) const {
|
||||
return headers_.Has(key);
|
||||
}
|
||||
|
||||
std::size_t content_length() const {
|
||||
return content_length_;
|
||||
}
|
||||
|
||||
void set_content_length(std::size_t content_length) {
|
||||
content_length_ = content_length;
|
||||
}
|
||||
|
||||
void SetBody(BodyPtr body, bool set_length);
|
||||
|
||||
BodyPtr body() const {
|
||||
return body_;
|
||||
}
|
||||
|
||||
//Added by UnknownObject at 2023-05-17
|
||||
//Add function of get all headers.
|
||||
Headers GetAllHeaders() const
|
||||
{
|
||||
return headers_;
|
||||
}
|
||||
|
||||
// Get the data from the (string) body.
|
||||
// Return empty string if the body is not a StringBody.
|
||||
const std::string& data() const;
|
||||
|
||||
// Get the body as a FileBody.
|
||||
// Return null if the body is not a FileBody.
|
||||
std::shared_ptr<FileBody> file_body() const;
|
||||
|
||||
// Check `Connection` header to see if it's "Keep-Alive".
|
||||
bool IsConnectionKeepAlive() const;
|
||||
|
||||
// Determine content encoding (gzip, deflate or unknown) from
|
||||
// `Content-Encoding` header.
|
||||
ContentEncoding GetContentEncoding() const;
|
||||
|
||||
// Check `Accept-Encoding` header to see if it contains "gzip".
|
||||
bool AcceptEncodingGzip() const;
|
||||
|
||||
// Set `Content-Type` header. E.g.,
|
||||
// SetContentType("application/json; charset=utf-8")
|
||||
void SetContentType(string_view content_type) {
|
||||
SetHeader(headers::kContentType, content_type);
|
||||
}
|
||||
|
||||
// Set `Content-Type` header. E.g.,
|
||||
// SetContentType("application/json", "utf-8")
|
||||
void SetContentType(string_view media_type, string_view charset);
|
||||
|
||||
// Make the message complete in order to be sent.
|
||||
virtual void Prepare() = 0;
|
||||
|
||||
// Get the payload for the socket to write.
|
||||
// This doesn't include the payload(s) of the body!
|
||||
Payload GetPayload() const;
|
||||
|
||||
// Dump to output stream for logging purpose.
|
||||
void Dump(std::ostream& os) const;
|
||||
|
||||
// Dump to string for logging purpose.
|
||||
std::string Dump() const;
|
||||
|
||||
protected:
|
||||
BodyPtr body_;
|
||||
|
||||
Headers headers_;
|
||||
|
||||
std::string start_line_;
|
||||
|
||||
std::size_t content_length_ = kInvalidLength;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_MESSAGE_H_
|
||||
@@ -0,0 +1,195 @@
|
||||
#ifndef WEBCC_PARSER_H_
|
||||
#define WEBCC_PARSER_H_
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
#include "webcc/common.h"
|
||||
#include "webcc/fs.h"
|
||||
#include "webcc/globals.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class Message;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
class BodyHandler {
|
||||
public:
|
||||
explicit BodyHandler(Message* message) : message_(message) {
|
||||
}
|
||||
|
||||
BodyHandler(const BodyHandler&) = delete;
|
||||
BodyHandler& operator=(const BodyHandler&) = delete;
|
||||
|
||||
virtual ~BodyHandler() = default;
|
||||
|
||||
virtual void AddContent(const char* data, std::size_t count) = 0;
|
||||
|
||||
virtual void AddContent(const std::string& data) = 0;
|
||||
|
||||
virtual std::size_t GetContentLength() const = 0;
|
||||
|
||||
virtual bool Finish() = 0;
|
||||
|
||||
protected:
|
||||
bool IsCompressed() const;
|
||||
|
||||
protected:
|
||||
Message* message_;
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
class StringBodyHandler : public BodyHandler {
|
||||
public:
|
||||
explicit StringBodyHandler(Message* message) : BodyHandler(message) {
|
||||
}
|
||||
|
||||
~StringBodyHandler() override = default;
|
||||
|
||||
void AddContent(const char* data, std::size_t count) override;
|
||||
void AddContent(const std::string& data) override;
|
||||
|
||||
std::size_t GetContentLength() const override {
|
||||
return content_.size();
|
||||
}
|
||||
|
||||
bool Finish() override;
|
||||
|
||||
private:
|
||||
std::string content_;
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
class FileBodyHandler : public BodyHandler {
|
||||
public:
|
||||
// NOTE: Might throw Error::kFileError.
|
||||
explicit FileBodyHandler(Message* message) : BodyHandler(message) {
|
||||
}
|
||||
|
||||
~FileBodyHandler() override = default;
|
||||
|
||||
// Open a temp file for data streaming.
|
||||
bool OpenFile();
|
||||
|
||||
void AddContent(const char* data, std::size_t count) override;
|
||||
void AddContent(const std::string& data) override;
|
||||
|
||||
std::size_t GetContentLength() const override {
|
||||
return streamed_size_;
|
||||
}
|
||||
|
||||
bool Finish() override;
|
||||
|
||||
private:
|
||||
std::size_t streamed_size_ = 0;
|
||||
fs::ofstream ofstream_;
|
||||
fs::path temp_path_;
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// HTTP request and response parser.
|
||||
class Parser {
|
||||
public:
|
||||
Parser();
|
||||
|
||||
Parser(const Parser&) = delete;
|
||||
Parser& operator=(const Parser&) = delete;
|
||||
|
||||
virtual ~Parser() = default;
|
||||
|
||||
void Init(Message* message);
|
||||
|
||||
bool finished() const {
|
||||
return finished_;
|
||||
}
|
||||
|
||||
// If the headers part has been parsed or not.
|
||||
bool header_ended() const {
|
||||
return header_ended_;
|
||||
}
|
||||
|
||||
// Get the length of the headers part.
|
||||
// Available after the headers have been parsed (see header_ended()).
|
||||
std::size_t header_length() const {
|
||||
return header_length_;
|
||||
}
|
||||
|
||||
// The content length parsed from `Content-Length` header.
|
||||
// kInvalidLength if the content is chunked.
|
||||
std::size_t content_length() const {
|
||||
return content_length_;
|
||||
}
|
||||
|
||||
// Parse the given length of data.
|
||||
// Return false if the parsing is failed.
|
||||
bool Parse(const char* data, std::size_t length);
|
||||
|
||||
protected:
|
||||
void Reset();
|
||||
|
||||
// Parse headers from pending data.
|
||||
// Return false only on syntax errors.
|
||||
bool ParseHeaders();
|
||||
|
||||
// Called when headers just parsed.
|
||||
// Return false if something is wrong.
|
||||
virtual bool OnHeadersEnd() = 0;
|
||||
|
||||
void CreateBodyHandler();
|
||||
|
||||
// Get next line (using delimiter CRLF) from the pending data.
|
||||
// The line will not contain a trailing CRLF.
|
||||
// If |erase| is true, the line, as well as the trailing CRLF, will be erased
|
||||
// from the pending data.
|
||||
bool GetNextLine(std::size_t off, std::string* line, bool erase);
|
||||
|
||||
virtual bool ParseStartLine(const std::string& line) = 0;
|
||||
|
||||
bool ParseHeaderLine(const std::string& line);
|
||||
|
||||
// Parse the given length of data.
|
||||
virtual bool ParseContent(const char* data, std::size_t length);
|
||||
|
||||
bool ParseFixedContent(const char* data, std::size_t length);
|
||||
|
||||
bool ParseChunkedContent(const char* data, std::size_t length);
|
||||
|
||||
bool ParseChunkSize(const std::string& line);
|
||||
|
||||
bool IsFixedContentFull() const;
|
||||
|
||||
// Return false if the compressed content cannot be decompressed.
|
||||
bool Finish();
|
||||
|
||||
protected:
|
||||
Message* message_ = nullptr;
|
||||
|
||||
std::unique_ptr<BodyHandler> body_handler_;
|
||||
|
||||
// Data streaming or not.
|
||||
bool stream_ = false;
|
||||
|
||||
// Data waiting to be parsed.
|
||||
std::string pending_data_;
|
||||
|
||||
// The length of the headers part.
|
||||
std::size_t header_length_ = 0;
|
||||
|
||||
// Temporary data and helper flags for parsing.
|
||||
std::size_t content_length_ = kInvalidLength;
|
||||
ContentType content_type_;
|
||||
bool start_line_parsed_ = false;
|
||||
bool content_length_parsed_ = false;
|
||||
bool header_ended_ = false;
|
||||
bool chunked_ = false;
|
||||
std::size_t chunk_size_ = kInvalidLength;
|
||||
bool finished_ = false;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_PARSER_H_
|
||||
@@ -0,0 +1,70 @@
|
||||
#ifndef WEBCC_QUEUE_H_
|
||||
#define WEBCC_QUEUE_H_
|
||||
|
||||
// A general message queue.
|
||||
|
||||
#include <condition_variable>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
namespace webcc {
|
||||
|
||||
template <typename T>
|
||||
class Queue {
|
||||
public:
|
||||
Queue() = default;
|
||||
|
||||
Queue(const Queue&) = delete;
|
||||
Queue& operator=(const Queue&) = delete;
|
||||
|
||||
T PopOrWait() {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
|
||||
// Wait for a message.
|
||||
not_empty_cv_.wait(lock, [this] { return !message_list_.empty(); });
|
||||
|
||||
T message = message_list_.front();
|
||||
message_list_.pop_front();
|
||||
return message;
|
||||
}
|
||||
|
||||
T Pop() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (message_list_.empty()) {
|
||||
return T();
|
||||
}
|
||||
|
||||
T message = message_list_.front();
|
||||
message_list_.pop_front();
|
||||
return message;
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
message_list_.clear();
|
||||
}
|
||||
|
||||
void Push(const T& message) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
message_list_.push_back(message);
|
||||
}
|
||||
not_empty_cv_.notify_one();
|
||||
}
|
||||
|
||||
std::size_t Size() const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return message_list_.size();
|
||||
}
|
||||
|
||||
private:
|
||||
std::list<T> message_list_;
|
||||
mutable std::mutex mutex_;
|
||||
std::condition_variable not_empty_cv_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_QUEUE_H_
|
||||
@@ -0,0 +1,93 @@
|
||||
#ifndef WEBCC_REQUEST_H_
|
||||
#define WEBCC_REQUEST_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "webcc/message.h"
|
||||
#include "webcc/url.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class Request : public Message {
|
||||
public:
|
||||
Request() = default;
|
||||
|
||||
explicit Request(const std::string& method) : method_(method) {
|
||||
}
|
||||
|
||||
~Request() override = default;
|
||||
|
||||
const std::string& method() const {
|
||||
return method_;
|
||||
}
|
||||
|
||||
void set_method(string_view method) {
|
||||
method_ = ToString(method);
|
||||
}
|
||||
|
||||
const Url& url() const {
|
||||
return url_;
|
||||
}
|
||||
|
||||
void set_url(Url&& url) {
|
||||
url_ = std::move(url);
|
||||
}
|
||||
|
||||
const std::string& host() const {
|
||||
return url_.host();
|
||||
}
|
||||
|
||||
const std::string& port() const {
|
||||
return url_.port();
|
||||
}
|
||||
|
||||
UrlQuery query() const {
|
||||
return UrlQuery{ url_.query() };
|
||||
}
|
||||
|
||||
const UrlArgs& args() const {
|
||||
return args_;
|
||||
}
|
||||
|
||||
void set_args(UrlArgs&& args) {
|
||||
args_ = std::move(args);
|
||||
}
|
||||
|
||||
const std::string& address() const {
|
||||
return address_;
|
||||
}
|
||||
|
||||
void set_address(std::string&& address) {
|
||||
address_ = std::move(address);
|
||||
}
|
||||
|
||||
// Check if the body is a multi-part form data.
|
||||
bool IsForm() const;
|
||||
|
||||
// Get the form parts from the body.
|
||||
// Only applicable to FormBody (i.e., multi-part form data).
|
||||
// Otherwise, exception Error(kDataError) will be thrown.
|
||||
const std::vector<FormPartPtr>& form_parts() const;
|
||||
|
||||
void Prepare() override;
|
||||
|
||||
private:
|
||||
std::string method_;
|
||||
|
||||
Url url_;
|
||||
|
||||
// The URL regex matched arguments (usually resource ID's).
|
||||
// Used by server only.
|
||||
UrlArgs args_;
|
||||
|
||||
// Client IP address.
|
||||
std::string address_;
|
||||
};
|
||||
|
||||
using RequestPtr = std::shared_ptr<Request>;
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_REQUEST_H_
|
||||
@@ -0,0 +1,230 @@
|
||||
#ifndef WEBCC_REQUEST_BUILDER_H_
|
||||
#define WEBCC_REQUEST_BUILDER_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "webcc/fs.h"
|
||||
#include "webcc/request.h"
|
||||
#include "webcc/url.h"
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Handy macros for creating a RequestBuilder.
|
||||
|
||||
#define WEBCC_RB webcc::RequestBuilder{}
|
||||
|
||||
// clang-format off
|
||||
#define WEBCC_GET(url) WEBCC_RB.Get(url, false)
|
||||
#define WEBCC_GET_ENC(url) WEBCC_RB.Get(url, true)
|
||||
#define WEBCC_HEAD(url) WEBCC_RB.Head(url, false)
|
||||
#define WEBCC_HEAD_ENC(url) WEBCC_RB.Head(url, true)
|
||||
#define WEBCC_POST(url) WEBCC_RB.Post(url, false)
|
||||
#define WEBCC_POST_ENC(url) WEBCC_RB.Post(url, true)
|
||||
#define WEBCC_PUT(url) WEBCC_RB.Put(url, false)
|
||||
#define WEBCC_PUT_ENC(url) WEBCC_RB.Put(url, true)
|
||||
#define WEBCC_DELETE(url) WEBCC_RB.Delete(url, false)
|
||||
#define WEBCC_DELETE_ENC(url) WEBCC_RB.Delete(url, true)
|
||||
#define WEBCC_PATCH(url) WEBCC_RB.Patch(url, false)
|
||||
#define WEBCC_PATCH_ENC(url) WEBCC_RB.Patch(url, true)
|
||||
// clang-format on
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class RequestBuilder {
|
||||
public:
|
||||
RequestBuilder() = default;
|
||||
|
||||
RequestBuilder(const RequestBuilder&) = delete;
|
||||
RequestBuilder& operator=(const RequestBuilder&) = delete;
|
||||
|
||||
// Build and return the request object.
|
||||
RequestPtr operator()();
|
||||
|
||||
RequestBuilder& Method(string_view method) {
|
||||
method_ = ToString(method);
|
||||
return *this;
|
||||
}
|
||||
|
||||
RequestBuilder& Get(string_view url, bool encode = false) {
|
||||
return Method(methods::kGet).Url(url, encode);
|
||||
}
|
||||
|
||||
RequestBuilder& Head(string_view url, bool encode = false) {
|
||||
return Method(methods::kHead).Url(url, encode);
|
||||
}
|
||||
|
||||
RequestBuilder& Post(string_view url, bool encode = false) {
|
||||
return Method(methods::kPost).Url(url, encode);
|
||||
}
|
||||
|
||||
RequestBuilder& Put(string_view url, bool encode = false) {
|
||||
return Method(methods::kPut).Url(url, encode);
|
||||
}
|
||||
|
||||
RequestBuilder& Delete(string_view url, bool encode = false) {
|
||||
return Method(methods::kDelete).Url(url, encode);
|
||||
}
|
||||
|
||||
RequestBuilder& Patch(string_view url, bool encode = false) {
|
||||
return Method(methods::kPatch).Url(url, encode);
|
||||
}
|
||||
|
||||
RequestBuilder& Url(string_view url, bool encode = false) {
|
||||
url_ = webcc::Url{ url, encode };
|
||||
return *this;
|
||||
}
|
||||
|
||||
RequestBuilder& Port(string_view port) {
|
||||
url_.set_port(port);
|
||||
return *this;
|
||||
}
|
||||
|
||||
RequestBuilder& Port(std::uint16_t port) {
|
||||
url_.set_port(std::to_string(port));
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Append a piece to the path.
|
||||
RequestBuilder& Path(string_view path, bool encode = false) {
|
||||
url_.AppendPath(path, encode);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Append a parameter to the query.
|
||||
RequestBuilder& Query(string_view key, string_view value,
|
||||
bool encode = false) {
|
||||
url_.AppendQuery(key, value, encode);
|
||||
return *this;
|
||||
}
|
||||
|
||||
RequestBuilder& MediaType(string_view media_type) {
|
||||
media_type_ = ToString(media_type);
|
||||
return *this;
|
||||
}
|
||||
|
||||
RequestBuilder& Charset(string_view charset) {
|
||||
charset_ = ToString(charset);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Set Media Type to "application/json".
|
||||
RequestBuilder& Json() {
|
||||
media_type_ = media_types::kApplicationJson;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Set Charset to "utf-8".
|
||||
RequestBuilder& Utf8() {
|
||||
charset_ = charsets::kUtf8;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Set (comma separated) content types to accept.
|
||||
// E.g., "application/json", "text/html, application/xhtml+xml".
|
||||
RequestBuilder& Accept(string_view content_types) {
|
||||
return Header(headers::kAccept, content_types);
|
||||
}
|
||||
|
||||
#if WEBCC_ENABLE_GZIP
|
||||
|
||||
// Accept Gzip compressed response data or not.
|
||||
RequestBuilder& AcceptGzip(bool gzip = true);
|
||||
|
||||
#endif // WEBCC_ENABLE_GZIP
|
||||
|
||||
RequestBuilder& Body(const std::string& data) {
|
||||
body_.reset(new StringBody{ data, false });
|
||||
return *this;
|
||||
}
|
||||
|
||||
RequestBuilder& Body(std::string&& data) {
|
||||
body_.reset(new StringBody{ std::move(data), false });
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Use the file content as body.
|
||||
// NOTE: Error::kFileError might be thrown.
|
||||
RequestBuilder& File(const fs::path& path, bool infer_media_type = true,
|
||||
std::size_t chunk_size = 1024);
|
||||
|
||||
// Add a form part.
|
||||
RequestBuilder& Form(FormPartPtr part) {
|
||||
form_parts_.push_back(part);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Add a form part of file.
|
||||
RequestBuilder& FormFile(string_view name, const fs::path& path,
|
||||
string_view media_type = "");
|
||||
|
||||
// Add a form part of string data.
|
||||
RequestBuilder& FormData(string_view name, std::string&& data,
|
||||
string_view media_type = "");
|
||||
|
||||
RequestBuilder& Header(string_view key, string_view value);
|
||||
|
||||
RequestBuilder& KeepAlive(bool keep_alive = true) {
|
||||
keep_alive_ = keep_alive;
|
||||
return *this;
|
||||
}
|
||||
|
||||
RequestBuilder& Auth(string_view type, string_view credentials);
|
||||
|
||||
RequestBuilder& AuthBasic(string_view login, string_view password);
|
||||
|
||||
RequestBuilder& AuthToken(string_view token);
|
||||
|
||||
// Add the `Date` header to the request.
|
||||
RequestBuilder& Date();
|
||||
|
||||
#if WEBCC_ENABLE_GZIP
|
||||
|
||||
// Compress the body data (only for string body).
|
||||
// NOTE:
|
||||
// Most servers don't support compressed requests.
|
||||
// Even the requests module from Python doesn't have a built-in support.
|
||||
// See: https://github.com/kennethreitz/requests/issues/1753
|
||||
RequestBuilder& Gzip(bool gzip = true) {
|
||||
gzip_ = gzip;
|
||||
return *this;
|
||||
}
|
||||
|
||||
#endif // WEBCC_ENABLE_GZIP
|
||||
|
||||
private:
|
||||
std::string method_;
|
||||
|
||||
// Namespace is added to avoid the conflict with `Url()` method.
|
||||
webcc::Url url_;
|
||||
|
||||
// Request body.
|
||||
BodyPtr body_;
|
||||
|
||||
// The media (or MIME) type of `Content-Type` header.
|
||||
// E.g., "application/json".
|
||||
std::string media_type_;
|
||||
|
||||
// The charset of `Content-Type` header.
|
||||
// E.g., "utf-8".
|
||||
std::string charset_;
|
||||
|
||||
// Files to upload for a POST request.
|
||||
std::vector<FormPartPtr> form_parts_;
|
||||
|
||||
// Additional headers with the following sequence:
|
||||
// { key1, value1, key2, value2, ... }
|
||||
Strings headers_;
|
||||
|
||||
// Persistent connection.
|
||||
bool keep_alive_ = true;
|
||||
|
||||
#if WEBCC_ENABLE_GZIP
|
||||
bool gzip_ = false;
|
||||
#endif // WEBCC_ENABLE_GZIP
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_REQUEST_BUILDER_H_
|
||||
@@ -0,0 +1,71 @@
|
||||
#ifndef WEBCC_REQUEST_PARSER_H_
|
||||
#define WEBCC_REQUEST_PARSER_H_
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#include "webcc/parser.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
using ViewMatcher =
|
||||
std::function<bool(const std::string&, const std::string&, bool*)>;
|
||||
|
||||
class Request;
|
||||
|
||||
class RequestParser : public Parser {
|
||||
public:
|
||||
RequestParser();
|
||||
|
||||
~RequestParser() override = default;
|
||||
|
||||
void Init(Request* request, ViewMatcher view_matcher);
|
||||
|
||||
private:
|
||||
// Override to match the URL against views and check if the matched view
|
||||
// asks for data streaming.
|
||||
bool OnHeadersEnd() override;
|
||||
|
||||
bool ParseStartLine(const std::string& line) override;
|
||||
|
||||
// Override to handle multipart form data which is request only.
|
||||
bool ParseContent(const char* data, std::size_t length) override;
|
||||
|
||||
// Multipart specific parsing helpers.
|
||||
|
||||
bool ParseMultipartContent(const char* data, std::size_t length);
|
||||
bool ParsePartHeaders(bool* need_more_data);
|
||||
bool GetNextBoundaryLine(std::size_t* b_off, std::size_t* b_len, bool* ended);
|
||||
|
||||
// Check if the str.substr(off, count) is a boundary.
|
||||
bool IsBoundary(const std::string& str, std::size_t off,
|
||||
std::size_t count, bool* end = nullptr) const;
|
||||
|
||||
private:
|
||||
// The result request message.
|
||||
Request* request_ = nullptr;
|
||||
|
||||
// A function for matching view once the headers of a request has been
|
||||
// received. The parsing will stop and fail if no view can be matched.
|
||||
ViewMatcher view_matcher_;
|
||||
|
||||
// Form data parsing steps.
|
||||
enum class Step {
|
||||
kStart,
|
||||
kBoundaryParsed,
|
||||
kHeadersParsed,
|
||||
kEnded,
|
||||
};
|
||||
|
||||
Step step_ = Step::kStart;
|
||||
|
||||
// The current form part being parsed.
|
||||
FormPartPtr part_;
|
||||
|
||||
// All form parts parsed.
|
||||
std::vector<FormPartPtr> form_parts_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_REQUEST_PARSER_H_
|
||||
@@ -0,0 +1,45 @@
|
||||
#ifndef WEBCC_RESPONSE_H_
|
||||
#define WEBCC_RESPONSE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "webcc/message.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class Response : public Message {
|
||||
public:
|
||||
explicit Response(Status status = Status::kOK) : status_(status) {
|
||||
}
|
||||
|
||||
~Response() override = default;
|
||||
|
||||
int status() const {
|
||||
return status_;
|
||||
}
|
||||
|
||||
void set_status(int status) {
|
||||
status_ = status;
|
||||
}
|
||||
|
||||
const std::string& reason() const {
|
||||
return reason_;
|
||||
}
|
||||
|
||||
void set_reason(const std::string& reason) {
|
||||
reason_ = reason;
|
||||
}
|
||||
|
||||
void Prepare() override;
|
||||
|
||||
private:
|
||||
int status_; // Status code
|
||||
std::string reason_; // Reason phrase
|
||||
};
|
||||
|
||||
using ResponsePtr = std::shared_ptr<Response>;
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_RESPONSE_H_
|
||||
@@ -0,0 +1,157 @@
|
||||
#ifndef WEBCC_RESPONSE_BUILDER_H_
|
||||
#define WEBCC_RESPONSE_BUILDER_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "webcc/fs.h"
|
||||
#include "webcc/request.h"
|
||||
#include "webcc/response.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class ResponseBuilder {
|
||||
public:
|
||||
ResponseBuilder() = default;
|
||||
|
||||
// NOTE:
|
||||
// Currently, |request| is necessary only when Gzip is enabled and the client
|
||||
// does want to accept Gzip compressed response.
|
||||
explicit ResponseBuilder(RequestPtr request) : request_(request), headers_() {
|
||||
}
|
||||
|
||||
ResponseBuilder(const ResponseBuilder&) = delete;
|
||||
ResponseBuilder& operator=(const ResponseBuilder&) = delete;
|
||||
|
||||
// Build
|
||||
ResponsePtr operator()();
|
||||
|
||||
// Some shortcuts for different status codes:
|
||||
|
||||
ResponseBuilder& OK() {
|
||||
return Code(Status::kOK);
|
||||
}
|
||||
|
||||
ResponseBuilder& Created() {
|
||||
return Code(Status::kCreated);
|
||||
}
|
||||
|
||||
ResponseBuilder& BadRequest() {
|
||||
return Code(Status::kBadRequest);
|
||||
}
|
||||
|
||||
ResponseBuilder& NotFound() {
|
||||
return Code(Status::kNotFound);
|
||||
}
|
||||
|
||||
ResponseBuilder& InternalServerError() {
|
||||
return Code(Status::kInternalServerError);
|
||||
}
|
||||
|
||||
ResponseBuilder& NotImplemented() {
|
||||
return Code(Status::kNotImplemented);
|
||||
}
|
||||
|
||||
ResponseBuilder& ServiceUnavailable() {
|
||||
return Code(Status::kServiceUnavailable);
|
||||
}
|
||||
|
||||
ResponseBuilder& Code(Status code) {
|
||||
code_ = code;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ResponseBuilder& MediaType(string_view media_type) {
|
||||
media_type_ = ToString(media_type);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ResponseBuilder& Charset(string_view charset) {
|
||||
charset_ = ToString(charset);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Set Media Type to "application/json".
|
||||
ResponseBuilder& Json() {
|
||||
media_type_ = media_types::kApplicationJson;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Set Charset to "utf-8".
|
||||
ResponseBuilder& Utf8() {
|
||||
charset_ = charsets::kUtf8;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ResponseBuilder& Body(const std::string& data) {
|
||||
body_.reset(new StringBody{ data, false });
|
||||
return *this;
|
||||
}
|
||||
|
||||
ResponseBuilder& Body(std::string&& data) {
|
||||
body_.reset(new StringBody{ std::move(data), false });
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Use the file content as body.
|
||||
// NOTE: Error::kFileError might be thrown.
|
||||
ResponseBuilder& File(const fs::path& path, bool infer_media_type = true,
|
||||
std::size_t chunk_size = 1024);
|
||||
|
||||
ResponseBuilder& Header(string_view key, string_view value);
|
||||
|
||||
// Add the `Date` header to the response.
|
||||
ResponseBuilder& Date();
|
||||
|
||||
//getter for information logger
|
||||
int GetCode() const
|
||||
{
|
||||
return (int)code_;
|
||||
}
|
||||
|
||||
size_t GetBodySize() const
|
||||
{
|
||||
if(body_ == nullptr)
|
||||
return 0;
|
||||
return body_->GetSize();
|
||||
}
|
||||
|
||||
bool IsBodyEmpty() const
|
||||
{
|
||||
return ((body_ == nullptr) || body_->IsEmpty());
|
||||
}
|
||||
|
||||
#if WEBCC_ENABLE_GZIP
|
||||
ResponseBuilder& Gzip(bool gzip = true) {
|
||||
gzip_ = gzip;
|
||||
return *this;
|
||||
}
|
||||
#endif // WEBCC_ENABLE_GZIP
|
||||
|
||||
private:
|
||||
RequestPtr request_;
|
||||
|
||||
// Status code.
|
||||
Status code_ = Status::kOK;
|
||||
|
||||
// Response body.
|
||||
BodyPtr body_;
|
||||
|
||||
// Media type of the body (e.g., "application/json").
|
||||
std::string media_type_;
|
||||
|
||||
// Character set of the body (e.g., "utf-8").
|
||||
std::string charset_;
|
||||
|
||||
#if WEBCC_ENABLE_GZIP
|
||||
// Compress the body data (only for string body).
|
||||
bool gzip_ = false;
|
||||
#endif // WEBCC_ENABLE_GZIP
|
||||
|
||||
// Additional headers.
|
||||
std::vector<std::string> headers_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_RESPONSE_BUILDER_H_
|
||||
@@ -0,0 +1,45 @@
|
||||
#ifndef WEBCC_RESPONSE_PARSER_H_
|
||||
#define WEBCC_RESPONSE_PARSER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "webcc/parser.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class Response;
|
||||
|
||||
class ResponseParser : public Parser {
|
||||
public:
|
||||
ResponseParser() = default;
|
||||
~ResponseParser() override = default;
|
||||
|
||||
void Init(Response* response, bool stream = false);
|
||||
|
||||
void set_ignore_body(bool ignore_body) {
|
||||
ignore_body_ = ignore_body;
|
||||
}
|
||||
|
||||
private:
|
||||
bool OnHeadersEnd() override {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parse HTTP start line; E.g., "HTTP/1.1 200 OK".
|
||||
bool ParseStartLine(const std::string& line) override;
|
||||
|
||||
// Override to allow to ignore the body of the response for HEAD request.
|
||||
bool ParseContent(const char* data, std::size_t length) override;
|
||||
|
||||
private:
|
||||
// The result response message.
|
||||
Response* response_ = nullptr;
|
||||
|
||||
// The response for HEAD request could also have `Content-Length` header,
|
||||
// set this flag to ignore it.
|
||||
bool ignore_body_ = false;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_RESPONSE_PARSER_H_
|
||||
@@ -0,0 +1,57 @@
|
||||
#ifndef WEBCC_ROUTER_H_
|
||||
#define WEBCC_ROUTER_H_
|
||||
|
||||
#include <regex>
|
||||
#include <string>
|
||||
|
||||
#include "webcc/globals.h"
|
||||
#include "webcc/view.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class Router {
|
||||
public:
|
||||
virtual ~Router() = default;
|
||||
|
||||
// Route a URL to a view.
|
||||
// The URL should start with "/". E.g., "/instances".
|
||||
bool Route(string_view url, ViewPtr view,
|
||||
const Strings& methods = { "GET" });
|
||||
|
||||
// Route a URL (as regular expression) to a view.
|
||||
// The URL should start with "/" and be a regular expression.
|
||||
// E.g., "/instances/(\\d+)".
|
||||
bool Route(const UrlRegex& regex_url, ViewPtr view,
|
||||
const Strings& methods = { "GET" });
|
||||
|
||||
// Find the view by HTTP method and URL (path).
|
||||
ViewPtr FindView(const std::string& method, const std::string& url,
|
||||
UrlArgs* args);
|
||||
|
||||
// Match the view by HTTP method and URL (path).
|
||||
// Return if a view is matched or not.
|
||||
// If the view asks for data streaming, |stream| will be set to true.
|
||||
bool MatchView(const std::string& method, const std::string& url,
|
||||
bool* stream);
|
||||
|
||||
// Direct access for ViewPtr pointers.
|
||||
// This can be used as server-based ip block/condiction checks
|
||||
// Added by UnknownObject as 2022-04-15
|
||||
size_t GetViewCount();
|
||||
ViewPtr& AccessView(size_t index);
|
||||
|
||||
private:
|
||||
struct RouteInfo {
|
||||
std::string url;
|
||||
std::regex url_regex;
|
||||
ViewPtr view;
|
||||
Strings methods;
|
||||
};
|
||||
|
||||
// Route table.
|
||||
std::vector<RouteInfo> routes_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_ROUTER_H_
|
||||
@@ -0,0 +1,154 @@
|
||||
#ifndef WEBCC_SERVER_H_
|
||||
#define WEBCC_SERVER_H_
|
||||
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "boost/asio/io_context.hpp"
|
||||
#include "boost/asio/ip/tcp.hpp"
|
||||
#include "boost/asio/signal_set.hpp"
|
||||
|
||||
#include "webcc/connection.h"
|
||||
#include "webcc/connection_pool.h"
|
||||
#include "webcc/fs.h"
|
||||
#include "webcc/queue.h"
|
||||
#include "webcc/router.h"
|
||||
#include "webcc/url.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class Server : public Router {
|
||||
public:
|
||||
Server(boost::asio::ip::tcp protocol, std::uint16_t port,
|
||||
const fs::path& doc_root = {});
|
||||
|
||||
Server(const Server&) = delete;
|
||||
Server& operator=(const Server&) = delete;
|
||||
|
||||
~Server() = default;
|
||||
|
||||
void set_buffer_size(std::size_t buffer_size) {
|
||||
if (buffer_size > 0) {
|
||||
buffer_size_ = buffer_size;
|
||||
}
|
||||
}
|
||||
|
||||
void set_file_chunk_size(std::size_t file_chunk_size) {
|
||||
assert(file_chunk_size > 0);
|
||||
file_chunk_size_ = file_chunk_size;
|
||||
}
|
||||
|
||||
// Start and run the server.
|
||||
// This method is blocking so will not return until Stop() is called (from
|
||||
// another thread) or a signal like SIGINT is caught.
|
||||
// When the request of a connection has been read, the connection is put into
|
||||
// a queue waiting for some worker thread to process. Normally, the more
|
||||
// |workers| you have, the more concurrency you gain (the concurrency also
|
||||
// depends on the number of CPU cores). The worker thread pops connections
|
||||
// from the queue one by one, prepares the response by the user provided View,
|
||||
// then sends it back to the client.
|
||||
// Meanwhile, the (event) loop, i.e., io_context, is also running in a number
|
||||
// (|loops|) of threads. Normally, one thread for the loop is good enough, but
|
||||
// it could be more than that.
|
||||
void Run(std::size_t workers = 1, std::size_t loops = 1);
|
||||
|
||||
// Stop the server.
|
||||
// This should be called from another thread since the Run() is blocking.
|
||||
void Stop();
|
||||
|
||||
// Is the server running?
|
||||
bool IsRunning() const;
|
||||
|
||||
// For High-Level api deleloper: to set the default server name insteaed of
|
||||
// webcc Added by UnknownObject at 2022-09-04
|
||||
void SetDefaultServerName(std::string server_name);
|
||||
|
||||
private:
|
||||
// Register signals which indicate when the server should exit.
|
||||
void AddSignals();
|
||||
|
||||
// Wait for a signal to stop the server.
|
||||
void AsyncWaitSignals();
|
||||
|
||||
// Listen on the given port.
|
||||
bool Listen(std::uint16_t port);
|
||||
|
||||
// Accept connections asynchronously.
|
||||
void AsyncAccept();
|
||||
|
||||
// Stop acceptor and worker threads, close all pending connections, and
|
||||
// finally stop the event loop.
|
||||
void DoStop();
|
||||
|
||||
// Worker thread routine.
|
||||
void WorkerRoutine();
|
||||
|
||||
// Clear pending connections from the queue and stop worker threads.
|
||||
void StopWorkers();
|
||||
|
||||
// Handle a connection (or more precisely, the request inside it).
|
||||
// Get the request from the connection, process it, prepare the response,
|
||||
// then send the response back to the client.
|
||||
// The connection will keep alive if it's a persistent connection. When next
|
||||
// request comes, this connection will be put back to the queue again.
|
||||
virtual void Handle(ConnectionPtr connection);
|
||||
|
||||
// Match the view by HTTP method and URL (path).
|
||||
// Return if a view or static file is matched or not.
|
||||
// If the view asks for data streaming, |stream| will be set to true.
|
||||
bool MatchViewOrStatic(const std::string& method, const std::string& url,
|
||||
bool* stream);
|
||||
|
||||
// Serve static files from the doc root.
|
||||
ResponsePtr ServeStatic(RequestPtr request);
|
||||
|
||||
private:
|
||||
// tcp::v4() or tcp::v6()
|
||||
boost::asio::ip::tcp protocol_;
|
||||
|
||||
// Port number.
|
||||
std::uint16_t port_ = 0;
|
||||
|
||||
// The directory with the static files to be served.
|
||||
fs::path doc_root_;
|
||||
|
||||
// The size of the buffer for reading request.
|
||||
std::size_t buffer_size_ = kBufferSize;
|
||||
|
||||
// The size of the chunk loaded into memory each time when serving a
|
||||
// static file.
|
||||
std::size_t file_chunk_size_ = 1024;
|
||||
|
||||
// Is the server running?
|
||||
bool running_ = false;
|
||||
|
||||
// The mutex for guarding the state of the server.
|
||||
std::mutex state_mutex_;
|
||||
|
||||
// The io_context used to perform asynchronous operations.
|
||||
boost::asio::io_context io_context_;
|
||||
|
||||
// Acceptor used to listen for incoming connections.
|
||||
boost::asio::ip::tcp::acceptor acceptor_;
|
||||
|
||||
// The connection pool which owns all live connections.
|
||||
ConnectionPool pool_;
|
||||
|
||||
// The signals for processing termination notifications.
|
||||
boost::asio::signal_set signals_;
|
||||
|
||||
// Worker threads.
|
||||
std::vector<std::thread> worker_threads_;
|
||||
|
||||
// The queue with connection waiting for the workers to process.
|
||||
Queue<ConnectionPtr> queue_;
|
||||
|
||||
// For High-Level api deleloper: to set the default server name insteaed of webcc
|
||||
// Added by UnknownObject at 2022-09-04
|
||||
std::string server_name__;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_SERVER_H_
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef WEBCC_SOCKET_H_
|
||||
#define WEBCC_SOCKET_H_
|
||||
|
||||
#include "webcc/socket_base.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class Socket : public SocketBase {
|
||||
public:
|
||||
explicit Socket(boost::asio::io_context& io_context);
|
||||
|
||||
void AsyncConnect(const std::string& host, const Endpoints& endpoints,
|
||||
ConnectHandler&& handler) override;
|
||||
|
||||
void AsyncWrite(const Payload& payload, WriteHandler&& handler) override;
|
||||
|
||||
void AsyncReadSome(ReadHandler&& handler, std::vector<char>* buffer) override;
|
||||
|
||||
bool Shutdown() override;
|
||||
|
||||
bool Close() override;
|
||||
|
||||
private:
|
||||
boost::asio::ip::tcp::socket socket_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_SOCKET_H_
|
||||
@@ -0,0 +1,45 @@
|
||||
#ifndef WEBCC_SOCKET_BASE_H_
|
||||
#define WEBCC_SOCKET_BASE_H_
|
||||
|
||||
#include "boost/asio/ip/tcp.hpp"
|
||||
|
||||
#include "webcc/globals.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class SocketBase {
|
||||
public:
|
||||
using Endpoints = boost::asio::ip::tcp::resolver::results_type;
|
||||
|
||||
using ConnectHandler = std::function<void(boost::system::error_code,
|
||||
boost::asio::ip::tcp::endpoint)>;
|
||||
|
||||
using WriteHandler =
|
||||
std::function<void(boost::system::error_code, std::size_t)>;
|
||||
|
||||
using ReadHandler =
|
||||
std::function<void(boost::system::error_code, std::size_t)>;
|
||||
|
||||
SocketBase() = default;
|
||||
|
||||
SocketBase(const SocketBase&) = delete;
|
||||
SocketBase& operator=(const SocketBase&) = delete;
|
||||
|
||||
virtual ~SocketBase() = default;
|
||||
|
||||
virtual void AsyncConnect(const std::string& host, const Endpoints& endpoints,
|
||||
ConnectHandler&& handler) = 0;
|
||||
|
||||
virtual void AsyncWrite(const Payload& payload, WriteHandler&& handler) = 0;
|
||||
|
||||
virtual void AsyncReadSome(ReadHandler&& handler,
|
||||
std::vector<char>* buffer) = 0;
|
||||
|
||||
virtual bool Shutdown() = 0;
|
||||
|
||||
virtual bool Close() = 0;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_SOCKET_BASE_H_
|
||||
@@ -0,0 +1,39 @@
|
||||
#ifndef WEBCC_SSL_CLIENT_H_
|
||||
#define WEBCC_SSL_CLIENT_H_
|
||||
|
||||
#include "boost/asio/ssl/context.hpp"
|
||||
|
||||
#include "webcc/client_base.h"
|
||||
#include "webcc/ssl_socket.h"
|
||||
|
||||
#if !WEBCC_ENABLE_SSL
|
||||
#error SSL must be enabled!
|
||||
#endif
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class SslClient final : public ClientBase {
|
||||
public:
|
||||
SslClient(boost::asio::io_context& io_context,
|
||||
boost::asio::ssl::context& ssl_context)
|
||||
: ClientBase(io_context), ssl_context_(ssl_context) {
|
||||
}
|
||||
|
||||
~SslClient() = default;
|
||||
|
||||
protected:
|
||||
void CreateSocket() override {
|
||||
socket_.reset(new SslSocket{ io_context_, ssl_context_ });
|
||||
}
|
||||
|
||||
void Resolve() override {
|
||||
AsyncResolve("443");
|
||||
}
|
||||
|
||||
private:
|
||||
boost::asio::ssl::context& ssl_context_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_SSL_CLIENT_H_
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef WEBCC_SSL_SOCKET_H_
|
||||
#define WEBCC_SSL_SOCKET_H_
|
||||
|
||||
#include "webcc/socket_base.h"
|
||||
|
||||
#include "boost/asio/ssl.hpp"
|
||||
|
||||
#if !WEBCC_ENABLE_SSL
|
||||
#error SSL must be enabled!
|
||||
#endif
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class SslSocket : public SocketBase {
|
||||
public:
|
||||
SslSocket(boost::asio::io_context& io_context,
|
||||
boost::asio::ssl::context& ssl_context);
|
||||
|
||||
void AsyncConnect(const std::string& host, const Endpoints& endpoints,
|
||||
ConnectHandler&& handler) override;
|
||||
|
||||
void AsyncWrite(const Payload& payload, WriteHandler&& handler) override;
|
||||
|
||||
void AsyncReadSome(ReadHandler&& handler, std::vector<char>* buffer) override;
|
||||
|
||||
bool Shutdown() override;
|
||||
|
||||
bool Close() override;
|
||||
|
||||
private:
|
||||
void OnConnect(boost::system::error_code ec,
|
||||
boost::asio::ip::tcp::endpoint endpoint);
|
||||
|
||||
ConnectHandler connect_handler_;
|
||||
boost::asio::ip::tcp::endpoint endpoint_;
|
||||
|
||||
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_stream_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_SSL_SOCKET_H_
|
||||
@@ -0,0 +1,37 @@
|
||||
#ifndef WEBCC_STRING_H_
|
||||
#define WEBCC_STRING_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "webcc/globals.h" // for string_view
|
||||
|
||||
namespace webcc {
|
||||
|
||||
// Get a randomly generated string with the given length.
|
||||
std::string RandomString(std::size_t length);
|
||||
|
||||
// Convert string to size_t.
|
||||
// Just a wrapper of std::stoul.
|
||||
bool ToSizeT(const std::string& str, int base, std::size_t* size);
|
||||
|
||||
void Trim(string_view& sv, const char* spaces = " ");
|
||||
|
||||
// Split string without copy.
|
||||
// |compress_token| is the same as boost::token_compress_on for boost::split.
|
||||
void Split(string_view input, char delim, bool compress_token,
|
||||
std::vector<string_view>* output);
|
||||
|
||||
// Split a key-value string.
|
||||
// E.g., split "Connection: Keep-Alive".
|
||||
bool SplitKV(string_view input, char delim, bool trim_spaces, string_view* key,
|
||||
string_view* value);
|
||||
|
||||
// Split a key-value string.
|
||||
// E.g., split "Connection: Keep-Alive".
|
||||
bool SplitKV(string_view input, char delim, bool trim_spaces,
|
||||
std::string* key, std::string* value);
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_STRING_H_
|
||||
@@ -0,0 +1,150 @@
|
||||
#ifndef WEBCC_URL_H_
|
||||
#define WEBCC_URL_H_
|
||||
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "webcc/globals.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// A simple implementation of URL (or URI).
|
||||
// TODO: Encoding of path
|
||||
class Url {
|
||||
public:
|
||||
// Encode URL different components.
|
||||
static std::string EncodeHost(string_view utf8_str);
|
||||
static std::string EncodePath(string_view utf8_str);
|
||||
static std::string EncodeQuery(string_view utf8_str);
|
||||
static std::string EncodeFull(string_view utf8_str);
|
||||
|
||||
public:
|
||||
Url() = default;
|
||||
|
||||
explicit Url(string_view str, bool encode = false);
|
||||
|
||||
const std::string& scheme() const {
|
||||
return scheme_;
|
||||
}
|
||||
|
||||
const std::string& host() const {
|
||||
return host_;
|
||||
}
|
||||
|
||||
const std::string& port() const {
|
||||
return port_;
|
||||
}
|
||||
|
||||
const std::string& path() const {
|
||||
return path_;
|
||||
}
|
||||
|
||||
const std::string& query() const {
|
||||
return query_;
|
||||
}
|
||||
|
||||
void set_port(string_view port) {
|
||||
port_ = ToString(port);
|
||||
}
|
||||
|
||||
// Force Set
|
||||
void ForceSet_Path(string_view path)
|
||||
{
|
||||
path_ = path;
|
||||
}
|
||||
|
||||
// Append a piece of path.
|
||||
void AppendPath(string_view piece, bool encode = false);
|
||||
|
||||
// Append a query parameter.
|
||||
void AppendQuery(string_view key, string_view value, bool encode = false);
|
||||
|
||||
private:
|
||||
void Parse(string_view str);
|
||||
|
||||
void Clear();
|
||||
|
||||
private:
|
||||
std::string scheme_;
|
||||
std::string host_;
|
||||
std::string port_;
|
||||
std::string path_;
|
||||
std::string query_;
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// For accessing URL query parameters.
|
||||
class UrlQuery {
|
||||
public:
|
||||
using Parameter = std::pair<std::string, std::string>;
|
||||
|
||||
UrlQuery() = default;
|
||||
|
||||
// The query string should be key-value pairs separated by '&'.
|
||||
explicit UrlQuery(const std::string& encoded_str);
|
||||
|
||||
bool Empty() const {
|
||||
return parameters_.empty();
|
||||
}
|
||||
|
||||
std::size_t Size() const {
|
||||
return parameters_.size();
|
||||
}
|
||||
|
||||
bool Has(const std::string& key) const {
|
||||
return Find(key) != parameters_.end();
|
||||
}
|
||||
|
||||
// Get a value by key.
|
||||
// Return empty string if the key doesn't exist.
|
||||
const std::string& Get(const std::string& key) const;
|
||||
|
||||
// Get a key-value pair by index.
|
||||
const Parameter& Get(std::size_t index) const;
|
||||
|
||||
void Add(const std::string& key, const std::string& value);
|
||||
|
||||
void Remove(const std::string& key);
|
||||
|
||||
// Return query string, encoded or not, joined with '&'.
|
||||
// E.g., "item=12731&color=blue&size=large".
|
||||
std::string ToString(bool encode = true) const;
|
||||
|
||||
private:
|
||||
using ConstIterator = std::vector<Parameter>::const_iterator;
|
||||
|
||||
ConstIterator Find(const std::string& key) const;
|
||||
|
||||
private:
|
||||
std::vector<Parameter> parameters_;
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Wrapper for URL as regular expression.
|
||||
// Used by Server::Route().
|
||||
class UrlRegex {
|
||||
public:
|
||||
explicit UrlRegex(string_view url) : url_(url) {
|
||||
}
|
||||
|
||||
std::regex operator()() const {
|
||||
std::regex::flag_type flags = std::regex::ECMAScript | std::regex::icase;
|
||||
return std::regex{ url_, flags };
|
||||
}
|
||||
|
||||
private:
|
||||
std::string url_;
|
||||
};
|
||||
|
||||
// Shortcut
|
||||
using R = UrlRegex;
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_URL_H_
|
||||
@@ -0,0 +1,51 @@
|
||||
#ifndef WEBCC_UTILITY_H_
|
||||
#define WEBCC_UTILITY_H_
|
||||
|
||||
#include <iosfwd>
|
||||
#include <string>
|
||||
|
||||
#include "boost/asio/ip/tcp.hpp"
|
||||
|
||||
#include "webcc/fs.h"
|
||||
#include "webcc/globals.h"
|
||||
|
||||
namespace webcc {
|
||||
namespace utility {
|
||||
|
||||
extern std::string CustomUA;
|
||||
|
||||
void SetCustomUA(const std::string& ua);
|
||||
|
||||
void SetCustomUA(const std::string& software, const std::string& version);
|
||||
|
||||
// Get default user agent for HTTP headers.
|
||||
const std::string& UserAgent();
|
||||
|
||||
// Get the timestamp for HTTP Date header field.
|
||||
// E.g., Wed, 21 Oct 2015 07:28:00 GMT
|
||||
// See: https://tools.ietf.org/html/rfc7231#section-7.1.1.2
|
||||
std::string HttpDate();
|
||||
|
||||
// Tell the size in bytes of the given file.
|
||||
// Return kInvalidLength (-1) on failure.
|
||||
std::size_t TellSize(const fs::path& path);
|
||||
|
||||
// Read entire file into string.
|
||||
bool ReadFile(const fs::path& path, std::string* output);
|
||||
|
||||
// Dump the string data line by line to achieve more readability.
|
||||
// Also limit the maximum size of the data to be dumped.
|
||||
void DumpByLine(const std::string& data, std::ostream& os, string_view prefix);
|
||||
|
||||
// Print TCP endpoint.
|
||||
// Usage: PrintEndpoint(std::cout, endpoint)
|
||||
void PrintEndpoint(std::ostream& ostream,
|
||||
const boost::asio::ip::tcp::endpoint& endpoint);
|
||||
|
||||
// TCP endpoint to string.
|
||||
std::string EndpointToString(const boost::asio::ip::tcp::endpoint& endpoint);
|
||||
|
||||
} // namespace utility
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_UTILITY_H_
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef WEBCC_VERSION_H_
|
||||
#define WEBCC_VERSION_H_
|
||||
|
||||
#define WEBCC_VERSION "0.2.0"
|
||||
|
||||
#endif // WEBCC_VERSION_H_
|
||||
@@ -0,0 +1,34 @@
|
||||
#ifndef WEBCC_VIEW_H_
|
||||
#define WEBCC_VIEW_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "webcc/request.h"
|
||||
#include "webcc/response.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class View {
|
||||
public:
|
||||
View() = default;
|
||||
|
||||
virtual ~View() = default;
|
||||
|
||||
View(const View&) = delete;
|
||||
View& operator=(const View&) = delete;
|
||||
|
||||
virtual ResponsePtr Handle(RequestPtr request) = 0;
|
||||
|
||||
// Return true if you want the request data of the given method to be streamed
|
||||
// to a temp file. Data streaming is useful for receiving large data, e.g.,
|
||||
// a JPEG image, posted from the client.
|
||||
virtual bool Stream(const std::string& /*method*/) {
|
||||
return false; // No streaming by default
|
||||
}
|
||||
};
|
||||
|
||||
using ViewPtr = std::shared_ptr<View>;
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_VIEW_H_
|
||||
Reference in New Issue
Block a user