strm) {
if (sent)
return;
if (!strm->CanRead())
throw std::runtime_error("Cannot read from stream");
if (strm->EndOfStream())
throw std::runtime_error("End of stream");
if (strm->CanSeek()) {
int64_t len = strm->GetLength();
std::string range = {};
if (this->requestHeaders.TryGetFirst("Range", range)) {
auto res = HttpUtils::SplitString(range, "=", 2);
if (res.size() == 2) {
if (res[0] != "bytes") {
this->statusCode = BadRequest;
this->WriteHeaders();
return;
}
res = HttpUtils::SplitString(res[1], ", ", 2);
if (res.size() != 1) {
this->statusCode = BadRequest;
this->WriteHeaders();
return;
}
auto dash = HttpUtils::SplitString(res[0], "-", 2);
int64_t begin = 0;
int64_t end = -1;
if (dash.size() == 1 &&
res[0].find_first_of('-') != std::string::npos) {
// NUMBER-
begin = std::stoll(dash[0]);
} else if (dash.size() == 2) {
// NUMBER-NUMBER
// or
//-NUMBER
if (dash[0].size() > 0) {
// NUMBER-NUMBER
begin = std::stoll(dash[0]);
end = std::stoll(dash[1]);
} else {
//-NUMBER
end = std::stoll(dash[1]);
}
} else {
this->statusCode = BadRequest;
this->WriteHeaders();
return;
}
if (end == -1) {
end = len - 1;
}
if (end > len - 1) {
this->statusCode = RangeNotSatisfiable;
this->WithSingleHeader("Content-Range",
"bytes */" + std::to_string(len));
this->WriteHeaders();
return;
}
if (begin >= end) {
this->statusCode = RangeNotSatisfiable;
this->WithSingleHeader("Content-Range",
"bytes */" + std::to_string(len));
this->WriteHeaders();
return;
}
int64_t myLen = (end - begin) + 1;
this->WithSingleHeader("Accept-Ranges", "bytes");
this->WithSingleHeader("Content-Length", std::to_string(myLen));
this->WithSingleHeader("Content-Range",
"bytes " + std::to_string(begin) + "-" +
std::to_string(end) + "/" +
std::to_string(len));
this->statusCode = PartialContent;
this->WriteHeaders();
if (this->method != "HEAD") {
strm->Seek(begin, SeekOrigin::Begin);
uint8_t buffer[1024];
size_t read = 0;
do {
read = sizeof(buffer);
myLen = (end - begin) + 1;
if (myLen < read)
read = (size_t)myLen;
if (read == 0)
break;
read = strm->Read(buffer, read);
if (read == 0)
break;
this->strm->WriteBlock(buffer, read);
begin += read;
} while (read > 0 && !this->strm->EndOfStream());
}
} else {
this->statusCode = BadRequest;
this->SendErrorPage(true);
return;
}
} else {
if (len > -1) {
this->WithSingleHeader("Accept-Range", "bytes");
this->WithSingleHeader("Content-Length", std::to_string(len));
this->WriteHeaders();
if (this->method != "HEAD")
strm->CopyTo(this->strm);
}
}
} else {
auto chunkedStream = this->OpenResponseStream();
if (method != "HEAD")
strm->CopyTo(chunkedStream);
}
}
ServerContext &ServerContext::WithHeader(std::string key, std::string value) {
this->responseHeaders.AddValue(key, value);
return *this;
}
ServerContext &ServerContext::WithSingleHeader(std::string key,
std::string value) {
this->responseHeaders.SetValue(key, value);
return *this;
}
ServerContext &ServerContext::WithMimeType(std::string mime) {
this->responseHeaders.SetValue("Content-Type", mime);
return *this;
}
ServerContext &ServerContext::WithContentDisposition(std::string filename,
bool isInline) {
ContentDisposition cd;
cd.type = isInline ? "inline" : "attachment";
cd.filename = filename;
// std::string cd;
// cd = (isInline ? "inline; filename*=UTF-8''" : "attachment;
// filename*=UTF-8''") + HttpUtils::UrlPathEncode(filename);
this->responseHeaders.SetValue("Content-Disposition", cd.ToString());
return *this;
}
ServerContext &ServerContext::WithLocationHeader(std::string url) {
return WithSingleHeader("Location", this->MakeAbsolute(url));
}
ServerContext &ServerContext::WithLocationHeader(std::string url,
StatusCode sc) {
this->statusCode = sc;
return WithSingleHeader("Location", this->MakeAbsolute(url));
}
void ServerContext::SendRedirect(std::string url) {
WithLocationHeader(url);
this->WriteHeaders();
}
void ServerContext::SendRedirect(std::string url, StatusCode sc) {
WithLocationHeader(url, sc);
this->WriteHeaders();
}
void ServerContext::SendNotFound() {
if (sent)
return;
statusCode = StatusCode::NotFound;
SendErrorPage(true);
}
void ServerContext::SendBadRequest() {
if (sent)
return;
statusCode = StatusCode::BadRequest;
SendErrorPage(false);
}
ServerContext &ServerContext::WithStatusCode(StatusCode code) {
this->statusCode = code;
return *this;
}
void ServerContext::SendException(std::exception &ex) {
if (this->debug) {
this->WithMimeType("text/html")
.WithStatusCode(StatusCode::InternalServerError)
.SendText(" Internal Server Error at " +
HttpUtils::HtmlEncode(this->originalPath) +
"Internal Server Error at " +
HttpUtils::HtmlEncode(this->originalPath) +
"
what(): " + HttpUtils::HtmlEncode(ex.what()) +
"
");
} else {
this->WithStatusCode(StatusCode::InternalServerError)
.SendErrorPage(true);
}
}
ServerContext &
ServerContext::WithHeaderIntercepter(std::function cb) {
this->headerhandlers.push(cb);
return *this;
}
ServerContext &ServerContext::WriteHeaders() {
if (this->sent)
return *this;
while (!this->headerhandlers.empty()) {
auto header = this->headerhandlers.front();
this->headerhandlers.pop();
if (header(*this)) {
break;
}
}
if (this->sent)
return *this;
this->sent = true;
StreamWriter writer(this->strm);
writer.newline = "\r\n";
writer.WriteLine("HTTP/1.1 " + std::to_string((int)statusCode) + " " +
HttpUtils::StatusCodeString(statusCode));
for (auto &hdr : responseHeaders.kvp) {
auto &key = hdr.first;
for (auto &val : hdr.second) {
writer.WriteLine(key + ": " + val);
}
}
writer.WriteLine();
return *this;
}
void HttpServer::Process(std::shared_ptr strm,
std::shared_ptr server, std::string ip,
uint16_t port, uint16_t serverPort, bool encrypted,
bool debug) {
TF_LOG("In process");
std::shared_ptr bStrm =
std::make_shared(strm);
StreamReader reader(bStrm);
ServerContext ctx(bStrm);
ctx.ip = ip;
ctx.port = port;
ctx.encrypted = encrypted;
ctx.serverPort = serverPort;
try {
bool firstLine = true;
std::string line;
while (reader.ReadLineHttp(line)) {
if (firstLine) {
auto v = HttpUtils::SplitString(line, " ", 3);
if (v.size() != 3) {
ctx.statusCode = BadRequest;
ctx.WithMimeType("text/plain")
.SendText("First line is not 3 elements");
return;
}
ctx.method = v[0];
auto pp = HttpUtils::SplitString(v[1], "?", 2);
pp.resize(2);
ctx.originalPath = pp[0];
ctx.path = ctx.originalPath;
TF_LOG(ctx.method + " with path " + ctx.path);
auto queryPart = pp[1];
if (!queryPart.empty()) {
HttpUtils::QueryParamsDecode(ctx.queryParams, queryPart);
}
ctx.version = v[2];
} else {
auto v = HttpUtils::SplitString(line, ": ", 2);
if (v.size() != 2) {
ctx.statusCode = BadRequest;
ctx.WithMimeType("text/plain")
.SendText("Header line is not 2 elements");
return;
}
ctx.requestHeaders.AddValue(v[0], v[1]);
}
line.clear();
firstLine = false;
}
if (firstLine)
return;
std::string type;
int64_t length;
if (ctx.requestHeaders.TryGetFirst("Content-Type", type) &&
type == "application/x-www-form-urlencoded" &&
ctx.requestHeaders.TryGetFirstInt("Content-Length", length)) {
size_t len = (size_t)length;
std::vector buffer(len);
len = bStrm->ReadBlock(buffer.data(), len);
std::string query((const char *)buffer.data(), len);
HttpUtils::QueryParamsDecode(ctx.queryParams, query);
}
if (!server->Handle(ctx)) {
if ((int)ctx.statusCode < 400)
ctx.SendNotFound();
else
ctx.SendErrorPage(true);
}
}
catch (std::exception &ex) {
ctx.SendException(ex);
} catch (std::string &ex) {
std::runtime_error re(ex);
ctx.SendException(re);
} catch (const char *ex) {
std::runtime_error re(ex);
ctx.SendException(re);
} catch (...) {
std::runtime_error ex("An unknown error occurred");
ctx.SendException(ex);
}
}
bool ServerContext::Debug() { return this->debug; }
ServerContext &ServerContext::WithDebug(bool debug) {
this->debug = debug;
return *this;
}
WebSocketConnection::~WebSocketConnection() {}
void ServerContext::StartWebSocketSession(
std::function,
std::function, std::function)>
onOpen,
std::function onReceive,
std::function onClose) {
std::shared_ptr wsc =
std::make_shared(onOpen, onReceive,
onClose);
StartWebSocketSession(wsc);
}
void ServerContext::StartWebSocketSession(
std::shared_ptr connection) {
WSServer svr(this, connection);
Threading::Thread thrd([&svr, &connection]() -> void {
try {
connection->OnOpen(
[&svr](WebSocketMessage &msg) -> void { svr.send_msg(&msg); },
[&svr]() -> void {
std::vector p = {(uint8_t)'p', (uint8_t)'i',
(uint8_t)'n', (uint8_t)'g'};
svr.ping_send(p);
},
[&svr]() -> void { svr.close(); });
} catch (...) {
}
});
svr.Start();
thrd.Join();
}
std::string ServerContext::GetServerRoot() {
if (this->originalPath == this->path)
return "/";
Tesses::Framework::Filesystem::VFSPath originalPath = this->originalPath;
Tesses::Framework::Filesystem::VFSPath path = this->path;
if (originalPath.path.size() <= path.path.size())
return "/";
originalPath.path.resize(originalPath.path.size() - path.path.size());
return originalPath.ToString();
}
std::string ServerContext::MakeAbsolute(std::string path) {
if (path.find("http://") == 0 || path.find("https://") == 0 ||
path.find("/") == 0)
return path;
Tesses::Framework::Filesystem::VFSPath path2 = GetServerRoot();
path2 = path2 / path;
return path2.CollapseRelativeParents().ToString();
}
} // namespace Tesses::Framework::Http