2020-12-26 14:12:05 -08:00
|
|
|
// Copyright (c) FIRST and other WPILib contributors.
|
|
|
|
|
// Open Source Software; you can modify and/or share it under the terms of
|
|
|
|
|
// the WPILib BSD license file in the root directory of this project.
|
2018-08-20 13:11:39 -07:00
|
|
|
|
2022-05-07 10:54:14 -07:00
|
|
|
#include "wpinet/HttpServerConnection.h"
|
2018-08-20 13:11:39 -07:00
|
|
|
|
2022-05-07 10:54:14 -07:00
|
|
|
#include <fmt/format.h>
|
|
|
|
|
#include <wpi/SmallString.h>
|
|
|
|
|
#include <wpi/SmallVector.h>
|
|
|
|
|
#include <wpi/SpanExtras.h>
|
|
|
|
|
#include <wpi/StringExtras.h>
|
|
|
|
|
#include <wpi/fmt/raw_ostream.h>
|
|
|
|
|
|
|
|
|
|
#include "wpinet/raw_uv_ostream.h"
|
2018-08-20 13:11:39 -07:00
|
|
|
|
|
|
|
|
using namespace wpi;
|
|
|
|
|
|
|
|
|
|
HttpServerConnection::HttpServerConnection(std::shared_ptr<uv::Stream> stream)
|
|
|
|
|
: m_stream(*stream) {
|
|
|
|
|
// process HTTP messages
|
2018-11-18 18:28:26 -08:00
|
|
|
m_messageCompleteConn =
|
|
|
|
|
m_request.messageComplete.connect_connection([this](bool keepAlive) {
|
|
|
|
|
m_keepAlive = keepAlive;
|
|
|
|
|
ProcessRequest();
|
|
|
|
|
});
|
2018-08-20 13:11:39 -07:00
|
|
|
|
2018-11-18 18:28:26 -08:00
|
|
|
// look for Accept-Encoding headers to determine if gzip is acceptable
|
|
|
|
|
m_request.messageBegin.connect([this] { m_acceptGzip = false; });
|
2021-06-06 16:13:58 -07:00
|
|
|
m_request.header.connect(
|
|
|
|
|
[this](std::string_view name, std::string_view value) {
|
|
|
|
|
if (wpi::equals_lower(name, "accept-encoding") &&
|
|
|
|
|
wpi::contains(value, "gzip")) {
|
|
|
|
|
m_acceptGzip = true;
|
|
|
|
|
}
|
|
|
|
|
});
|
2018-08-20 13:11:39 -07:00
|
|
|
|
2018-11-18 18:28:26 -08:00
|
|
|
// pass incoming data to HTTP parser
|
2018-12-09 01:36:36 -08:00
|
|
|
m_dataConn =
|
2018-11-18 18:28:26 -08:00
|
|
|
stream->data.connect_connection([this](uv::Buffer& buf, size_t size) {
|
2021-06-06 16:13:58 -07:00
|
|
|
m_request.Execute({buf.base, size});
|
2018-11-18 18:28:26 -08:00
|
|
|
if (m_request.HasError()) {
|
|
|
|
|
// could not parse; just close the connection
|
|
|
|
|
m_stream.Close();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2018-08-20 13:11:39 -07:00
|
|
|
// close when remote side closes
|
2018-12-09 01:36:36 -08:00
|
|
|
m_endConn =
|
|
|
|
|
stream->end.connect_connection([h = stream.get()] { h->Close(); });
|
2018-08-20 13:11:39 -07:00
|
|
|
|
|
|
|
|
// start reading
|
|
|
|
|
stream->StartRead();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void HttpServerConnection::BuildCommonHeaders(raw_ostream& os) {
|
|
|
|
|
os << "Server: WebServer/1.0\r\n"
|
|
|
|
|
"Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, "
|
|
|
|
|
"post-check=0, max-age=0\r\n"
|
|
|
|
|
"Pragma: no-cache\r\n"
|
|
|
|
|
"Expires: Mon, 3 Jan 2000 12:34:56 GMT\r\n";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void HttpServerConnection::BuildHeader(raw_ostream& os, int code,
|
2021-06-06 16:13:58 -07:00
|
|
|
std::string_view codeText,
|
|
|
|
|
std::string_view contentType,
|
2018-08-20 13:11:39 -07:00
|
|
|
uint64_t contentLength,
|
2021-06-06 16:13:58 -07:00
|
|
|
std::string_view extra) {
|
|
|
|
|
fmt::print(os, "HTTP/{}.{} {} {}\r\n", m_request.GetMajor(),
|
|
|
|
|
m_request.GetMinor(), code, codeText);
|
2020-12-28 12:58:06 -08:00
|
|
|
if (contentLength == 0) {
|
|
|
|
|
m_keepAlive = false;
|
|
|
|
|
}
|
|
|
|
|
if (!m_keepAlive) {
|
|
|
|
|
os << "Connection: close\r\n";
|
|
|
|
|
}
|
2018-08-20 13:11:39 -07:00
|
|
|
BuildCommonHeaders(os);
|
|
|
|
|
os << "Content-Type: " << contentType << "\r\n";
|
2020-12-28 12:58:06 -08:00
|
|
|
if (contentLength != 0) {
|
2021-06-06 16:13:58 -07:00
|
|
|
fmt::print(os, "Content-Length: {}\r\n", contentLength);
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2018-08-20 13:11:39 -07:00
|
|
|
os << "Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: *\r\n";
|
2021-06-06 16:13:58 -07:00
|
|
|
if (!extra.empty()) {
|
|
|
|
|
os << extra;
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2018-08-20 13:11:39 -07:00
|
|
|
os << "\r\n"; // header ends with a blank line
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-15 16:33:14 -07:00
|
|
|
void HttpServerConnection::SendData(std::span<const uv::Buffer> bufs,
|
2018-08-20 13:11:39 -07:00
|
|
|
bool closeAfter) {
|
2021-06-06 19:51:14 -07:00
|
|
|
m_stream.Write(bufs, [closeAfter, stream = &m_stream](auto bufs, uv::Error) {
|
2020-12-28 12:58:06 -08:00
|
|
|
for (auto&& buf : bufs) {
|
|
|
|
|
buf.Deallocate();
|
|
|
|
|
}
|
|
|
|
|
if (closeAfter) {
|
|
|
|
|
stream->Close();
|
|
|
|
|
}
|
2018-08-20 13:11:39 -07:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-06 16:13:58 -07:00
|
|
|
void HttpServerConnection::SendResponse(int code, std::string_view codeText,
|
|
|
|
|
std::string_view contentType,
|
|
|
|
|
std::string_view content,
|
|
|
|
|
std::string_view extraHeader) {
|
2018-08-20 13:11:39 -07:00
|
|
|
SmallVector<uv::Buffer, 4> toSend;
|
|
|
|
|
raw_uv_ostream os{toSend, 4096};
|
|
|
|
|
BuildHeader(os, code, codeText, contentType, content.size(), extraHeader);
|
|
|
|
|
os << content;
|
|
|
|
|
// close after write completes if we aren't keeping alive
|
|
|
|
|
SendData(os.bufs(), !m_keepAlive);
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-06 16:13:58 -07:00
|
|
|
void HttpServerConnection::SendStaticResponse(
|
|
|
|
|
int code, std::string_view codeText, std::string_view contentType,
|
|
|
|
|
std::string_view content, bool gzipped, std::string_view extraHeader) {
|
2018-11-18 18:28:26 -08:00
|
|
|
// TODO: handle remote side not accepting gzip (very rare)
|
|
|
|
|
|
2021-06-06 16:13:58 -07:00
|
|
|
std::string_view contentEncodingHeader;
|
2020-12-28 12:58:06 -08:00
|
|
|
if (gzipped /* && m_acceptGzip*/) {
|
2018-11-18 18:28:26 -08:00
|
|
|
contentEncodingHeader = "Content-Encoding: gzip\r\n";
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2018-11-18 18:28:26 -08:00
|
|
|
|
|
|
|
|
SmallVector<uv::Buffer, 4> bufs;
|
|
|
|
|
raw_uv_ostream os{bufs, 4096};
|
|
|
|
|
BuildHeader(os, code, codeText, contentType, content.size(),
|
2021-06-06 16:13:58 -07:00
|
|
|
fmt::format("{}{}", extraHeader, contentEncodingHeader));
|
2018-11-18 18:28:26 -08:00
|
|
|
// can send content without copying
|
|
|
|
|
bufs.emplace_back(content);
|
|
|
|
|
|
2019-05-17 17:35:09 -07:00
|
|
|
m_stream.Write(bufs, [closeAfter = !m_keepAlive, stream = &m_stream](
|
2021-06-06 19:51:14 -07:00
|
|
|
auto bufs, uv::Error) {
|
2018-11-18 18:28:26 -08:00
|
|
|
// don't deallocate the static content
|
2021-06-06 19:51:14 -07:00
|
|
|
for (auto&& buf : wpi::drop_back(bufs)) {
|
2020-12-28 12:58:06 -08:00
|
|
|
buf.Deallocate();
|
|
|
|
|
}
|
|
|
|
|
if (closeAfter) {
|
|
|
|
|
stream->Close();
|
|
|
|
|
}
|
2018-11-18 18:28:26 -08:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-06 16:13:58 -07:00
|
|
|
void HttpServerConnection::SendError(int code, std::string_view message) {
|
|
|
|
|
std::string_view codeText, extra, baseMessage;
|
2018-08-20 13:11:39 -07:00
|
|
|
switch (code) {
|
|
|
|
|
case 401:
|
|
|
|
|
codeText = "Unauthorized";
|
|
|
|
|
extra = "WWW-Authenticate: Basic realm=\"CameraServer\"";
|
|
|
|
|
baseMessage = "401: Not Authenticated!";
|
|
|
|
|
break;
|
|
|
|
|
case 404:
|
|
|
|
|
codeText = "Not Found";
|
|
|
|
|
baseMessage = "404: Not Found!";
|
|
|
|
|
break;
|
|
|
|
|
case 500:
|
|
|
|
|
codeText = "Internal Server Error";
|
|
|
|
|
baseMessage = "500: Internal Server Error!";
|
|
|
|
|
break;
|
|
|
|
|
case 400:
|
|
|
|
|
codeText = "Bad Request";
|
|
|
|
|
baseMessage = "400: Not Found!";
|
|
|
|
|
break;
|
|
|
|
|
case 403:
|
|
|
|
|
codeText = "Forbidden";
|
|
|
|
|
baseMessage = "403: Forbidden!";
|
|
|
|
|
break;
|
|
|
|
|
case 503:
|
|
|
|
|
codeText = "Service Unavailable";
|
|
|
|
|
baseMessage = "503: Service Unavailable";
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
code = 501;
|
|
|
|
|
codeText = "Not Implemented";
|
|
|
|
|
baseMessage = "501: Not Implemented!";
|
|
|
|
|
break;
|
|
|
|
|
}
|
2021-06-06 16:13:58 -07:00
|
|
|
SmallString<256> content{baseMessage};
|
2018-08-20 13:11:39 -07:00
|
|
|
content += "\r\n";
|
2021-06-06 16:13:58 -07:00
|
|
|
content += message;
|
|
|
|
|
SendResponse(code, codeText, "text/plain", content.str(), extra);
|
2018-08-20 13:11:39 -07:00
|
|
|
}
|