/*
*
* Author, Copyright: Oleg Borodin <onborodin@gmail.com>
*
*/
#include <sys/types.h>
#include <sys/event.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <functional>
#include <condition_variable>
#include <queue>
#include <thread>
#include <mutex>
#include <chrono>
#include <memory>
#include <stdexcept>
#include <iomanip>
#include <regex>
#include "server.hpp"
namespace utils {
std::vector<std::string> split(const std::string& source, const std::string& delimiter) {
std::vector<std::string> substringv;
std::size_t begin = 0;
std::size_t position = std::string::npos;
while ((position = source.find(delimiter, begin)) != std::string::npos) {
std::string substring = source.substr(begin, position - begin);
begin = position + delimiter.size();
substringv.push_back(substring);
}
std::string substring = source.substr(begin);
substringv.push_back(substring);
return substringv;
}
std::string timestamp() {
std::stringstream ss;
std::time_t now = std::time(0);
ss << std::put_time(std::gmtime(&now), "%a, %d %b %Y %T %Z");
return ss.str();
}
}
namespace srv5 {
server::server(int port, int backlog, int threads) : backlog(backlog), threads(threads) {
::SSL_load_error_strings();
::OpenSSL_add_ssl_algorithms();
const SSL_METHOD *method = SSLv23_server_method();
context = ::SSL_CTX_new(method);
if (!context) {
throw std::runtime_error("unable to create SSL context");
}
::SSL_CTX_set_ecdh_auto(context, 1);
if (::SSL_CTX_use_certificate_file(context, "server.crt", SSL_FILETYPE_PEM) <= 0) {
throw std::runtime_error("unable to read certificate file");
}
if (::SSL_CTX_use_PrivateKey_file(context, "server.key", SSL_FILETYPE_PEM) <= 0 ) {
throw std::runtime_error("unable to read key file");
}
if((socket = ::socket(PF_INET, SOCK_STREAM, 0)) < 0) {
throw std::runtime_error("cannot socket()");
}
int optval = 1;
if (::setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) {
throw std::runtime_error("cannot set socket opttion");
}
auto paddr = reinterpret_cast<struct sockaddr_in*>(&addr);
paddr->sin_family = AF_INET;
paddr->sin_addr.s_addr = INADDR_ANY;
paddr->sin_port = htons(port);
paddr->sin_len = sizeof(struct sockaddr_in);
if (::bind(socket, reinterpret_cast<struct sockaddr*>(paddr), paddr->sin_len) < 0) {
throw std::runtime_error("cannot bind socket");
}
}
void server::run() {
if (::listen(socket, backlog) < 0) {
throw std::runtime_error("cannot listen socket");
}
int newsocket;
while ((newsocket = ::accept(socket, NULL, 0)) > 0) {
push(std::move(newsocket));
}
}
void server::push(int socket) {
std::unique_lock<std::mutex> lock(mutex);
queue.push(socket);
cv.notify_all();
}
void server::team() {
auto worker = [&]{
while(true) {
std::unique_lock<std::mutex> lock(mutex);
cv.wait(lock, [&]{ return !queue.empty(); });
while(!queue.empty()) {
auto socket = queue.front();
queue.pop();
cv.notify_one();
SSL *ssl = ::SSL_new(context);
::SSL_set_fd(ssl, socket);
if (::SSL_accept(ssl) > 0) {
char buffer[512];
std::string in;
while (in.find("\r\n\r\n", 0) == std::string::npos) {
int size = ::SSL_read(ssl, buffer, 512);
in.append(buffer, size);
}
auto headerv = utils::split(in, "\r\n");
auto line = headerv.front();
std::smatch match;
std::regex_search(line, match, std::regex("^(GET)[ ]+(.*)[ ]+(HTTP/1.[01])$"));
if (match.size() == 4) {
auto resourse = utils::split(match[2], "?").at(0);
if (resourse == "/") resourse = "/index.html";
std::string body;
std::size_t size = 0;
auto path = "./public/" + resourse;
std::ifstream is(path, std::ios::binary | std::ios::in);
if (is) {
char buffer[1024];
while (is.read(buffer, sizeof(buffer)).gcount() > 0) {
body.append(buffer, is.gcount());
size += is.gcount();
}
}
std::string mime = "application/octet-stream";
auto ext = path.substr(path.find_last_of(".") + 1);
if (ext == path) ext = "";
if (ext == "html") { mime = "text/html"; }
else if (ext == "css") { mime = "text/css"; }
else if (ext == "js") { mime = "application/javascript"; }
else if (ext == "png") { mime = "image/png"; }
else if (ext == "jpg") { mime = "image/jpeg"; }
else if (ext == "ico") { mime = "image/x-icon"; };
std::stringstream head;
if (size > 0) {
head << "HTTP/1.1 200 OK\r\n";
head << "Content-Length: " << size << "\r\n";
head << "Content-Type: " << mime << "\r\n";
head << "Connection: close\r\n";
} else {
head << "HTTP/1.1 404 Not found\r\n";
head << "Content-Type: text/plain\r\n";
head << "Connection: close\r\n";
}
head << "Server: Srv5/0.1\r\n";
head << "Date: " << utils::timestamp() << "\r\n";
head << "\r\n";
std::string res;
res.append(head.str());
res.append(body);
::SSL_write(ssl, res.c_str(), res.length());
}
}
::SSL_free(ssl);
::shutdown(socket, SHUT_RDWR);
::close(socket);
}
}
};
for (std::size_t i = 0; i < threads; ++i) {
auto t = new std::thread(std::move(worker));
std::shared_ptr<std::thread> thread(std::move(t));
pool.push_back(thread);
}
for (std::size_t i = 0; i < pool.size(); ++i) {
pool[i]->detach();
}
}
server::~server() {
::close(socket);
}
void server::stop() {
std::cerr << "# stop server and exit" << std::endl;
::shutdown(socket, SHUT_RDWR);
::close(socket);
::exit(0);
}
} // namespace srv5