Provide filename for artefact downloads
This commit is contained in:
parent
69a81f7583
commit
d5e2b5489d
|
@ -195,7 +195,8 @@ public:
|
|||
std::shared_ptr<BuildProcessSession> makeBuildProcess(
|
||||
std::string &&displayName, std::string &&logFilePath, ProcessHandler &&handler, AssociatedLocks &&locks = AssociatedLocks());
|
||||
void terminateOngoingBuildProcesses();
|
||||
void streamFile(const WebAPI::Params ¶ms, const std::string &filePath, std::string_view fileMimeType);
|
||||
void streamFile(const WebAPI::Params ¶ms, const std::string &filePath, boost::beast::string_view fileMimeType,
|
||||
boost::beast::string_view contentDisposition = boost::beast::string_view());
|
||||
ServiceSetup *setup();
|
||||
|
||||
protected:
|
||||
|
|
|
@ -407,7 +407,8 @@ void BuildAction::terminateOngoingBuildProcesses()
|
|||
}
|
||||
}
|
||||
|
||||
void BuildAction::streamFile(const WebAPI::Params ¶ms, const std::string &filePath, std::string_view fileMimeType)
|
||||
void BuildAction::streamFile(
|
||||
const WebAPI::Params ¶ms, const std::string &filePath, boost::beast::string_view fileMimeType, boost::beast::string_view contentDisposition)
|
||||
{
|
||||
auto buildProcess = std::shared_ptr<BuildProcessSession>();
|
||||
if (const auto outputLock = std::unique_lock<std::mutex>(m_outputSessionMutex); m_outputSession && m_outputSession->logFilePath() == filePath) {
|
||||
|
@ -418,12 +419,12 @@ void BuildAction::streamFile(const WebAPI::Params ¶ms, const std::string &fi
|
|||
}
|
||||
if (!buildProcess) {
|
||||
// simply send the file if there's no ongoing process writing to it anymore
|
||||
params.session.respond(filePath.data(), fileMimeType.data(), params.target.path);
|
||||
params.session.respond(filePath.data(), fileMimeType, contentDisposition, params.target.path);
|
||||
return;
|
||||
}
|
||||
|
||||
// stream the output of the ongoing process
|
||||
auto chunkResponse = WebAPI::Render::makeChunkResponse(params.request(), fileMimeType.data());
|
||||
auto chunkResponse = WebAPI::Render::makeChunkResponse(params.request(), fileMimeType, contentDisposition);
|
||||
boost::beast::http::async_write_header(params.session.socket(), chunkResponse->serializer,
|
||||
[chunkResponse, filePath, buildProcess, session = params.session.shared_from_this()](
|
||||
const boost::system::error_code &error, std::size_t) mutable {
|
||||
|
|
|
@ -146,7 +146,7 @@ std::shared_ptr<Response> makeServerError(const Request &request, std::string_vi
|
|||
return res;
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> makeData(const Request &request, const string &buffer, const char *mimeType)
|
||||
std::shared_ptr<Response> makeData(const Request &request, const string &buffer, boost::beast::string_view mimeType)
|
||||
{
|
||||
const auto res = make_shared<Response>(http::status::ok, request.version());
|
||||
res->set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
||||
|
@ -158,7 +158,7 @@ std::shared_ptr<Response> makeData(const Request &request, const string &buffer,
|
|||
return res;
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> makeData(const Request &request, string &&buffer, const char *mimeType)
|
||||
std::shared_ptr<Response> makeData(const Request &request, string &&buffer, boost::beast::string_view mimeType)
|
||||
{
|
||||
const auto res = make_shared<Response>(http::status::ok, request.version());
|
||||
res->set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
||||
|
@ -170,11 +170,13 @@ std::shared_ptr<Response> makeData(const Request &request, string &&buffer, cons
|
|||
return res;
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> makeData(const Request &request, rapidjson::StringBuffer &&buffer, const char *mimeType)
|
||||
std::shared_ptr<Response> makeData(const Request &request, rapidjson::StringBuffer &&buffer, boost::beast::string_view mimeType)
|
||||
{
|
||||
const auto res = make_shared<Response>(http::status::ok, request.version());
|
||||
res->set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
||||
res->set(http::field::content_type, mimeType);
|
||||
if (!mimeType.empty()) {
|
||||
res->set(http::field::content_type, mimeType);
|
||||
}
|
||||
res->set(http::field::access_control_allow_origin, "*");
|
||||
res->keep_alive(request.keep_alive());
|
||||
res->body().assign(buffer.GetString(), buffer.GetSize());
|
||||
|
@ -182,11 +184,17 @@ std::shared_ptr<Response> makeData(const Request &request, rapidjson::StringBuff
|
|||
return res;
|
||||
}
|
||||
|
||||
std::shared_ptr<FileResponse> makeFile(const Request &request, const char *filePath, const char *mimeType, boost::beast::error_code &ec)
|
||||
std::shared_ptr<FileResponse> makeFile(const Request &request, const char *filePath, boost::beast::string_view mimeType,
|
||||
boost::beast::string_view contentDisposition, boost::beast::error_code &ec)
|
||||
{
|
||||
const auto fileResponse = std::make_shared<FileResponse>();
|
||||
fileResponse->set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
||||
fileResponse->set(http::field::content_type, mimeType);
|
||||
if (!mimeType.empty()) {
|
||||
fileResponse->set(http::field::content_type, mimeType);
|
||||
}
|
||||
if (!contentDisposition.empty()) {
|
||||
fileResponse->set(http::field::content_disposition, contentDisposition);
|
||||
}
|
||||
fileResponse->set(http::field::access_control_allow_origin, "*");
|
||||
fileResponse->keep_alive(request.keep_alive());
|
||||
fileResponse->body().open(filePath, file_mode::scan, ec);
|
||||
|
@ -202,10 +210,16 @@ inline ChunkResponse::ChunkResponse()
|
|||
response.chunked(true);
|
||||
}
|
||||
|
||||
std::shared_ptr<ChunkResponse> makeChunkResponse(const Request &request, const char *mimeType)
|
||||
std::shared_ptr<ChunkResponse> makeChunkResponse(
|
||||
const Request &request, boost::beast::string_view mimeType, boost::beast::string_view contentDisposition)
|
||||
{
|
||||
const auto chunkResponse = std::make_shared<ChunkResponse>();
|
||||
chunkResponse->response.set(boost::beast::http::field::content_type, mimeType);
|
||||
if (!mimeType.empty()) {
|
||||
chunkResponse->response.set(boost::beast::http::field::content_type, mimeType);
|
||||
}
|
||||
if (!contentDisposition.empty()) {
|
||||
chunkResponse->response.set(boost::beast::http::field::content_disposition, contentDisposition);
|
||||
}
|
||||
chunkResponse->response.keep_alive(request.keep_alive());
|
||||
return chunkResponse;
|
||||
}
|
||||
|
|
|
@ -31,14 +31,16 @@ std::shared_ptr<Response> makeNotFound(const Request &request, std::string_view
|
|||
std::shared_ptr<Response> makeAuthRequired(const Request &request);
|
||||
std::shared_ptr<Response> makeForbidden(const Request &request);
|
||||
std::shared_ptr<Response> makeServerError(const Request &request, std::string_view what);
|
||||
std::shared_ptr<Response> makeData(const Request &request, const std::string &data, const char *mimeType);
|
||||
std::shared_ptr<Response> makeData(const Request &request, std::string &&data, const char *mimeType);
|
||||
std::shared_ptr<Response> makeData(const Request &request, RAPIDJSON_NAMESPACE::StringBuffer &&buffer, const char *mimeType);
|
||||
std::shared_ptr<Response> makeData(const Request &request, const std::string &data, boost::beast::string_view mimeType);
|
||||
std::shared_ptr<Response> makeData(const Request &request, std::string &&data, boost::beast::string_view mimeType);
|
||||
std::shared_ptr<Response> makeData(const Request &request, RAPIDJSON_NAMESPACE::StringBuffer &&buffer, boost::beast::string_view mimeType);
|
||||
std::shared_ptr<Response> makeText(const Request &request, const std::string &text);
|
||||
std::shared_ptr<Response> makeText(const Request &request, std::string &&text);
|
||||
std::shared_ptr<Response> makeJson(const Request &request, std::string &&json);
|
||||
std::shared_ptr<FileResponse> makeFile(const Request &request, const char *filePath, const char *mimeType, boost::beast::error_code &ec);
|
||||
std::shared_ptr<ChunkResponse> makeChunkResponse(const Request &request, const char *mimeType);
|
||||
std::shared_ptr<FileResponse> makeFile(const Request &request, const char *filePath, boost::beast::string_view mimeType,
|
||||
boost::beast::string_view contentDisposition, boost::beast::error_code &ec);
|
||||
std::shared_ptr<ChunkResponse> makeChunkResponse(
|
||||
const Request &request, boost::beast::string_view mimeType, boost::beast::string_view contentDisposition);
|
||||
|
||||
inline std::shared_ptr<Response> makeText(const Request &request, const std::string &text)
|
||||
{
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#define CPP_UTILITIES_PATHHELPER_STRING_VIEW
|
||||
|
||||
#include "./params.h"
|
||||
#include "./render.h"
|
||||
#include "./routes.h"
|
||||
|
@ -5,6 +7,7 @@
|
|||
#include "../serversetup.h"
|
||||
|
||||
#include <c++utilities/conversion/stringbuilder.h>
|
||||
#include <c++utilities/io/path.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <variant>
|
||||
|
@ -129,9 +132,16 @@ static void getBuildActionFile(
|
|||
if (name.empty()) {
|
||||
return;
|
||||
}
|
||||
auto mimeType = std::string_view("application/octet-stream");
|
||||
auto mimeType = boost::beast::string_view("application/octet-stream");
|
||||
auto contentDisposition = std::string();
|
||||
if (determineMimeType) {
|
||||
mimeType = Session::determineMimeType(name, mimeType);
|
||||
if (const auto detectedMimeType = Session::determineMimeType(name, boost::beast::string_view()); !detectedMimeType.empty()) {
|
||||
mimeType = detectedMimeType;
|
||||
} else {
|
||||
auto fileName = CppUtilities::fileName(name);
|
||||
findAndReplace(fileName, "\"", "");
|
||||
contentDisposition = "attachment; filename=\"" % fileName + "\"";
|
||||
}
|
||||
}
|
||||
auto buildActionsSearchResult = findBuildActions(params, std::move(handler), false, 1);
|
||||
if (!buildActionsSearchResult.ok) {
|
||||
|
@ -141,7 +151,7 @@ static void getBuildActionFile(
|
|||
auto &buildAction = buildActionsSearchResult.actions.front();
|
||||
for (const auto &logFile : (*buildAction).*fileList) {
|
||||
if (name == logFile) {
|
||||
buildAction->streamFile(params, name, mimeType);
|
||||
buildAction->streamFile(params, name, mimeType, contentDisposition);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -102,7 +102,7 @@ void Session::received(boost::system::error_code ec, size_t bytesTransferred)
|
|||
// handle requests to static files (intended for development only; use NGINX in production)
|
||||
if (!m_setup.webServer.staticFilesPath.empty() && (path.find("../") == string::npos || path.find("..\\") == string::npos)) {
|
||||
const auto filePath = argsToString(m_setup.webServer.staticFilesPath, params.target.path);
|
||||
respond(filePath.data(), determineMimeType(params.target.path).data(), params.target.path);
|
||||
respond(filePath.data(), determineMimeType(params.target.path).data(), boost::beast::string_view(), params.target.path);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -128,11 +128,12 @@ void Session::respond(std::shared_ptr<Response> &&response)
|
|||
m_res = move(response);
|
||||
}
|
||||
|
||||
void Session::respond(const char *localFilePath, const char *mimeType, std::string_view urlPath)
|
||||
void Session::respond(
|
||||
const char *localFilePath, boost::beast::string_view mimeType, boost::beast::string_view contentDisposition, std::string_view urlPath)
|
||||
{
|
||||
// make response with file body
|
||||
auto ec = boost::beast::error_code{};
|
||||
auto response = Render::makeFile(m_parser->get(), localFilePath, mimeType, ec);
|
||||
auto response = Render::makeFile(m_parser->get(), localFilePath, mimeType, contentDisposition, ec);
|
||||
if (ec.failed()) {
|
||||
respond(Render::makeNotFound(m_parser->get(), urlPath));
|
||||
return;
|
||||
|
@ -167,7 +168,7 @@ void Session::responded(boost::system::error_code ec, std::size_t bytesTransferr
|
|||
receive();
|
||||
}
|
||||
|
||||
std::string_view Session::determineMimeType(std::string_view path, std::string_view fallback)
|
||||
boost::beast::string_view Session::determineMimeType(std::string_view path, boost::beast::string_view fallback)
|
||||
{
|
||||
if (path.ends_with(".html")) {
|
||||
return "text/html";
|
||||
|
|
|
@ -22,14 +22,15 @@ public:
|
|||
|
||||
void receive();
|
||||
void respond(std::shared_ptr<Response> &&response);
|
||||
void respond(const char *localFilePath, const char *mimeType, std::string_view urlPath);
|
||||
void respond(
|
||||
const char *localFilePath, boost::beast::string_view mimeType, boost::beast::string_view contentDisposition, std::string_view urlPath);
|
||||
void close();
|
||||
const Request &request() const;
|
||||
void assignEmptyRequest();
|
||||
boost::asio::ip::tcp::socket &socket();
|
||||
void received(boost::system::error_code ec, std::size_t bytesTransferred);
|
||||
void responded(boost::system::error_code ec, std::size_t bytesTransferred, bool shouldClose);
|
||||
static std::string_view determineMimeType(std::string_view path, std::string_view fallback = "text/plain");
|
||||
static boost::beast::string_view determineMimeType(std::string_view path, boost::beast::string_view fallback = "text/plain");
|
||||
|
||||
private:
|
||||
boost::asio::ip::tcp::socket m_socket;
|
||||
|
|
Loading…
Reference in New Issue