upgrade core add header check and incrace upload speed

This commit is contained in:
UnknownObject
2026-08-05 17:35:27 +08:00
parent 45af94e535
commit 1f11a9d348
76 changed files with 2059 additions and 642 deletions
+214 -178
View File
@@ -9,241 +9,277 @@
#include "webcc/fs.h"
#include "webcc/globals.h"
namespace webcc {
namespace webcc
{
// -----------------------------------------------------------------------------
using Header = std::pair<std::string, std::string>;
using Header = std::pair<std::string, std::string>;
class Headers {
public:
std::size_t size() const {
return headers_.size();
}
class Headers
{
//fix core dumped
public:
Headers() : headers_()
{
bool empty() const {
return headers_.empty();
}
}
public:
std::size_t size() const
{
return headers_.size();
}
const std::vector<Header>& data() const {
return headers_;
}
bool empty() const
{
return headers_.empty();
}
bool Set(string_view key, string_view value);
const std::vector<Header>& data() const
{
return headers_;
}
bool Has(string_view key) const;
bool Set(string_view key, string_view value);
// Get header by index.
const Header& Get(std::size_t index) const {
assert(index < size());
return headers_[index];
}
bool Has(string_view key) const;
// 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;
// Get header by index.
const Header& Get(std::size_t index) const
{
assert(index < size());
return headers_[index];
}
void Clear() {
headers_.clear();
}
// 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;
private:
std::vector<Header>::iterator Find(string_view key);
void Clear()
{
headers_.clear();
}
std::vector<Header> headers_;
};
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);
// 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 Reset();
void Parse(string_view str);
bool Valid() const;
void Reset();
bool multipart() const {
return multipart_;
}
bool Valid() const;
const std::string& media_type() const {
return media_type_;
}
bool multipart() const
{
return multipart_;
}
const std::string& charset() const {
assert(!multipart_);
return additional_;
}
const std::string& media_type() const
{
return media_type_;
}
const std::string& boundary() const {
assert(multipart_);
return additional_;
}
const std::string& charset() const
{
assert(!multipart_);
return additional_;
}
private:
void Init(string_view str);
const std::string& boundary() const
{
assert(multipart_);
return additional_;
}
private:
std::string media_type_;
std::string additional_;
bool multipart_ = false;
};
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_;
}
// 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);
}
const std::string& name() const {
return name_;
}
bool valid() const
{
return valid_;
}
const std::string& file_name() const {
return file_name_;
}
const std::string& name() const
{
return name_;
}
private:
bool Init(string_view str);
const std::string& file_name() const
{
return file_name_;
}
private:
std::string name_;
std::string file_name_;
bool valid_ = false;
};
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;
class FormPart;
using FormPartPtr = std::shared_ptr<FormPart>;
FormPart(const FormPart&) = delete;
FormPart& operator=(const FormPart&) = delete;
// A part of the multipart form data.
class FormPart
{
public:
FormPart() = default;
// 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 = "");
FormPart(const FormPart&) = delete;
FormPart& operator=(const FormPart&) = delete;
// 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 = "");
// 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 = "");
// API: SERVER
const std::string& name() const {
return name_;
}
// 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/PARSER
void set_name(const std::string& name) {
name_ = name;
}
void ReserveData(std::size_t capacity);
// API: SERVER
const std::string& file_name() const {
return file_name_;
}
// API: SERVER
const std::string& name() const
{
return name_;
}
// API: SERVER/PARSER
void set_file_name(const std::string& file_name) {
file_name_ = file_name;
}
// API: SERVER/PARSER
void set_name(const std::string& name)
{
name_ = name;
}
// API: SERVER
const std::string& media_type() const {
return media_type_;
}
// API: SERVER
const std::string& file_name() const
{
return file_name_;
}
// API: SERVER
const std::string& data() const {
return data_;
}
// API: SERVER/PARSER
void set_file_name(const std::string& file_name)
{
file_name_ = file_name;
}
// API: SERVER/PARSER
void AppendData(const std::string& data) {
data_.append(data);
}
// API: SERVER
const std::string& media_type() const
{
return media_type_;
}
// API: SERVER/PARSER
void AppendData(const char* data, std::size_t count) {
data_.append(data, count);
}
// API: SERVER
const std::string& data() const
{
return data_;
}
// API: CLIENT
void Prepare(Payload* payload);
// API: SERVER/PARSER
void AppendData(const std::string& data)
{
data_.append(data);
}
// Free the memory of the data.
void Free();
// API: SERVER/PARSER
void AppendData(const char* data, std::size_t count)
{
data_.append(data, count);
}
// Get the size of the whole payload.
// Used by the request to calculate content length.
std::size_t GetSize();
// API: CLIENT
void Prepare(Payload* payload);
// Get the size of the data.
std::size_t GetDataSize();
// Free the memory of the data.
void Free();
// Dump to output stream for logging purpose.
void Dump(std::ostream& os, string_view prefix) const;
// Get the size of the whole payload.
// Used by the request to calculate content length.
std::size_t GetSize();
private:
// Generate headers from properties.
void SetHeaders();
// Get the size of the data.
std::size_t GetDataSize();
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_;
// Dump to output stream for logging purpose.
void Dump(std::ostream& os, string_view prefix) const;
// The path of the file to post.
fs::path path_;
private:
// Generate headers from properties.
void SetHeaders();
// The original local file name.
// E.g., "baby.jpg".
std::string file_name_;
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 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_;
// The path of the file to post.
fs::path path_;
// Headers generated from the above properties.
// Only Used to prepare payload.
Headers headers_;
// The original local file name.
// E.g., "baby.jpg".
std::string file_name_;
std::string data_;
};
// 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_;
};
inline std::size_t g_max_multipart_size = 1024 * 1024 * 1024;
} // namespace webcc
+4 -1
View File
@@ -1,4 +1,4 @@
#ifndef WEBCC_CONNECTION_H_
#ifndef WEBCC_CONNECTION_H_
#define WEBCC_CONNECTION_H_
#include <memory>
@@ -101,6 +101,9 @@ private:
// The response to be sent back to the client.
ResponsePtr response_;
// 标识是否检查过请求头
bool header_validated_ = false;
};
} // namespace webcc
+2 -2
View File
@@ -1,4 +1,4 @@
#ifndef WEBCC_GLOBALS_H_
#ifndef WEBCC_GLOBALS_H_
#define WEBCC_GLOBALS_H_
#include <cassert>
@@ -175,7 +175,7 @@ enum Status {
kProxyAuthenticationRequired = 407,
// 某些服务器会在空闲连接上发送此响应,即使客户端之前没有任何请求。这意味着服务器希望关闭此未使用的连接。
kRequestTimeout = 408,
// 当请求与服务器的当前状态冲突时,发送此响应。
// 当请求与服务器的当前状态冲突时,发送此响应。在
kConflict = 409,
// 当请求的内容已从服务器永久删除,且没有转发地址时,发送此响应。
kGone = 410,
+64 -43
View File
@@ -1,70 +1,91 @@
#ifndef WEBCC_REQUEST_PARSER_H_
#ifndef WEBCC_REQUEST_PARSER_H_
#define WEBCC_REQUEST_PARSER_H_
#include <functional>
#include <string>
#include "webcc/parser.h"
#include "webcc/view.h"
namespace webcc {
namespace webcc
{
using ViewMatcher =
std::function<bool(const std::string&, const std::string&, bool*)>;
using ViewMatcher =
std::function<bool(const std::string&, const std::string&, bool*, ViewPtr*)>;
class Request;
class Request;
class RequestParser : public Parser {
public:
RequestParser();
class RequestParser : public Parser
{
public:
RequestParser();
~RequestParser() override = default;
~RequestParser() override = default;
void Init(Request* request, ViewMatcher view_matcher);
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;
// 【新增】:检查 Header 是否已解析完毕
bool IsHeaderParsed() const
{
return header_parsed_;
}
bool ParseStartLine(const std::string& line) override;
ViewPtr MatchedView() const
{
return matched_view_;
}
// Override to handle multipart form data which is request only.
bool ParseContent(const char* data, std::size_t length) override;
private:
// Override to match the URL against views and check if the matched view
// asks for data streaming.
bool OnHeadersEnd() override;
// Multipart specific parsing helpers.
bool ParseStartLine(const std::string& line) override;
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);
// Override to handle multipart form data which is request only.
bool ParseContent(const char* data, std::size_t length) override;
// 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;
// Multipart specific parsing helpers.
private:
// The result request message.
Request* request_ = nullptr;
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);
// 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_;
// 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;
// Form data parsing steps.
enum class Step {
kStart,
kBoundaryParsed,
kHeadersParsed,
kEnded,
};
private:
// The result request message.
Request* request_ = nullptr;
Step step_ = Step::kStart;
// 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_;
// The current form part being parsed.
FormPartPtr part_;
// Form data parsing steps.
enum class Step
{
kStart,
kBoundaryParsed,
kHeadersParsed,
kEnded,
};
// All form parts parsed.
std::vector<FormPartPtr> form_parts_;
};
Step step_ = Step::kStart;
// The current form part being parsed.
FormPartPtr part_;
// All form parts parsed.
std::vector<FormPartPtr> form_parts_;
// 【新增】:Header 解析完成标志
bool header_parsed_ = false;
// View映射(用于请求头检查)
ViewPtr matched_view_ = nullptr;
};
} // namespace webcc
+1 -1
View File
@@ -32,7 +32,7 @@ public:
// 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);
bool* stream, ViewPtr* out_view = nullptr);
// Direct access for ViewPtr pointers.
// This can be used as server-based ip block/condiction checks
+106 -99
View File
@@ -16,138 +16,145 @@
#include "webcc/router.h"
#include "webcc/url.h"
namespace webcc {
namespace webcc
{
class Server : public Router {
public:
Server(boost::asio::ip::tcp protocol, std::uint16_t port,
const fs::path& doc_root = {});
using HeaderValidator = std::function<bool(const RequestPtr&)>;
Server(const Server&) = delete;
Server& operator=(const Server&) = delete;
class Server : public Router
{
public:
Server(boost::asio::ip::tcp protocol, std::uint16_t port,
const fs::path& doc_root = {});
~Server() = default;
Server(const Server&) = delete;
Server& operator=(const Server&) = delete;
void set_buffer_size(std::size_t buffer_size) {
if (buffer_size > 0) {
buffer_size_ = buffer_size;
}
}
~Server() = default;
void set_file_chunk_size(std::size_t file_chunk_size) {
assert(file_chunk_size > 0);
file_chunk_size_ = file_chunk_size;
}
void set_buffer_size(std::size_t buffer_size)
{
if (buffer_size > 0)
{
buffer_size_ = buffer_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);
void set_file_chunk_size(std::size_t file_chunk_size)
{
assert(file_chunk_size > 0);
file_chunk_size_ = file_chunk_size;
}
// Stop the server.
// This should be called from another thread since the Run() is blocking.
void Stop();
// 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);
// Is the server running?
bool IsRunning() const;
// Stop the server.
// This should be called from another thread since the Run() is blocking.
void Stop();
// 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);
// Is the server running?
bool IsRunning() const;
private:
// Register signals which indicate when the server should exit.
void AddSignals();
// 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);
// Wait for a signal to stop the server.
void AsyncWaitSignals();
private:
// Register signals which indicate when the server should exit.
void AddSignals();
// Listen on the given port.
bool Listen(std::uint16_t port);
// Wait for a signal to stop the server.
void AsyncWaitSignals();
// Accept connections asynchronously.
void AsyncAccept();
// Listen on the given port.
bool Listen(std::uint16_t port);
// Stop acceptor and worker threads, close all pending connections, and
// finally stop the event loop.
void DoStop();
// Accept connections asynchronously.
void AsyncAccept();
// Worker thread routine.
void WorkerRoutine();
// Stop acceptor and worker threads, close all pending connections, and
// finally stop the event loop.
void DoStop();
// Clear pending connections from the queue and stop worker threads.
void StopWorkers();
// Worker thread routine.
void WorkerRoutine();
// 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);
// Clear pending connections from the queue and stop worker threads.
void StopWorkers();
// 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);
// 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);
// Serve static files from the doc root.
ResponsePtr ServeStatic(RequestPtr request);
// 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, ViewPtr* out_view = nullptr);
private:
// tcp::v4() or tcp::v6()
boost::asio::ip::tcp protocol_;
// Serve static files from the doc root.
ResponsePtr ServeStatic(RequestPtr request);
// Port number.
std::uint16_t port_ = 0;
private:
// tcp::v4() or tcp::v6()
boost::asio::ip::tcp protocol_;
// The directory with the static files to be served.
fs::path doc_root_;
// Port number.
std::uint16_t port_ = 0;
// The size of the buffer for reading request.
std::size_t buffer_size_ = kBufferSize;
// The directory with the static files to be served.
fs::path doc_root_;
// The size of the chunk loaded into memory each time when serving a
// static file.
std::size_t file_chunk_size_ = 1024;
// The size of the buffer for reading request.
std::size_t buffer_size_ = kBufferSize;
// Is the server running?
bool running_ = false;
// The size of the chunk loaded into memory each time when serving a
// static file.
std::size_t file_chunk_size_ = 1024;
// The mutex for guarding the state of the server.
std::mutex state_mutex_;
// Is the server running?
bool running_ = false;
// The io_context used to perform asynchronous operations.
boost::asio::io_context io_context_;
// The mutex for guarding the state of the server.
std::mutex state_mutex_;
// Acceptor used to listen for incoming connections.
boost::asio::ip::tcp::acceptor acceptor_;
// The io_context used to perform asynchronous operations.
boost::asio::io_context io_context_;
// The connection pool which owns all live connections.
ConnectionPool pool_;
// Acceptor used to listen for incoming connections.
boost::asio::ip::tcp::acceptor acceptor_;
// The signals for processing termination notifications.
boost::asio::signal_set signals_;
// The connection pool which owns all live connections.
ConnectionPool pool_;
// Worker threads.
std::vector<std::thread> worker_threads_;
// The signals for processing termination notifications.
boost::asio::signal_set signals_;
// The queue with connection waiting for the workers to process.
Queue<ConnectionPtr> queue_;
// Worker threads.
std::vector<std::thread> worker_threads_;
// 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__;
};
// 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
+9 -1
View File
@@ -1,4 +1,4 @@
#ifndef WEBCC_VIEW_H_
#ifndef WEBCC_VIEW_H_
#define WEBCC_VIEW_H_
#include <memory>
@@ -25,6 +25,14 @@ public:
virtual bool Stream(const std::string& /*method*/) {
return false; // No streaming by default
}
// 【核心新增】:请求头预校验虚函数
// 当 Header 刚解析完毕、尚未接收 Body 时触发。
// 返回 true 继续接收数据;返回 false 强行 RST 切断连接。
virtual bool ValidateHeader(RequestPtr /*request*/)
{
return true; // 默认不校验,全放行
}
};
using ViewPtr = std::shared_ptr<View>;