Added .clang-format file and applied style to source files
This commit is contained in:
parent
3ee9f8dc52
commit
e50d2fc63a
13 changed files with 2552 additions and 2538 deletions
9
.clang-format
Normal file
9
.clang-format
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
IndentWidth: 2
|
||||
AccessModifierOffset: -2
|
||||
UseTab: Never
|
||||
ColumnLimit: 0
|
||||
MaxEmptyLinesToKeep: 2
|
||||
SpaceBeforeParens: Never
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping: {BeforeElse: true, BeforeCatch: true}
|
||||
NamespaceIndentation: All
|
||||
1116
client_http.hpp
1116
client_http.hpp
File diff suppressed because it is too large
Load diff
230
client_https.hpp
230
client_https.hpp
|
|
@ -1,5 +1,5 @@
|
|||
#ifndef CLIENT_HTTPS_HPP
|
||||
#define CLIENT_HTTPS_HPP
|
||||
#define CLIENT_HTTPS_HPP
|
||||
|
||||
#include "client_http.hpp"
|
||||
|
||||
|
|
@ -10,125 +10,125 @@
|
|||
#endif
|
||||
|
||||
namespace SimpleWeb {
|
||||
typedef asio::ssl::stream<asio::ip::tcp::socket> HTTPS;
|
||||
|
||||
template<>
|
||||
class Client<HTTPS> : public ClientBase<HTTPS> {
|
||||
public:
|
||||
friend ClientBase<HTTPS>;
|
||||
|
||||
Client(const std::string& server_port_path, bool verify_certificate=true,
|
||||
const std::string& cert_file=std::string(), const std::string& private_key_file=std::string(),
|
||||
const std::string& verify_file=std::string()) :
|
||||
ClientBase<HTTPS>::ClientBase(server_port_path, 443), context(asio::ssl::context::tlsv12) {
|
||||
if(cert_file.size()>0 && private_key_file.size()>0) {
|
||||
context.use_certificate_chain_file(cert_file);
|
||||
context.use_private_key_file(private_key_file, asio::ssl::context::pem);
|
||||
}
|
||||
|
||||
if(verify_certificate)
|
||||
context.set_verify_callback(asio::ssl::rfc2818_verification(host));
|
||||
|
||||
if(verify_file.size()>0)
|
||||
context.load_verify_file(verify_file);
|
||||
else
|
||||
context.set_default_verify_paths();
|
||||
|
||||
if(verify_file.size()>0 || verify_certificate)
|
||||
context.set_verify_mode(asio::ssl::verify_peer);
|
||||
else
|
||||
context.set_verify_mode(asio::ssl::verify_none);
|
||||
}
|
||||
typedef asio::ssl::stream<asio::ip::tcp::socket> HTTPS;
|
||||
|
||||
protected:
|
||||
asio::ssl::context context;
|
||||
|
||||
std::shared_ptr<Connection> create_connection() override {
|
||||
return std::make_shared<Connection>(host, port, config, std::unique_ptr<HTTPS>(new HTTPS(*io_service, context)));
|
||||
}
|
||||
|
||||
static void connect(const std::shared_ptr<Session> &session) {
|
||||
if(!session->connection->socket->lowest_layer().is_open()) {
|
||||
auto resolver=std::make_shared<asio::ip::tcp::resolver>(*session->io_service);
|
||||
resolver->async_resolve(*session->connection->query, [session, resolver] (const error_code &ec, asio::ip::tcp::resolver::iterator it){
|
||||
template <>
|
||||
class Client<HTTPS> : public ClientBase<HTTPS> {
|
||||
public:
|
||||
friend ClientBase<HTTPS>;
|
||||
|
||||
Client(const std::string &server_port_path, bool verify_certificate = true, const std::string &cert_file = std::string(),
|
||||
const std::string &private_key_file = std::string(), const std::string &verify_file = std::string())
|
||||
: ClientBase<HTTPS>::ClientBase(server_port_path, 443), context(asio::ssl::context::tlsv12) {
|
||||
if(cert_file.size() > 0 && private_key_file.size() > 0) {
|
||||
context.use_certificate_chain_file(cert_file);
|
||||
context.use_private_key_file(private_key_file, asio::ssl::context::pem);
|
||||
}
|
||||
|
||||
if(verify_certificate)
|
||||
context.set_verify_callback(asio::ssl::rfc2818_verification(host));
|
||||
|
||||
if(verify_file.size() > 0)
|
||||
context.load_verify_file(verify_file);
|
||||
else
|
||||
context.set_default_verify_paths();
|
||||
|
||||
if(verify_file.size() > 0 || verify_certificate)
|
||||
context.set_verify_mode(asio::ssl::verify_peer);
|
||||
else
|
||||
context.set_verify_mode(asio::ssl::verify_none);
|
||||
}
|
||||
|
||||
protected:
|
||||
asio::ssl::context context;
|
||||
|
||||
std::shared_ptr<Connection> create_connection() override {
|
||||
return std::make_shared<Connection>(host, port, config, std::unique_ptr<HTTPS>(new HTTPS(*io_service, context)));
|
||||
}
|
||||
|
||||
static void connect(const std::shared_ptr<Session> &session) {
|
||||
if(!session->connection->socket->lowest_layer().is_open()) {
|
||||
auto resolver = std::make_shared<asio::ip::tcp::resolver>(*session->io_service);
|
||||
resolver->async_resolve(*session->connection->query, [session, resolver](const error_code &ec, asio::ip::tcp::resolver::iterator it) {
|
||||
if(!ec) {
|
||||
auto timer = get_timeout_timer(session, session->connection->config.timeout_connect);
|
||||
asio::async_connect(session->connection->socket->lowest_layer(), it, [session, resolver, timer](const error_code &ec, asio::ip::tcp::resolver::iterator /*it*/) {
|
||||
if(timer)
|
||||
timer->cancel();
|
||||
if(!ec) {
|
||||
asio::ip::tcp::no_delay option(true);
|
||||
session->connection->socket->lowest_layer().set_option(option);
|
||||
|
||||
if(!session->connection->config.proxy_server.empty()) {
|
||||
auto write_buffer = std::make_shared<asio::streambuf>();
|
||||
std::ostream write_stream(write_buffer.get());
|
||||
auto host_port = session->connection->host + ':' + std::to_string(session->connection->port);
|
||||
write_stream << "CONNECT " + host_port + " HTTP/1.1\r\n"
|
||||
<< "Host: " << host_port << "\r\n\r\n";
|
||||
auto timer = get_timeout_timer(session, session->connection->config.timeout_connect);
|
||||
asio::async_write(session->connection->socket->next_layer(), *write_buffer, [session, write_buffer, timer](const error_code &ec, size_t /*bytes_transferred*/) {
|
||||
if(timer)
|
||||
timer->cancel();
|
||||
if(!ec) {
|
||||
auto timer=get_timeout_timer(session, session->connection->config.timeout_connect);
|
||||
asio::async_connect(session->connection->socket->lowest_layer(), it, [session, resolver, timer] (const error_code &ec, asio::ip::tcp::resolver::iterator /*it*/){
|
||||
if(timer)
|
||||
timer->cancel();
|
||||
if(!ec) {
|
||||
asio::ip::tcp::no_delay option(true);
|
||||
session->connection->socket->lowest_layer().set_option(option);
|
||||
|
||||
if(!session->connection->config.proxy_server.empty()) {
|
||||
auto write_buffer=std::make_shared<asio::streambuf>();
|
||||
std::ostream write_stream(write_buffer.get());
|
||||
auto host_port=session->connection->host+':'+std::to_string(session->connection->port);
|
||||
write_stream << "CONNECT "+host_port+" HTTP/1.1\r\n" << "Host: " << host_port << "\r\n\r\n";
|
||||
auto timer=get_timeout_timer(session, session->connection->config.timeout_connect);
|
||||
asio::async_write(session->connection->socket->next_layer(), *write_buffer, [session, write_buffer, timer](const error_code &ec, size_t /*bytes_transferred*/) {
|
||||
if(timer)
|
||||
timer->cancel();
|
||||
if(!ec) {
|
||||
std::shared_ptr<Response> response(new Response());
|
||||
auto timer=get_timeout_timer(session, session->connection->config.timeout_connect);
|
||||
asio::async_read_until(session->connection->socket->next_layer(), response->content_buffer, "\r\n\r\n", [session, response, timer](const error_code& ec, size_t /*bytes_transferred*/) {
|
||||
if(timer)
|
||||
timer->cancel();
|
||||
if(!ec) {
|
||||
parse_response_header(response);
|
||||
if (response->status_code.empty() || response->status_code.compare(0, 3, "200") != 0) {
|
||||
close(session);
|
||||
session->callback(make_error_code::make_error_code(errc::permission_denied));
|
||||
}
|
||||
else
|
||||
handshake(session);
|
||||
}
|
||||
else {
|
||||
close(session);
|
||||
session->callback(ec);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
close(session);
|
||||
session->callback(ec);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
handshake(session);
|
||||
}
|
||||
else {
|
||||
close(session);
|
||||
session->callback(ec);
|
||||
}
|
||||
});
|
||||
std::shared_ptr<Response> response(new Response());
|
||||
auto timer = get_timeout_timer(session, session->connection->config.timeout_connect);
|
||||
asio::async_read_until(session->connection->socket->next_layer(), response->content_buffer, "\r\n\r\n", [session, response, timer](const error_code &ec, size_t /*bytes_transferred*/) {
|
||||
if(timer)
|
||||
timer->cancel();
|
||||
if(!ec) {
|
||||
parse_response_header(response);
|
||||
if(response->status_code.empty() || response->status_code.compare(0, 3, "200") != 0) {
|
||||
close(session);
|
||||
session->callback(make_error_code::make_error_code(errc::permission_denied));
|
||||
}
|
||||
else
|
||||
handshake(session);
|
||||
}
|
||||
else {
|
||||
close(session);
|
||||
session->callback(ec);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
close(session);
|
||||
session->callback(ec);
|
||||
close(session);
|
||||
session->callback(ec);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
write(session);
|
||||
}
|
||||
|
||||
static void handshake(const std::shared_ptr<Session> &session) {
|
||||
auto timer=get_timeout_timer(session, session->connection->config.timeout_connect);
|
||||
session->connection->socket->async_handshake(asio::ssl::stream_base::client, [session, timer](const error_code& ec) {
|
||||
if(timer)
|
||||
timer->cancel();
|
||||
if(!ec)
|
||||
write(session);
|
||||
else {
|
||||
close(session);
|
||||
session->callback(ec);
|
||||
});
|
||||
}
|
||||
else
|
||||
handshake(session);
|
||||
}
|
||||
else {
|
||||
close(session);
|
||||
session->callback(ec);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
else {
|
||||
close(session);
|
||||
session->callback(ec);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
write(session);
|
||||
}
|
||||
|
||||
#endif /* CLIENT_HTTPS_HPP */
|
||||
static void handshake(const std::shared_ptr<Session> &session) {
|
||||
auto timer = get_timeout_timer(session, session->connection->config.timeout_connect);
|
||||
session->connection->socket->async_handshake(asio::ssl::stream_base::client, [session, timer](const error_code &ec) {
|
||||
if(timer)
|
||||
timer->cancel();
|
||||
if(!ec)
|
||||
write(session);
|
||||
else {
|
||||
close(session);
|
||||
session->callback(ec);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
} // namespace SimpleWeb
|
||||
|
||||
#endif /* CLIENT_HTTPS_HPP */
|
||||
|
|
|
|||
387
crypto.hpp
387
crypto.hpp
|
|
@ -1,219 +1,220 @@
|
|||
#ifndef SIMPLE_WEB_CRYPTO_HPP
|
||||
#define SIMPLE_WEB_CRYPTO_HPP
|
||||
#define SIMPLE_WEB_CRYPTO_HPP
|
||||
|
||||
#include <string>
|
||||
#include <cmath>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <istream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
//Moving these to a seperate namespace for minimal global namespace cluttering does not work with clang++
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/buffer.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/md5.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
namespace SimpleWeb {
|
||||
//TODO 2017: remove workaround for MSVS 2012
|
||||
#if _MSC_VER == 1700 //MSVS 2012 has no definition for round()
|
||||
inline double round(double x) { //custom definition of round() for positive numbers
|
||||
return floor(x + 0.5);
|
||||
}
|
||||
#endif
|
||||
//TODO 2017: remove workaround for MSVS 2012
|
||||
#if _MSC_VER == 1700 //MSVS 2012 has no definition for round()
|
||||
inline double round(double x) { //custom definition of round() for positive numbers
|
||||
return floor(x + 0.5);
|
||||
}
|
||||
#endif
|
||||
|
||||
class Crypto {
|
||||
const static size_t buffer_size=131072;
|
||||
class Crypto {
|
||||
const static size_t buffer_size = 131072;
|
||||
|
||||
public:
|
||||
class Base64 {
|
||||
public:
|
||||
class Base64 {
|
||||
public:
|
||||
static std::string encode(const std::string &ascii) {
|
||||
std::string base64;
|
||||
|
||||
BIO *bio, *b64;
|
||||
BUF_MEM *bptr=BUF_MEM_new();
|
||||
static std::string encode(const std::string &ascii) {
|
||||
std::string base64;
|
||||
|
||||
b64 = BIO_new(BIO_f_base64());
|
||||
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
|
||||
bio = BIO_new(BIO_s_mem());
|
||||
BIO_push(b64, bio);
|
||||
BIO_set_mem_buf(b64, bptr, BIO_CLOSE);
|
||||
|
||||
//Write directly to base64-buffer to avoid copy
|
||||
int base64_length=static_cast<int>(round(4*ceil((double)ascii.size()/3.0)));
|
||||
base64.resize(base64_length);
|
||||
bptr->length=0;
|
||||
bptr->max=base64_length+1;
|
||||
bptr->data=(char*)&base64[0];
|
||||
BIO *bio, *b64;
|
||||
BUF_MEM *bptr = BUF_MEM_new();
|
||||
|
||||
BIO_write(b64, &ascii[0], static_cast<int>(ascii.size()));
|
||||
BIO_flush(b64);
|
||||
b64 = BIO_new(BIO_f_base64());
|
||||
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
|
||||
bio = BIO_new(BIO_s_mem());
|
||||
BIO_push(b64, bio);
|
||||
BIO_set_mem_buf(b64, bptr, BIO_CLOSE);
|
||||
|
||||
//To keep &base64[0] through BIO_free_all(b64)
|
||||
bptr->length=0;
|
||||
bptr->max=0;
|
||||
bptr->data=nullptr;
|
||||
//Write directly to base64-buffer to avoid copy
|
||||
int base64_length = static_cast<int>(round(4 * ceil((double)ascii.size() / 3.0)));
|
||||
base64.resize(base64_length);
|
||||
bptr->length = 0;
|
||||
bptr->max = base64_length + 1;
|
||||
bptr->data = (char *)&base64[0];
|
||||
|
||||
BIO_free_all(b64);
|
||||
|
||||
return base64;
|
||||
}
|
||||
|
||||
static std::string decode(const std::string &base64) {
|
||||
std::string ascii;
|
||||
|
||||
//Resize ascii, however, the size is a up to two bytes too large.
|
||||
ascii.resize((6*base64.size())/8);
|
||||
BIO *b64, *bio;
|
||||
BIO_write(b64, &ascii[0], static_cast<int>(ascii.size()));
|
||||
BIO_flush(b64);
|
||||
|
||||
b64 = BIO_new(BIO_f_base64());
|
||||
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
|
||||
bio = BIO_new_mem_buf((char*)&base64[0], static_cast<int>(base64.size()));
|
||||
bio = BIO_push(b64, bio);
|
||||
//To keep &base64[0] through BIO_free_all(b64)
|
||||
bptr->length = 0;
|
||||
bptr->max = 0;
|
||||
bptr->data = nullptr;
|
||||
|
||||
int decoded_length = BIO_read(bio, &ascii[0], static_cast<int>(ascii.size()));
|
||||
ascii.resize(decoded_length);
|
||||
BIO_free_all(b64);
|
||||
|
||||
BIO_free_all(b64);
|
||||
|
||||
return ascii;
|
||||
}
|
||||
};
|
||||
|
||||
/// Return hex string from bytes in input string.
|
||||
static std::string to_hex_string(const std::string &input) {
|
||||
std::stringstream hex_stream;
|
||||
hex_stream << std::hex << std::internal << std::setfill('0');
|
||||
for (auto &byte : input)
|
||||
hex_stream << std::setw(2) << static_cast<int>(static_cast<unsigned char>(byte));
|
||||
return hex_stream.str();
|
||||
}
|
||||
|
||||
static std::string md5(const std::string &input, size_t iterations=1) {
|
||||
std::string hash;
|
||||
|
||||
hash.resize(128 / 8);
|
||||
MD5(reinterpret_cast<const unsigned char*>(&input[0]), input.size(), reinterpret_cast<unsigned char*>(&hash[0]));
|
||||
|
||||
for (size_t c = 1; c < iterations; ++c)
|
||||
MD5(reinterpret_cast<const unsigned char*>(&hash[0]), hash.size(), reinterpret_cast<unsigned char*>(&hash[0]));
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
static std::string md5(std::istream &stream, size_t iterations=1) {
|
||||
MD5_CTX context;
|
||||
MD5_Init(&context);
|
||||
std::streamsize read_length;
|
||||
std::vector<char> buffer(buffer_size);
|
||||
while((read_length=stream.read(&buffer[0], buffer_size).gcount())>0)
|
||||
MD5_Update(&context, buffer.data(), read_length);
|
||||
std::string hash;
|
||||
hash.resize(128 / 8);
|
||||
MD5_Final(reinterpret_cast<unsigned char*>(&hash[0]), &context);
|
||||
|
||||
for (size_t c = 1; c < iterations; ++c)
|
||||
MD5(reinterpret_cast<const unsigned char*>(&hash[0]), hash.size(), reinterpret_cast<unsigned char*>(&hash[0]));
|
||||
|
||||
return hash;
|
||||
}
|
||||
return base64;
|
||||
}
|
||||
|
||||
static std::string sha1(const std::string &input, size_t iterations=1) {
|
||||
std::string hash;
|
||||
|
||||
hash.resize(160 / 8);
|
||||
SHA1(reinterpret_cast<const unsigned char*>(&input[0]), input.size(), reinterpret_cast<unsigned char*>(&hash[0]));
|
||||
|
||||
for (size_t c = 1; c < iterations; ++c)
|
||||
SHA1(reinterpret_cast<const unsigned char*>(&hash[0]), hash.size(), reinterpret_cast<unsigned char*>(&hash[0]));
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
static std::string sha1(std::istream &stream, size_t iterations=1) {
|
||||
SHA_CTX context;
|
||||
SHA1_Init(&context);
|
||||
std::streamsize read_length;
|
||||
std::vector<char> buffer(buffer_size);
|
||||
while((read_length=stream.read(&buffer[0], buffer_size).gcount())>0)
|
||||
SHA1_Update(&context, buffer.data(), read_length);
|
||||
std::string hash;
|
||||
hash.resize(160 / 8);
|
||||
SHA1_Final(reinterpret_cast<unsigned char*>(&hash[0]), &context);
|
||||
|
||||
for (size_t c = 1; c < iterations; ++c)
|
||||
SHA1(reinterpret_cast<const unsigned char*>(&hash[0]), hash.size(), reinterpret_cast<unsigned char*>(&hash[0]));
|
||||
|
||||
return hash;
|
||||
}
|
||||
static std::string decode(const std::string &base64) {
|
||||
std::string ascii;
|
||||
|
||||
static std::string sha256(const std::string &input, size_t iterations=1) {
|
||||
std::string hash;
|
||||
|
||||
hash.resize(256 / 8);
|
||||
SHA256(reinterpret_cast<const unsigned char*>(&input[0]), input.size(), reinterpret_cast<unsigned char*>(&hash[0]));
|
||||
|
||||
for (size_t c = 1; c < iterations; ++c)
|
||||
SHA256(reinterpret_cast<const unsigned char*>(&hash[0]), hash.size(), reinterpret_cast<unsigned char*>(&hash[0]));
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
static std::string sha256(std::istream &stream, size_t iterations=1) {
|
||||
SHA256_CTX context;
|
||||
SHA256_Init(&context);
|
||||
std::streamsize read_length;
|
||||
std::vector<char> buffer(buffer_size);
|
||||
while((read_length=stream.read(&buffer[0], buffer_size).gcount())>0)
|
||||
SHA256_Update(&context, buffer.data(), read_length);
|
||||
std::string hash;
|
||||
hash.resize(256 / 8);
|
||||
SHA256_Final(reinterpret_cast<unsigned char*>(&hash[0]), &context);
|
||||
|
||||
for (size_t c = 1; c < iterations; ++c)
|
||||
SHA256(reinterpret_cast<const unsigned char*>(&hash[0]), hash.size(), reinterpret_cast<unsigned char*>(&hash[0]));
|
||||
|
||||
return hash;
|
||||
}
|
||||
//Resize ascii, however, the size is a up to two bytes too large.
|
||||
ascii.resize((6 * base64.size()) / 8);
|
||||
BIO *b64, *bio;
|
||||
|
||||
static std::string sha512(const std::string &input, size_t iterations=1) {
|
||||
std::string hash;
|
||||
|
||||
hash.resize(512 / 8);
|
||||
SHA512(reinterpret_cast<const unsigned char*>(&input[0]), input.size(), reinterpret_cast<unsigned char*>(&hash[0]));
|
||||
|
||||
for (size_t c = 1; c < iterations; ++c)
|
||||
SHA512(reinterpret_cast<const unsigned char*>(&hash[0]), hash.size(), reinterpret_cast<unsigned char*>(&hash[0]));
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
static std::string sha512(std::istream &stream, size_t iterations=1) {
|
||||
SHA512_CTX context;
|
||||
SHA512_Init(&context);
|
||||
std::streamsize read_length;
|
||||
std::vector<char> buffer(buffer_size);
|
||||
while((read_length=stream.read(&buffer[0], buffer_size).gcount())>0)
|
||||
SHA512_Update(&context, buffer.data(), read_length);
|
||||
std::string hash;
|
||||
hash.resize(512 / 8);
|
||||
SHA512_Final(reinterpret_cast<unsigned char*>(&hash[0]), &context);
|
||||
|
||||
for (size_t c = 1; c < iterations; ++c)
|
||||
SHA512(reinterpret_cast<const unsigned char*>(&hash[0]), hash.size(), reinterpret_cast<unsigned char*>(&hash[0]));
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
/// key_size is number of bytes of the returned key.
|
||||
static std::string pbkdf2(const std::string &password, const std::string &salt, int iterations, int key_size) {
|
||||
std::string key;
|
||||
key.resize(key_size);
|
||||
PKCS5_PBKDF2_HMAC_SHA1(password.c_str(), password.size(),
|
||||
reinterpret_cast<const unsigned char*>(salt.c_str()), salt.size(), iterations,
|
||||
key_size, reinterpret_cast<unsigned char*>(&key[0]));
|
||||
return key;
|
||||
}
|
||||
b64 = BIO_new(BIO_f_base64());
|
||||
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
|
||||
bio = BIO_new_mem_buf((char *)&base64[0], static_cast<int>(base64.size()));
|
||||
bio = BIO_push(b64, bio);
|
||||
|
||||
int decoded_length = BIO_read(bio, &ascii[0], static_cast<int>(ascii.size()));
|
||||
ascii.resize(decoded_length);
|
||||
|
||||
BIO_free_all(b64);
|
||||
|
||||
return ascii;
|
||||
}
|
||||
};
|
||||
|
||||
/// Return hex string from bytes in input string.
|
||||
static std::string to_hex_string(const std::string &input) {
|
||||
std::stringstream hex_stream;
|
||||
hex_stream << std::hex << std::internal << std::setfill('0');
|
||||
for(auto &byte : input)
|
||||
hex_stream << std::setw(2) << static_cast<int>(static_cast<unsigned char>(byte));
|
||||
return hex_stream.str();
|
||||
}
|
||||
|
||||
static std::string md5(const std::string &input, size_t iterations = 1) {
|
||||
std::string hash;
|
||||
|
||||
hash.resize(128 / 8);
|
||||
MD5(reinterpret_cast<const unsigned char *>(&input[0]), input.size(), reinterpret_cast<unsigned char *>(&hash[0]));
|
||||
|
||||
for(size_t c = 1; c < iterations; ++c)
|
||||
MD5(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
static std::string md5(std::istream &stream, size_t iterations = 1) {
|
||||
MD5_CTX context;
|
||||
MD5_Init(&context);
|
||||
std::streamsize read_length;
|
||||
std::vector<char> buffer(buffer_size);
|
||||
while((read_length = stream.read(&buffer[0], buffer_size).gcount()) > 0)
|
||||
MD5_Update(&context, buffer.data(), read_length);
|
||||
std::string hash;
|
||||
hash.resize(128 / 8);
|
||||
MD5_Final(reinterpret_cast<unsigned char *>(&hash[0]), &context);
|
||||
|
||||
for(size_t c = 1; c < iterations; ++c)
|
||||
MD5(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
static std::string sha1(const std::string &input, size_t iterations = 1) {
|
||||
std::string hash;
|
||||
|
||||
hash.resize(160 / 8);
|
||||
SHA1(reinterpret_cast<const unsigned char *>(&input[0]), input.size(), reinterpret_cast<unsigned char *>(&hash[0]));
|
||||
|
||||
for(size_t c = 1; c < iterations; ++c)
|
||||
SHA1(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
static std::string sha1(std::istream &stream, size_t iterations = 1) {
|
||||
SHA_CTX context;
|
||||
SHA1_Init(&context);
|
||||
std::streamsize read_length;
|
||||
std::vector<char> buffer(buffer_size);
|
||||
while((read_length = stream.read(&buffer[0], buffer_size).gcount()) > 0)
|
||||
SHA1_Update(&context, buffer.data(), read_length);
|
||||
std::string hash;
|
||||
hash.resize(160 / 8);
|
||||
SHA1_Final(reinterpret_cast<unsigned char *>(&hash[0]), &context);
|
||||
|
||||
for(size_t c = 1; c < iterations; ++c)
|
||||
SHA1(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
static std::string sha256(const std::string &input, size_t iterations = 1) {
|
||||
std::string hash;
|
||||
|
||||
hash.resize(256 / 8);
|
||||
SHA256(reinterpret_cast<const unsigned char *>(&input[0]), input.size(), reinterpret_cast<unsigned char *>(&hash[0]));
|
||||
|
||||
for(size_t c = 1; c < iterations; ++c)
|
||||
SHA256(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
static std::string sha256(std::istream &stream, size_t iterations = 1) {
|
||||
SHA256_CTX context;
|
||||
SHA256_Init(&context);
|
||||
std::streamsize read_length;
|
||||
std::vector<char> buffer(buffer_size);
|
||||
while((read_length = stream.read(&buffer[0], buffer_size).gcount()) > 0)
|
||||
SHA256_Update(&context, buffer.data(), read_length);
|
||||
std::string hash;
|
||||
hash.resize(256 / 8);
|
||||
SHA256_Final(reinterpret_cast<unsigned char *>(&hash[0]), &context);
|
||||
|
||||
for(size_t c = 1; c < iterations; ++c)
|
||||
SHA256(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
static std::string sha512(const std::string &input, size_t iterations = 1) {
|
||||
std::string hash;
|
||||
|
||||
hash.resize(512 / 8);
|
||||
SHA512(reinterpret_cast<const unsigned char *>(&input[0]), input.size(), reinterpret_cast<unsigned char *>(&hash[0]));
|
||||
|
||||
for(size_t c = 1; c < iterations; ++c)
|
||||
SHA512(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
static std::string sha512(std::istream &stream, size_t iterations = 1) {
|
||||
SHA512_CTX context;
|
||||
SHA512_Init(&context);
|
||||
std::streamsize read_length;
|
||||
std::vector<char> buffer(buffer_size);
|
||||
while((read_length = stream.read(&buffer[0], buffer_size).gcount()) > 0)
|
||||
SHA512_Update(&context, buffer.data(), read_length);
|
||||
std::string hash;
|
||||
hash.resize(512 / 8);
|
||||
SHA512_Final(reinterpret_cast<unsigned char *>(&hash[0]), &context);
|
||||
|
||||
for(size_t c = 1; c < iterations; ++c)
|
||||
SHA512(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
/// key_size is number of bytes of the returned key.
|
||||
static std::string pbkdf2(const std::string &password, const std::string &salt, int iterations, int key_size) {
|
||||
std::string key;
|
||||
key.resize(key_size);
|
||||
PKCS5_PBKDF2_HMAC_SHA1(password.c_str(), password.size(),
|
||||
reinterpret_cast<const unsigned char *>(salt.c_str()), salt.size(), iterations,
|
||||
key_size, reinterpret_cast<unsigned char *>(&key[0]));
|
||||
return key;
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif /* SIMPLE_WEB_CRYPTO_HPP */
|
||||
#endif /* SIMPLE_WEB_CRYPTO_HPP */
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
#include "server_http.hpp"
|
||||
#include "client_http.hpp"
|
||||
#include "server_http.hpp"
|
||||
|
||||
//Added for the json-example
|
||||
#define BOOST_SPIRIT_THREADSAFE
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
|
||||
//Added for the default_resource example
|
||||
#include <fstream>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#ifdef HAVE_OPENSSL
|
||||
#include "crypto.hpp"
|
||||
#endif
|
||||
|
|
@ -23,224 +23,224 @@ typedef SimpleWeb::Server<SimpleWeb::HTTP> HttpServer;
|
|||
typedef SimpleWeb::Client<SimpleWeb::HTTP> HttpClient;
|
||||
|
||||
//Added for the default_resource example
|
||||
void default_resource_send(const HttpServer &server, const shared_ptr<HttpServer::Response> &response,
|
||||
const shared_ptr<ifstream> &ifs);
|
||||
void default_resource_send(const HttpServer &server, const shared_ptr<HttpServer::Response> &response, const shared_ptr<ifstream> &ifs);
|
||||
|
||||
int main() {
|
||||
//HTTP-server at port 8080 using 1 thread
|
||||
//Unless you do more heavy non-threaded processing in the resources,
|
||||
//1 thread is usually faster than several threads
|
||||
HttpServer server;
|
||||
server.config.port=8080;
|
||||
|
||||
//Add resources using path-regex and method-string, and an anonymous function
|
||||
//POST-example for the path /string, responds the posted string
|
||||
server.resource["^/string$"]["POST"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
|
||||
//Retrieve string:
|
||||
auto content=request->content.string();
|
||||
//request->content.string() is a convenience function for:
|
||||
//stringstream ss;
|
||||
//ss << request->content.rdbuf();
|
||||
//auto content=ss.str();
|
||||
|
||||
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << content.length() << "\r\n\r\n" << content;
|
||||
|
||||
|
||||
// Alternatively, use one of the convenience functions, for instance:
|
||||
// response->write(content);
|
||||
};
|
||||
|
||||
//POST-example for the path /json, responds firstName+" "+lastName from the posted json
|
||||
//Responds with an appropriate error message if the posted json is not valid, or if firstName or lastName is missing
|
||||
//Example posted json:
|
||||
//{
|
||||
// "firstName": "John",
|
||||
// "lastName": "Smith",
|
||||
// "age": 25
|
||||
//}
|
||||
server.resource["^/json$"]["POST"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
|
||||
try {
|
||||
ptree pt;
|
||||
read_json(request->content, pt);
|
||||
//HTTP-server at port 8080 using 1 thread
|
||||
//Unless you do more heavy non-threaded processing in the resources,
|
||||
//1 thread is usually faster than several threads
|
||||
HttpServer server;
|
||||
server.config.port = 8080;
|
||||
|
||||
auto name=pt.get<string>("firstName")+" "+pt.get<string>("lastName");
|
||||
//Add resources using path-regex and method-string, and an anonymous function
|
||||
//POST-example for the path /string, responds the posted string
|
||||
server.resource["^/string$"]["POST"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
|
||||
//Retrieve string:
|
||||
auto content = request->content.string();
|
||||
//request->content.string() is a convenience function for:
|
||||
//stringstream ss;
|
||||
//ss << request->content.rdbuf();
|
||||
//auto content=ss.str();
|
||||
|
||||
*response << "HTTP/1.1 200 OK\r\n"
|
||||
<< "Content-Type: application/json\r\n"
|
||||
<< "Content-Length: " << name.length() << "\r\n\r\n"
|
||||
<< name;
|
||||
}
|
||||
catch(const exception &e) {
|
||||
*response << "HTTP/1.1 400 Bad Request\r\nContent-Length: " << strlen(e.what()) << "\r\n\r\n" << e.what();
|
||||
}
|
||||
|
||||
|
||||
// Alternatively, using a convenience function:
|
||||
// try {
|
||||
// ptree pt;
|
||||
// read_json(request->content, pt);
|
||||
|
||||
// auto name=pt.get<string>("firstName")+" "+pt.get<string>("lastName");
|
||||
// response->write(name, {{"Content-Type", "application/json"}});
|
||||
// }
|
||||
// catch(const exception &e) {
|
||||
// response->write(SimpleWeb::StatusCode::client_error_bad_request, e.what());
|
||||
// }
|
||||
};
|
||||
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << content.length() << "\r\n\r\n"
|
||||
<< content;
|
||||
|
||||
//GET-example for the path /info
|
||||
//Responds with request-information
|
||||
server.resource["^/info$"]["GET"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
|
||||
stringstream stream;
|
||||
stream << "<h1>Request from " << request->remote_endpoint_address << " (" << request->remote_endpoint_port << ")</h1>";
|
||||
stream << request->method << " " << request->path << " HTTP/" << request->http_version << "<br>";
|
||||
for(auto &header: request->header)
|
||||
stream << header.first << ": " << header.second << "<br>";
|
||||
|
||||
//find length of content_stream (length received using content_stream.tellp())
|
||||
stream.seekp(0, ios::end);
|
||||
|
||||
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << stream.tellp() << "\r\n\r\n" << stream.rdbuf();
|
||||
|
||||
|
||||
// Alternatively, using a convenience function:
|
||||
// stringstream stream;
|
||||
// stream << "<h1>Request from " << request->remote_endpoint_address << " (" << request->remote_endpoint_port << ")</h1>";
|
||||
// stream << request->method << " " << request->path << " HTTP/" << request->http_version << "<br>";
|
||||
// for(auto &header: request->header)
|
||||
// stream << header.first << ": " << header.second << "<br>";
|
||||
// response->write(stream);
|
||||
};
|
||||
|
||||
//GET-example for the path /match/[number], responds with the matched string in path (number)
|
||||
//For instance a request GET /match/123 will receive: 123
|
||||
server.resource["^/match/([0-9]+)$"]["GET"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
|
||||
string number=request->path_match[1];
|
||||
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << number.length() << "\r\n\r\n" << number;
|
||||
|
||||
|
||||
// Alternatively, using a convenience function:
|
||||
// response->write(request->path_match[1]);
|
||||
};
|
||||
|
||||
//Get example simulating heavy work in a separate thread
|
||||
server.resource["^/work$"]["GET"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> /*request*/) {
|
||||
thread work_thread([response] {
|
||||
this_thread::sleep_for(chrono::seconds(5));
|
||||
response->write("Work done");
|
||||
});
|
||||
work_thread.detach();
|
||||
};
|
||||
|
||||
//Default GET-example. If no other matches, this anonymous function will be called.
|
||||
//Will respond with content in the web/-directory, and its subdirectories.
|
||||
//Default file: index.html
|
||||
//Can for instance be used to retrieve an HTML 5 client that uses REST-resources on this server
|
||||
server.default_resource["GET"]=[&server](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
|
||||
try {
|
||||
auto web_root_path=boost::filesystem::canonical("web");
|
||||
auto path=boost::filesystem::canonical(web_root_path/request->path);
|
||||
//Check if path is within web_root_path
|
||||
if(distance(web_root_path.begin(), web_root_path.end())>distance(path.begin(), path.end()) ||
|
||||
!equal(web_root_path.begin(), web_root_path.end(), path.begin()))
|
||||
throw invalid_argument("path must be within root path");
|
||||
if(boost::filesystem::is_directory(path))
|
||||
path/="index.html";
|
||||
|
||||
SimpleWeb::CaseInsensitiveMultimap header;
|
||||
|
||||
// Uncomment the following line to enable Cache-Control
|
||||
// header.emplace("Cache-Control", "max-age=86400");
|
||||
// Alternatively, use one of the convenience functions, for instance:
|
||||
// response->write(content);
|
||||
};
|
||||
|
||||
//POST-example for the path /json, responds firstName+" "+lastName from the posted json
|
||||
//Responds with an appropriate error message if the posted json is not valid, or if firstName or lastName is missing
|
||||
//Example posted json:
|
||||
//{
|
||||
// "firstName": "John",
|
||||
// "lastName": "Smith",
|
||||
// "age": 25
|
||||
//}
|
||||
server.resource["^/json$"]["POST"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
|
||||
try {
|
||||
ptree pt;
|
||||
read_json(request->content, pt);
|
||||
|
||||
auto name = pt.get<string>("firstName") + " " + pt.get<string>("lastName");
|
||||
|
||||
*response << "HTTP/1.1 200 OK\r\n"
|
||||
<< "Content-Type: application/json\r\n"
|
||||
<< "Content-Length: " << name.length() << "\r\n\r\n"
|
||||
<< name;
|
||||
}
|
||||
catch(const exception &e) {
|
||||
*response << "HTTP/1.1 400 Bad Request\r\nContent-Length: " << strlen(e.what()) << "\r\n\r\n"
|
||||
<< e.what();
|
||||
}
|
||||
|
||||
|
||||
// Alternatively, using a convenience function:
|
||||
// try {
|
||||
// ptree pt;
|
||||
// read_json(request->content, pt);
|
||||
|
||||
// auto name=pt.get<string>("firstName")+" "+pt.get<string>("lastName");
|
||||
// response->write(name, {{"Content-Type", "application/json"}});
|
||||
// }
|
||||
// catch(const exception &e) {
|
||||
// response->write(SimpleWeb::StatusCode::client_error_bad_request, e.what());
|
||||
// }
|
||||
};
|
||||
|
||||
//GET-example for the path /info
|
||||
//Responds with request-information
|
||||
server.resource["^/info$"]["GET"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
|
||||
stringstream stream;
|
||||
stream << "<h1>Request from " << request->remote_endpoint_address << " (" << request->remote_endpoint_port << ")</h1>";
|
||||
stream << request->method << " " << request->path << " HTTP/" << request->http_version << "<br>";
|
||||
for(auto &header : request->header)
|
||||
stream << header.first << ": " << header.second << "<br>";
|
||||
|
||||
//find length of content_stream (length received using content_stream.tellp())
|
||||
stream.seekp(0, ios::end);
|
||||
|
||||
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << stream.tellp() << "\r\n\r\n"
|
||||
<< stream.rdbuf();
|
||||
|
||||
|
||||
// Alternatively, using a convenience function:
|
||||
// stringstream stream;
|
||||
// stream << "<h1>Request from " << request->remote_endpoint_address << " (" << request->remote_endpoint_port << ")</h1>";
|
||||
// stream << request->method << " " << request->path << " HTTP/" << request->http_version << "<br>";
|
||||
// for(auto &header: request->header)
|
||||
// stream << header.first << ": " << header.second << "<br>";
|
||||
// response->write(stream);
|
||||
};
|
||||
|
||||
//GET-example for the path /match/[number], responds with the matched string in path (number)
|
||||
//For instance a request GET /match/123 will receive: 123
|
||||
server.resource["^/match/([0-9]+)$"]["GET"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
|
||||
string number = request->path_match[1];
|
||||
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << number.length() << "\r\n\r\n"
|
||||
<< number;
|
||||
|
||||
|
||||
// Alternatively, using a convenience function:
|
||||
// response->write(request->path_match[1]);
|
||||
};
|
||||
|
||||
//Get example simulating heavy work in a separate thread
|
||||
server.resource["^/work$"]["GET"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> /*request*/) {
|
||||
thread work_thread([response] {
|
||||
this_thread::sleep_for(chrono::seconds(5));
|
||||
response->write("Work done");
|
||||
});
|
||||
work_thread.detach();
|
||||
};
|
||||
|
||||
//Default GET-example. If no other matches, this anonymous function will be called.
|
||||
//Will respond with content in the web/-directory, and its subdirectories.
|
||||
//Default file: index.html
|
||||
//Can for instance be used to retrieve an HTML 5 client that uses REST-resources on this server
|
||||
server.default_resource["GET"] = [&server](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
|
||||
try {
|
||||
auto web_root_path = boost::filesystem::canonical("web");
|
||||
auto path = boost::filesystem::canonical(web_root_path / request->path);
|
||||
//Check if path is within web_root_path
|
||||
if(distance(web_root_path.begin(), web_root_path.end()) > distance(path.begin(), path.end()) ||
|
||||
!equal(web_root_path.begin(), web_root_path.end(), path.begin()))
|
||||
throw invalid_argument("path must be within root path");
|
||||
if(boost::filesystem::is_directory(path))
|
||||
path /= "index.html";
|
||||
|
||||
SimpleWeb::CaseInsensitiveMultimap header;
|
||||
|
||||
// Uncomment the following line to enable Cache-Control
|
||||
// header.emplace("Cache-Control", "max-age=86400");
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
// Uncomment the following lines to enable ETag
|
||||
// {
|
||||
// ifstream ifs(path.string(), ifstream::in | ios::binary);
|
||||
// if(ifs) {
|
||||
// auto hash=SimpleWeb::Crypto::to_hex_string(SimpleWeb::Crypto::md5(ifs));
|
||||
// header.emplace("ETag", "\""+hash+"\"");
|
||||
// auto it=request->header.find("If-None-Match");
|
||||
// if(it!=request->header.end()) {
|
||||
// if(!it->second.empty() && it->second.compare(1, hash.size(), hash)==0) {
|
||||
// response->write(SimpleWeb::StatusCode::redirection_not_modified, header);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// throw invalid_argument("could not read file");
|
||||
// }
|
||||
// Uncomment the following lines to enable ETag
|
||||
// {
|
||||
// ifstream ifs(path.string(), ifstream::in | ios::binary);
|
||||
// if(ifs) {
|
||||
// auto hash = SimpleWeb::Crypto::to_hex_string(SimpleWeb::Crypto::md5(ifs));
|
||||
// header.emplace("ETag", "\"" + hash + "\"");
|
||||
// auto it = request->header.find("If-None-Match");
|
||||
// if(it != request->header.end()) {
|
||||
// if(!it->second.empty() && it->second.compare(1, hash.size(), hash) == 0) {
|
||||
// response->write(SimpleWeb::StatusCode::redirection_not_modified, header);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// throw invalid_argument("could not read file");
|
||||
// }
|
||||
#endif
|
||||
|
||||
auto ifs=make_shared<ifstream>();
|
||||
ifs->open(path.string(), ifstream::in | ios::binary | ios::ate);
|
||||
|
||||
if(*ifs) {
|
||||
auto length=ifs->tellg();
|
||||
ifs->seekg(0, ios::beg);
|
||||
|
||||
header.emplace("Content-Length", to_string(length));
|
||||
response->write(header);
|
||||
default_resource_send(server, response, ifs);
|
||||
}
|
||||
else
|
||||
throw invalid_argument("could not read file");
|
||||
}
|
||||
catch(const exception &e) {
|
||||
response->write(SimpleWeb::StatusCode::client_error_bad_request, "Could not open path "+request->path+": "+e.what());
|
||||
}
|
||||
};
|
||||
|
||||
server.on_error=[](std::shared_ptr<HttpServer::Request> /*request*/, const SimpleWeb::error_code &/*ec*/) {
|
||||
// handle errors here
|
||||
};
|
||||
|
||||
thread server_thread([&server](){
|
||||
//Start server
|
||||
server.start();
|
||||
});
|
||||
|
||||
//Wait for server to start so that the client can connect
|
||||
this_thread::sleep_for(chrono::seconds(1));
|
||||
|
||||
//Client examples
|
||||
HttpClient client("localhost:8080");
|
||||
|
||||
// synchronous request examples
|
||||
auto r1=client.request("GET", "/match/123");
|
||||
cout << r1->content.rdbuf() << endl; // Alternatively, use the convenience function r1->content.string()
|
||||
auto ifs = make_shared<ifstream>();
|
||||
ifs->open(path.string(), ifstream::in | ios::binary | ios::ate);
|
||||
|
||||
string json_string="{\"firstName\": \"John\",\"lastName\": \"Smith\",\"age\": 25}";
|
||||
auto r2=client.request("POST", "/string", json_string);
|
||||
cout << r2->content.rdbuf() << endl;
|
||||
|
||||
// asynchronous request example
|
||||
client.request("POST", "/json", json_string, [](std::shared_ptr<HttpClient::Response> response, const SimpleWeb::error_code &ec) {
|
||||
if(!ec)
|
||||
cout << response->content.rdbuf() << endl;
|
||||
});
|
||||
client.io_service->reset(); // needed because the io_service has been run already in the synchronous examples
|
||||
client.io_service->run();
|
||||
|
||||
server_thread.join();
|
||||
|
||||
return 0;
|
||||
}
|
||||
if(*ifs) {
|
||||
auto length = ifs->tellg();
|
||||
ifs->seekg(0, ios::beg);
|
||||
|
||||
void default_resource_send(const HttpServer &server, const shared_ptr<HttpServer::Response> &response,
|
||||
const shared_ptr<ifstream> &ifs) {
|
||||
//read and send 128 KB at a time
|
||||
static vector<char> buffer(131072); // Safe when server is running on one thread
|
||||
streamsize read_length;
|
||||
if((read_length=ifs->read(&buffer[0], buffer.size()).gcount())>0) {
|
||||
response->write(&buffer[0], read_length);
|
||||
if(read_length==static_cast<streamsize>(buffer.size())) {
|
||||
server.send(response, [&server, response, ifs](const SimpleWeb::error_code &ec) {
|
||||
if(!ec)
|
||||
default_resource_send(server, response, ifs);
|
||||
else
|
||||
cerr << "Connection interrupted" << endl;
|
||||
});
|
||||
}
|
||||
header.emplace("Content-Length", to_string(length));
|
||||
response->write(header);
|
||||
default_resource_send(server, response, ifs);
|
||||
}
|
||||
else
|
||||
throw invalid_argument("could not read file");
|
||||
}
|
||||
catch(const exception &e) {
|
||||
response->write(SimpleWeb::StatusCode::client_error_bad_request, "Could not open path " + request->path + ": " + e.what());
|
||||
}
|
||||
};
|
||||
|
||||
server.on_error = [](std::shared_ptr<HttpServer::Request> /*request*/, const SimpleWeb::error_code & /*ec*/) {
|
||||
// handle errors here
|
||||
};
|
||||
|
||||
thread server_thread([&server]() {
|
||||
//Start server
|
||||
server.start();
|
||||
});
|
||||
|
||||
//Wait for server to start so that the client can connect
|
||||
this_thread::sleep_for(chrono::seconds(1));
|
||||
|
||||
//Client examples
|
||||
HttpClient client("localhost:8080");
|
||||
|
||||
// synchronous request examples
|
||||
auto r1 = client.request("GET", "/match/123");
|
||||
cout << r1->content.rdbuf() << endl; // Alternatively, use the convenience function r1->content.string()
|
||||
|
||||
string json_string = "{\"firstName\": \"John\",\"lastName\": \"Smith\",\"age\": 25}";
|
||||
auto r2 = client.request("POST", "/string", json_string);
|
||||
cout << r2->content.rdbuf() << endl;
|
||||
|
||||
// asynchronous request example
|
||||
client.request("POST", "/json", json_string, [](std::shared_ptr<HttpClient::Response> response, const SimpleWeb::error_code &ec) {
|
||||
if(!ec)
|
||||
cout << response->content.rdbuf() << endl;
|
||||
});
|
||||
client.io_service->reset(); // needed because the io_service has been run already in the synchronous examples
|
||||
client.io_service->run();
|
||||
|
||||
server_thread.join();
|
||||
}
|
||||
|
||||
void default_resource_send(const HttpServer &server, const shared_ptr<HttpServer::Response> &response, const shared_ptr<ifstream> &ifs) {
|
||||
//read and send 128 KB at a time
|
||||
static vector<char> buffer(131072); // Safe when server is running on one thread
|
||||
streamsize read_length;
|
||||
if((read_length = ifs->read(&buffer[0], buffer.size()).gcount()) > 0) {
|
||||
response->write(&buffer[0], read_length);
|
||||
if(read_length == static_cast<streamsize>(buffer.size())) {
|
||||
server.send(response, [&server, response, ifs](const SimpleWeb::error_code &ec) {
|
||||
if(!ec)
|
||||
default_resource_send(server, response, ifs);
|
||||
else
|
||||
cerr << "Connection interrupted" << endl;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
#include "server_https.hpp"
|
||||
#include "client_https.hpp"
|
||||
#include "server_https.hpp"
|
||||
|
||||
//Added for the json-example
|
||||
#define BOOST_SPIRIT_THREADSAFE
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
|
||||
//Added for the default_resource example
|
||||
#include <fstream>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include "crypto.hpp"
|
||||
#include <algorithm>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
//Added for the json-example:
|
||||
|
|
@ -21,225 +21,225 @@ typedef SimpleWeb::Server<SimpleWeb::HTTPS> HttpsServer;
|
|||
typedef SimpleWeb::Client<SimpleWeb::HTTPS> HttpsClient;
|
||||
|
||||
//Added for the default_resource example
|
||||
void default_resource_send(const HttpsServer &server, const shared_ptr<HttpsServer::Response> &response,
|
||||
const shared_ptr<ifstream> &ifs);
|
||||
void default_resource_send(const HttpsServer &server, const shared_ptr<HttpsServer::Response> &response, const shared_ptr<ifstream> &ifs);
|
||||
|
||||
int main() {
|
||||
//HTTPS-server at port 8080 using 1 thread
|
||||
//Unless you do more heavy non-threaded processing in the resources,
|
||||
//1 thread is usually faster than several threads
|
||||
HttpsServer server("server.crt", "server.key");
|
||||
server.config.port=8080;
|
||||
|
||||
//Add resources using path-regex and method-string, and an anonymous function
|
||||
//POST-example for the path /string, responds the posted string
|
||||
server.resource["^/string$"]["POST"]=[](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> request) {
|
||||
//Retrieve string:
|
||||
auto content=request->content.string();
|
||||
//request->content.string() is a convenience function for:
|
||||
//stringstream ss;
|
||||
//ss << request->content.rdbuf();
|
||||
//auto content=ss.str();
|
||||
|
||||
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << content.length() << "\r\n\r\n" << content;
|
||||
|
||||
|
||||
// Alternatively, use one of the convenience functions, for instance:
|
||||
// response->write(content);
|
||||
};
|
||||
|
||||
//POST-example for the path /json, responds firstName+" "+lastName from the posted json
|
||||
//Responds with an appropriate error message if the posted json is not valid, or if firstName or lastName is missing
|
||||
//Example posted json:
|
||||
//{
|
||||
// "firstName": "John",
|
||||
// "lastName": "Smith",
|
||||
// "age": 25
|
||||
//}
|
||||
server.resource["^/json$"]["POST"]=[](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> request) {
|
||||
try {
|
||||
ptree pt;
|
||||
read_json(request->content, pt);
|
||||
//HTTPS-server at port 8080 using 1 thread
|
||||
//Unless you do more heavy non-threaded processing in the resources,
|
||||
//1 thread is usually faster than several threads
|
||||
HttpsServer server("server.crt", "server.key");
|
||||
server.config.port = 8080;
|
||||
|
||||
auto name=pt.get<string>("firstName")+" "+pt.get<string>("lastName");
|
||||
//Add resources using path-regex and method-string, and an anonymous function
|
||||
//POST-example for the path /string, responds the posted string
|
||||
server.resource["^/string$"]["POST"] = [](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> request) {
|
||||
//Retrieve string:
|
||||
auto content = request->content.string();
|
||||
//request->content.string() is a convenience function for:
|
||||
//stringstream ss;
|
||||
//ss << request->content.rdbuf();
|
||||
//auto content=ss.str();
|
||||
|
||||
*response << "HTTP/1.1 200 OK\r\n"
|
||||
<< "Content-Type: application/json\r\n"
|
||||
<< "Content-Length: " << name.length() << "\r\n\r\n"
|
||||
<< name;
|
||||
}
|
||||
catch(const exception &e) {
|
||||
*response << "HTTP/1.1 400 Bad Request\r\nContent-Length: " << strlen(e.what()) << "\r\n\r\n" << e.what();
|
||||
}
|
||||
|
||||
|
||||
// Alternatively, using a convenience function:
|
||||
// try {
|
||||
// ptree pt;
|
||||
// read_json(request->content, pt);
|
||||
|
||||
// auto name=pt.get<string>("firstName")+" "+pt.get<string>("lastName");
|
||||
// response->write(name, {{"Content-Type", "application/json"}});
|
||||
// }
|
||||
// catch(const exception &e) {
|
||||
// response->write(SimpleWeb::StatusCode::client_error_bad_request, e.what());
|
||||
// }
|
||||
};
|
||||
|
||||
//GET-example for the path /info
|
||||
//Responds with request-information
|
||||
server.resource["^/info$"]["GET"]=[](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> request) {
|
||||
stringstream stream;
|
||||
stream << "<h1>Request from " << request->remote_endpoint_address << " (" << request->remote_endpoint_port << ")</h1>";
|
||||
stream << request->method << " " << request->path << " HTTP/" << request->http_version << "<br>";
|
||||
for(auto &header: request->header)
|
||||
stream << header.first << ": " << header.second << "<br>";
|
||||
|
||||
//find length of content_stream (length received using content_stream.tellp())
|
||||
stream.seekp(0, ios::end);
|
||||
|
||||
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << stream.tellp() << "\r\n\r\n" << stream.rdbuf();
|
||||
|
||||
|
||||
// Alternatively, using a convenience function:
|
||||
// stringstream stream;
|
||||
// stream << "<h1>Request from " << request->remote_endpoint_address << " (" << request->remote_endpoint_port << ")</h1>";
|
||||
// stream << request->method << " " << request->path << " HTTP/" << request->http_version << "<br>";
|
||||
// for(auto &header: request->header)
|
||||
// stream << header.first << ": " << header.second << "<br>";
|
||||
// response->write(stream);
|
||||
};
|
||||
|
||||
//GET-example for the path /match/[number], responds with the matched string in path (number)
|
||||
//For instance a request GET /match/123 will receive: 123
|
||||
server.resource["^/match/([0-9]+)$"]["GET"]=[](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> request) {
|
||||
string number=request->path_match[1];
|
||||
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << number.length() << "\r\n\r\n" << number;
|
||||
|
||||
|
||||
// Alternatively, using a convenience function:
|
||||
// response->write(request->path_match[1]);
|
||||
};
|
||||
|
||||
//Get example simulating heavy work in a separate thread
|
||||
server.resource["^/work$"]["GET"]=[](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> /*request*/) {
|
||||
thread work_thread([response] {
|
||||
this_thread::sleep_for(chrono::seconds(5));
|
||||
response->write("Work done");
|
||||
});
|
||||
work_thread.detach();
|
||||
};
|
||||
|
||||
//Default GET-example. If no other matches, this anonymous function will be called.
|
||||
//Will respond with content in the web/-directory, and its subdirectories.
|
||||
//Default file: index.html
|
||||
//Can for instance be used to retrieve an HTML 5 client that uses REST-resources on this server
|
||||
server.default_resource["GET"]=[&server](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> request) {
|
||||
try {
|
||||
auto web_root_path=boost::filesystem::canonical("web");
|
||||
auto path=boost::filesystem::canonical(web_root_path/request->path);
|
||||
//Check if path is within web_root_path
|
||||
if(distance(web_root_path.begin(), web_root_path.end())>distance(path.begin(), path.end()) ||
|
||||
!equal(web_root_path.begin(), web_root_path.end(), path.begin()))
|
||||
throw invalid_argument("path must be within root path");
|
||||
if(boost::filesystem::is_directory(path))
|
||||
path/="index.html";
|
||||
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << content.length() << "\r\n\r\n"
|
||||
<< content;
|
||||
|
||||
SimpleWeb::CaseInsensitiveMultimap header;
|
||||
|
||||
// Uncomment the following line to enable Cache-Control
|
||||
// header.emplace("Cache-Control", "max-age=86400");
|
||||
|
||||
// Alternatively, use one of the convenience functions, for instance:
|
||||
// response->write(content);
|
||||
};
|
||||
|
||||
//POST-example for the path /json, responds firstName+" "+lastName from the posted json
|
||||
//Responds with an appropriate error message if the posted json is not valid, or if firstName or lastName is missing
|
||||
//Example posted json:
|
||||
//{
|
||||
// "firstName": "John",
|
||||
// "lastName": "Smith",
|
||||
// "age": 25
|
||||
//}
|
||||
server.resource["^/json$"]["POST"] = [](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> request) {
|
||||
try {
|
||||
ptree pt;
|
||||
read_json(request->content, pt);
|
||||
|
||||
auto name = pt.get<string>("firstName") + " " + pt.get<string>("lastName");
|
||||
|
||||
*response << "HTTP/1.1 200 OK\r\n"
|
||||
<< "Content-Type: application/json\r\n"
|
||||
<< "Content-Length: " << name.length() << "\r\n\r\n"
|
||||
<< name;
|
||||
}
|
||||
catch(const exception &e) {
|
||||
*response << "HTTP/1.1 400 Bad Request\r\nContent-Length: " << strlen(e.what()) << "\r\n\r\n"
|
||||
<< e.what();
|
||||
}
|
||||
|
||||
|
||||
// Alternatively, using a convenience function:
|
||||
// try {
|
||||
// ptree pt;
|
||||
// read_json(request->content, pt);
|
||||
|
||||
// auto name=pt.get<string>("firstName")+" "+pt.get<string>("lastName");
|
||||
// response->write(name, {{"Content-Type", "application/json"}});
|
||||
// }
|
||||
// catch(const exception &e) {
|
||||
// response->write(SimpleWeb::StatusCode::client_error_bad_request, e.what());
|
||||
// }
|
||||
};
|
||||
|
||||
//GET-example for the path /info
|
||||
//Responds with request-information
|
||||
server.resource["^/info$"]["GET"] = [](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> request) {
|
||||
stringstream stream;
|
||||
stream << "<h1>Request from " << request->remote_endpoint_address << " (" << request->remote_endpoint_port << ")</h1>";
|
||||
stream << request->method << " " << request->path << " HTTP/" << request->http_version << "<br>";
|
||||
for(auto &header : request->header)
|
||||
stream << header.first << ": " << header.second << "<br>";
|
||||
|
||||
//find length of content_stream (length received using content_stream.tellp())
|
||||
stream.seekp(0, ios::end);
|
||||
|
||||
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << stream.tellp() << "\r\n\r\n"
|
||||
<< stream.rdbuf();
|
||||
|
||||
|
||||
// Alternatively, using a convenience function:
|
||||
// stringstream stream;
|
||||
// stream << "<h1>Request from " << request->remote_endpoint_address << " (" << request->remote_endpoint_port << ")</h1>";
|
||||
// stream << request->method << " " << request->path << " HTTP/" << request->http_version << "<br>";
|
||||
// for(auto &header: request->header)
|
||||
// stream << header.first << ": " << header.second << "<br>";
|
||||
// response->write(stream);
|
||||
};
|
||||
|
||||
//GET-example for the path /match/[number], responds with the matched string in path (number)
|
||||
//For instance a request GET /match/123 will receive: 123
|
||||
server.resource["^/match/([0-9]+)$"]["GET"] = [](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> request) {
|
||||
string number = request->path_match[1];
|
||||
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << number.length() << "\r\n\r\n"
|
||||
<< number;
|
||||
|
||||
|
||||
// Alternatively, using a convenience function:
|
||||
// response->write(request->path_match[1]);
|
||||
};
|
||||
|
||||
//Get example simulating heavy work in a separate thread
|
||||
server.resource["^/work$"]["GET"] = [](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> /*request*/) {
|
||||
thread work_thread([response] {
|
||||
this_thread::sleep_for(chrono::seconds(5));
|
||||
response->write("Work done");
|
||||
});
|
||||
work_thread.detach();
|
||||
};
|
||||
|
||||
//Default GET-example. If no other matches, this anonymous function will be called.
|
||||
//Will respond with content in the web/-directory, and its subdirectories.
|
||||
//Default file: index.html
|
||||
//Can for instance be used to retrieve an HTML 5 client that uses REST-resources on this server
|
||||
server.default_resource["GET"] = [&server](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> request) {
|
||||
try {
|
||||
auto web_root_path = boost::filesystem::canonical("web");
|
||||
auto path = boost::filesystem::canonical(web_root_path / request->path);
|
||||
//Check if path is within web_root_path
|
||||
if(distance(web_root_path.begin(), web_root_path.end()) > distance(path.begin(), path.end()) ||
|
||||
!equal(web_root_path.begin(), web_root_path.end(), path.begin()))
|
||||
throw invalid_argument("path must be within root path");
|
||||
if(boost::filesystem::is_directory(path))
|
||||
path /= "index.html";
|
||||
|
||||
SimpleWeb::CaseInsensitiveMultimap header;
|
||||
|
||||
// Uncomment the following line to enable Cache-Control
|
||||
// header.emplace("Cache-Control", "max-age=86400");
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
// Uncomment the following lines to enable ETag
|
||||
// {
|
||||
// ifstream ifs(path.string(), ifstream::in | ios::binary);
|
||||
// if(ifs) {
|
||||
// auto hash=SimpleWeb::Crypto::to_hex_string(SimpleWeb::Crypto::md5(ifs));
|
||||
// header.emplace("ETag", "\""+hash+"\"");
|
||||
// auto it=request->header.find("If-None-Match");
|
||||
// if(it!=request->header.end()) {
|
||||
// if(!it->second.empty() && it->second.compare(1, hash.size(), hash)==0) {
|
||||
// response->write(SimpleWeb::StatusCode::redirection_not_modified, header);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// throw invalid_argument("could not read file");
|
||||
// }
|
||||
// Uncomment the following lines to enable ETag
|
||||
// {
|
||||
// ifstream ifs(path.string(), ifstream::in | ios::binary);
|
||||
// if(ifs) {
|
||||
// auto hash = SimpleWeb::Crypto::to_hex_string(SimpleWeb::Crypto::md5(ifs));
|
||||
// header.emplace("ETag", "\"" + hash + "\"");
|
||||
// auto it = request->header.find("If-None-Match");
|
||||
// if(it != request->header.end()) {
|
||||
// if(!it->second.empty() && it->second.compare(1, hash.size(), hash) == 0) {
|
||||
// response->write(SimpleWeb::StatusCode::redirection_not_modified, header);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// throw invalid_argument("could not read file");
|
||||
// }
|
||||
#endif
|
||||
|
||||
auto ifs=make_shared<ifstream>();
|
||||
ifs->open(path.string(), ifstream::in | ios::binary | ios::ate);
|
||||
|
||||
if(*ifs) {
|
||||
auto length=ifs->tellg();
|
||||
ifs->seekg(0, ios::beg);
|
||||
|
||||
header.emplace("Content-Length", to_string(length));
|
||||
response->write(header);
|
||||
default_resource_send(server, response, ifs);
|
||||
}
|
||||
else
|
||||
throw invalid_argument("could not read file");
|
||||
}
|
||||
catch(const exception &e) {
|
||||
response->write(SimpleWeb::StatusCode::client_error_bad_request, "Could not open path "+request->path+": "+e.what());
|
||||
}
|
||||
};
|
||||
|
||||
server.on_error=[](std::shared_ptr<HttpsServer::Request> /*request*/, const SimpleWeb::error_code &/*ec*/) {
|
||||
// handle errors here
|
||||
};
|
||||
|
||||
thread server_thread([&server](){
|
||||
//Start server
|
||||
server.start();
|
||||
});
|
||||
|
||||
//Wait for server to start so that the client can connect
|
||||
this_thread::sleep_for(chrono::seconds(1));
|
||||
|
||||
//Client examples
|
||||
//Second Client() parameter set to false: no certificate verification
|
||||
HttpsClient client("localhost:8080", false);
|
||||
|
||||
// synchronous request examples
|
||||
auto r1=client.request("GET", "/match/123");
|
||||
cout << r1->content.rdbuf() << endl; // Alternatively, use the convenience function r1->content.string()
|
||||
auto ifs = make_shared<ifstream>();
|
||||
ifs->open(path.string(), ifstream::in | ios::binary | ios::ate);
|
||||
|
||||
string json_string="{\"firstName\": \"John\",\"lastName\": \"Smith\",\"age\": 25}";
|
||||
auto r2=client.request("POST", "/string", json_string);
|
||||
cout << r2->content.rdbuf() << endl;
|
||||
|
||||
// asynchronous request example
|
||||
client.request("POST", "/json", json_string, [](std::shared_ptr<HttpsClient::Response> response, const SimpleWeb::error_code &ec) {
|
||||
if(!ec)
|
||||
cout << response->content.rdbuf() << endl;
|
||||
});
|
||||
client.io_service->reset(); // needed because the io_service has been run already in the synchronous examples
|
||||
client.io_service->run();
|
||||
|
||||
server_thread.join();
|
||||
|
||||
return 0;
|
||||
}
|
||||
if(*ifs) {
|
||||
auto length = ifs->tellg();
|
||||
ifs->seekg(0, ios::beg);
|
||||
|
||||
void default_resource_send(const HttpsServer &server, const shared_ptr<HttpsServer::Response> &response,
|
||||
const shared_ptr<ifstream> &ifs) {
|
||||
//read and send 128 KB at a time
|
||||
static vector<char> buffer(131072); // Safe when server is running on one thread
|
||||
streamsize read_length;
|
||||
if((read_length=ifs->read(&buffer[0], buffer.size()).gcount())>0) {
|
||||
response->write(&buffer[0], read_length);
|
||||
if(read_length==static_cast<streamsize>(buffer.size())) {
|
||||
server.send(response, [&server, response, ifs](const SimpleWeb::error_code &ec) {
|
||||
if(!ec)
|
||||
default_resource_send(server, response, ifs);
|
||||
else
|
||||
cerr << "Connection interrupted" << endl;
|
||||
});
|
||||
}
|
||||
header.emplace("Content-Length", to_string(length));
|
||||
response->write(header);
|
||||
default_resource_send(server, response, ifs);
|
||||
}
|
||||
else
|
||||
throw invalid_argument("could not read file");
|
||||
}
|
||||
catch(const exception &e) {
|
||||
response->write(SimpleWeb::StatusCode::client_error_bad_request, "Could not open path " + request->path + ": " + e.what());
|
||||
}
|
||||
};
|
||||
|
||||
server.on_error = [](std::shared_ptr<HttpsServer::Request> /*request*/, const SimpleWeb::error_code & /*ec*/) {
|
||||
// handle errors here
|
||||
};
|
||||
|
||||
thread server_thread([&server]() {
|
||||
//Start server
|
||||
server.start();
|
||||
});
|
||||
|
||||
//Wait for server to start so that the client can connect
|
||||
this_thread::sleep_for(chrono::seconds(1));
|
||||
|
||||
//Client examples
|
||||
//Second Client() parameter set to false: no certificate verification
|
||||
HttpsClient client("localhost:8080", false);
|
||||
|
||||
// synchronous request examples
|
||||
auto r1 = client.request("GET", "/match/123");
|
||||
cout << r1->content.rdbuf() << endl; // Alternatively, use the convenience function r1->content.string()
|
||||
|
||||
string json_string = "{\"firstName\": \"John\",\"lastName\": \"Smith\",\"age\": 25}";
|
||||
auto r2 = client.request("POST", "/string", json_string);
|
||||
cout << r2->content.rdbuf() << endl;
|
||||
|
||||
// asynchronous request example
|
||||
client.request("POST", "/json", json_string, [](std::shared_ptr<HttpsClient::Response> response, const SimpleWeb::error_code &ec) {
|
||||
if(!ec)
|
||||
cout << response->content.rdbuf() << endl;
|
||||
});
|
||||
client.io_service->reset(); // needed because the io_service has been run already in the synchronous examples
|
||||
client.io_service->run();
|
||||
|
||||
server_thread.join();
|
||||
}
|
||||
|
||||
void default_resource_send(const HttpsServer &server, const shared_ptr<HttpsServer::Response> &response, const shared_ptr<ifstream> &ifs) {
|
||||
//read and send 128 KB at a time
|
||||
static vector<char> buffer(131072); // Safe when server is running on one thread
|
||||
streamsize read_length;
|
||||
if((read_length = ifs->read(&buffer[0], buffer.size()).gcount()) > 0) {
|
||||
response->write(&buffer[0], read_length);
|
||||
if(read_length == static_cast<streamsize>(buffer.size())) {
|
||||
server.send(response, [&server, response, ifs](const SimpleWeb::error_code &ec) {
|
||||
if(!ec)
|
||||
default_resource_send(server, response, ifs);
|
||||
else
|
||||
cerr << "Connection interrupted" << endl;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
931
server_http.hpp
931
server_http.hpp
|
|
@ -1,523 +1,526 @@
|
|||
#ifndef SERVER_HTTP_HPP
|
||||
#define SERVER_HTTP_HPP
|
||||
#define SERVER_HTTP_HPP
|
||||
|
||||
#include "utility.hpp"
|
||||
#include <map>
|
||||
#include <thread>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
#ifdef USE_STANDALONE_ASIO
|
||||
#include <asio.hpp>
|
||||
namespace SimpleWeb {
|
||||
using error_code = std::error_code;
|
||||
using errc = std::errc;
|
||||
namespace make_error_code = std;
|
||||
}
|
||||
using error_code = std::error_code;
|
||||
using errc = std::errc;
|
||||
namespace make_error_code = std;
|
||||
} // namespace SimpleWeb
|
||||
#else
|
||||
#include <boost/asio.hpp>
|
||||
namespace SimpleWeb {
|
||||
namespace asio = boost::asio;
|
||||
using error_code = boost::system::error_code;
|
||||
namespace errc = boost::system::errc;
|
||||
namespace make_error_code = boost::system::errc;
|
||||
}
|
||||
namespace asio = boost::asio;
|
||||
using error_code = boost::system::error_code;
|
||||
namespace errc = boost::system::errc;
|
||||
namespace make_error_code = boost::system::errc;
|
||||
} // namespace SimpleWeb
|
||||
#endif
|
||||
|
||||
// Late 2017 TODO: remove the following checks and always use std::regex
|
||||
#ifdef USE_BOOST_REGEX
|
||||
#include <boost/regex.hpp>
|
||||
namespace SimpleWeb {
|
||||
namespace regex = boost;
|
||||
namespace regex = boost;
|
||||
}
|
||||
#else
|
||||
#include <regex>
|
||||
namespace SimpleWeb {
|
||||
namespace regex = std;
|
||||
namespace regex = std;
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace SimpleWeb {
|
||||
template <class socket_type>
|
||||
class Server;
|
||||
|
||||
template <class socket_type>
|
||||
class ServerBase {
|
||||
template <class socket_type>
|
||||
class Server;
|
||||
|
||||
template <class socket_type>
|
||||
class ServerBase {
|
||||
public:
|
||||
virtual ~ServerBase() {}
|
||||
|
||||
class Response : public std::ostream {
|
||||
friend class ServerBase<socket_type>;
|
||||
|
||||
asio::streambuf streambuf;
|
||||
|
||||
std::shared_ptr<socket_type> socket;
|
||||
|
||||
Response(const std::shared_ptr<socket_type> &socket) : std::ostream(&streambuf), socket(socket) {}
|
||||
|
||||
template <class size_type>
|
||||
void write_header(const CaseInsensitiveMultimap &header, size_type size) {
|
||||
bool content_length_written = false;
|
||||
bool chunked_transfer_encoding = false;
|
||||
for(auto &field : header) {
|
||||
if(!content_length_written && case_insensitive_equal(field.first, "content-length"))
|
||||
content_length_written = true;
|
||||
else if(!chunked_transfer_encoding && case_insensitive_equal(field.first, "transfer-encoding") && case_insensitive_equal(field.second, "chunked"))
|
||||
chunked_transfer_encoding = true;
|
||||
|
||||
*this << field.first << ": " << field.second << "\r\n";
|
||||
}
|
||||
if(!content_length_written && !chunked_transfer_encoding && !close_connection_after_response)
|
||||
*this << "Content-Length: " << size << "\r\n\r\n";
|
||||
else
|
||||
*this << "\r\n";
|
||||
}
|
||||
|
||||
public:
|
||||
virtual ~ServerBase() {}
|
||||
size_t size() {
|
||||
return streambuf.size();
|
||||
}
|
||||
|
||||
class Response : public std::ostream {
|
||||
friend class ServerBase<socket_type>;
|
||||
/// Write directly to stream buffer using std::ostream::write
|
||||
void write(const char_type *ptr, std::streamsize n) {
|
||||
std::ostream::write(ptr, n);
|
||||
}
|
||||
|
||||
asio::streambuf streambuf;
|
||||
/// Convenience function for writing status line, potential header fields, and empty content
|
||||
void write(StatusCode status_code = StatusCode::success_ok, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {
|
||||
*this << "HTTP/1.1 " << SimpleWeb::status_code(status_code) << "\r\n";
|
||||
write_header(header, 0);
|
||||
}
|
||||
|
||||
std::shared_ptr<socket_type> socket;
|
||||
/// Convenience function for writing status line, header fields, and content
|
||||
void write(StatusCode status_code, const std::string &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {
|
||||
*this << "HTTP/1.1 " << SimpleWeb::status_code(status_code) << "\r\n";
|
||||
write_header(header, content.size());
|
||||
if(!content.empty())
|
||||
*this << content;
|
||||
}
|
||||
|
||||
Response(const std::shared_ptr<socket_type> &socket): std::ostream(&streambuf), socket(socket) {}
|
||||
/// Convenience function for writing status line, header fields, and content
|
||||
void write(StatusCode status_code, std::istream &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {
|
||||
*this << "HTTP/1.1 " << SimpleWeb::status_code(status_code) << "\r\n";
|
||||
content.seekg(0, std::ios::end);
|
||||
auto size = content.tellg();
|
||||
content.seekg(0, std::ios::beg);
|
||||
write_header(header, size);
|
||||
if(size)
|
||||
*this << content.rdbuf();
|
||||
}
|
||||
|
||||
template<class size_type>
|
||||
void write_header(const CaseInsensitiveMultimap &header, size_type size) {
|
||||
bool content_length_written=false;
|
||||
bool chunked_transfer_encoding=false;
|
||||
for(auto &field: header) {
|
||||
if(!content_length_written && case_insensitive_equal(field.first, "content-length"))
|
||||
content_length_written=true;
|
||||
else if(!chunked_transfer_encoding && case_insensitive_equal(field.first, "transfer-encoding") && case_insensitive_equal(field.second, "chunked"))
|
||||
chunked_transfer_encoding=true;
|
||||
|
||||
*this << field.first << ": " << field.second << "\r\n";
|
||||
}
|
||||
if(!content_length_written && !chunked_transfer_encoding && !close_connection_after_response)
|
||||
*this << "Content-Length: " << size << "\r\n\r\n";
|
||||
else
|
||||
*this << "\r\n";
|
||||
}
|
||||
public:
|
||||
size_t size() {
|
||||
return streambuf.size();
|
||||
}
|
||||
|
||||
/// Write directly to stream buffer using std::ostream::write
|
||||
void write(const char_type *ptr, std::streamsize n) {
|
||||
std::ostream::write(ptr, n);
|
||||
}
|
||||
|
||||
/// Convenience function for writing status line, potential header fields, and empty content
|
||||
void write(StatusCode status_code=StatusCode::success_ok, const CaseInsensitiveMultimap &header=CaseInsensitiveMultimap()) {
|
||||
*this << "HTTP/1.1 " << SimpleWeb::status_code(status_code) << "\r\n";
|
||||
write_header(header, 0);
|
||||
}
|
||||
|
||||
/// Convenience function for writing status line, header fields, and content
|
||||
void write(StatusCode status_code, const std::string &content, const CaseInsensitiveMultimap &header=CaseInsensitiveMultimap()) {
|
||||
*this << "HTTP/1.1 " << SimpleWeb::status_code(status_code) << "\r\n";
|
||||
write_header(header, content.size());
|
||||
if(!content.empty())
|
||||
*this << content;
|
||||
}
|
||||
|
||||
/// Convenience function for writing status line, header fields, and content
|
||||
void write(StatusCode status_code, std::istream &content, const CaseInsensitiveMultimap &header=CaseInsensitiveMultimap()) {
|
||||
*this << "HTTP/1.1 " << SimpleWeb::status_code(status_code) << "\r\n";
|
||||
content.seekg(0, std::ios::end);
|
||||
auto size=content.tellg();
|
||||
content.seekg(0, std::ios::beg);
|
||||
write_header(header, size);
|
||||
if(size)
|
||||
*this << content.rdbuf();
|
||||
}
|
||||
|
||||
/// Convenience function for writing success status line, header fields, and content
|
||||
void write(const std::string &content, const CaseInsensitiveMultimap &header=CaseInsensitiveMultimap()) {
|
||||
write(StatusCode::success_ok, content, header);
|
||||
}
|
||||
|
||||
/// Convenience function for writing success status line, header fields, and content
|
||||
void write(std::istream &content, const CaseInsensitiveMultimap &header=CaseInsensitiveMultimap()) {
|
||||
write(StatusCode::success_ok, content, header);
|
||||
}
|
||||
|
||||
/// Convenience function for writing success status line, and header fields
|
||||
void write(const CaseInsensitiveMultimap &header) {
|
||||
write(StatusCode::success_ok, std::string(), header);
|
||||
}
|
||||
/// Convenience function for writing success status line, header fields, and content
|
||||
void write(const std::string &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {
|
||||
write(StatusCode::success_ok, content, header);
|
||||
}
|
||||
|
||||
/// If true, force server to close the connection after the response have been sent.
|
||||
///
|
||||
/// This is useful when implementing a HTTP/1.0-server sending content
|
||||
/// without specifying the content length.
|
||||
bool close_connection_after_response = false;
|
||||
};
|
||||
|
||||
class Content : public std::istream {
|
||||
friend class ServerBase<socket_type>;
|
||||
public:
|
||||
size_t size() {
|
||||
return streambuf.size();
|
||||
}
|
||||
/// Convenience function to return std::string. Note that the stream buffer is emptied when this functions is used.
|
||||
std::string string() {
|
||||
std::stringstream ss;
|
||||
ss << rdbuf();
|
||||
return ss.str();
|
||||
}
|
||||
private:
|
||||
asio::streambuf &streambuf;
|
||||
Content(asio::streambuf &streambuf): std::istream(&streambuf), streambuf(streambuf) {}
|
||||
};
|
||||
|
||||
class Request {
|
||||
friend class ServerBase<socket_type>;
|
||||
friend class Server<socket_type>;
|
||||
public:
|
||||
std::string method, path, http_version;
|
||||
/// Convenience function for writing success status line, header fields, and content
|
||||
void write(std::istream &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {
|
||||
write(StatusCode::success_ok, content, header);
|
||||
}
|
||||
|
||||
Content content;
|
||||
/// Convenience function for writing success status line, and header fields
|
||||
void write(const CaseInsensitiveMultimap &header) {
|
||||
write(StatusCode::success_ok, std::string(), header);
|
||||
}
|
||||
|
||||
CaseInsensitiveMultimap header;
|
||||
/// If true, force server to close the connection after the response have been sent.
|
||||
///
|
||||
/// This is useful when implementing a HTTP/1.0-server sending content
|
||||
/// without specifying the content length.
|
||||
bool close_connection_after_response = false;
|
||||
};
|
||||
|
||||
regex::smatch path_match;
|
||||
|
||||
std::string remote_endpoint_address;
|
||||
unsigned short remote_endpoint_port;
|
||||
|
||||
/// Returns query keys with percent-decoded values.
|
||||
CaseInsensitiveMultimap parse_query_string() {
|
||||
auto pos = path.find('?');
|
||||
if (pos != std::string::npos && pos + 1 < path.size())
|
||||
return SimpleWeb::QueryString::parse(path.substr(pos + 1));
|
||||
else
|
||||
return CaseInsensitiveMultimap();
|
||||
}
|
||||
class Content : public std::istream {
|
||||
friend class ServerBase<socket_type>;
|
||||
|
||||
private:
|
||||
Request(const socket_type &socket): content(streambuf) {
|
||||
try {
|
||||
remote_endpoint_address=socket.lowest_layer().remote_endpoint().address().to_string();
|
||||
remote_endpoint_port=socket.lowest_layer().remote_endpoint().port();
|
||||
}
|
||||
catch(...) {}
|
||||
}
|
||||
|
||||
asio::streambuf streambuf;
|
||||
};
|
||||
|
||||
class Config {
|
||||
friend class ServerBase<socket_type>;
|
||||
public:
|
||||
size_t size() {
|
||||
return streambuf.size();
|
||||
}
|
||||
/// Convenience function to return std::string. Note that the stream buffer is emptied when this functions is used.
|
||||
std::string string() {
|
||||
std::stringstream ss;
|
||||
ss << rdbuf();
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
Config(unsigned short port): port(port) {}
|
||||
public:
|
||||
/// Port number to use. Defaults to 80 for HTTP and 443 for HTTPS.
|
||||
unsigned short port;
|
||||
/// Number of threads that the server will use when start() is called. Defaults to 1 thread.
|
||||
size_t thread_pool_size=1;
|
||||
/// Timeout on request handling. Defaults to 5 seconds.
|
||||
size_t timeout_request=5;
|
||||
/// Timeout on content handling. Defaults to 300 seconds.
|
||||
size_t timeout_content=300;
|
||||
/// IPv4 address in dotted decimal form or IPv6 address in hexadecimal notation.
|
||||
/// If empty, the address will be any address.
|
||||
std::string address;
|
||||
/// Set to false to avoid binding the socket to an address that is already in use. Defaults to true.
|
||||
bool reuse_address=true;
|
||||
};
|
||||
///Set before calling start().
|
||||
Config config;
|
||||
|
||||
private:
|
||||
class regex_orderable : public regex::regex {
|
||||
std::string str;
|
||||
public:
|
||||
regex_orderable(const char *regex_cstr) : regex::regex(regex_cstr), str(regex_cstr) {}
|
||||
regex_orderable(const std::string ®ex_str) : regex::regex(regex_str), str(regex_str) {}
|
||||
bool operator<(const regex_orderable &rhs) const {
|
||||
return str<rhs.str;
|
||||
}
|
||||
};
|
||||
asio::streambuf &streambuf;
|
||||
Content(asio::streambuf &streambuf) : std::istream(&streambuf), streambuf(streambuf) {}
|
||||
};
|
||||
|
||||
class Request {
|
||||
friend class ServerBase<socket_type>;
|
||||
friend class Server<socket_type>;
|
||||
|
||||
public:
|
||||
/// Warning: do not add or remove resources after start() is called
|
||||
std::map<regex_orderable, std::map<std::string,
|
||||
std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Response>, std::shared_ptr<typename ServerBase<socket_type>::Request>)> > > resource;
|
||||
|
||||
std::map<std::string,
|
||||
std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Response>, std::shared_ptr<typename ServerBase<socket_type>::Request>)> > default_resource;
|
||||
|
||||
std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Request>, const error_code&)> on_error;
|
||||
|
||||
std::function<void(std::shared_ptr<socket_type> socket, std::shared_ptr<typename ServerBase<socket_type>::Request>)> on_upgrade;
|
||||
|
||||
virtual void start() {
|
||||
if(!io_service)
|
||||
io_service=std::make_shared<asio::io_service>();
|
||||
std::string method, path, http_version;
|
||||
|
||||
if(io_service->stopped())
|
||||
io_service->reset();
|
||||
Content content;
|
||||
|
||||
asio::ip::tcp::endpoint endpoint;
|
||||
if(config.address.size()>0)
|
||||
endpoint=asio::ip::tcp::endpoint(asio::ip::address::from_string(config.address), config.port);
|
||||
else
|
||||
endpoint=asio::ip::tcp::endpoint(asio::ip::tcp::v4(), config.port);
|
||||
|
||||
if(!acceptor)
|
||||
acceptor=std::unique_ptr<asio::ip::tcp::acceptor>(new asio::ip::tcp::acceptor(*io_service));
|
||||
acceptor->open(endpoint.protocol());
|
||||
acceptor->set_option(asio::socket_base::reuse_address(config.reuse_address));
|
||||
acceptor->bind(endpoint);
|
||||
acceptor->listen();
|
||||
|
||||
accept();
|
||||
|
||||
//If thread_pool_size>1, start m_io_service.run() in (thread_pool_size-1) threads for thread-pooling
|
||||
threads.clear();
|
||||
for(size_t c=1;c<config.thread_pool_size;c++) {
|
||||
threads.emplace_back([this]() {
|
||||
io_service->run();
|
||||
});
|
||||
}
|
||||
CaseInsensitiveMultimap header;
|
||||
|
||||
//Main thread
|
||||
if(config.thread_pool_size>0)
|
||||
io_service->run();
|
||||
regex::smatch path_match;
|
||||
|
||||
//Wait for the rest of the threads, if any, to finish as well
|
||||
for(auto& t: threads) {
|
||||
t.join();
|
||||
}
|
||||
std::string remote_endpoint_address;
|
||||
unsigned short remote_endpoint_port;
|
||||
|
||||
/// Returns query keys with percent-decoded values.
|
||||
CaseInsensitiveMultimap parse_query_string() {
|
||||
auto pos = path.find('?');
|
||||
if(pos != std::string::npos && pos + 1 < path.size())
|
||||
return SimpleWeb::QueryString::parse(path.substr(pos + 1));
|
||||
else
|
||||
return CaseInsensitiveMultimap();
|
||||
}
|
||||
|
||||
private:
|
||||
Request(const socket_type &socket) : content(streambuf) {
|
||||
try {
|
||||
remote_endpoint_address = socket.lowest_layer().remote_endpoint().address().to_string();
|
||||
remote_endpoint_port = socket.lowest_layer().remote_endpoint().port();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
acceptor->close();
|
||||
if(config.thread_pool_size>0)
|
||||
io_service->stop();
|
||||
catch(...) {
|
||||
}
|
||||
|
||||
///Use this function if you need to recursively send parts of a longer message
|
||||
void send(const std::shared_ptr<Response> &response, const std::function<void(const error_code&)>& callback=nullptr) const {
|
||||
asio::async_write(*response->socket, response->streambuf, [this, response, callback](const error_code& ec, size_t /*bytes_transferred*/) {
|
||||
if(callback)
|
||||
callback(ec);
|
||||
});
|
||||
}
|
||||
|
||||
asio::streambuf streambuf;
|
||||
};
|
||||
|
||||
class Config {
|
||||
friend class ServerBase<socket_type>;
|
||||
|
||||
Config(unsigned short port) : port(port) {}
|
||||
|
||||
public:
|
||||
/// Port number to use. Defaults to 80 for HTTP and 443 for HTTPS.
|
||||
unsigned short port;
|
||||
/// Number of threads that the server will use when start() is called. Defaults to 1 thread.
|
||||
size_t thread_pool_size = 1;
|
||||
/// Timeout on request handling. Defaults to 5 seconds.
|
||||
size_t timeout_request = 5;
|
||||
/// Timeout on content handling. Defaults to 300 seconds.
|
||||
size_t timeout_content = 300;
|
||||
/// IPv4 address in dotted decimal form or IPv6 address in hexadecimal notation.
|
||||
/// If empty, the address will be any address.
|
||||
std::string address;
|
||||
/// Set to false to avoid binding the socket to an address that is already in use. Defaults to true.
|
||||
bool reuse_address = true;
|
||||
};
|
||||
///Set before calling start().
|
||||
Config config;
|
||||
|
||||
private:
|
||||
class regex_orderable : public regex::regex {
|
||||
std::string str;
|
||||
|
||||
public:
|
||||
regex_orderable(const char *regex_cstr) : regex::regex(regex_cstr), str(regex_cstr) {}
|
||||
regex_orderable(const std::string ®ex_str) : regex::regex(regex_str), str(regex_str) {}
|
||||
bool operator<(const regex_orderable &rhs) const {
|
||||
return str < rhs.str;
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
/// Warning: do not add or remove resources after start() is called
|
||||
std::map<regex_orderable, std::map<std::string, std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Response>, std::shared_ptr<typename ServerBase<socket_type>::Request>)>>> resource;
|
||||
|
||||
std::map<std::string, std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Response>, std::shared_ptr<typename ServerBase<socket_type>::Request>)>> default_resource;
|
||||
|
||||
std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Request>, const error_code &)> on_error;
|
||||
|
||||
std::function<void(std::shared_ptr<socket_type> socket, std::shared_ptr<typename ServerBase<socket_type>::Request>)> on_upgrade;
|
||||
|
||||
virtual void start() {
|
||||
if(!io_service)
|
||||
io_service = std::make_shared<asio::io_service>();
|
||||
|
||||
if(io_service->stopped())
|
||||
io_service->reset();
|
||||
|
||||
asio::ip::tcp::endpoint endpoint;
|
||||
if(config.address.size() > 0)
|
||||
endpoint = asio::ip::tcp::endpoint(asio::ip::address::from_string(config.address), config.port);
|
||||
else
|
||||
endpoint = asio::ip::tcp::endpoint(asio::ip::tcp::v4(), config.port);
|
||||
|
||||
if(!acceptor)
|
||||
acceptor = std::unique_ptr<asio::ip::tcp::acceptor>(new asio::ip::tcp::acceptor(*io_service));
|
||||
acceptor->open(endpoint.protocol());
|
||||
acceptor->set_option(asio::socket_base::reuse_address(config.reuse_address));
|
||||
acceptor->bind(endpoint);
|
||||
acceptor->listen();
|
||||
|
||||
accept();
|
||||
|
||||
//If thread_pool_size>1, start m_io_service.run() in (thread_pool_size-1) threads for thread-pooling
|
||||
threads.clear();
|
||||
for(size_t c = 1; c < config.thread_pool_size; c++) {
|
||||
threads.emplace_back([this]() {
|
||||
io_service->run();
|
||||
});
|
||||
}
|
||||
|
||||
//Main thread
|
||||
if(config.thread_pool_size > 0)
|
||||
io_service->run();
|
||||
|
||||
//Wait for the rest of the threads, if any, to finish as well
|
||||
for(auto &t : threads) {
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
|
||||
void stop() {
|
||||
acceptor->close();
|
||||
if(config.thread_pool_size > 0)
|
||||
io_service->stop();
|
||||
}
|
||||
|
||||
///Use this function if you need to recursively send parts of a longer message
|
||||
void send(const std::shared_ptr<Response> &response, const std::function<void(const error_code &)> &callback = nullptr) const {
|
||||
asio::async_write(*response->socket, response->streambuf, [this, response, callback](const error_code &ec, size_t /*bytes_transferred*/) {
|
||||
if(callback)
|
||||
callback(ec);
|
||||
});
|
||||
}
|
||||
|
||||
/// If you have your own asio::io_service, store its pointer here before running start().
|
||||
/// You might also want to set config.thread_pool_size to 0.
|
||||
std::shared_ptr<asio::io_service> io_service;
|
||||
|
||||
protected:
|
||||
std::unique_ptr<asio::ip::tcp::acceptor> acceptor;
|
||||
std::vector<std::thread> threads;
|
||||
|
||||
ServerBase(unsigned short port) : config(port) {}
|
||||
|
||||
virtual void accept() = 0;
|
||||
|
||||
std::shared_ptr<asio::deadline_timer> get_timeout_timer(const std::shared_ptr<socket_type> &socket, long seconds) {
|
||||
if(seconds == 0)
|
||||
return nullptr;
|
||||
|
||||
auto timer = std::make_shared<asio::deadline_timer>(*io_service);
|
||||
timer->expires_from_now(boost::posix_time::seconds(seconds));
|
||||
timer->async_wait([socket](const error_code &ec) {
|
||||
if(!ec) {
|
||||
error_code ec;
|
||||
socket->lowest_layer().shutdown(asio::ip::tcp::socket::shutdown_both, ec);
|
||||
socket->lowest_layer().close();
|
||||
}
|
||||
});
|
||||
return timer;
|
||||
}
|
||||
|
||||
/// If you have your own asio::io_service, store its pointer here before running start().
|
||||
/// You might also want to set config.thread_pool_size to 0.
|
||||
std::shared_ptr<asio::io_service> io_service;
|
||||
protected:
|
||||
std::unique_ptr<asio::ip::tcp::acceptor> acceptor;
|
||||
std::vector<std::thread> threads;
|
||||
|
||||
ServerBase(unsigned short port) : config(port) {}
|
||||
|
||||
virtual void accept()=0;
|
||||
|
||||
std::shared_ptr<asio::deadline_timer> get_timeout_timer(const std::shared_ptr<socket_type> &socket, long seconds) {
|
||||
if(seconds==0)
|
||||
return nullptr;
|
||||
|
||||
auto timer=std::make_shared<asio::deadline_timer>(*io_service);
|
||||
timer->expires_from_now(boost::posix_time::seconds(seconds));
|
||||
timer->async_wait([socket](const error_code& ec){
|
||||
if(!ec) {
|
||||
error_code ec;
|
||||
socket->lowest_layer().shutdown(asio::ip::tcp::socket::shutdown_both, ec);
|
||||
socket->lowest_layer().close();
|
||||
}
|
||||
});
|
||||
return timer;
|
||||
}
|
||||
|
||||
void read_request_and_content(const std::shared_ptr<socket_type> &socket) {
|
||||
//Create new streambuf (Request::streambuf) for async_read_until()
|
||||
//shared_ptr is used to pass temporary objects to the asynchronous functions
|
||||
std::shared_ptr<Request> request(new Request(*socket));
|
||||
void read_request_and_content(const std::shared_ptr<socket_type> &socket) {
|
||||
//Create new streambuf (Request::streambuf) for async_read_until()
|
||||
//shared_ptr is used to pass temporary objects to the asynchronous functions
|
||||
std::shared_ptr<Request> request(new Request(*socket));
|
||||
|
||||
//Set timeout on the following asio::async-read or write function
|
||||
auto timer=this->get_timeout_timer(socket, config.timeout_request);
|
||||
|
||||
asio::async_read_until(*socket, request->streambuf, "\r\n\r\n",
|
||||
[this, socket, request, timer](const error_code& ec, size_t bytes_transferred) {
|
||||
if(timer)
|
||||
timer->cancel();
|
||||
if(!ec) {
|
||||
//request->streambuf.size() is not necessarily the same as bytes_transferred, from Boost-docs:
|
||||
//"After a successful async_read_until operation, the streambuf may contain additional data beyond the delimiter"
|
||||
//The chosen solution is to extract lines from the stream directly when parsing the header. What is left of the
|
||||
//streambuf (maybe some bytes of the content) is appended to in the async_read-function below (for retrieving content).
|
||||
size_t num_additional_bytes=request->streambuf.size()-bytes_transferred;
|
||||
|
||||
if(!this->parse_request(request))
|
||||
return;
|
||||
|
||||
//If content, read that as well
|
||||
auto it=request->header.find("Content-Length");
|
||||
if(it!=request->header.end()) {
|
||||
unsigned long long content_length;
|
||||
try {
|
||||
content_length=stoull(it->second);
|
||||
}
|
||||
catch(const std::exception &e) {
|
||||
if(on_error)
|
||||
on_error(request, make_error_code::make_error_code(errc::protocol_error));
|
||||
return;
|
||||
}
|
||||
if(content_length>num_additional_bytes) {
|
||||
//Set timeout on the following asio::async-read or write function
|
||||
auto timer=this->get_timeout_timer(socket, config.timeout_content);
|
||||
asio::async_read(*socket, request->streambuf,
|
||||
asio::transfer_exactly(content_length-num_additional_bytes),
|
||||
[this, socket, request, timer]
|
||||
(const error_code& ec, size_t /*bytes_transferred*/) {
|
||||
if(timer)
|
||||
timer->cancel();
|
||||
if(!ec)
|
||||
this->find_resource(socket, request);
|
||||
else if(on_error)
|
||||
on_error(request, ec);
|
||||
});
|
||||
}
|
||||
else
|
||||
this->find_resource(socket, request);
|
||||
}
|
||||
else
|
||||
this->find_resource(socket, request);
|
||||
}
|
||||
else if(on_error)
|
||||
on_error(request, ec);
|
||||
});
|
||||
}
|
||||
//Set timeout on the following asio::async-read or write function
|
||||
auto timer = this->get_timeout_timer(socket, config.timeout_request);
|
||||
|
||||
bool parse_request(const std::shared_ptr<Request> &request) const {
|
||||
std::string line;
|
||||
getline(request->content, line);
|
||||
size_t method_end;
|
||||
if((method_end=line.find(' '))!=std::string::npos) {
|
||||
size_t path_end;
|
||||
if((path_end=line.find(' ', method_end+1))!=std::string::npos) {
|
||||
request->method=line.substr(0, method_end);
|
||||
request->path=line.substr(method_end+1, path_end-method_end-1);
|
||||
asio::async_read_until(*socket, request->streambuf, "\r\n\r\n", [this, socket, request, timer](const error_code &ec, size_t bytes_transferred) {
|
||||
if(timer)
|
||||
timer->cancel();
|
||||
if(!ec) {
|
||||
//request->streambuf.size() is not necessarily the same as bytes_transferred, from Boost-docs:
|
||||
//"After a successful async_read_until operation, the streambuf may contain additional data beyond the delimiter"
|
||||
//The chosen solution is to extract lines from the stream directly when parsing the header. What is left of the
|
||||
//streambuf (maybe some bytes of the content) is appended to in the async_read-function below (for retrieving content).
|
||||
size_t num_additional_bytes = request->streambuf.size() - bytes_transferred;
|
||||
|
||||
size_t protocol_end;
|
||||
if((protocol_end=line.find('/', path_end+1))!=std::string::npos) {
|
||||
if(line.compare(path_end+1, protocol_end-path_end-1, "HTTP")!=0)
|
||||
return false;
|
||||
request->http_version=line.substr(protocol_end+1, line.size()-protocol_end-2);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
||||
getline(request->content, line);
|
||||
size_t param_end;
|
||||
while((param_end=line.find(':'))!=std::string::npos) {
|
||||
size_t value_start=param_end+1;
|
||||
if((value_start)<line.size()) {
|
||||
if(line[value_start]==' ')
|
||||
value_start++;
|
||||
if(value_start<line.size())
|
||||
request->header.emplace(line.substr(0, param_end), line.substr(value_start, line.size()-value_start-1));
|
||||
}
|
||||
|
||||
getline(request->content, line);
|
||||
}
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void find_resource(const std::shared_ptr<socket_type> &socket, const std::shared_ptr<Request> &request) {
|
||||
//Upgrade connection
|
||||
if(on_upgrade) {
|
||||
auto it=request->header.find("Upgrade");
|
||||
if(it!=request->header.end()) {
|
||||
on_upgrade(socket, request);
|
||||
return;
|
||||
}
|
||||
}
|
||||
//Find path- and method-match, and call write_response
|
||||
for(auto ®ex_method: resource) {
|
||||
auto it=regex_method.second.find(request->method);
|
||||
if(it!=regex_method.second.end()) {
|
||||
regex::smatch sm_res;
|
||||
if(regex::regex_match(request->path, sm_res, regex_method.first)) {
|
||||
request->path_match=std::move(sm_res);
|
||||
write_response(socket, request, it->second);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
auto it=default_resource.find(request->method);
|
||||
if(it!=default_resource.end()) {
|
||||
write_response(socket, request, it->second);
|
||||
}
|
||||
}
|
||||
|
||||
void write_response(const std::shared_ptr<socket_type> &socket, const std::shared_ptr<Request> &request,
|
||||
std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Response>,
|
||||
std::shared_ptr<typename ServerBase<socket_type>::Request>)>& resource_function) {
|
||||
//Set timeout on the following asio::async-read or write function
|
||||
auto timer=this->get_timeout_timer(socket, config.timeout_content);
|
||||
|
||||
auto response=std::shared_ptr<Response>(new Response(socket), [this, request, timer](Response *response_ptr) {
|
||||
auto response=std::shared_ptr<Response>(response_ptr);
|
||||
this->send(response, [this, response, request, timer](const error_code& ec) {
|
||||
if(timer)
|
||||
timer->cancel();
|
||||
if(!ec) {
|
||||
if (response->close_connection_after_response)
|
||||
return;
|
||||
|
||||
auto range=request->header.equal_range("Connection");
|
||||
for(auto it=range.first;it!=range.second;it++) {
|
||||
if(case_insensitive_equal(it->second, "close")) {
|
||||
return;
|
||||
} else if (case_insensitive_equal(it->second, "keep-alive")) {
|
||||
this->read_request_and_content(response->socket);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(request->http_version >= "1.1")
|
||||
this->read_request_and_content(response->socket);
|
||||
}
|
||||
else if(on_error)
|
||||
on_error(request, ec);
|
||||
});
|
||||
});
|
||||
if(!this->parse_request(request))
|
||||
return;
|
||||
|
||||
//If content, read that as well
|
||||
auto it = request->header.find("Content-Length");
|
||||
if(it != request->header.end()) {
|
||||
unsigned long long content_length;
|
||||
try {
|
||||
resource_function(response, request);
|
||||
content_length = stoull(it->second);
|
||||
}
|
||||
catch(const std::exception &e) {
|
||||
if(on_error)
|
||||
on_error(request, make_error_code::make_error_code(errc::operation_canceled));
|
||||
return;
|
||||
if(on_error)
|
||||
on_error(request, make_error_code::make_error_code(errc::protocol_error));
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<class socket_type>
|
||||
class Server : public ServerBase<socket_type> {};
|
||||
|
||||
typedef asio::ip::tcp::socket HTTP;
|
||||
|
||||
template<>
|
||||
class Server<HTTP> : public ServerBase<HTTP> {
|
||||
public:
|
||||
DEPRECATED Server(unsigned short port, size_t thread_pool_size=1, long timeout_request=5, long timeout_content=300) :
|
||||
Server() {
|
||||
config.port=port;
|
||||
config.thread_pool_size=thread_pool_size;
|
||||
config.timeout_request=timeout_request;
|
||||
config.timeout_content=timeout_content;
|
||||
}
|
||||
|
||||
Server() : ServerBase<HTTP>::ServerBase(80) {}
|
||||
|
||||
protected:
|
||||
void accept() {
|
||||
//Create new socket for this connection
|
||||
//Shared_ptr is used to pass temporary objects to the asynchronous functions
|
||||
auto socket=std::make_shared<HTTP>(*io_service);
|
||||
|
||||
acceptor->async_accept(*socket, [this, socket](const error_code& ec){
|
||||
//Immediately start accepting a new connection (if io_service hasn't been stopped)
|
||||
if (ec != asio::error::operation_aborted)
|
||||
accept();
|
||||
|
||||
if(!ec) {
|
||||
asio::ip::tcp::no_delay option(true);
|
||||
socket->set_option(option);
|
||||
|
||||
this->read_request_and_content(socket);
|
||||
}
|
||||
if(content_length > num_additional_bytes) {
|
||||
//Set timeout on the following asio::async-read or write function
|
||||
auto timer = this->get_timeout_timer(socket, config.timeout_content);
|
||||
asio::async_read(*socket, request->streambuf, asio::transfer_exactly(content_length - num_additional_bytes), [this, socket, request, timer](const error_code &ec, size_t /*bytes_transferred*/) {
|
||||
if(timer)
|
||||
timer->cancel();
|
||||
if(!ec)
|
||||
this->find_resource(socket, request);
|
||||
else if(on_error)
|
||||
on_error(std::shared_ptr<Request>(new Request(*socket)), ec);
|
||||
});
|
||||
on_error(request, ec);
|
||||
});
|
||||
}
|
||||
else
|
||||
this->find_resource(socket, request);
|
||||
}
|
||||
else
|
||||
this->find_resource(socket, request);
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif /* SERVER_HTTP_HPP */
|
||||
else if(on_error)
|
||||
on_error(request, ec);
|
||||
});
|
||||
}
|
||||
|
||||
bool parse_request(const std::shared_ptr<Request> &request) const {
|
||||
std::string line;
|
||||
getline(request->content, line);
|
||||
size_t method_end;
|
||||
if((method_end = line.find(' ')) != std::string::npos) {
|
||||
size_t path_end;
|
||||
if((path_end = line.find(' ', method_end + 1)) != std::string::npos) {
|
||||
request->method = line.substr(0, method_end);
|
||||
request->path = line.substr(method_end + 1, path_end - method_end - 1);
|
||||
|
||||
size_t protocol_end;
|
||||
if((protocol_end = line.find('/', path_end + 1)) != std::string::npos) {
|
||||
if(line.compare(path_end + 1, protocol_end - path_end - 1, "HTTP") != 0)
|
||||
return false;
|
||||
request->http_version = line.substr(protocol_end + 1, line.size() - protocol_end - 2);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
||||
getline(request->content, line);
|
||||
size_t param_end;
|
||||
while((param_end = line.find(':')) != std::string::npos) {
|
||||
size_t value_start = param_end + 1;
|
||||
if((value_start) < line.size()) {
|
||||
if(line[value_start] == ' ')
|
||||
value_start++;
|
||||
if(value_start < line.size())
|
||||
request->header.emplace(line.substr(0, param_end), line.substr(value_start, line.size() - value_start - 1));
|
||||
}
|
||||
|
||||
getline(request->content, line);
|
||||
}
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void find_resource(const std::shared_ptr<socket_type> &socket, const std::shared_ptr<Request> &request) {
|
||||
//Upgrade connection
|
||||
if(on_upgrade) {
|
||||
auto it = request->header.find("Upgrade");
|
||||
if(it != request->header.end()) {
|
||||
on_upgrade(socket, request);
|
||||
return;
|
||||
}
|
||||
}
|
||||
//Find path- and method-match, and call write_response
|
||||
for(auto ®ex_method : resource) {
|
||||
auto it = regex_method.second.find(request->method);
|
||||
if(it != regex_method.second.end()) {
|
||||
regex::smatch sm_res;
|
||||
if(regex::regex_match(request->path, sm_res, regex_method.first)) {
|
||||
request->path_match = std::move(sm_res);
|
||||
write_response(socket, request, it->second);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
auto it = default_resource.find(request->method);
|
||||
if(it != default_resource.end()) {
|
||||
write_response(socket, request, it->second);
|
||||
}
|
||||
}
|
||||
|
||||
void write_response(const std::shared_ptr<socket_type> &socket, const std::shared_ptr<Request> &request,
|
||||
std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Response>, std::shared_ptr<typename ServerBase<socket_type>::Request>)> &resource_function) {
|
||||
//Set timeout on the following asio::async-read or write function
|
||||
auto timer = this->get_timeout_timer(socket, config.timeout_content);
|
||||
|
||||
auto response = std::shared_ptr<Response>(new Response(socket), [this, request, timer](Response *response_ptr) {
|
||||
auto response = std::shared_ptr<Response>(response_ptr);
|
||||
this->send(response, [this, response, request, timer](const error_code &ec) {
|
||||
if(timer)
|
||||
timer->cancel();
|
||||
if(!ec) {
|
||||
if(response->close_connection_after_response)
|
||||
return;
|
||||
|
||||
auto range = request->header.equal_range("Connection");
|
||||
for(auto it = range.first; it != range.second; it++) {
|
||||
if(case_insensitive_equal(it->second, "close")) {
|
||||
return;
|
||||
}
|
||||
else if(case_insensitive_equal(it->second, "keep-alive")) {
|
||||
this->read_request_and_content(response->socket);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(request->http_version >= "1.1")
|
||||
this->read_request_and_content(response->socket);
|
||||
}
|
||||
else if(on_error)
|
||||
on_error(request, ec);
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
resource_function(response, request);
|
||||
}
|
||||
catch(const std::exception &e) {
|
||||
if(on_error)
|
||||
on_error(request, make_error_code::make_error_code(errc::operation_canceled));
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class socket_type>
|
||||
class Server : public ServerBase<socket_type> {};
|
||||
|
||||
typedef asio::ip::tcp::socket HTTP;
|
||||
|
||||
template <>
|
||||
class Server<HTTP> : public ServerBase<HTTP> {
|
||||
public:
|
||||
DEPRECATED Server(unsigned short port, size_t thread_pool_size = 1, long timeout_request = 5, long timeout_content = 300) : Server() {
|
||||
config.port = port;
|
||||
config.thread_pool_size = thread_pool_size;
|
||||
config.timeout_request = timeout_request;
|
||||
config.timeout_content = timeout_content;
|
||||
}
|
||||
|
||||
Server() : ServerBase<HTTP>::ServerBase(80) {}
|
||||
|
||||
protected:
|
||||
void accept() {
|
||||
//Create new socket for this connection
|
||||
//Shared_ptr is used to pass temporary objects to the asynchronous functions
|
||||
auto socket = std::make_shared<HTTP>(*io_service);
|
||||
|
||||
acceptor->async_accept(*socket, [this, socket](const error_code &ec) {
|
||||
//Immediately start accepting a new connection (if io_service hasn't been stopped)
|
||||
if(ec != asio::error::operation_aborted)
|
||||
accept();
|
||||
|
||||
if(!ec) {
|
||||
asio::ip::tcp::no_delay option(true);
|
||||
socket->set_option(option);
|
||||
|
||||
this->read_request_and_content(socket);
|
||||
}
|
||||
else if(on_error)
|
||||
on_error(std::shared_ptr<Request>(new Request(*socket)), ec);
|
||||
});
|
||||
}
|
||||
};
|
||||
} // namespace SimpleWeb
|
||||
|
||||
#endif /* SERVER_HTTP_HPP */
|
||||
|
|
|
|||
158
server_https.hpp
158
server_https.hpp
|
|
@ -1,5 +1,5 @@
|
|||
#ifndef SERVER_HTTPS_HPP
|
||||
#define SERVER_HTTPS_HPP
|
||||
#define SERVER_HTTPS_HPP
|
||||
|
||||
#include "server_http.hpp"
|
||||
|
||||
|
|
@ -9,88 +9,84 @@
|
|||
#include <boost/asio/ssl.hpp>
|
||||
#endif
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
#include <algorithm>
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
namespace SimpleWeb {
|
||||
typedef asio::ssl::stream<asio::ip::tcp::socket> HTTPS;
|
||||
|
||||
template<>
|
||||
class Server<HTTPS> : public ServerBase<HTTPS> {
|
||||
std::string session_id_context;
|
||||
bool set_session_id_context=false;
|
||||
public:
|
||||
DEPRECATED Server(unsigned short port, size_t thread_pool_size, const std::string& cert_file, const std::string& private_key_file,
|
||||
long timeout_request=5, long timeout_content=300,
|
||||
const std::string& verify_file=std::string()) :
|
||||
Server(cert_file, private_key_file, verify_file) {
|
||||
config.port=port;
|
||||
config.thread_pool_size=thread_pool_size;
|
||||
config.timeout_request=timeout_request;
|
||||
config.timeout_content=timeout_content;
|
||||
}
|
||||
|
||||
Server(const std::string& cert_file, const std::string& private_key_file, const std::string& verify_file=std::string()):
|
||||
ServerBase<HTTPS>::ServerBase(443), context(asio::ssl::context::tlsv12) {
|
||||
context.use_certificate_chain_file(cert_file);
|
||||
context.use_private_key_file(private_key_file, asio::ssl::context::pem);
|
||||
|
||||
if(verify_file.size()>0) {
|
||||
context.load_verify_file(verify_file);
|
||||
context.set_verify_mode(asio::ssl::verify_peer | asio::ssl::verify_fail_if_no_peer_cert |
|
||||
asio::ssl::verify_client_once);
|
||||
set_session_id_context=true;
|
||||
}
|
||||
}
|
||||
|
||||
void start() {
|
||||
if(set_session_id_context) {
|
||||
// Creating session_id_context from address:port but reversed due to small SSL_MAX_SSL_SESSION_ID_LENGTH
|
||||
session_id_context=std::to_string(config.port)+':';
|
||||
session_id_context.append(config.address.rbegin(), config.address.rend());
|
||||
SSL_CTX_set_session_id_context(context.native_handle(), reinterpret_cast<const unsigned char*>(session_id_context.data()),
|
||||
std::min<size_t>(session_id_context.size(), SSL_MAX_SSL_SESSION_ID_LENGTH));
|
||||
}
|
||||
ServerBase::start();
|
||||
typedef asio::ssl::stream<asio::ip::tcp::socket> HTTPS;
|
||||
|
||||
template <>
|
||||
class Server<HTTPS> : public ServerBase<HTTPS> {
|
||||
std::string session_id_context;
|
||||
bool set_session_id_context = false;
|
||||
|
||||
public:
|
||||
DEPRECATED Server(unsigned short port, size_t thread_pool_size, const std::string &cert_file, const std::string &private_key_file,
|
||||
long timeout_request = 5, long timeout_content = 300, const std::string &verify_file = std::string())
|
||||
: Server(cert_file, private_key_file, verify_file) {
|
||||
config.port = port;
|
||||
config.thread_pool_size = thread_pool_size;
|
||||
config.timeout_request = timeout_request;
|
||||
config.timeout_content = timeout_content;
|
||||
}
|
||||
|
||||
Server(const std::string &cert_file, const std::string &private_key_file, const std::string &verify_file = std::string())
|
||||
: ServerBase<HTTPS>::ServerBase(443), context(asio::ssl::context::tlsv12) {
|
||||
context.use_certificate_chain_file(cert_file);
|
||||
context.use_private_key_file(private_key_file, asio::ssl::context::pem);
|
||||
|
||||
if(verify_file.size() > 0) {
|
||||
context.load_verify_file(verify_file);
|
||||
context.set_verify_mode(asio::ssl::verify_peer | asio::ssl::verify_fail_if_no_peer_cert | asio::ssl::verify_client_once);
|
||||
set_session_id_context = true;
|
||||
}
|
||||
}
|
||||
|
||||
void start() {
|
||||
if(set_session_id_context) {
|
||||
// Creating session_id_context from address:port but reversed due to small SSL_MAX_SSL_SESSION_ID_LENGTH
|
||||
session_id_context = std::to_string(config.port) + ':';
|
||||
session_id_context.append(config.address.rbegin(), config.address.rend());
|
||||
SSL_CTX_set_session_id_context(context.native_handle(), reinterpret_cast<const unsigned char *>(session_id_context.data()),
|
||||
std::min<size_t>(session_id_context.size(), SSL_MAX_SSL_SESSION_ID_LENGTH));
|
||||
}
|
||||
ServerBase::start();
|
||||
}
|
||||
|
||||
protected:
|
||||
asio::ssl::context context;
|
||||
|
||||
void accept() {
|
||||
//Create new socket for this connection
|
||||
//Shared_ptr is used to pass temporary objects to the asynchronous functions
|
||||
auto socket = std::make_shared<HTTPS>(*io_service, context);
|
||||
|
||||
acceptor->async_accept((*socket).lowest_layer(), [this, socket](const error_code &ec) {
|
||||
//Immediately start accepting a new connection (if io_service hasn't been stopped)
|
||||
if(ec != asio::error::operation_aborted)
|
||||
accept();
|
||||
|
||||
|
||||
if(!ec) {
|
||||
asio::ip::tcp::no_delay option(true);
|
||||
socket->lowest_layer().set_option(option);
|
||||
|
||||
//Set timeout on the following asio::ssl::stream::async_handshake
|
||||
auto timer = get_timeout_timer(socket, config.timeout_request);
|
||||
socket->async_handshake(asio::ssl::stream_base::server, [this, socket, timer](const error_code &ec) {
|
||||
if(timer)
|
||||
timer->cancel();
|
||||
if(!ec)
|
||||
read_request_and_content(socket);
|
||||
else if(on_error)
|
||||
on_error(std::shared_ptr<Request>(new Request(*socket)), ec);
|
||||
});
|
||||
}
|
||||
else if(on_error)
|
||||
on_error(std::shared_ptr<Request>(new Request(*socket)), ec);
|
||||
});
|
||||
}
|
||||
};
|
||||
} // namespace SimpleWeb
|
||||
|
||||
protected:
|
||||
asio::ssl::context context;
|
||||
|
||||
void accept() {
|
||||
//Create new socket for this connection
|
||||
//Shared_ptr is used to pass temporary objects to the asynchronous functions
|
||||
auto socket=std::make_shared<HTTPS>(*io_service, context);
|
||||
|
||||
acceptor->async_accept((*socket).lowest_layer(), [this, socket](const error_code& ec) {
|
||||
//Immediately start accepting a new connection (if io_service hasn't been stopped)
|
||||
if (ec != asio::error::operation_aborted)
|
||||
accept();
|
||||
|
||||
|
||||
if(!ec) {
|
||||
asio::ip::tcp::no_delay option(true);
|
||||
socket->lowest_layer().set_option(option);
|
||||
|
||||
//Set timeout on the following asio::ssl::stream::async_handshake
|
||||
auto timer=get_timeout_timer(socket, config.timeout_request);
|
||||
socket->async_handshake(asio::ssl::stream_base::server, [this, socket, timer]
|
||||
(const error_code& ec) {
|
||||
if(timer)
|
||||
timer->cancel();
|
||||
if(!ec)
|
||||
read_request_and_content(socket);
|
||||
else if(on_error)
|
||||
on_error(std::shared_ptr<Request>(new Request(*socket)), ec);
|
||||
});
|
||||
}
|
||||
else if(on_error)
|
||||
on_error(std::shared_ptr<Request>(new Request(*socket)), ec);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
#endif /* SERVER_HTTPS_HPP */
|
||||
|
||||
#endif /* SERVER_HTTPS_HPP */
|
||||
|
|
|
|||
286
status_code.hpp
286
status_code.hpp
|
|
@ -5,153 +5,153 @@
|
|||
#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
|
||||
};
|
||||
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 static std::vector<std::pair<StatusCode, std::string>> &status_codes() {
|
||||
static std::vector<std::pair<StatusCode, std::string>> status_codes = {
|
||||
{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_codes;
|
||||
}
|
||||
|
||||
inline StatusCode status_code(const std::string &status_code_str) {
|
||||
for(auto &status_code : status_codes()) {
|
||||
if(status_code.second == status_code_str)
|
||||
return status_code.first;
|
||||
inline const static std::vector<std::pair<StatusCode, std::string>> &status_codes() {
|
||||
static std::vector<std::pair<StatusCode, std::string>> status_codes = {
|
||||
{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_codes;
|
||||
}
|
||||
return StatusCode::unknown;
|
||||
}
|
||||
|
||||
inline const std::string &status_code(StatusCode status_code_enum) {
|
||||
for(auto &status_code : status_codes()) {
|
||||
if(status_code.first == status_code_enum)
|
||||
return status_code.second;
|
||||
inline StatusCode status_code(const std::string &status_code_str) {
|
||||
for(auto &status_code : status_codes()) {
|
||||
if(status_code.second == status_code_str)
|
||||
return status_code.first;
|
||||
}
|
||||
return StatusCode::unknown;
|
||||
}
|
||||
|
||||
inline const std::string &status_code(StatusCode status_code_enum) {
|
||||
for(auto &status_code : status_codes()) {
|
||||
if(status_code.first == status_code_enum)
|
||||
return status_code.second;
|
||||
}
|
||||
return status_codes()[0].second;
|
||||
}
|
||||
return status_codes()[0].second;
|
||||
}
|
||||
} // namespace SimpleWeb
|
||||
|
||||
#endif // SIMPLE_WEB_SERVER_STATUS_CODE_HPP
|
||||
|
|
|
|||
|
|
@ -1,77 +1,72 @@
|
|||
#include <vector>
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
|
||||
#include "crypto.hpp"
|
||||
|
||||
using namespace std;
|
||||
using namespace SimpleWeb;
|
||||
|
||||
const vector<pair<string, string> > base64_string_tests = {
|
||||
const vector<pair<string, string>> base64_string_tests = {
|
||||
{"", ""},
|
||||
{"f" , "Zg=="},
|
||||
{"f", "Zg=="},
|
||||
{"fo", "Zm8="},
|
||||
{"foo", "Zm9v"},
|
||||
{"foob", "Zm9vYg=="},
|
||||
{"fooba", "Zm9vYmE="},
|
||||
{"foobar", "Zm9vYmFy"}
|
||||
};
|
||||
{"foobar", "Zm9vYmFy"}};
|
||||
|
||||
const vector<pair<string, string> > md5_string_tests = {
|
||||
const vector<pair<string, string>> md5_string_tests = {
|
||||
{"", "d41d8cd98f00b204e9800998ecf8427e"},
|
||||
{"The quick brown fox jumps over the lazy dog", "9e107d9d372bb6826bd81d3542a419d6"}
|
||||
};
|
||||
{"The quick brown fox jumps over the lazy dog", "9e107d9d372bb6826bd81d3542a419d6"}};
|
||||
|
||||
const vector<pair<string, string> > sha1_string_tests = {
|
||||
const vector<pair<string, string>> sha1_string_tests = {
|
||||
{"", "da39a3ee5e6b4b0d3255bfef95601890afd80709"},
|
||||
{"The quick brown fox jumps over the lazy dog", "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12"}
|
||||
};
|
||||
{"The quick brown fox jumps over the lazy dog", "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12"}};
|
||||
|
||||
const vector<pair<string, string> > sha256_string_tests = {
|
||||
const vector<pair<string, string>> sha256_string_tests = {
|
||||
{"", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"},
|
||||
{"The quick brown fox jumps over the lazy dog", "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592"}
|
||||
};
|
||||
{"The quick brown fox jumps over the lazy dog", "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592"}};
|
||||
|
||||
const vector<pair<string, string> > sha512_string_tests = {
|
||||
const vector<pair<string, string>> sha512_string_tests = {
|
||||
{"", "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"},
|
||||
{"The quick brown fox jumps over the lazy dog", "07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6"}
|
||||
};
|
||||
{"The quick brown fox jumps over the lazy dog", "07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6"}};
|
||||
|
||||
int main() {
|
||||
for(auto& string_test: base64_string_tests) {
|
||||
assert(Crypto::Base64::encode(string_test.first)==string_test.second);
|
||||
assert(Crypto::Base64::decode(string_test.second)==string_test.first);
|
||||
}
|
||||
|
||||
for(auto& string_test: md5_string_tests) {
|
||||
assert(Crypto::to_hex_string(Crypto::md5(string_test.first)) == string_test.second);
|
||||
stringstream ss(string_test.first);
|
||||
assert(Crypto::to_hex_string(Crypto::md5(ss)) == string_test.second);
|
||||
}
|
||||
|
||||
for(auto& string_test: sha1_string_tests) {
|
||||
assert(Crypto::to_hex_string(Crypto::sha1(string_test.first)) == string_test.second);
|
||||
stringstream ss(string_test.first);
|
||||
assert(Crypto::to_hex_string(Crypto::sha1(ss)) == string_test.second);
|
||||
}
|
||||
|
||||
for(auto& string_test: sha256_string_tests) {
|
||||
assert(Crypto::to_hex_string(Crypto::sha256(string_test.first)) == string_test.second);
|
||||
stringstream ss(string_test.first);
|
||||
assert(Crypto::to_hex_string(Crypto::sha256(ss)) == string_test.second);
|
||||
}
|
||||
|
||||
for(auto& string_test: sha512_string_tests) {
|
||||
assert(Crypto::to_hex_string(Crypto::sha512(string_test.first)) == string_test.second);
|
||||
stringstream ss(string_test.first);
|
||||
assert(Crypto::to_hex_string(Crypto::sha512(ss)) == string_test.second);
|
||||
}
|
||||
|
||||
//Testing iterations
|
||||
assert(Crypto::to_hex_string(Crypto::sha1("Test", 1)) == "640ab2bae07bedc4c163f679a746f7ab7fb5d1fa");
|
||||
assert(Crypto::to_hex_string(Crypto::sha1("Test", 2)) == "af31c6cbdecd88726d0a9b3798c71ef41f1624d5");
|
||||
stringstream ss("Test");
|
||||
assert(Crypto::to_hex_string(Crypto::sha1(ss, 2)) == "af31c6cbdecd88726d0a9b3798c71ef41f1624d5");
|
||||
|
||||
assert(Crypto::to_hex_string(Crypto::pbkdf2("Password", "Salt", 4096, 128 / 8)) == "f66df50f8aaa11e4d9721e1312ff2e66");
|
||||
assert(Crypto::to_hex_string(Crypto::pbkdf2("Password", "Salt", 8192, 512 / 8)) == "a941ccbc34d1ee8ebbd1d34824a419c3dc4eac9cbc7c36ae6c7ca8725e2b618a6ad22241e787af937b0960cf85aa8ea3a258f243e05d3cc9b08af5dd93be046c");
|
||||
for(auto &string_test : base64_string_tests) {
|
||||
assert(Crypto::Base64::encode(string_test.first) == string_test.second);
|
||||
assert(Crypto::Base64::decode(string_test.second) == string_test.first);
|
||||
}
|
||||
|
||||
for(auto &string_test : md5_string_tests) {
|
||||
assert(Crypto::to_hex_string(Crypto::md5(string_test.first)) == string_test.second);
|
||||
stringstream ss(string_test.first);
|
||||
assert(Crypto::to_hex_string(Crypto::md5(ss)) == string_test.second);
|
||||
}
|
||||
|
||||
for(auto &string_test : sha1_string_tests) {
|
||||
assert(Crypto::to_hex_string(Crypto::sha1(string_test.first)) == string_test.second);
|
||||
stringstream ss(string_test.first);
|
||||
assert(Crypto::to_hex_string(Crypto::sha1(ss)) == string_test.second);
|
||||
}
|
||||
|
||||
for(auto &string_test : sha256_string_tests) {
|
||||
assert(Crypto::to_hex_string(Crypto::sha256(string_test.first)) == string_test.second);
|
||||
stringstream ss(string_test.first);
|
||||
assert(Crypto::to_hex_string(Crypto::sha256(ss)) == string_test.second);
|
||||
}
|
||||
|
||||
for(auto &string_test : sha512_string_tests) {
|
||||
assert(Crypto::to_hex_string(Crypto::sha512(string_test.first)) == string_test.second);
|
||||
stringstream ss(string_test.first);
|
||||
assert(Crypto::to_hex_string(Crypto::sha512(ss)) == string_test.second);
|
||||
}
|
||||
|
||||
//Testing iterations
|
||||
assert(Crypto::to_hex_string(Crypto::sha1("Test", 1)) == "640ab2bae07bedc4c163f679a746f7ab7fb5d1fa");
|
||||
assert(Crypto::to_hex_string(Crypto::sha1("Test", 2)) == "af31c6cbdecd88726d0a9b3798c71ef41f1624d5");
|
||||
stringstream ss("Test");
|
||||
assert(Crypto::to_hex_string(Crypto::sha1(ss, 2)) == "af31c6cbdecd88726d0a9b3798c71ef41f1624d5");
|
||||
|
||||
assert(Crypto::to_hex_string(Crypto::pbkdf2("Password", "Salt", 4096, 128 / 8)) == "f66df50f8aaa11e4d9721e1312ff2e66");
|
||||
assert(Crypto::to_hex_string(Crypto::pbkdf2("Password", "Salt", 8192, 512 / 8)) == "a941ccbc34d1ee8ebbd1d34824a419c3dc4eac9cbc7c36ae6c7ca8725e2b618a6ad22241e787af937b0960cf85aa8ea3a258f243e05d3cc9b08af5dd93be046c");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#include "server_http.hpp"
|
||||
#include "client_http.hpp"
|
||||
#include "server_http.hpp"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
|
|
@ -9,235 +9,239 @@ typedef SimpleWeb::Server<SimpleWeb::HTTP> HttpServer;
|
|||
typedef SimpleWeb::Client<SimpleWeb::HTTP> HttpClient;
|
||||
|
||||
int main() {
|
||||
HttpServer server;
|
||||
server.config.port=8080;
|
||||
|
||||
server.resource["^/string$"]["POST"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
|
||||
auto content=request->content.string();
|
||||
|
||||
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << content.length() << "\r\n\r\n" << content;
|
||||
};
|
||||
|
||||
server.resource["^/string2$"]["POST"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
|
||||
response->write(request->content.string());
|
||||
};
|
||||
|
||||
server.resource["^/string3$"]["POST"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
|
||||
std::stringstream stream;
|
||||
stream << request->content.rdbuf();
|
||||
response->write(stream);
|
||||
};
|
||||
|
||||
server.resource["^/string4$"]["POST"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> /*request*/) {
|
||||
response->write(SimpleWeb::StatusCode::client_error_forbidden, {{"Test1", "test2"}, {"tesT3", "test4"}});
|
||||
};
|
||||
|
||||
server.resource["^/info$"]["GET"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
|
||||
stringstream content_stream;
|
||||
content_stream << request->method << " " << request->path << " " << request->http_version << " ";
|
||||
content_stream << request->header.find("test parameter")->second;
|
||||
HttpServer server;
|
||||
server.config.port = 8080;
|
||||
|
||||
content_stream.seekp(0, ios::end);
|
||||
|
||||
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << content_stream.tellp() << "\r\n\r\n" << content_stream.rdbuf();
|
||||
};
|
||||
|
||||
server.resource["^/match/([0-9]+)$"]["GET"]=[&server](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
|
||||
string number=request->path_match[1];
|
||||
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << number.length() << "\r\n\r\n" << number;
|
||||
};
|
||||
|
||||
server.resource["^/header$"]["GET"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
|
||||
auto content=request->header.find("test1")->second+request->header.find("test2")->second;
|
||||
|
||||
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << content.length() << "\r\n\r\n" << content;
|
||||
};
|
||||
|
||||
thread server_thread([&server](){
|
||||
//Start server
|
||||
server.start();
|
||||
server.resource["^/string$"]["POST"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
|
||||
auto content = request->content.string();
|
||||
|
||||
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << content.length() << "\r\n\r\n"
|
||||
<< content;
|
||||
};
|
||||
|
||||
server.resource["^/string2$"]["POST"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
|
||||
response->write(request->content.string());
|
||||
};
|
||||
|
||||
server.resource["^/string3$"]["POST"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
|
||||
std::stringstream stream;
|
||||
stream << request->content.rdbuf();
|
||||
response->write(stream);
|
||||
};
|
||||
|
||||
server.resource["^/string4$"]["POST"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> /*request*/) {
|
||||
response->write(SimpleWeb::StatusCode::client_error_forbidden, {{"Test1", "test2"}, {"tesT3", "test4"}});
|
||||
};
|
||||
|
||||
server.resource["^/info$"]["GET"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
|
||||
stringstream content_stream;
|
||||
content_stream << request->method << " " << request->path << " " << request->http_version << " ";
|
||||
content_stream << request->header.find("test parameter")->second;
|
||||
|
||||
content_stream.seekp(0, ios::end);
|
||||
|
||||
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << content_stream.tellp() << "\r\n\r\n"
|
||||
<< content_stream.rdbuf();
|
||||
};
|
||||
|
||||
server.resource["^/match/([0-9]+)$"]["GET"] = [&server](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
|
||||
string number = request->path_match[1];
|
||||
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << number.length() << "\r\n\r\n"
|
||||
<< number;
|
||||
};
|
||||
|
||||
server.resource["^/header$"]["GET"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
|
||||
auto content = request->header.find("test1")->second + request->header.find("test2")->second;
|
||||
|
||||
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << content.length() << "\r\n\r\n"
|
||||
<< content;
|
||||
};
|
||||
|
||||
thread server_thread([&server]() {
|
||||
//Start server
|
||||
server.start();
|
||||
});
|
||||
|
||||
this_thread::sleep_for(chrono::seconds(1));
|
||||
{
|
||||
HttpClient client("localhost:8080");
|
||||
|
||||
{
|
||||
stringstream output;
|
||||
auto r = client.request("POST", "/string", "A string");
|
||||
assert(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);
|
||||
output << r->content.rdbuf();
|
||||
assert(output.str() == "A string");
|
||||
}
|
||||
|
||||
{
|
||||
stringstream output;
|
||||
auto r = client.request("POST", "/string", "A string");
|
||||
assert(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);
|
||||
assert(r->content.string() == "A string");
|
||||
}
|
||||
|
||||
{
|
||||
stringstream output;
|
||||
auto r = client.request("POST", "/string2", "A string");
|
||||
assert(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);
|
||||
output << r->content.rdbuf();
|
||||
assert(output.str() == "A string");
|
||||
}
|
||||
|
||||
{
|
||||
stringstream output;
|
||||
auto r = client.request("POST", "/string3", "A string");
|
||||
assert(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);
|
||||
output << r->content.rdbuf();
|
||||
assert(output.str() == "A string");
|
||||
}
|
||||
|
||||
{
|
||||
stringstream output;
|
||||
auto r = client.request("POST", "/string4", "A string");
|
||||
assert(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::client_error_forbidden);
|
||||
assert(r->header.size() == 3);
|
||||
assert(r->header.find("test1")->second == "test2");
|
||||
assert(r->header.find("tEst3")->second == "test4");
|
||||
assert(r->header.find("content-length")->second == "0");
|
||||
output << r->content.rdbuf();
|
||||
assert(output.str() == "");
|
||||
}
|
||||
|
||||
{
|
||||
stringstream output;
|
||||
stringstream content("A string");
|
||||
auto r = client.request("POST", "/string", content);
|
||||
output << r->content.rdbuf();
|
||||
assert(output.str() == "A string");
|
||||
}
|
||||
|
||||
{
|
||||
stringstream output;
|
||||
auto r = client.request("GET", "/info", "", {{"Test Parameter", "test value"}});
|
||||
output << r->content.rdbuf();
|
||||
assert(output.str() == "GET /info 1.1 test value");
|
||||
}
|
||||
|
||||
{
|
||||
stringstream output;
|
||||
auto r = client.request("GET", "/match/123");
|
||||
output << r->content.rdbuf();
|
||||
assert(output.str() == "123");
|
||||
}
|
||||
}
|
||||
{
|
||||
HttpClient client("localhost:8080");
|
||||
|
||||
HttpClient::Connection *connection;
|
||||
{
|
||||
// test performing the stream version of the request methods first
|
||||
stringstream output;
|
||||
stringstream content("A string");
|
||||
auto r = client.request("POST", "/string", content);
|
||||
output << r->content.rdbuf();
|
||||
assert(output.str() == "A string");
|
||||
assert(client.connections->size() == 1);
|
||||
connection = client.connections->front().get();
|
||||
}
|
||||
|
||||
{
|
||||
stringstream output;
|
||||
auto r = client.request("POST", "/string", "A string");
|
||||
output << r->content.rdbuf();
|
||||
assert(output.str() == "A string");
|
||||
assert(client.connections->size() == 1);
|
||||
assert(connection == client.connections->front().get());
|
||||
}
|
||||
|
||||
{
|
||||
stringstream output;
|
||||
auto r = client.request("GET", "/header", "", {{"test1", "test"}, {"test2", "ing"}});
|
||||
output << r->content.rdbuf();
|
||||
assert(output.str() == "testing");
|
||||
assert(client.connections->size() == 1);
|
||||
assert(connection == client.connections->front().get());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
HttpClient client("localhost:8080");
|
||||
bool call = false;
|
||||
client.request("GET", "/match/123", [&call](shared_ptr<HttpClient::Response> response, const SimpleWeb::error_code &ec) {
|
||||
assert(!ec);
|
||||
stringstream output;
|
||||
output << response->content.rdbuf();
|
||||
assert(output.str() == "123");
|
||||
call = true;
|
||||
});
|
||||
|
||||
this_thread::sleep_for(chrono::seconds(1));
|
||||
client.io_service->run();
|
||||
assert(call);
|
||||
|
||||
{
|
||||
HttpClient client("localhost:8080");
|
||||
|
||||
{
|
||||
stringstream output;
|
||||
auto r=client.request("POST", "/string", "A string");
|
||||
assert(SimpleWeb::status_code(r->status_code)==SimpleWeb::StatusCode::success_ok);
|
||||
output << r->content.rdbuf();
|
||||
assert(output.str()=="A string");
|
||||
}
|
||||
|
||||
{
|
||||
stringstream output;
|
||||
auto r=client.request("POST", "/string", "A string");
|
||||
assert(SimpleWeb::status_code(r->status_code)==SimpleWeb::StatusCode::success_ok);
|
||||
assert(r->content.string()=="A string");
|
||||
}
|
||||
|
||||
{
|
||||
stringstream output;
|
||||
auto r=client.request("POST", "/string2", "A string");
|
||||
assert(SimpleWeb::status_code(r->status_code)==SimpleWeb::StatusCode::success_ok);
|
||||
output << r->content.rdbuf();
|
||||
assert(output.str()=="A string");
|
||||
}
|
||||
|
||||
{
|
||||
stringstream output;
|
||||
auto r=client.request("POST", "/string3", "A string");
|
||||
assert(SimpleWeb::status_code(r->status_code)==SimpleWeb::StatusCode::success_ok);
|
||||
output << r->content.rdbuf();
|
||||
assert(output.str()=="A string");
|
||||
}
|
||||
|
||||
{
|
||||
stringstream output;
|
||||
auto r=client.request("POST", "/string4", "A string");
|
||||
assert(SimpleWeb::status_code(r->status_code)==SimpleWeb::StatusCode::client_error_forbidden);
|
||||
assert(r->header.size()==3);
|
||||
assert(r->header.find("test1")->second=="test2");
|
||||
assert(r->header.find("tEst3")->second=="test4");
|
||||
assert(r->header.find("content-length")->second=="0");
|
||||
output << r->content.rdbuf();
|
||||
assert(output.str()=="");
|
||||
}
|
||||
|
||||
{
|
||||
stringstream output;
|
||||
stringstream content("A string");
|
||||
auto r=client.request("POST", "/string", content);
|
||||
output << r->content.rdbuf();
|
||||
assert(output.str()=="A string");
|
||||
}
|
||||
|
||||
{
|
||||
stringstream output;
|
||||
auto r=client.request("GET", "/info", "", {{"Test Parameter", "test value"}});
|
||||
output << r->content.rdbuf();
|
||||
assert(output.str()=="GET /info 1.1 test value");
|
||||
}
|
||||
|
||||
{
|
||||
stringstream output;
|
||||
auto r=client.request("GET", "/match/123");
|
||||
output << r->content.rdbuf();
|
||||
assert(output.str()=="123");
|
||||
}
|
||||
}
|
||||
{
|
||||
HttpClient client("localhost:8080");
|
||||
|
||||
HttpClient::Connection *connection;
|
||||
{
|
||||
// test performing the stream version of the request methods first
|
||||
stringstream output;
|
||||
stringstream content("A string");
|
||||
auto r=client.request("POST", "/string", content);
|
||||
output << r->content.rdbuf();
|
||||
assert(output.str()=="A string");
|
||||
assert(client.connections->size()==1);
|
||||
connection=client.connections->front().get();
|
||||
}
|
||||
|
||||
{
|
||||
stringstream output;
|
||||
auto r=client.request("POST", "/string", "A string");
|
||||
output << r->content.rdbuf();
|
||||
assert(output.str()=="A string");
|
||||
assert(client.connections->size()==1);
|
||||
assert(connection==client.connections->front().get());
|
||||
}
|
||||
|
||||
{
|
||||
stringstream output;
|
||||
auto r=client.request("GET", "/header", "", {{"test1", "test"}, {"test2", "ing"}});
|
||||
output << r->content.rdbuf();
|
||||
assert(output.str()=="testing");
|
||||
assert(client.connections->size()==1);
|
||||
assert(connection==client.connections->front().get());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
HttpClient client("localhost:8080");
|
||||
bool call=false;
|
||||
client.request("GET", "/match/123", [&call](shared_ptr<HttpClient::Response> response, const SimpleWeb::error_code &ec) {
|
||||
vector<int> calls(100);
|
||||
vector<thread> threads;
|
||||
for(size_t c = 0; c < 100; ++c) {
|
||||
calls[c] = 0;
|
||||
threads.emplace_back([c, &client, &calls] {
|
||||
client.request("GET", "/match/123", [c, &calls](shared_ptr<HttpClient::Response> response, const SimpleWeb::error_code &ec) {
|
||||
assert(!ec);
|
||||
stringstream output;
|
||||
output << response->content.rdbuf();
|
||||
assert(output.str()=="123");
|
||||
call=true;
|
||||
assert(output.str() == "123");
|
||||
calls[c] = 1;
|
||||
});
|
||||
});
|
||||
client.io_service->run();
|
||||
}
|
||||
for(auto &thread : threads)
|
||||
thread.join();
|
||||
assert(client.connections->size() == 100);
|
||||
client.io_service->reset();
|
||||
client.io_service->run();
|
||||
assert(client.connections->size() == 1);
|
||||
for(auto call : calls)
|
||||
assert(call);
|
||||
|
||||
{
|
||||
vector<int> calls(100);
|
||||
vector<thread> threads;
|
||||
for(size_t c=0;c<100;++c) {
|
||||
calls[c]=0;
|
||||
threads.emplace_back([c, &client, &calls] {
|
||||
client.request("GET", "/match/123", [c, &calls](shared_ptr<HttpClient::Response> response, const SimpleWeb::error_code &ec) {
|
||||
assert(!ec);
|
||||
stringstream output;
|
||||
output << response->content.rdbuf();
|
||||
assert(output.str()=="123");
|
||||
calls[c]=1;
|
||||
});
|
||||
});
|
||||
}
|
||||
for(auto &thread: threads)
|
||||
thread.join();
|
||||
assert(client.connections->size()==100);
|
||||
client.io_service->reset();
|
||||
client.io_service->run();
|
||||
assert(client.connections->size()==1);
|
||||
for(auto call: calls)
|
||||
assert(call);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
HttpClient client("localhost:8080");
|
||||
assert(client.connections->size() == 0);
|
||||
for(size_t c = 0; c < 5000; ++c) {
|
||||
auto r1 = client.request("POST", "/string", "A string");
|
||||
assert(SimpleWeb::status_code(r1->status_code) == SimpleWeb::StatusCode::success_ok);
|
||||
assert(r1->content.string() == "A string");
|
||||
assert(client.connections->size() == 1);
|
||||
|
||||
stringstream content("A string");
|
||||
auto r2 = client.request("POST", "/string", content);
|
||||
assert(SimpleWeb::status_code(r2->status_code) == SimpleWeb::StatusCode::success_ok);
|
||||
assert(r2->content.string() == "A string");
|
||||
assert(client.connections->size() == 1);
|
||||
}
|
||||
}
|
||||
|
||||
for(size_t c = 0; c < 500; ++c) {
|
||||
{
|
||||
HttpClient client("localhost:8080");
|
||||
assert(client.connections->size()==0);
|
||||
for(size_t c=0;c<5000;++c) {
|
||||
auto r1=client.request("POST", "/string", "A string");
|
||||
assert(SimpleWeb::status_code(r1->status_code)==SimpleWeb::StatusCode::success_ok);
|
||||
assert(r1->content.string()=="A string");
|
||||
assert(client.connections->size()==1);
|
||||
|
||||
stringstream content("A string");
|
||||
auto r2 = client.request("POST", "/string", content);
|
||||
assert(SimpleWeb::status_code(r2->status_code) == SimpleWeb::StatusCode::success_ok);
|
||||
assert(r2->content.string() == "A string");
|
||||
assert(client.connections->size() == 1);
|
||||
}
|
||||
HttpClient client("localhost:8080");
|
||||
auto r = client.request("POST", "/string", "A string");
|
||||
assert(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);
|
||||
assert(r->content.string() == "A string");
|
||||
assert(client.connections->size() == 1);
|
||||
}
|
||||
|
||||
for(size_t c=0;c<500;++c) {
|
||||
{
|
||||
HttpClient client("localhost:8080");
|
||||
auto r=client.request("POST", "/string", "A string");
|
||||
assert(SimpleWeb::status_code(r->status_code)==SimpleWeb::StatusCode::success_ok);
|
||||
assert(r->content.string()=="A string");
|
||||
assert(client.connections->size()==1);
|
||||
}
|
||||
|
||||
{
|
||||
HttpClient client("localhost:8080");
|
||||
stringstream content("A string");
|
||||
auto r = client.request("POST", "/string", content);
|
||||
assert(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);
|
||||
assert(r->content.string() == "A string");
|
||||
assert(client.connections->size() == 1);
|
||||
}
|
||||
|
||||
{
|
||||
HttpClient client("localhost:8080");
|
||||
stringstream content("A string");
|
||||
auto r = client.request("POST", "/string", content);
|
||||
assert(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);
|
||||
assert(r->content.string() == "A string");
|
||||
assert(client.connections->size() == 1);
|
||||
}
|
||||
|
||||
server.stop();
|
||||
server_thread.join();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
server.stop();
|
||||
server_thread.join();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,198 +1,198 @@
|
|||
#include "server_http.hpp"
|
||||
#include "client_http.hpp"
|
||||
#include <iostream>
|
||||
#include "server_http.hpp"
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
using namespace SimpleWeb;
|
||||
|
||||
class ServerTest : public ServerBase<HTTP> {
|
||||
public:
|
||||
ServerTest() : ServerBase<HTTP>::ServerBase(8080) {}
|
||||
|
||||
void accept() {}
|
||||
|
||||
void parse_request_test() {
|
||||
HTTP socket(*io_service);
|
||||
std::shared_ptr<Request> request(new Request(socket));
|
||||
|
||||
std::ostream stream(&request->content.streambuf);
|
||||
stream << "GET /test/ HTTP/1.1\r\n";
|
||||
stream << "TestHeader: test\r\n";
|
||||
stream << "TestHeader2:test2\r\n";
|
||||
stream << "TestHeader3:test3a\r\n";
|
||||
stream << "TestHeader3:test3b\r\n";
|
||||
stream << "\r\n";
|
||||
|
||||
assert(parse_request(request));
|
||||
|
||||
assert(request->method=="GET");
|
||||
assert(request->path=="/test/");
|
||||
assert(request->http_version=="1.1");
|
||||
|
||||
assert(request->header.size()==4);
|
||||
auto header_it=request->header.find("TestHeader");
|
||||
assert(header_it!=request->header.end() && header_it->second=="test");
|
||||
header_it=request->header.find("TestHeader2");
|
||||
assert(header_it!=request->header.end() && header_it->second=="test2");
|
||||
|
||||
header_it=request->header.find("testheader");
|
||||
assert(header_it!=request->header.end() && header_it->second=="test");
|
||||
header_it=request->header.find("testheader2");
|
||||
assert(header_it!=request->header.end() && header_it->second=="test2");
|
||||
|
||||
auto range=request->header.equal_range("testheader3");
|
||||
auto first=range.first;
|
||||
auto second=first;
|
||||
++second;
|
||||
assert(range.first!=request->header.end() && range.second!=request->header.end() &&
|
||||
((first->second=="test3a" && second->second=="test3b") ||
|
||||
(first->second=="test3b" && second->second=="test3a")));
|
||||
}
|
||||
ServerTest() : ServerBase<HTTP>::ServerBase(8080) {}
|
||||
|
||||
void accept() {}
|
||||
|
||||
void parse_request_test() {
|
||||
HTTP socket(*io_service);
|
||||
std::shared_ptr<Request> request(new Request(socket));
|
||||
|
||||
std::ostream stream(&request->content.streambuf);
|
||||
stream << "GET /test/ HTTP/1.1\r\n";
|
||||
stream << "TestHeader: test\r\n";
|
||||
stream << "TestHeader2:test2\r\n";
|
||||
stream << "TestHeader3:test3a\r\n";
|
||||
stream << "TestHeader3:test3b\r\n";
|
||||
stream << "\r\n";
|
||||
|
||||
assert(parse_request(request));
|
||||
|
||||
assert(request->method == "GET");
|
||||
assert(request->path == "/test/");
|
||||
assert(request->http_version == "1.1");
|
||||
|
||||
assert(request->header.size() == 4);
|
||||
auto header_it = request->header.find("TestHeader");
|
||||
assert(header_it != request->header.end() && header_it->second == "test");
|
||||
header_it = request->header.find("TestHeader2");
|
||||
assert(header_it != request->header.end() && header_it->second == "test2");
|
||||
|
||||
header_it = request->header.find("testheader");
|
||||
assert(header_it != request->header.end() && header_it->second == "test");
|
||||
header_it = request->header.find("testheader2");
|
||||
assert(header_it != request->header.end() && header_it->second == "test2");
|
||||
|
||||
auto range = request->header.equal_range("testheader3");
|
||||
auto first = range.first;
|
||||
auto second = first;
|
||||
++second;
|
||||
assert(range.first != request->header.end() && range.second != request->header.end() &&
|
||||
((first->second == "test3a" && second->second == "test3b") ||
|
||||
(first->second == "test3b" && second->second == "test3a")));
|
||||
}
|
||||
};
|
||||
|
||||
class ClientTest : public ClientBase<HTTP> {
|
||||
public:
|
||||
ClientTest(const std::string& server_port_path) : ClientBase<HTTP>::ClientBase(server_port_path, 80) {}
|
||||
|
||||
std::shared_ptr<Connection> create_connection() override {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void constructor_parse_test1() {
|
||||
assert(host=="test.org");
|
||||
assert(port==8080);
|
||||
}
|
||||
|
||||
void constructor_parse_test2() {
|
||||
assert(host=="test.org");
|
||||
assert(port==80);
|
||||
}
|
||||
|
||||
void parse_response_header_test() {
|
||||
std::shared_ptr<Response> response(new Response());
|
||||
|
||||
ostream stream(&response->content_buffer);
|
||||
stream << "HTTP/1.1 200 OK\r\n";
|
||||
stream << "TestHeader: test\r\n";
|
||||
stream << "TestHeader2:test2\r\n";
|
||||
stream << "TestHeader3:test3a\r\n";
|
||||
stream << "TestHeader3:test3b\r\n";
|
||||
stream << "\r\n";
|
||||
|
||||
parse_response_header(response);
|
||||
|
||||
assert(response->http_version=="1.1");
|
||||
assert(response->status_code=="200 OK");
|
||||
|
||||
assert(response->header.size()==4);
|
||||
auto header_it=response->header.find("TestHeader");
|
||||
assert(header_it!=response->header.end() && header_it->second=="test");
|
||||
header_it=response->header.find("TestHeader2");
|
||||
assert(header_it!=response->header.end() && header_it->second=="test2");
|
||||
|
||||
header_it=response->header.find("testheader");
|
||||
assert(header_it!=response->header.end() && header_it->second=="test");
|
||||
header_it=response->header.find("testheader2");
|
||||
assert(header_it!=response->header.end() && header_it->second=="test2");
|
||||
|
||||
auto range=response->header.equal_range("testheader3");
|
||||
auto first=range.first;
|
||||
auto second=first;
|
||||
++second;
|
||||
assert(range.first!=response->header.end() && range.second!=response->header.end() &&
|
||||
((first->second=="test3a" && second->second=="test3b") ||
|
||||
(first->second=="test3b" && second->second=="test3a")));
|
||||
}
|
||||
ClientTest(const std::string &server_port_path) : ClientBase<HTTP>::ClientBase(server_port_path, 80) {}
|
||||
|
||||
std::shared_ptr<Connection> create_connection() override {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void constructor_parse_test1() {
|
||||
assert(host == "test.org");
|
||||
assert(port == 8080);
|
||||
}
|
||||
|
||||
void constructor_parse_test2() {
|
||||
assert(host == "test.org");
|
||||
assert(port == 80);
|
||||
}
|
||||
|
||||
void parse_response_header_test() {
|
||||
std::shared_ptr<Response> response(new Response());
|
||||
|
||||
ostream stream(&response->content_buffer);
|
||||
stream << "HTTP/1.1 200 OK\r\n";
|
||||
stream << "TestHeader: test\r\n";
|
||||
stream << "TestHeader2:test2\r\n";
|
||||
stream << "TestHeader3:test3a\r\n";
|
||||
stream << "TestHeader3:test3b\r\n";
|
||||
stream << "\r\n";
|
||||
|
||||
parse_response_header(response);
|
||||
|
||||
assert(response->http_version == "1.1");
|
||||
assert(response->status_code == "200 OK");
|
||||
|
||||
assert(response->header.size() == 4);
|
||||
auto header_it = response->header.find("TestHeader");
|
||||
assert(header_it != response->header.end() && header_it->second == "test");
|
||||
header_it = response->header.find("TestHeader2");
|
||||
assert(header_it != response->header.end() && header_it->second == "test2");
|
||||
|
||||
header_it = response->header.find("testheader");
|
||||
assert(header_it != response->header.end() && header_it->second == "test");
|
||||
header_it = response->header.find("testheader2");
|
||||
assert(header_it != response->header.end() && header_it->second == "test2");
|
||||
|
||||
auto range = response->header.equal_range("testheader3");
|
||||
auto first = range.first;
|
||||
auto second = first;
|
||||
++second;
|
||||
assert(range.first != response->header.end() && range.second != response->header.end() &&
|
||||
((first->second == "test3a" && second->second == "test3b") ||
|
||||
(first->second == "test3b" && second->second == "test3a")));
|
||||
}
|
||||
};
|
||||
|
||||
int main() {
|
||||
assert(case_insensitive_equal("Test", "tesT"));
|
||||
assert(case_insensitive_equal("tesT", "test"));
|
||||
assert(!case_insensitive_equal("test", "tseT"));
|
||||
CaseInsensitiveEqual equal;
|
||||
assert(equal("Test", "tesT"));
|
||||
assert(equal("tesT", "test"));
|
||||
assert(!equal("test", "tset"));
|
||||
CaseInsensitiveHash hash;
|
||||
assert(hash("Test")==hash("tesT"));
|
||||
assert(hash("tesT")==hash("test"));
|
||||
assert(hash("test")!=hash("tset"));
|
||||
|
||||
auto percent_decoded="testing æøå !#$&'()*+,/:;=?@[]";
|
||||
auto percent_encoded="testing+æøå+%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D";
|
||||
assert(Percent::encode(percent_decoded)==percent_encoded);
|
||||
assert(Percent::decode(percent_encoded)==percent_decoded);
|
||||
assert(Percent::decode(Percent::encode(percent_decoded))==percent_decoded);
|
||||
|
||||
SimpleWeb::CaseInsensitiveMultimap fields={{"test1", "æøå"}, {"test2", "!#$&'()*+,/:;=?@[]"}};
|
||||
auto query_string1="test1=æøå&test2=%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D";
|
||||
auto query_string2="test2=%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D&test1=æøå";
|
||||
auto query_string_result=QueryString::create(fields);
|
||||
assert(query_string_result==query_string1 || query_string_result==query_string2);
|
||||
auto fields_result1=QueryString::parse(query_string1);
|
||||
auto fields_result2=QueryString::parse(query_string2);
|
||||
assert(fields_result1==fields_result2 && fields_result1==fields);
|
||||
|
||||
ServerTest serverTest;
|
||||
serverTest.io_service=std::make_shared<asio::io_service>();
|
||||
|
||||
serverTest.parse_request_test();
|
||||
|
||||
ClientTest clientTest("test.org:8080");
|
||||
clientTest.constructor_parse_test1();
|
||||
|
||||
ClientTest clientTest2("test.org");
|
||||
clientTest2.constructor_parse_test2();
|
||||
|
||||
clientTest2.parse_response_header_test();
|
||||
assert(case_insensitive_equal("Test", "tesT"));
|
||||
assert(case_insensitive_equal("tesT", "test"));
|
||||
assert(!case_insensitive_equal("test", "tseT"));
|
||||
CaseInsensitiveEqual equal;
|
||||
assert(equal("Test", "tesT"));
|
||||
assert(equal("tesT", "test"));
|
||||
assert(!equal("test", "tset"));
|
||||
CaseInsensitiveHash hash;
|
||||
assert(hash("Test") == hash("tesT"));
|
||||
assert(hash("tesT") == hash("test"));
|
||||
assert(hash("test") != hash("tset"));
|
||||
|
||||
auto percent_decoded = "testing æøå !#$&'()*+,/:;=?@[]";
|
||||
auto percent_encoded = "testing+æøå+%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D";
|
||||
assert(Percent::encode(percent_decoded) == percent_encoded);
|
||||
assert(Percent::decode(percent_encoded) == percent_decoded);
|
||||
assert(Percent::decode(Percent::encode(percent_decoded)) == percent_decoded);
|
||||
|
||||
SimpleWeb::CaseInsensitiveMultimap fields = {{"test1", "æøå"}, {"test2", "!#$&'()*+,/:;=?@[]"}};
|
||||
auto query_string1 = "test1=æøå&test2=%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D";
|
||||
auto query_string2 = "test2=%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D&test1=æøå";
|
||||
auto query_string_result = QueryString::create(fields);
|
||||
assert(query_string_result == query_string1 || query_string_result == query_string2);
|
||||
auto fields_result1 = QueryString::parse(query_string1);
|
||||
auto fields_result2 = QueryString::parse(query_string2);
|
||||
assert(fields_result1 == fields_result2 && fields_result1 == fields);
|
||||
|
||||
ServerTest serverTest;
|
||||
serverTest.io_service = std::make_shared<asio::io_service>();
|
||||
|
||||
serverTest.parse_request_test();
|
||||
|
||||
ClientTest clientTest("test.org:8080");
|
||||
clientTest.constructor_parse_test1();
|
||||
|
||||
ClientTest clientTest2("test.org");
|
||||
clientTest2.constructor_parse_test2();
|
||||
|
||||
clientTest2.parse_response_header_test();
|
||||
|
||||
|
||||
asio::io_service io_service;
|
||||
asio::ip::tcp::socket socket(io_service);
|
||||
SimpleWeb::Server<HTTP>::Request request(socket);
|
||||
asio::io_service io_service;
|
||||
asio::ip::tcp::socket socket(io_service);
|
||||
SimpleWeb::Server<HTTP>::Request request(socket);
|
||||
{
|
||||
request.path = "/?";
|
||||
auto queries = request.parse_query_string();
|
||||
assert(queries.empty());
|
||||
}
|
||||
{
|
||||
request.path = "/";
|
||||
auto queries = request.parse_query_string();
|
||||
assert(queries.empty());
|
||||
}
|
||||
{
|
||||
request.path = "/?=";
|
||||
auto queries = request.parse_query_string();
|
||||
assert(queries.empty());
|
||||
}
|
||||
{
|
||||
request.path = "/?=test";
|
||||
auto queries = request.parse_query_string();
|
||||
assert(queries.empty());
|
||||
}
|
||||
{
|
||||
request.path = "/?a=1%202%20%203&b=3+4&c&d=æ%25ø%26å%3F";
|
||||
auto queries = request.parse_query_string();
|
||||
{
|
||||
request.path = "/?";
|
||||
auto queries = request.parse_query_string();
|
||||
assert(queries.empty());
|
||||
auto range = queries.equal_range("a");
|
||||
assert(range.first != range.second);
|
||||
assert(range.first->second == "1 2 3");
|
||||
}
|
||||
{
|
||||
request.path = "/";
|
||||
auto queries = request.parse_query_string();
|
||||
assert(queries.empty());
|
||||
auto range = queries.equal_range("b");
|
||||
assert(range.first != range.second);
|
||||
assert(range.first->second == "3 4");
|
||||
}
|
||||
{
|
||||
request.path = "/?=";
|
||||
auto queries = request.parse_query_string();
|
||||
assert(queries.empty());
|
||||
auto range = queries.equal_range("c");
|
||||
assert(range.first != range.second);
|
||||
assert(range.first->second == "");
|
||||
}
|
||||
{
|
||||
request.path = "/?=test";
|
||||
auto queries = request.parse_query_string();
|
||||
assert(queries.empty());
|
||||
}
|
||||
{
|
||||
request.path = "/?a=1%202%20%203&b=3+4&c&d=æ%25ø%26å%3F";
|
||||
auto queries = request.parse_query_string();
|
||||
{
|
||||
auto range = queries.equal_range("a");
|
||||
assert(range.first != range.second);
|
||||
assert(range.first->second == "1 2 3");
|
||||
}
|
||||
{
|
||||
auto range = queries.equal_range("b");
|
||||
assert(range.first != range.second);
|
||||
assert(range.first->second == "3 4");
|
||||
}
|
||||
{
|
||||
auto range = queries.equal_range("c");
|
||||
assert(range.first != range.second);
|
||||
assert(range.first->second == "");
|
||||
}
|
||||
{
|
||||
auto range = queries.equal_range("d");
|
||||
assert(range.first != range.second);
|
||||
assert(range.first->second == "æ%ø&å?");
|
||||
}
|
||||
auto range = queries.equal_range("d");
|
||||
assert(range.first != range.second);
|
||||
assert(range.first->second == "æ%ø&å?");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
228
utility.hpp
228
utility.hpp
|
|
@ -20,136 +20,136 @@
|
|||
namespace SimpleWeb {
|
||||
#ifndef CASE_INSENSITIVE_EQUAL_AND_HASH
|
||||
#define CASE_INSENSITIVE_EQUAL_AND_HASH
|
||||
inline bool case_insensitive_equal(const std::string &str1, const std::string &str2) {
|
||||
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 {
|
||||
return case_insensitive_equal(str1, str2);
|
||||
inline bool case_insensitive_equal(const std::string &str1, const std::string &str2) {
|
||||
return str1.size() == str2.size() &&
|
||||
std::equal(str1.begin(), str1.end(), str2.begin(), [](char a, char b) {
|
||||
return tolower(a) == tolower(b);
|
||||
});
|
||||
}
|
||||
};
|
||||
// Based on https://stackoverflow.com/questions/2590677/how-do-i-combine-hash-values-in-c0x/2595226#2595226
|
||||
class CaseInsensitiveHash {
|
||||
public:
|
||||
size_t operator()(const std::string &str) const {
|
||||
size_t h = 0;
|
||||
std::hash<int> hash;
|
||||
for(auto c : str)
|
||||
h ^= hash(tolower(c)) + 0x9e3779b9 + (h << 6) + (h >> 2);
|
||||
return h;
|
||||
}
|
||||
};
|
||||
class CaseInsensitiveEqual {
|
||||
public:
|
||||
bool operator()(const std::string &str1, const std::string &str2) const {
|
||||
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:
|
||||
size_t operator()(const std::string &str) const {
|
||||
size_t h = 0;
|
||||
std::hash<int> hash;
|
||||
for(auto c : str)
|
||||
h ^= hash(tolower(c)) + 0x9e3779b9 + (h << 6) + (h >> 2);
|
||||
return h;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
typedef std::unordered_multimap<std::string, std::string, CaseInsensitiveHash, CaseInsensitiveEqual> CaseInsensitiveMultimap;
|
||||
typedef std::unordered_multimap<std::string, std::string, CaseInsensitiveHash, CaseInsensitiveEqual> CaseInsensitiveMultimap;
|
||||
|
||||
/// Percent encoding and decoding
|
||||
class Percent {
|
||||
public:
|
||||
/// Returns percent-encoded string
|
||||
static std::string encode(const std::string &value) {
|
||||
static auto hex_chars = "0123456789ABCDEF";
|
||||
/// Percent encoding and decoding
|
||||
class Percent {
|
||||
public:
|
||||
/// Returns percent-encoded string
|
||||
static std::string encode(const std::string &value) {
|
||||
static auto hex_chars = "0123456789ABCDEF";
|
||||
|
||||
std::string result;
|
||||
result.reserve(value.size()); // minimum size of result
|
||||
std::string result;
|
||||
result.reserve(value.size()); // minimum size of result
|
||||
|
||||
for(auto &chr : value) {
|
||||
if(chr == ' ')
|
||||
result += '+';
|
||||
else if(chr == '!' || chr == '#' || chr == '$' || (chr >= '&' && chr <= ',') || (chr >= '/' && chr <= ';') || chr == '=' || chr == '?' || chr == '@' || chr == '[' || chr == ']')
|
||||
result += std::string("%") + hex_chars[chr >> 4] + hex_chars[chr & 15];
|
||||
else
|
||||
result += chr;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Returns percent-decoded string
|
||||
static std::string decode(const std::string &value) {
|
||||
std::string result;
|
||||
result.reserve(value.size() / 3 + (value.size() % 3)); // minimum size of result
|
||||
|
||||
for(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;
|
||||
for(auto &chr : value) {
|
||||
if(chr == ' ')
|
||||
result += '+';
|
||||
else if(chr == '!' || chr == '#' || chr == '$' || (chr >= '&' && chr <= ',') || (chr >= '/' && chr <= ';') || chr == '=' || chr == '?' || chr == '@' || chr == '[' || chr == ']')
|
||||
result += std::string("%") + hex_chars[chr >> 4] + hex_chars[chr & 15];
|
||||
else
|
||||
result += chr;
|
||||
}
|
||||
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) {
|
||||
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) {
|
||||
CaseInsensitiveMultimap result;
|
||||
|
||||
if(query_string.empty())
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t name_pos = 0;
|
||||
size_t name_end_pos = -1;
|
||||
size_t value_pos = -1;
|
||||
for(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);
|
||||
/// Returns percent-decoded string
|
||||
static std::string decode(const std::string &value) {
|
||||
std::string result;
|
||||
result.reserve(value.size() / 3 + (value.size() % 3)); // minimum size of result
|
||||
|
||||
for(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) {
|
||||
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) {
|
||||
CaseInsensitiveMultimap result;
|
||||
|
||||
if(query_string.empty())
|
||||
return result;
|
||||
|
||||
size_t name_pos = 0;
|
||||
size_t name_end_pos = -1;
|
||||
size_t value_pos = -1;
|
||||
for(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 = -1;
|
||||
value_pos = -1;
|
||||
}
|
||||
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 == std::string::npos ? std::string() : query_string.substr(value_pos, c - value_pos);
|
||||
auto value = value_pos >= query_string.size() ? std::string() : query_string.substr(value_pos);
|
||||
result.emplace(std::move(name), Percent::decode(value));
|
||||
}
|
||||
name_pos = c + 1;
|
||||
name_end_pos = -1;
|
||||
value_pos = -1;
|
||||
}
|
||||
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;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
/// Returns query keys with percent-decoded values.
|
||||
DEPRECATED inline CaseInsensitiveMultimap parse_query_string(const std::string &query_string) {
|
||||
return QueryString::parse(query_string);
|
||||
}
|
||||
};
|
||||
|
||||
/// Returns query keys with percent-decoded values.
|
||||
DEPRECATED inline CaseInsensitiveMultimap parse_query_string(const std::string &query_string) {
|
||||
return QueryString::parse(query_string);
|
||||
}
|
||||
} // namespace SimpleWeb
|
||||
|
||||
#endif // SIMPLE_WEB_SERVER_UTILITY_HPP
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue