Skip to content
Snippets Groups Projects
Commit 260755ec authored by Ole Christian Eidheim's avatar Ole Christian Eidheim Committed by eidheim
Browse files

Initial commit

parents
No related branches found
No related tags found
No related merge requests found
IndentWidth: 2
AccessModifierOffset: -2
UseTab: Never
ColumnLimit: 0
MaxEmptyLinesToKeep: 2
SpaceBeforeParens: Never
BreakBeforeBraces: Custom
BraceWrapping: {BeforeElse: true, BeforeCatch: true}
NamespaceIndentation: All
AllowShortCaseLabelsOnASingleLine: true
build
cmake_minimum_required (VERSION 3.0)
project (buggy-web-server)
add_compile_options(-std=c++11 -Wall -Wextra)
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wthread-safety)
endif()
add_library(simple-web-server INTERFACE)
target_include_directories(simple-web-server INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
find_package(Threads REQUIRED)
target_link_libraries(simple-web-server INTERFACE ${CMAKE_THREAD_LIBS_INIT})
find_package(Boost 1.53.0 COMPONENTS system thread REQUIRED)
target_link_libraries(simple-web-server INTERFACE ${Boost_LIBRARIES})
target_include_directories(simple-web-server INTERFACE ${Boost_INCLUDE_DIR})
add_executable(web_server main.cpp)
target_link_libraries(web_server simple-web-server)
LICENSE 0 → 100644
The MIT License (MIT)
Copyright (c) 2014-2020 Ole Christian Eidheim
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
README.md 0 → 100644
# An example, not entirely stable, web server
## Prerequisites
- Linux or MacOS
- Optionally: the C++ IDE [juCi++](https://gitlab.com/cppit/jucipp)
- If you do not install juCi++, you will need to install additional dependencies such as g++, cmake and make
## Installing dependencies
### Debian based distributions
`sudo apt-get install libboost-thread-dev libboost-regex-dev`
### Arch Linux based distributions
`sudo pacman -S boost`
### MacOS
`brew install boost`
## Download
```sh
git clone https://gitlab.stud.iie.ntnu.no/eidheim/buggy-web-server
```
## Compile and run
### Alternative 1
In a terminal:
```sh
juci buggy-web-server
```
In juCi++, choose Compile and Run in the Project menu.
Use Kill Last Process in the Project menu to stop the server.
### Alternative 2
In a terminal:
```sh
cd buggy-web-server
mkdir build
cd build
cmake ..
make
./web_server # Press control-c to stop the server
```
## See web page
### Alternative 1
Open `http://localhost:8080` in a web browser
### Alternative 2
In a terminal:
```sh
telnet localhost 8080
GET / HTTP/1.1 # Press enter twice here
```
## Debugging
First, stop the web server (if it is running).
### Alternative 1
In juCi++, choose Start in the Debug menu.
#### Backtrace of a stopped process
Select Run Command in the Debug menu, and run `bt`.
### Alternative 2
In a terminal:
```sh
cd build # In case you are not in this folder already
mkdir debug
cd debug
cmake -DCMAKE_BUILD_TYPE=Debug ../..
make
gdb ./web_server # In MacOS, instead use: lldb ./web_server
run
```
#### Backtrace of a stopped process
```sh
bt
```
#ifndef SIMPLE_WEB_ASIO_COMPATIBILITY_HPP
#define SIMPLE_WEB_ASIO_COMPATIBILITY_HPP
#include <memory>
#ifdef USE_STANDALONE_ASIO
#include <asio.hpp>
#include <asio/steady_timer.hpp>
namespace SimpleWeb {
namespace error = asio::error;
using error_code = std::error_code;
using errc = std::errc;
using system_error = std::system_error;
namespace make_error_code = std;
} // namespace SimpleWeb
#else
#include <boost/asio.hpp>
#include <boost/asio/steady_timer.hpp>
namespace SimpleWeb {
namespace asio = boost::asio;
namespace error = asio::error;
using error_code = boost::system::error_code;
namespace errc = boost::system::errc;
using system_error = boost::system::system_error;
namespace make_error_code = boost::system::errc;
} // namespace SimpleWeb
#endif
namespace SimpleWeb {
#if(USE_STANDALONE_ASIO && ASIO_VERSION >= 101300) || BOOST_ASIO_VERSION >= 101300
using io_context = asio::io_context;
using resolver_results = asio::ip::tcp::resolver::results_type;
using async_connect_endpoint = asio::ip::tcp::endpoint;
template <typename handler_type>
inline void post(io_context &context, handler_type &&handler) {
asio::post(context, std::forward<handler_type>(handler));
}
inline void restart(io_context &context) noexcept {
context.restart();
}
inline asio::ip::address make_address(const std::string &str) noexcept {
return asio::ip::make_address(str);
}
template <typename socket_type>
asio::executor get_socket_executor(socket_type &socket) {
return socket.get_executor();
}
template <typename handler_type>
void async_resolve(asio::ip::tcp::resolver &resolver, const std::pair<std::string, std::string> &host_port, handler_type &&handler) {
resolver.async_resolve(host_port.first, host_port.second, std::forward<handler_type>(handler));
}
inline asio::executor_work_guard<io_context::executor_type> make_work_guard(io_context &context) {
return asio::make_work_guard(context);
}
#else
using io_context = asio::io_service;
using resolver_results = asio::ip::tcp::resolver::iterator;
using async_connect_endpoint = asio::ip::tcp::resolver::iterator;
template <typename handler_type>
inline void post(io_context &context, handler_type &&handler) {
context.post(std::forward<handler_type>(handler));
}
inline void restart(io_context &context) noexcept {
context.reset();
}
inline asio::ip::address make_address(const std::string &str) noexcept {
return asio::ip::address::from_string(str);
}
template <typename socket_type>
io_context &get_socket_executor(socket_type &socket) {
return socket.get_io_service();
}
template <typename handler_type>
void async_resolve(asio::ip::tcp::resolver &resolver, const std::pair<std::string, std::string> &host_port, handler_type &&handler) {
resolver.async_resolve(asio::ip::tcp::resolver::query(host_port.first, host_port.second), std::forward<handler_type>(handler));
}
inline io_context::work make_work_guard(io_context &context) {
return io_context::work(context);
}
#endif
} // namespace SimpleWeb
#endif /* SIMPLE_WEB_ASIO_COMPATIBILITY_HPP */
This diff is collapsed.
main.cpp 0 → 100644
#include "server_http.hpp"
typedef SimpleWeb::Server<SimpleWeb::HTTP> HttpServer;
int main() {
// Construct HTTP-server at port 8080 using 1 thread, and without request timeout
HttpServer server;
server.config.port = 8080;
server.config.timeout_request = 0;
// Response to all GET requests
server.resource["^.*$"]["GET"] = [](std::shared_ptr<HttpServer::Response> response, std::shared_ptr<HttpServer::Request> /*request*/) {
response->write("<h1>The web server is working!</h1>");
};
server.start();
}
mutex.hpp 0 → 100644
// Based on https://clang.llvm.org/docs/ThreadSafetyAnalysis.html
#ifndef SIMPLE_WEB_MUTEX_HPP
#define SIMPLE_WEB_MUTEX_HPP
#include <mutex>
// Enable thread safety attributes only with clang.
#if defined(__clang__) && (!defined(SWIG))
#define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x))
#else
#define THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op
#endif
#define CAPABILITY(x) \
THREAD_ANNOTATION_ATTRIBUTE__(capability(x))
#define SCOPED_CAPABILITY \
THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable)
#define GUARDED_BY(x) \
THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x))
#define PT_GUARDED_BY(x) \
THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x))
#define ACQUIRED_BEFORE(...) \
THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__))
#define ACQUIRED_AFTER(...) \
THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__))
#define REQUIRES(...) \
THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__))
#define REQUIRES_SHARED(...) \
THREAD_ANNOTATION_ATTRIBUTE__(requires_shared_capability(__VA_ARGS__))
#define ACQUIRE(...) \
THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__))
#define ACQUIRE_SHARED(...) \
THREAD_ANNOTATION_ATTRIBUTE__(acquire_shared_capability(__VA_ARGS__))
#define RELEASE(...) \
THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__))
#define RELEASE_SHARED(...) \
THREAD_ANNOTATION_ATTRIBUTE__(release_shared_capability(__VA_ARGS__))
#define TRY_ACQUIRE(...) \
THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_capability(__VA_ARGS__))
#define TRY_ACQUIRE_SHARED(...) \
THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_shared_capability(__VA_ARGS__))
#define EXCLUDES(...) \
THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__))
#define ASSERT_CAPABILITY(x) \
THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x))
#define ASSERT_SHARED_CAPABILITY(x) \
THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_capability(x))
#define RETURN_CAPABILITY(x) \
THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x))
#define NO_THREAD_SAFETY_ANALYSIS \
THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis)
namespace SimpleWeb {
/// Mutex class that is annotated for Clang Thread Safety Analysis.
class CAPABILITY("mutex") Mutex {
std::mutex mutex;
public:
void lock() ACQUIRE() {
mutex.lock();
}
void unlock() RELEASE() {
mutex.unlock();
}
};
/// Scoped mutex guard class that is annotated for Clang Thread Safety Analysis.
class SCOPED_CAPABILITY LockGuard {
Mutex &mutex;
bool locked = true;
public:
LockGuard(Mutex &mutex_) ACQUIRE(mutex_) : mutex(mutex_) {
mutex.lock();
}
void unlock() RELEASE() {
mutex.unlock();
locked = false;
}
~LockGuard() RELEASE() {
if(locked)
mutex.unlock();
}
};
} // namespace SimpleWeb
#endif // SIMPLE_WEB_MUTEX_HPP
This diff is collapsed.
#ifndef SIMPLE_WEB_STATUS_CODE_HPP
#define SIMPLE_WEB_STATUS_CODE_HPP
#include <cstdlib>
#include <map>
#include <string>
#include <unordered_map>
#include <vector>
namespace SimpleWeb {
enum class StatusCode {
unknown = 0,
information_continue = 100,
information_switching_protocols,
information_processing,
success_ok = 200,
success_created,
success_accepted,
success_non_authoritative_information,
success_no_content,
success_reset_content,
success_partial_content,
success_multi_status,
success_already_reported,
success_im_used = 226,
redirection_multiple_choices = 300,
redirection_moved_permanently,
redirection_found,
redirection_see_other,
redirection_not_modified,
redirection_use_proxy,
redirection_switch_proxy,
redirection_temporary_redirect,
redirection_permanent_redirect,
client_error_bad_request = 400,
client_error_unauthorized,
client_error_payment_required,
client_error_forbidden,
client_error_not_found,
client_error_method_not_allowed,
client_error_not_acceptable,
client_error_proxy_authentication_required,
client_error_request_timeout,
client_error_conflict,
client_error_gone,
client_error_length_required,
client_error_precondition_failed,
client_error_payload_too_large,
client_error_uri_too_long,
client_error_unsupported_media_type,
client_error_range_not_satisfiable,
client_error_expectation_failed,
client_error_im_a_teapot,
client_error_misdirection_required = 421,
client_error_unprocessable_entity,
client_error_locked,
client_error_failed_dependency,
client_error_upgrade_required = 426,
client_error_precondition_required = 428,
client_error_too_many_requests,
client_error_request_header_fields_too_large = 431,
client_error_unavailable_for_legal_reasons = 451,
server_error_internal_server_error = 500,
server_error_not_implemented,
server_error_bad_gateway,
server_error_service_unavailable,
server_error_gateway_timeout,
server_error_http_version_not_supported,
server_error_variant_also_negotiates,
server_error_insufficient_storage,
server_error_loop_detected,
server_error_not_extended = 510,
server_error_network_authentication_required
};
inline const std::map<StatusCode, std::string> &status_code_strings() {
static const std::map<StatusCode, std::string> status_code_strings = {
{StatusCode::unknown, ""},
{StatusCode::information_continue, "100 Continue"},
{StatusCode::information_switching_protocols, "101 Switching Protocols"},
{StatusCode::information_processing, "102 Processing"},
{StatusCode::success_ok, "200 OK"},
{StatusCode::success_created, "201 Created"},
{StatusCode::success_accepted, "202 Accepted"},
{StatusCode::success_non_authoritative_information, "203 Non-Authoritative Information"},
{StatusCode::success_no_content, "204 No Content"},
{StatusCode::success_reset_content, "205 Reset Content"},
{StatusCode::success_partial_content, "206 Partial Content"},
{StatusCode::success_multi_status, "207 Multi-Status"},
{StatusCode::success_already_reported, "208 Already Reported"},
{StatusCode::success_im_used, "226 IM Used"},
{StatusCode::redirection_multiple_choices, "300 Multiple Choices"},
{StatusCode::redirection_moved_permanently, "301 Moved Permanently"},
{StatusCode::redirection_found, "302 Found"},
{StatusCode::redirection_see_other, "303 See Other"},
{StatusCode::redirection_not_modified, "304 Not Modified"},
{StatusCode::redirection_use_proxy, "305 Use Proxy"},
{StatusCode::redirection_switch_proxy, "306 Switch Proxy"},
{StatusCode::redirection_temporary_redirect, "307 Temporary Redirect"},
{StatusCode::redirection_permanent_redirect, "308 Permanent Redirect"},
{StatusCode::client_error_bad_request, "400 Bad Request"},
{StatusCode::client_error_unauthorized, "401 Unauthorized"},
{StatusCode::client_error_payment_required, "402 Payment Required"},
{StatusCode::client_error_forbidden, "403 Forbidden"},
{StatusCode::client_error_not_found, "404 Not Found"},
{StatusCode::client_error_method_not_allowed, "405 Method Not Allowed"},
{StatusCode::client_error_not_acceptable, "406 Not Acceptable"},
{StatusCode::client_error_proxy_authentication_required, "407 Proxy Authentication Required"},
{StatusCode::client_error_request_timeout, "408 Request Timeout"},
{StatusCode::client_error_conflict, "409 Conflict"},
{StatusCode::client_error_gone, "410 Gone"},
{StatusCode::client_error_length_required, "411 Length Required"},
{StatusCode::client_error_precondition_failed, "412 Precondition Failed"},
{StatusCode::client_error_payload_too_large, "413 Payload Too Large"},
{StatusCode::client_error_uri_too_long, "414 URI Too Long"},
{StatusCode::client_error_unsupported_media_type, "415 Unsupported Media Type"},
{StatusCode::client_error_range_not_satisfiable, "416 Range Not Satisfiable"},
{StatusCode::client_error_expectation_failed, "417 Expectation Failed"},
{StatusCode::client_error_im_a_teapot, "418 I'm a teapot"},
{StatusCode::client_error_misdirection_required, "421 Misdirected Request"},
{StatusCode::client_error_unprocessable_entity, "422 Unprocessable Entity"},
{StatusCode::client_error_locked, "423 Locked"},
{StatusCode::client_error_failed_dependency, "424 Failed Dependency"},
{StatusCode::client_error_upgrade_required, "426 Upgrade Required"},
{StatusCode::client_error_precondition_required, "428 Precondition Required"},
{StatusCode::client_error_too_many_requests, "429 Too Many Requests"},
{StatusCode::client_error_request_header_fields_too_large, "431 Request Header Fields Too Large"},
{StatusCode::client_error_unavailable_for_legal_reasons, "451 Unavailable For Legal Reasons"},
{StatusCode::server_error_internal_server_error, "500 Internal Server Error"},
{StatusCode::server_error_not_implemented, "501 Not Implemented"},
{StatusCode::server_error_bad_gateway, "502 Bad Gateway"},
{StatusCode::server_error_service_unavailable, "503 Service Unavailable"},
{StatusCode::server_error_gateway_timeout, "504 Gateway Timeout"},
{StatusCode::server_error_http_version_not_supported, "505 HTTP Version Not Supported"},
{StatusCode::server_error_variant_also_negotiates, "506 Variant Also Negotiates"},
{StatusCode::server_error_insufficient_storage, "507 Insufficient Storage"},
{StatusCode::server_error_loop_detected, "508 Loop Detected"},
{StatusCode::server_error_not_extended, "510 Not Extended"},
{StatusCode::server_error_network_authentication_required, "511 Network Authentication Required"}};
return status_code_strings;
}
inline StatusCode status_code(const std::string &status_code_string) noexcept {
if(status_code_string.size() < 3)
return StatusCode::unknown;
auto number = status_code_string.substr(0, 3);
if(number[0] < '0' || number[0] > '9' || number[1] < '0' || number[1] > '9' || number[2] < '0' || number[2] > '9')
return StatusCode::unknown;
class StringToStatusCode : public std::unordered_map<std::string, SimpleWeb::StatusCode> {
public:
StringToStatusCode() {
for(auto &status_code : status_code_strings())
emplace(status_code.second.substr(0, 3), status_code.first);
}
};
static StringToStatusCode string_to_status_code;
auto pos = string_to_status_code.find(number);
if(pos == string_to_status_code.end())
return static_cast<StatusCode>(atoi(number.c_str()));
return pos->second;
}
inline const std::string &status_code(StatusCode status_code_enum) noexcept {
auto pos = status_code_strings().find(status_code_enum);
if(pos == status_code_strings().end()) {
static std::string empty_string;
return empty_string;
}
return pos->second;
}
} // namespace SimpleWeb
#endif // SIMPLE_WEB_STATUS_CODE_HPP
#ifndef SIMPLE_WEB_UTILITY_HPP
#define SIMPLE_WEB_UTILITY_HPP
#include "status_code.hpp"
#include <atomic>
#include <chrono>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
#ifndef DEPRECATED
#if defined(__GNUC__) || defined(__clang__)
#define DEPRECATED __attribute__((deprecated))
#elif defined(_MSC_VER)
#define DEPRECATED __declspec(deprecated)
#else
#define DEPRECATED
#endif
#endif
#if __cplusplus > 201402L || _MSVC_LANG > 201402L
#include <string_view>
namespace SimpleWeb {
using string_view = std::string_view;
}
#elif !defined(USE_STANDALONE_ASIO)
#include <boost/utility/string_ref.hpp>
namespace SimpleWeb {
using string_view = boost::string_ref;
}
#else
namespace SimpleWeb {
using string_view = const std::string &;
}
#endif
namespace SimpleWeb {
inline bool case_insensitive_equal(const std::string &str1, const std::string &str2) noexcept {
return str1.size() == str2.size() &&
std::equal(str1.begin(), str1.end(), str2.begin(), [](char a, char b) {
return tolower(a) == tolower(b);
});
}
class CaseInsensitiveEqual {
public:
bool operator()(const std::string &str1, const std::string &str2) const noexcept {
return case_insensitive_equal(str1, str2);
}
};
// Based on https://stackoverflow.com/questions/2590677/how-do-i-combine-hash-values-in-c0x/2595226#2595226
class CaseInsensitiveHash {
public:
std::size_t operator()(const std::string &str) const noexcept {
std::size_t h = 0;
std::hash<int> hash;
for(auto c : str)
h ^= hash(tolower(c)) + 0x9e3779b9 + (h << 6) + (h >> 2);
return h;
}
};
using CaseInsensitiveMultimap = std::unordered_multimap<std::string, std::string, CaseInsensitiveHash, CaseInsensitiveEqual>;
/// Percent encoding and decoding
class Percent {
public:
/// Returns percent-encoded string
static std::string encode(const std::string &value) noexcept {
static auto hex_chars = "0123456789ABCDEF";
std::string result;
result.reserve(value.size()); // Minimum size of result
for(auto &chr : value) {
if(!((chr >= '0' && chr <= '9') || (chr >= 'A' && chr <= 'Z') || (chr >= 'a' && chr <= 'z') || chr == '-' || chr == '.' || chr == '_' || chr == '~'))
result += std::string("%") + hex_chars[static_cast<unsigned char>(chr) >> 4] + hex_chars[static_cast<unsigned char>(chr) & 15];
else
result += chr;
}
return result;
}
/// Returns percent-decoded string
static std::string decode(const std::string &value) noexcept {
std::string result;
result.reserve(value.size() / 3 + (value.size() % 3)); // Minimum size of result
for(std::size_t i = 0; i < value.size(); ++i) {
auto &chr = value[i];
if(chr == '%' && i + 2 < value.size()) {
auto hex = value.substr(i + 1, 2);
auto decoded_chr = static_cast<char>(std::strtol(hex.c_str(), nullptr, 16));
result += decoded_chr;
i += 2;
}
else if(chr == '+')
result += ' ';
else
result += chr;
}
return result;
}
};
/// Query string creation and parsing
class QueryString {
public:
/// Returns query string created from given field names and values
static std::string create(const CaseInsensitiveMultimap &fields) noexcept {
std::string result;
bool first = true;
for(auto &field : fields) {
result += (!first ? "&" : "") + field.first + '=' + Percent::encode(field.second);
first = false;
}
return result;
}
/// Returns query keys with percent-decoded values.
static CaseInsensitiveMultimap parse(const std::string &query_string) noexcept {
CaseInsensitiveMultimap result;
if(query_string.empty())
return result;
std::size_t name_pos = 0;
auto name_end_pos = std::string::npos;
auto value_pos = std::string::npos;
for(std::size_t c = 0; c < query_string.size(); ++c) {
if(query_string[c] == '&') {
auto name = query_string.substr(name_pos, (name_end_pos == std::string::npos ? c : name_end_pos) - name_pos);
if(!name.empty()) {
auto value = value_pos == std::string::npos ? std::string() : query_string.substr(value_pos, c - value_pos);
result.emplace(std::move(name), Percent::decode(value));
}
name_pos = c + 1;
name_end_pos = std::string::npos;
value_pos = std::string::npos;
}
else if(query_string[c] == '=') {
name_end_pos = c;
value_pos = c + 1;
}
}
if(name_pos < query_string.size()) {
auto name = query_string.substr(name_pos, name_end_pos - name_pos);
if(!name.empty()) {
auto value = value_pos >= query_string.size() ? std::string() : query_string.substr(value_pos);
result.emplace(std::move(name), Percent::decode(value));
}
}
return result;
}
};
class HttpHeader {
public:
/// Parse header fields from stream
static CaseInsensitiveMultimap parse(std::istream &stream) noexcept {
CaseInsensitiveMultimap result;
std::string line;
std::size_t param_end;
while(getline(stream, line) && (param_end = line.find(':')) != std::string::npos) {
std::size_t value_start = param_end + 1;
while(value_start + 1 < line.size() && line[value_start] == ' ')
++value_start;
if(value_start < line.size())
result.emplace(line.substr(0, param_end), line.substr(value_start, line.size() - value_start - (line.back() == '\r' ? 1 : 0)));
}
return result;
}
class FieldValue {
public:
class SemicolonSeparatedAttributes {
public:
/// Parse Set-Cookie or Content-Disposition from given header field value.
/// Attribute values are percent-decoded.
static CaseInsensitiveMultimap parse(const std::string &value) {
CaseInsensitiveMultimap result;
std::size_t name_start_pos = std::string::npos;
std::size_t name_end_pos = std::string::npos;
std::size_t value_start_pos = std::string::npos;
for(std::size_t c = 0; c < value.size(); ++c) {
if(name_start_pos == std::string::npos) {
if(value[c] != ' ' && value[c] != ';')
name_start_pos = c;
}
else {
if(name_end_pos == std::string::npos) {
if(value[c] == ';') {
result.emplace(value.substr(name_start_pos, c - name_start_pos), std::string());
name_start_pos = std::string::npos;
}
else if(value[c] == '=')
name_end_pos = c;
}
else {
if(value_start_pos == std::string::npos) {
if(value[c] == '"' && c + 1 < value.size())
value_start_pos = c + 1;
else
value_start_pos = c;
}
else if(value[c] == '"' || value[c] == ';') {
result.emplace(value.substr(name_start_pos, name_end_pos - name_start_pos), Percent::decode(value.substr(value_start_pos, c - value_start_pos)));
name_start_pos = std::string::npos;
name_end_pos = std::string::npos;
value_start_pos = std::string::npos;
}
}
}
}
if(name_start_pos != std::string::npos) {
if(name_end_pos == std::string::npos)
result.emplace(value.substr(name_start_pos), std::string());
else if(value_start_pos != std::string::npos) {
if(value.back() == '"')
result.emplace(value.substr(name_start_pos, name_end_pos - name_start_pos), Percent::decode(value.substr(value_start_pos, value.size() - 1)));
else
result.emplace(value.substr(name_start_pos, name_end_pos - name_start_pos), Percent::decode(value.substr(value_start_pos)));
}
}
return result;
}
};
};
};
class RequestMessage {
public:
/** Parse request line and header fields from a request stream.
*
* @param[in] stream Stream to parse.
* @param[out] method HTTP method.
* @param[out] path Path from request URI.
* @param[out] query_string Query string from request URI.
* @param[out] version HTTP version.
* @param[out] header Header fields.
*
* @return True if stream is parsed successfully, false if not.
*/
static bool parse(std::istream &stream, std::string &method, std::string &path, std::string &query_string, std::string &version, CaseInsensitiveMultimap &header) noexcept {
std::string line;
std::size_t method_end;
if(getline(stream, line) && (method_end = line.find(' ')) != std::string::npos) {
method = line.substr(0, method_end);
std::size_t query_start = std::string::npos;
std::size_t path_and_query_string_end = std::string::npos;
for(std::size_t i = method_end + 1; i < line.size(); ++i) {
if(line[i] == '?' && (i + 1) < line.size())
query_start = i + 1;
else if(line[i] == ' ') {
path_and_query_string_end = i;
break;
}
}
if(path_and_query_string_end != std::string::npos) {
if(query_start != std::string::npos) {
path = line.substr(method_end + 1, query_start - method_end - 2);
query_string = line.substr(query_start, path_and_query_string_end - query_start);
}
else
path = line.substr(method_end + 1, path_and_query_string_end - method_end - 1);
std::size_t protocol_end;
if((protocol_end = line.find('/', path_and_query_string_end + 1)) != std::string::npos) {
if(line.compare(path_and_query_string_end + 1, protocol_end - path_and_query_string_end - 1, "HTTP") != 0)
return false;
version = line.substr(protocol_end + 1, line.size() - protocol_end - 2);
}
else
return false;
header = HttpHeader::parse(stream);
}
else
return false;
}
else
return false;
return true;
}
};
class ResponseMessage {
public:
/** Parse status line and header fields from a response stream.
*
* @param[in] stream Stream to parse.
* @param[out] version HTTP version.
* @param[out] status_code HTTP status code.
* @param[out] header Header fields.
*
* @return True if stream is parsed successfully, false if not.
*/
static bool parse(std::istream &stream, std::string &version, std::string &status_code, CaseInsensitiveMultimap &header) noexcept {
std::string line;
std::size_t version_end;
if(getline(stream, line) && (version_end = line.find(' ')) != std::string::npos) {
if(5 < line.size())
version = line.substr(5, version_end - 5);
else
return false;
if((version_end + 1) < line.size())
status_code = line.substr(version_end + 1, line.size() - (version_end + 1) - (line.back() == '\r' ? 1 : 0));
else
return false;
header = HttpHeader::parse(stream);
}
else
return false;
return true;
}
};
/// Date class working with formats specified in RFC 7231 Date/Time Formats
class Date {
public:
/// Returns the given std::chrono::system_clock::time_point as a string with the following format: Wed, 31 Jul 2019 11:34:23 GMT.
static std::string to_string(const std::chrono::system_clock::time_point time_point) noexcept {
static std::string result_cache;
static std::chrono::system_clock::time_point last_time_point;
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
if(std::chrono::duration_cast<std::chrono::seconds>(time_point - last_time_point).count() == 0 && !result_cache.empty())
return result_cache;
last_time_point = time_point;
std::string result;
result.reserve(29);
auto time = std::chrono::system_clock::to_time_t(time_point);
tm tm;
#if defined(_MSC_VER) || defined(__MINGW32__)
if(gmtime_s(&tm, &time) != 0)
return {};
auto gmtime = &tm;
#else
auto gmtime = gmtime_r(&time, &tm);
if(!gmtime)
return {};
#endif
switch(gmtime->tm_wday) {
case 0: result += "Sun, "; break;
case 1: result += "Mon, "; break;
case 2: result += "Tue, "; break;
case 3: result += "Wed, "; break;
case 4: result += "Thu, "; break;
case 5: result += "Fri, "; break;
case 6: result += "Sat, "; break;
}
result += gmtime->tm_mday < 10 ? '0' : static_cast<char>(gmtime->tm_mday / 10 + 48);
result += static_cast<char>(gmtime->tm_mday % 10 + 48);
switch(gmtime->tm_mon) {
case 0: result += " Jan "; break;
case 1: result += " Feb "; break;
case 2: result += " Mar "; break;
case 3: result += " Apr "; break;
case 4: result += " May "; break;
case 5: result += " Jun "; break;
case 6: result += " Jul "; break;
case 7: result += " Aug "; break;
case 8: result += " Sep "; break;
case 9: result += " Oct "; break;
case 10: result += " Nov "; break;
case 11: result += " Dec "; break;
}
auto year = gmtime->tm_year + 1900;
result += static_cast<char>(year / 1000 + 48);
result += static_cast<char>((year / 100) % 10 + 48);
result += static_cast<char>((year / 10) % 10 + 48);
result += static_cast<char>(year % 10 + 48);
result += ' ';
result += gmtime->tm_hour < 10 ? '0' : static_cast<char>(gmtime->tm_hour / 10 + 48);
result += static_cast<char>(gmtime->tm_hour % 10 + 48);
result += ':';
result += gmtime->tm_min < 10 ? '0' : static_cast<char>(gmtime->tm_min / 10 + 48);
result += static_cast<char>(gmtime->tm_min % 10 + 48);
result += ':';
result += gmtime->tm_sec < 10 ? '0' : static_cast<char>(gmtime->tm_sec / 10 + 48);
result += static_cast<char>(gmtime->tm_sec % 10 + 48);
result += " GMT";
result_cache = result;
return result;
}
};
} // namespace SimpleWeb
#ifdef __SSE2__
#include <emmintrin.h>
namespace SimpleWeb {
inline void spin_loop_pause() noexcept { _mm_pause(); }
} // namespace SimpleWeb
// TODO: need verification that the following checks are correct:
#elif defined(_MSC_VER) && _MSC_VER >= 1800 && (defined(_M_X64) || defined(_M_IX86))
#include <intrin.h>
namespace SimpleWeb {
inline void spin_loop_pause() noexcept { _mm_pause(); }
} // namespace SimpleWeb
#else
namespace SimpleWeb {
inline void spin_loop_pause() noexcept {}
} // namespace SimpleWeb
#endif
namespace SimpleWeb {
/// Makes it possible to for instance cancel Asio handlers without stopping asio::io_service.
class ScopeRunner {
/// Scope count that is set to -1 if scopes are to be canceled.
std::atomic<long> count;
public:
class SharedLock {
friend class ScopeRunner;
std::atomic<long> &count;
SharedLock(std::atomic<long> &count) noexcept : count(count) {}
SharedLock &operator=(const SharedLock &) = delete;
SharedLock(const SharedLock &) = delete;
public:
~SharedLock() noexcept {
count.fetch_sub(1);
}
};
ScopeRunner() noexcept : count(0) {}
/// Returns nullptr if scope should be exited, or a shared lock otherwise.
/// The shared lock ensures that a potential destructor call is delayed until all locks are released.
std::unique_ptr<SharedLock> continue_lock() noexcept {
long expected = count;
while(expected >= 0 && !count.compare_exchange_weak(expected, expected + 1))
spin_loop_pause();
if(expected < 0)
return nullptr;
else
return std::unique_ptr<SharedLock>(new SharedLock(count));
}
/// Blocks until all shared locks are released, then prevents future shared locks.
void stop() noexcept {
long expected = 0;
while(!count.compare_exchange_weak(expected, -1)) {
if(expected < 0)
return;
expected = 0;
spin_loop_pause();
}
}
};
} // namespace SimpleWeb
#endif // SIMPLE_WEB_UTILITY_HPP
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment