mirror of https://github.com/JAJames/Jupiter.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
880 lines
27 KiB
880 lines
27 KiB
/**
|
|
* Copyright (C) 2015-2016 Jessica James.
|
|
*
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
|
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*
|
|
* Written by Jessica James <jessica.aj@outlook.com>
|
|
*/
|
|
|
|
#include <ctime>
|
|
#include <chrono>
|
|
#include "String.h"
|
|
#include "CString.h"
|
|
#include "Reference_String.h"
|
|
#include "TCPSocket.h"
|
|
#include "ArrayList.h"
|
|
#include "HTTP.h"
|
|
#include "HTTP_Server.h"
|
|
|
|
using namespace Jupiter::literals;
|
|
|
|
static const Jupiter::ReferenceString HTTP_REQUEST_ENDING = "\r\n\r\n"_jrs;
|
|
|
|
// HTTPCommand
|
|
|
|
enum HTTPCommand
|
|
{
|
|
GET,
|
|
HEAD,
|
|
UNKNOWN,
|
|
NONE_SPECIFIED
|
|
};
|
|
|
|
// HTTPVersion
|
|
|
|
enum HTTPVersion
|
|
{
|
|
HTTP_1_0,
|
|
HTTP_1_1,
|
|
HTTP_Unsupported
|
|
};
|
|
|
|
// HTTP::Server::Content
|
|
|
|
Jupiter::HTTP::Server::Content::Content(const Jupiter::ReadableString &in_name, Jupiter::HTTP::Server::HTTPFunction in_function) : name(in_name)
|
|
{
|
|
Jupiter::HTTP::Server::Content::function = in_function;
|
|
Jupiter::HTTP::Server::Content::name_checksum = Jupiter::HTTP::Server::Content::name.calcChecksum(); // switch to calcChecksumi to make case-insensitive
|
|
}
|
|
|
|
Jupiter::ReadableString *Jupiter::HTTP::Server::Content::execute(const Jupiter::ReadableString &query_string)
|
|
{
|
|
return Jupiter::HTTP::Server::Content::function(query_string);
|
|
}
|
|
|
|
// HTTP::Server::Directory
|
|
|
|
Jupiter::HTTP::Server::Directory::Directory(const Jupiter::ReadableString &in_name) : name(in_name)
|
|
{
|
|
name_checksum = Jupiter::HTTP::Server::Directory::name.calcChecksum();
|
|
}
|
|
|
|
Jupiter::HTTP::Server::Directory::~Directory()
|
|
{
|
|
Jupiter::HTTP::Server::Directory::directories.emptyAndDelete();
|
|
Jupiter::HTTP::Server::Directory::content.emptyAndDelete();
|
|
}
|
|
|
|
// host/dir/content
|
|
// .hook("dir/subdir/", content)
|
|
|
|
void Jupiter::HTTP::Server::Directory::hook(const Jupiter::ReadableString &in_name, Content *in_content)
|
|
{
|
|
Jupiter::ReferenceString in_name_ref = in_name;
|
|
in_name_ref.shiftRight(in_name_ref.span('/'));
|
|
|
|
if (in_name_ref.isEmpty()) // Hook content
|
|
Jupiter::HTTP::Server::Directory::content.add(in_content);
|
|
else
|
|
{
|
|
size_t index = in_name_ref.find('/');
|
|
Jupiter::ReferenceString dir_name;
|
|
if (index == Jupiter::INVALID_INDEX)
|
|
dir_name = in_name_ref;
|
|
else
|
|
dir_name = in_name_ref.substring(size_t{ 0 }, index);
|
|
|
|
in_name_ref.shiftRight(dir_name.size());
|
|
Jupiter::HTTP::Server::Directory *directory;
|
|
unsigned int dir_name_checksum = dir_name.calcChecksum();
|
|
index = Jupiter::HTTP::Server::Directory::directories.size();
|
|
while (index != 0)
|
|
{
|
|
directory = Jupiter::HTTP::Server::Directory::directories.get(--index);
|
|
if (directory->name_checksum == dir_name_checksum && directory->name.equals(dir_name))
|
|
{
|
|
directory->hook(in_name_ref, in_content);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// create directories
|
|
directory = new Jupiter::HTTP::Server::Directory(dir_name);
|
|
Jupiter::HTTP::Server::Directory::directories.add(directory);
|
|
|
|
directory_add_loop:
|
|
in_name_ref.shiftRight(in_name_ref.span('/'));
|
|
if (in_name_ref.isNotEmpty())
|
|
{
|
|
// add directory
|
|
index = in_name_ref.find('/');
|
|
if (index != Jupiter::INVALID_INDEX)
|
|
{
|
|
directory->directories.add(new Jupiter::HTTP::Server::Directory(in_name_ref.substring(size_t{ 0 }, index)));
|
|
directory = directory->directories.get(directories.size() - 1);
|
|
in_name_ref.shiftRight(index + 1);
|
|
goto directory_add_loop;
|
|
}
|
|
directory->directories.add(new Jupiter::HTTP::Server::Directory(in_name_ref));
|
|
directory = directory->directories.get(directories.size() - 1);
|
|
}
|
|
|
|
// add content
|
|
directory->content.add(in_content);
|
|
}
|
|
}
|
|
|
|
bool Jupiter::HTTP::Server::Directory::remove(const Jupiter::ReadableString &path, const Jupiter::ReadableString &content_name)
|
|
{
|
|
size_t index;
|
|
Jupiter::ReferenceString in_name_ref = path;
|
|
in_name_ref.shiftRight(in_name_ref.span('/'));
|
|
unsigned int checksum;
|
|
|
|
if (in_name_ref.isEmpty()) // Remove content
|
|
{
|
|
Jupiter::HTTP::Server::Content *content_node;
|
|
checksum = content_name.calcChecksum();
|
|
index = Jupiter::HTTP::Server::Directory::content.size();
|
|
while (index != 0)
|
|
{
|
|
content_node = Jupiter::HTTP::Server::Directory::content.get(--index);
|
|
if (content_node->name_checksum == checksum && content_node->name.equals(content_name))
|
|
{
|
|
delete Jupiter::HTTP::Server::Directory::content.remove(index);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
else // Call remove() on next directory in path
|
|
{
|
|
index = in_name_ref.find('/');
|
|
Jupiter::ReferenceString dir_name;
|
|
if (index == Jupiter::INVALID_INDEX)
|
|
dir_name = in_name_ref;
|
|
else
|
|
dir_name = in_name_ref.substring(size_t{ 0 }, index);
|
|
|
|
in_name_ref.shiftRight(dir_name.size());
|
|
Jupiter::HTTP::Server::Directory *directory;
|
|
checksum = dir_name.calcChecksum();
|
|
index = Jupiter::HTTP::Server::Directory::directories.size();
|
|
while (index != 0)
|
|
{
|
|
directory = Jupiter::HTTP::Server::Directory::directories.get(--index);
|
|
if (directory->name_checksum == checksum && directory->name.equals(dir_name))
|
|
return directory->remove(in_name_ref, content_name);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Jupiter::HTTP::Server::Directory::has(const Jupiter::ReadableString &in_name)
|
|
{
|
|
return this->find(in_name) != nullptr;
|
|
}
|
|
|
|
Jupiter::HTTP::Server::Content *Jupiter::HTTP::Server::Directory::find(const Jupiter::ReadableString &in_name)
|
|
{
|
|
Jupiter::ReferenceString in_name_ref = in_name;
|
|
in_name_ref.shiftRight(in_name_ref.span("/"_jrs));
|
|
|
|
size_t index = in_name_ref.find('/');
|
|
if (index == Jupiter::INVALID_INDEX) // Search content
|
|
{
|
|
unsigned int content_name_checksum = in_name_ref.calcChecksum();
|
|
index = Jupiter::HTTP::Server::Directory::content.size();
|
|
Jupiter::HTTP::Server::Content *content_itr;
|
|
while (index != 0)
|
|
{
|
|
content_itr = Jupiter::HTTP::Server::Directory::content.get(--index);
|
|
if (content_itr->name_checksum == content_name_checksum && content_itr->name.equals(in_name_ref))
|
|
return content_itr;
|
|
}
|
|
return nullptr; // No such content
|
|
}
|
|
|
|
Jupiter::ReferenceString dir_name(in_name_ref.ptr(), index);
|
|
in_name_ref.shiftRight(dir_name.size() + 1);
|
|
Jupiter::HTTP::Server::Directory *directory;
|
|
unsigned int dir_name_checksum = dir_name.calcChecksum();
|
|
index = Jupiter::HTTP::Server::Directory::directories.size();
|
|
|
|
while (index != 0)
|
|
{
|
|
directory = Jupiter::HTTP::Server::Directory::directories.get(--index);
|
|
if (directory->name_checksum == dir_name_checksum && directory->name.equals(dir_name))
|
|
return directory->find(in_name_ref);
|
|
}
|
|
|
|
return nullptr; // No such directory
|
|
}
|
|
|
|
Jupiter::ReadableString *Jupiter::HTTP::Server::Directory::execute(const Jupiter::ReadableString &in_name, const Jupiter::ReadableString &query_string)
|
|
{
|
|
Jupiter::HTTP::Server::Content *content_ptr = Jupiter::HTTP::Server::Directory::find(in_name);
|
|
if (content_ptr == nullptr)
|
|
return nullptr;
|
|
return content_ptr->execute(query_string);
|
|
}
|
|
|
|
// HTTP::Server::Host
|
|
|
|
Jupiter::HTTP::Server::Host::Host(const Jupiter::ReadableString &in_name) : Directory(in_name)
|
|
{
|
|
name_checksum = Jupiter::HTTP::Server::Host::name.calcChecksumi();
|
|
}
|
|
|
|
// HTTPSession struct
|
|
|
|
struct HTTPSession
|
|
{
|
|
Jupiter::Socket sock;
|
|
Jupiter::String request;
|
|
bool keep_alive = false;
|
|
Jupiter::HTTP::Server::Host *host = nullptr;
|
|
HTTPVersion version = HTTPVersion::HTTP_1_0;
|
|
std::chrono::steady_clock::time_point last_active = std::chrono::steady_clock::now();
|
|
HTTPSession(Jupiter::Socket &&in_sock);
|
|
~HTTPSession();
|
|
};
|
|
|
|
HTTPSession::HTTPSession(Jupiter::Socket &&in_sock) : sock(std::move(in_sock))
|
|
{
|
|
}
|
|
|
|
HTTPSession::~HTTPSession()
|
|
{
|
|
}
|
|
|
|
// Server::Data struct
|
|
|
|
struct Jupiter::HTTP::Server::Data
|
|
{
|
|
/** Data */
|
|
Jupiter::ArrayList<Jupiter::HTTP::Server::Host> hosts;
|
|
Jupiter::ArrayList<Socket> ports;
|
|
Jupiter::ArrayList<HTTPSession> sessions;
|
|
std::chrono::milliseconds session_timeout = std::chrono::milliseconds(2000); // TODO: Config variable
|
|
std::chrono::milliseconds keep_alive_session_timeout = std::chrono::milliseconds(5000); // TODO: Config variable
|
|
size_t max_request_size = 1024; // TODO: Config variable
|
|
bool permit_keept_alive = true; // TODO: Config variable
|
|
|
|
/** Foward functions */
|
|
void hook(const Jupiter::ReadableString &host, const Jupiter::ReadableString &path, Content *in_content);
|
|
bool remove(const Jupiter::ReadableString &hostname);
|
|
//bool remove(const Jupiter::ReadableString &hostname, const Jupiter::ReadableString &path);
|
|
bool remove(const Jupiter::ReadableString &hostname, const Jupiter::ReadableString &path, const Jupiter::ReadableString &name);
|
|
bool has(const Jupiter::ReadableString &hostname);
|
|
bool has(const Jupiter::ReadableString &hostname, const Jupiter::ReadableString &name);
|
|
Jupiter::HTTP::Server::Host *find_host(const Jupiter::ReadableString &name);
|
|
Content *find(const Jupiter::ReadableString &name);
|
|
Content *find(const Jupiter::ReadableString &hostname, const Jupiter::ReadableString &name);
|
|
Jupiter::ReadableString *execute(const Jupiter::ReadableString &name, const Jupiter::ReadableString &query_string);
|
|
Jupiter::ReadableString *execute(const Jupiter::ReadableString &hostname, const Jupiter::ReadableString &name, const Jupiter::ReadableString &query_string);
|
|
|
|
int process_request(HTTPSession &session);
|
|
|
|
/** Constructors */
|
|
Data();
|
|
Data(const Data &source) = delete;
|
|
Data(Data &&source) = delete;
|
|
|
|
/** Destructor */
|
|
~Data();
|
|
};
|
|
|
|
// Data constructor
|
|
|
|
Jupiter::HTTP::Server::Data::Data()
|
|
{
|
|
// hosts[0] is always the "global" namespace.
|
|
Jupiter::HTTP::Server::Data::hosts.add(new Jupiter::HTTP::Server::Host(Jupiter::HTTP::Server::global_namespace));
|
|
}
|
|
|
|
// Data destructor
|
|
|
|
Jupiter::HTTP::Server::Data::~Data()
|
|
{
|
|
Jupiter::HTTP::Server::Data::hosts.emptyAndDelete();
|
|
Jupiter::HTTP::Server::Data::sessions.emptyAndDelete();
|
|
Jupiter::HTTP::Server::Data::ports.emptyAndDelete();
|
|
}
|
|
|
|
// Data functions
|
|
|
|
void Jupiter::HTTP::Server::Data::hook(const Jupiter::ReadableString &hostname, const Jupiter::ReadableString &in_path, Content *in_content)
|
|
{
|
|
Jupiter::ReferenceString path = in_path;
|
|
Jupiter::ReferenceString dir_name;
|
|
Jupiter::HTTP::Server::Host *host = Jupiter::HTTP::Server::Data::find_host(hostname);
|
|
|
|
if (host == nullptr)
|
|
{
|
|
host = new Jupiter::HTTP::Server::Host(hostname);
|
|
Jupiter::HTTP::Server::Data::hosts.add(host);
|
|
// OPTIMIZE: create directory tree and return.
|
|
}
|
|
|
|
path.shiftRight(path.span('/'));
|
|
if (path.isEmpty())
|
|
host->content.add(in_content);
|
|
else
|
|
host->hook(path, in_content);
|
|
}
|
|
|
|
bool Jupiter::HTTP::Server::Data::remove(const Jupiter::ReadableString &hostname)
|
|
{
|
|
unsigned int name_checksum = hostname.calcChecksumi();
|
|
size_t index = Jupiter::HTTP::Server::Data::hosts.size();
|
|
Jupiter::HTTP::Server::Host *host;
|
|
while (index != 0)
|
|
{
|
|
host = Jupiter::HTTP::Server::Data::hosts.get(--index);
|
|
if (name_checksum == host->name_checksum && host->name.equalsi(hostname))
|
|
{
|
|
delete Jupiter::HTTP::Server::Data::hosts.remove(index);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// name: path/to/resource OR path/
|
|
bool Jupiter::HTTP::Server::Data::remove(const Jupiter::ReadableString &hostname, const Jupiter::ReadableString &path, const Jupiter::ReadableString &name)
|
|
{
|
|
Jupiter::HTTP::Server::Host *host = Jupiter::HTTP::Server::Data::find_host(hostname);
|
|
if (host == nullptr)
|
|
return false;
|
|
return host->remove(path, name);
|
|
}
|
|
|
|
bool Jupiter::HTTP::Server::Data::has(const Jupiter::ReadableString &hostname)
|
|
{
|
|
unsigned int name_checksum = hostname.calcChecksumi();
|
|
size_t index = Jupiter::HTTP::Server::Data::hosts.size();
|
|
Jupiter::HTTP::Server::Host *host;
|
|
while (index != 0)
|
|
{
|
|
host = Jupiter::HTTP::Server::Data::hosts.get(--index);
|
|
if (name_checksum == host->name_checksum && host->name.equalsi(hostname))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Jupiter::HTTP::Server::Data::has(const Jupiter::ReadableString &hostname, const Jupiter::ReadableString &name)
|
|
{
|
|
unsigned int name_checksum = hostname.calcChecksumi();
|
|
size_t index = Jupiter::HTTP::Server::Data::hosts.size();
|
|
Jupiter::HTTP::Server::Host *host;
|
|
while (index != 0)
|
|
{
|
|
host = Jupiter::HTTP::Server::Data::hosts.get(--index);
|
|
if (name_checksum == host->name_checksum && host->name.equalsi(hostname))
|
|
{
|
|
name_checksum = name.calcChecksum(); // switch to equalsi to make case-insensitive
|
|
index = host->content.size();
|
|
Jupiter::HTTP::Server::Content *content;
|
|
while (index != 0)
|
|
{
|
|
content = host->content.get(--index);
|
|
if (name_checksum == content->name_checksum && content->name.equals(name)) // switch to equalsi to make case-insensitive
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Jupiter::HTTP::Server::Host *Jupiter::HTTP::Server::Data::find_host(const Jupiter::ReadableString &name)
|
|
{
|
|
unsigned int name_checksum = name.calcChecksumi();
|
|
size_t index = Jupiter::HTTP::Server::Data::hosts.size();
|
|
Jupiter::HTTP::Server::Host *host;
|
|
while (index != 0)
|
|
{
|
|
host = Jupiter::HTTP::Server::Data::hosts.get(--index);
|
|
if (name_checksum == host->name_checksum && host->name.equalsi(name))
|
|
return host;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
Jupiter::HTTP::Server::Content *Jupiter::HTTP::Server::Data::find(const Jupiter::ReadableString &name)
|
|
{
|
|
return Jupiter::HTTP::Server::Data::hosts.get(0)->find(name);
|
|
}
|
|
|
|
Jupiter::HTTP::Server::Content *Jupiter::HTTP::Server::Data::find(const Jupiter::ReadableString &hostname, const Jupiter::ReadableString &name)
|
|
{
|
|
Jupiter::HTTP::Server::Host *host = Jupiter::HTTP::Server::Data::find_host(hostname);
|
|
if (host == nullptr)
|
|
return nullptr;
|
|
|
|
return host->find(name);
|
|
}
|
|
|
|
Jupiter::ReadableString *Jupiter::HTTP::Server::Data::execute(const Jupiter::ReadableString &name, const Jupiter::ReadableString &query_string)
|
|
{
|
|
Jupiter::HTTP::Server::Content *content = Jupiter::HTTP::Server::Data::find(name);
|
|
if (content == nullptr)
|
|
return nullptr;
|
|
return content->execute(query_string);
|
|
}
|
|
|
|
Jupiter::ReadableString *Jupiter::HTTP::Server::Data::execute(const Jupiter::ReadableString &hostname, const Jupiter::ReadableString &name, const Jupiter::ReadableString &query_string)
|
|
{
|
|
Jupiter::HTTP::Server::Content *content = Jupiter::HTTP::Server::Data::find(hostname, name);
|
|
if (content == nullptr)
|
|
return nullptr;
|
|
return content->execute(query_string);
|
|
}
|
|
|
|
char *html_time()
|
|
{
|
|
time_t rawtime = time(0);
|
|
char *rtime = new char[64];
|
|
strftime(rtime, 64, "%a, %d %b %Y %H:%M:%S GMT", gmtime(&rawtime));
|
|
return rtime;
|
|
}
|
|
|
|
int Jupiter::HTTP::Server::Data::process_request(HTTPSession &session)
|
|
{
|
|
Jupiter::ReadableString::TokenizeResult<Jupiter::Reference_String> lines = Jupiter::ReferenceString::tokenize(session.request, STRING_LITERAL_AS_REFERENCE(ENDL));
|
|
HTTPCommand command = HTTPCommand::NONE_SPECIFIED;
|
|
Content *content = nullptr;
|
|
Jupiter::ReferenceString query_string;
|
|
Jupiter::ReferenceString first_token;
|
|
size_t index = 0;
|
|
size_t span;
|
|
|
|
auto get_line_offset = [&session, &lines](size_t index)
|
|
{
|
|
size_t offset = 0;
|
|
while (index != 0)
|
|
offset += lines.tokens[--index].size() + 2;
|
|
return offset;
|
|
};
|
|
|
|
while (index != lines.token_count)
|
|
{
|
|
Jupiter::ReferenceString &line = lines.tokens[index++];
|
|
|
|
// trim front-end spaces.
|
|
span = line.span(" "_jrs);
|
|
if (span != 0)
|
|
line.shiftRight(span);
|
|
|
|
if (line.isEmpty()) // end of http request
|
|
{
|
|
Jupiter::String result(256);
|
|
switch (command)
|
|
{
|
|
case HTTPCommand::GET:
|
|
case HTTPCommand::HEAD:
|
|
if (content != nullptr)
|
|
{
|
|
// 200 (success)
|
|
Jupiter::ReadableString *content_result = content->execute(query_string);
|
|
|
|
switch (session.version)
|
|
{
|
|
default:
|
|
case HTTPVersion::HTTP_1_0:
|
|
result = "HTTP/1.0 200 OK"_jrs ENDL;
|
|
break;
|
|
case HTTPVersion::HTTP_1_1:
|
|
result = "HTTP/1.1 200 OK"_jrs ENDL;
|
|
break;
|
|
}
|
|
|
|
result += "Date: "_jrs;
|
|
char *time_header = html_time();
|
|
result += time_header;
|
|
delete[] time_header;
|
|
result += ENDL;
|
|
|
|
result += "Server: "_jrs JUPITER_VERSION ENDL;
|
|
|
|
result += Jupiter::StringS::Format("Content-Length: %u" ENDL, content_result->size());
|
|
|
|
if (session.keep_alive)
|
|
result += "Connection: keep-alive"_jrs ENDL;
|
|
else
|
|
result += "Connection: close"_jrs ENDL;
|
|
|
|
result += "Content-Type: "_jrs;
|
|
if (content->type == nullptr)
|
|
result += Jupiter::HTTP::Content::Type::Text::PLAIN;
|
|
else
|
|
result += *content->type;
|
|
if (content->charset != nullptr)
|
|
{
|
|
result += "; charset="_jrs;
|
|
result += *content->charset;
|
|
}
|
|
result += ENDL;
|
|
|
|
if (content->language != nullptr)
|
|
{
|
|
result += "Content-Language: "_jrs;
|
|
result += *content->language;
|
|
result += ENDL;
|
|
}
|
|
|
|
result += ENDL;
|
|
if (command == HTTPCommand::GET)
|
|
result += *content_result;
|
|
|
|
if (content->free_result)
|
|
delete content_result;
|
|
|
|
session.sock.send(result);
|
|
}
|
|
else
|
|
{
|
|
// 404 (not found)
|
|
|
|
switch (session.version)
|
|
{
|
|
default:
|
|
case HTTPVersion::HTTP_1_0:
|
|
result = "HTTP/1.0 404 Not Found"_jrs ENDL;
|
|
break;
|
|
case HTTPVersion::HTTP_1_1:
|
|
result = "HTTP/1.1 404 Not Found"_jrs ENDL;
|
|
break;
|
|
}
|
|
|
|
char *time_header = html_time();
|
|
result += "Date: "_jrs ENDL;
|
|
result += time_header;
|
|
delete[] time_header;
|
|
|
|
result += "Server: "_jrs JUPITER_VERSION ENDL;
|
|
|
|
result += "Content-Length: 0"_jrs ENDL;
|
|
|
|
if (session.keep_alive)
|
|
result += "Connection: keep-alive"_jrs ENDL;
|
|
else
|
|
result += "Connection: close"_jrs ENDL;
|
|
|
|
result += ENDL ENDL;
|
|
session.sock.send(result);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (session.keep_alive == false) // not keep-alive -- will be destroyed on return
|
|
break;
|
|
if (index == lines.token_count) // end of packet
|
|
session.request.erase();
|
|
else // end of request -- another request is following
|
|
session.request.shiftRight(get_line_offset(index));
|
|
|
|
if (session.request.find(HTTP_REQUEST_ENDING) != Jupiter::INVALID_INDEX) // there's another full request already received
|
|
return Jupiter::HTTP::Server::Data::process_request(session);
|
|
break;
|
|
}
|
|
|
|
// Not empty
|
|
first_token = line.getToken(0, ' ');
|
|
|
|
if (first_token.get(first_token.size() - 1) == ':') // header field
|
|
{
|
|
first_token.truncate(1); // trim trailing ':'
|
|
if (first_token.equalsi("HOST"_jrs))
|
|
session.host = Jupiter::HTTP::Server::Data::find_host(line.getWord(1, " "));
|
|
else if (first_token.equalsi("CONNECTION"_jrs))
|
|
{
|
|
Jupiter::ReferenceString connection_type = line.getWord(1, " ");
|
|
if (connection_type.equalsi("keep-alive"_jrs))
|
|
session.keep_alive = Jupiter::HTTP::Server::Data::permit_keept_alive;
|
|
}
|
|
}
|
|
else // command
|
|
{
|
|
if (first_token.equals("GET"_jrs))
|
|
{
|
|
command = HTTPCommand::GET;
|
|
|
|
query_string = line.getWord(1, " ");
|
|
span = query_string.find('?'); // repurposing 'span'
|
|
if (span == Jupiter::INVALID_INDEX)
|
|
{
|
|
if (session.host == nullptr)
|
|
content = Jupiter::HTTP::Server::Data::find(query_string);
|
|
else
|
|
content = session.host->find(query_string);
|
|
query_string.erase();
|
|
}
|
|
else
|
|
{
|
|
if (session.host == nullptr)
|
|
content = Jupiter::HTTP::Server::Data::find(query_string.substring(size_t{ 0 }, span));
|
|
else
|
|
content = session.host->find(query_string.substring(size_t{ 0 }, span));
|
|
query_string.shiftRight(span + 1);
|
|
// decode query_string here
|
|
}
|
|
|
|
Jupiter::ReferenceString protocol_str = line.getWord(2, " ");
|
|
if (protocol_str.equalsi("http/1.0"_jrs))
|
|
session.version = HTTPVersion::HTTP_1_0;
|
|
else if (protocol_str.equalsi("http/1.1"_jrs))
|
|
{
|
|
session.version = HTTPVersion::HTTP_1_1;
|
|
session.keep_alive = Jupiter::HTTP::Server::Data::permit_keept_alive;
|
|
}
|
|
}
|
|
else if (first_token.equals("HEAD"_jrs))
|
|
{
|
|
command = HTTPCommand::HEAD;
|
|
|
|
query_string = line.getWord(1, " ");
|
|
span = query_string.find('?'); // repurposing 'span'
|
|
if (span == Jupiter::INVALID_INDEX)
|
|
{
|
|
if (session.host == nullptr)
|
|
content = Jupiter::HTTP::Server::Data::find(query_string);
|
|
else
|
|
content = session.host->find(query_string);
|
|
query_string.erase();
|
|
}
|
|
else
|
|
{
|
|
if (session.host == nullptr)
|
|
content = Jupiter::HTTP::Server::Data::find(query_string.substring(size_t{ 0 }, span));
|
|
else
|
|
content = session.host->find(query_string.substring(size_t{ 0 }, span));
|
|
query_string.shiftRight(span + 1);
|
|
// decode query_string here
|
|
}
|
|
|
|
Jupiter::ReferenceString protocol_str = line.getWord(2, " ");
|
|
if (protocol_str.equalsi("http/1.0"_jrs))
|
|
session.version = HTTPVersion::HTTP_1_0;
|
|
else if (protocol_str.equalsi("http/1.1"_jrs))
|
|
{
|
|
session.version = HTTPVersion::HTTP_1_1;
|
|
session.keep_alive = Jupiter::HTTP::Server::Data::permit_keept_alive;
|
|
}
|
|
}
|
|
else
|
|
command = HTTPCommand::UNKNOWN;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/** HTTP::Server */
|
|
|
|
// Server constructors
|
|
|
|
Jupiter::HTTP::Server::Server()
|
|
{
|
|
Jupiter::HTTP::Server::data_ = new Jupiter::HTTP::Server::Data();
|
|
}
|
|
|
|
Jupiter::HTTP::Server::Server(Jupiter::HTTP::Server &&source)
|
|
{
|
|
Jupiter::HTTP::Server::data_ = source.data_;
|
|
source.data_ = new Jupiter::HTTP::Server::Data();
|
|
}
|
|
|
|
Jupiter::HTTP::Server::~Server()
|
|
{
|
|
delete Jupiter::HTTP::Server::data_;
|
|
}
|
|
|
|
// Server functions
|
|
|
|
void Jupiter::HTTP::Server::hook(const Jupiter::ReadableString &host, const Jupiter::ReadableString &name, Content *content)
|
|
{
|
|
return Jupiter::HTTP::Server::data_->hook(host, name, content);
|
|
}
|
|
|
|
bool Jupiter::HTTP::Server::remove(const Jupiter::ReadableString &host)
|
|
{
|
|
return Jupiter::HTTP::Server::data_->remove(host);
|
|
}
|
|
|
|
/*bool Jupiter::HTTP::Server::remove(const Jupiter::ReadableString &host, const Jupiter::ReadableString &path)
|
|
{
|
|
return Jupiter::HTTP::Server::data_->remove(host, path);
|
|
}*/
|
|
|
|
bool Jupiter::HTTP::Server::remove(const Jupiter::ReadableString &host, const Jupiter::ReadableString &path, const Jupiter::ReadableString &name)
|
|
{
|
|
return Jupiter::HTTP::Server::data_->remove(host, path, name);
|
|
}
|
|
|
|
bool Jupiter::HTTP::Server::has(const Jupiter::ReadableString &host)
|
|
{
|
|
return Jupiter::HTTP::Server::data_->has(host);
|
|
}
|
|
|
|
bool Jupiter::HTTP::Server::has(const Jupiter::ReadableString &host, const Jupiter::ReadableString &name)
|
|
{
|
|
return Jupiter::HTTP::Server::data_->has(host, name);
|
|
}
|
|
|
|
Jupiter::HTTP::Server::Content *Jupiter::HTTP::Server::find(const Jupiter::ReadableString &name)
|
|
{
|
|
return Jupiter::HTTP::Server::data_->find(name);
|
|
}
|
|
|
|
Jupiter::HTTP::Server::Content *Jupiter::HTTP::Server::find(const Jupiter::ReadableString &host, const Jupiter::ReadableString &name)
|
|
{
|
|
return Jupiter::HTTP::Server::data_->find(host, name);
|
|
}
|
|
|
|
Jupiter::ReadableString *Jupiter::HTTP::Server::execute(const Jupiter::ReadableString &name, const Jupiter::ReadableString &query_string)
|
|
{
|
|
return Jupiter::HTTP::Server::data_->execute(name, query_string);
|
|
}
|
|
|
|
Jupiter::ReadableString *Jupiter::HTTP::Server::execute(const Jupiter::ReadableString &host, const Jupiter::ReadableString &name, const Jupiter::ReadableString &query_string)
|
|
{
|
|
return Jupiter::HTTP::Server::data_->execute(host, name, query_string);
|
|
}
|
|
|
|
bool Jupiter::HTTP::Server::bind(const Jupiter::ReadableString &hostname, uint16_t port)
|
|
{
|
|
Jupiter::TCPSocket *socket = new Jupiter::TCPSocket();
|
|
if (socket->bind(Jupiter::CStringS(hostname).c_str(), port, true))
|
|
{
|
|
socket->setBlocking(false);
|
|
Jupiter::HTTP::Server::data_->ports.add(socket);
|
|
return true;
|
|
}
|
|
delete socket;
|
|
return false;
|
|
}
|
|
|
|
bool Jupiter::HTTP::Server::tls_bind(const Jupiter::ReadableString &hostname, uint16_t port)
|
|
{
|
|
Jupiter::SecureTCPSocket *socket = new Jupiter::SecureTCPSocket();
|
|
if (socket->bind(Jupiter::CStringS(hostname).c_str(), port, true))
|
|
{
|
|
Jupiter::HTTP::Server::data_->ports.add(socket);
|
|
return true;
|
|
}
|
|
delete socket;
|
|
return false;
|
|
}
|
|
|
|
int Jupiter::HTTP::Server::think()
|
|
{
|
|
Jupiter::Socket *socket;
|
|
HTTPSession *session;
|
|
|
|
// Process existing clients
|
|
size_t index = Jupiter::HTTP::Server::data_->sessions.size();
|
|
while (index != 0)
|
|
{
|
|
session = Jupiter::HTTP::Server::data_->sessions.get(--index);
|
|
std::chrono::steady_clock::now();
|
|
if (session->sock.isShutdown())
|
|
{
|
|
if (session->sock.recv() == 0)
|
|
delete Jupiter::HTTP::Server::data_->sessions.remove(index);
|
|
}
|
|
else if ((std::chrono::steady_clock::now() > session->last_active + Jupiter::HTTP::Server::data_->keep_alive_session_timeout)
|
|
|| (session->keep_alive == false && std::chrono::steady_clock::now() > session->last_active + Jupiter::HTTP::Server::data_->session_timeout))
|
|
delete Jupiter::HTTP::Server::data_->sessions.remove(index);
|
|
//session->sock.shutdown();
|
|
else if (session->sock.recv() > 0)
|
|
{
|
|
const Jupiter::ReadableString &sock_buffer = session->sock.getBuffer();
|
|
if (session->request.size() + sock_buffer.size() <= Jupiter::HTTP::Server::data_->max_request_size) // accept
|
|
{
|
|
session->request += sock_buffer;
|
|
if (session->request.find(HTTP_REQUEST_ENDING) != Jupiter::INVALID_INDEX) // completed request
|
|
{
|
|
session->last_active = std::chrono::steady_clock::now();
|
|
Jupiter::HTTP::Server::data_->process_request(*session);
|
|
if (session->keep_alive == false) // remove completed session
|
|
delete Jupiter::HTTP::Server::data_->sessions.remove(index);
|
|
//session->sock.shutdown();
|
|
}
|
|
else if (session->request.size() == Jupiter::HTTP::Server::data_->max_request_size) // reject (full buffer)
|
|
delete Jupiter::HTTP::Server::data_->sessions.remove(index);
|
|
}
|
|
else // reject
|
|
delete Jupiter::HTTP::Server::data_->sessions.remove(index);
|
|
}
|
|
else if (session->sock.getLastError() != 10035)
|
|
delete Jupiter::HTTP::Server::data_->sessions.remove(index);
|
|
}
|
|
|
|
// Process incoming clients
|
|
index = Jupiter::HTTP::Server::data_->ports.size();
|
|
while (index != 0)
|
|
{
|
|
socket = Jupiter::HTTP::Server::data_->ports.get(--index);
|
|
socket = socket->accept();
|
|
if (socket != nullptr)
|
|
{
|
|
socket->setBlocking(false);
|
|
session = new HTTPSession(std::move(*socket));
|
|
if (session->sock.recv() > 0) // data received
|
|
{
|
|
const Jupiter::ReadableString &sock_buffer = session->sock.getBuffer();
|
|
if (sock_buffer.size() < Jupiter::HTTP::Server::data_->max_request_size) // accept
|
|
{
|
|
session->request = session->sock.getBuffer();
|
|
if (sock_buffer.find(HTTP_REQUEST_ENDING) != Jupiter::INVALID_INDEX) // completed request
|
|
{
|
|
Jupiter::HTTP::Server::data_->process_request(*session);
|
|
if (session->keep_alive) // session will live for 30 seconds.
|
|
Jupiter::HTTP::Server::data_->sessions.add(session);
|
|
else // session completed
|
|
delete session;
|
|
}
|
|
else // store for more processing
|
|
Jupiter::HTTP::Server::data_->sessions.add(session);
|
|
}
|
|
else if (sock_buffer.size() == Jupiter::HTTP::Server::data_->max_request_size)
|
|
{
|
|
if (sock_buffer.find(HTTP_REQUEST_ENDING) == Jupiter::INVALID_INDEX) // reject (too large)
|
|
delete session;
|
|
else // accept (max size, completed request)
|
|
{
|
|
session->request = session->sock.getBuffer();
|
|
Jupiter::HTTP::Server::data_->process_request(*session);
|
|
if (session->keep_alive) // session will live for 30 seconds.
|
|
Jupiter::HTTP::Server::data_->sessions.add(session);
|
|
else // session completed
|
|
delete session;
|
|
}
|
|
}
|
|
else // reject (too large)
|
|
delete session;
|
|
}
|
|
else if (session->sock.getLastError() == 10035) // store for more processing
|
|
Jupiter::HTTP::Server::data_->sessions.add(session);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
const Jupiter::ReadableString &Jupiter::HTTP::Server::global_namespace = Jupiter::ReferenceString::empty;
|
|
const Jupiter::ReadableString &Jupiter::HTTP::Server::server_string = "Jupiter"_jrs;
|