Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "3rdparty/drogon"]
path = 3rdparty/drogon
url = https://github.com/drogonframework/drogon.git
[submodule "3rdparty/magic_enum"]
path = 3rdparty/magic_enum
url = https://github.com/Neargye/magic_enum.git
1 change: 1 addition & 0 deletions 3rdparty/magic_enum
Submodule magic_enum added at 126539
7 changes: 6 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ option(BUILD_EXAMPLES "Build examples" OFF)
option(BUILD_CTL "Build drogon_ctl" OFF)
add_subdirectory(3rdparty/drogon)

add_subdirectory(3rdparty/magic_enum)

set(Boost_USE_STATIC_LIBS ON)
find_package(Boost REQUIRED COMPONENTS program_options)


add_subdirectory(src)

add_executable(simple_inference_server main.cpp)
target_link_libraries(simple_inference_server drogon Boost::program_options)
target_link_libraries(simple_inference_server Boost::program_options libsimple_inference_server)
122 changes: 6 additions & 116 deletions main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,118 +4,11 @@
#include <spdlog/spdlog.h>
#include <fstream>

#include "src/Controller.h"

using namespace drogon;


struct GlobalConfig
{
std::filesystem::path invokePath;
std::filesystem::path indexPath;
} globalConfig;

bool invokeProcessing( const std::filesystem::path& input, const std::filesystem::path& output )
{
auto exitCode = system( fmt::format( "{} {} {}", globalConfig.invokePath.string(), input.string(), output.string() ).c_str() );
return exitCode == 0;
}

std::string getInputFileName( const std::string& taskId )
{
return taskId + ".input.zip";
}
std::filesystem::path getInputPath( const std::string& taskId )
{
return std::filesystem::path{ app().getUploadPath() } / getInputFileName( taskId );
}
std::filesystem::path getOutputPath( const std::string& taskId )
{
return std::filesystem::path{ app().getUploadPath() } / ( taskId + ".zip" );
}


Task<HttpResponsePtr> trackResultHandler( HttpRequestPtr req )
{
const auto maybeTask = req->getOptionalParameter<std::string>( "task" );
const auto maybeOutput = maybeTask.transform( getOutputPath );

if ( !maybeOutput || !std::filesystem::exists( *maybeOutput ) )
{
auto resp = HttpResponse::newHttpResponse();
resp->setBody( "The task is not available (yet)" );
resp->setStatusCode( HttpStatusCode::k200OK );
resp->addHeader( "Refresh", "10" );
co_return resp;
}

auto resp = HttpResponse::newHttpResponse();
resp->setBody( fmt::format( "Result can be downloaded <a href=\"./get_result?task={}\">here</a>", *maybeTask ) );
resp->setStatusCode( HttpStatusCode::k200OK );
co_return resp;
}

Task<HttpResponsePtr> getResultHandler( HttpRequestPtr req )
{
const auto maybeTask = req->getOptionalParameter<std::string>( "task" );
const auto maybeOutput = maybeTask.transform( getOutputPath );
if ( !maybeOutput || !std::filesystem::exists( *maybeOutput ) )
{
co_return HttpResponse::newNotFoundResponse();
}

auto resp = HttpResponse::newFileResponse( *maybeOutput, maybeOutput->filename() );
co_return resp;
}

Task<HttpResponsePtr> submitTaskHandler( HttpRequestPtr req )
{
MultiPartParser filesUpload;
if ( filesUpload.parse(req) != 0 || filesUpload.getFiles().size() != 1 )
{
auto resp = HttpResponse::newHttpResponse();
resp->setBody( "Must only be one file" );
resp->setStatusCode( k403Forbidden );
co_return resp;
}

auto file = filesUpload.getFiles()[0];
if ( !file.getFileName().ends_with( ".zip" ) )
{
auto resp = HttpResponse::newHttpResponse();
resp->setBody( "Must be zip file" );
resp->setStatusCode( k403Forbidden );
co_return resp;
}

const auto taskId = drogon::utils::getUuid();
file.saveAs( getInputFileName( taskId ) );

app().getLoop()->queueInLoop( [taskId]
{
const auto inputPath = getInputPath( taskId );
const auto outputPath = getOutputPath( taskId );
invokeProcessing( inputPath, outputPath );
} );
using namespace drogon;

auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode( HttpStatusCode::k202Accepted );
resp->setBody( fmt::format( "Your requested has been accepted. You can track it <a href=\"./track_result?task={}\">here</a>",
taskId ) );
co_return resp;
}

Task<HttpResponsePtr> rootHandler( HttpRequestPtr req )
{
auto resp = HttpResponse::newHttpResponse( HttpStatusCode::k200OK, CT_TEXT_HTML );
if ( exists( globalConfig.indexPath ) )
{
std::ifstream fin( globalConfig.indexPath );
std::stringstream buffer;
buffer << fin.rdbuf();
resp->setBody( buffer.str() );
}
co_return resp;
}

// Fix parsing std::filesystem::path with spaces (see https://github.com/boostorg/program_options/issues/69)
namespace boost
Expand All @@ -133,12 +26,13 @@ int main( int argc, char** argv )
std::string host;
std::filesystem::path certPath, keyPath;
int port;
Controller::Config config;

po::options_description desc( "Simple Inference Server" );
desc.add_options()
( "help,h", "Display help message" )
( "invokePath", po::value( &globalConfig.invokePath )->required(), "Path to the script that will be invoked" )
( "indexPath", po::value( &globalConfig.indexPath )->default_value( {} ), "Path to the index.html" )
( "invokePath", po::value( &config.invokePath )->required(), "Path to the script that will be invoked" )
( "indexPath", po::value( &config.indexPath )->default_value( {} ), "Path to the index.html" )
( "host", po::value( &host )->default_value( "127.0.0.1" ), "Host to bind to" )
( "port", po::value( &port )->default_value( 7654 ), "Port to bind to" )
( "cert", po::value( &certPath )->default_value( {} ), "Path to SSL certificate" )
Expand All @@ -163,11 +57,6 @@ int main( int argc, char** argv )
return -1;
}

app().registerHandler( "/track_result", std::function{ trackResultHandler } );
app().registerHandler( "/get_result", std::function{ getResultHandler } );
app().registerHandler( "/submit", std::function{ submitTaskHandler }, {Post } );
app().registerHandler( "/", std::function{ rootHandler } );

const bool useSSL = exists( certPath ) && exists( keyPath );
if ( useSSL && !app().supportSSL() )
{
Expand All @@ -185,6 +74,7 @@ int main( int argc, char** argv )
app()
.setClientMaxBodySize( 1024*1024*1024 ) // 1gb
.addListener( host, port, useSSL, certPath, keyPath )
.registerController( std::make_shared<Controller>( config ) )
.run();

return 0;
Expand Down
4 changes: 2 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ It is useful when you have an early-stage prototype, and you want to share it wi
## Workflow

- Accepts `POST` request with a single `.zip` file to the endpoint `/submit`
- Displays the URL to track the task if it was accepted (`/track_result?task=...`)
- When processing is finished, the track page displays the URL to download result (`get_result?task=...`)
- Displays the URL to track the job if it was accepted (`/get_job_info?job_id=...`)
- When processing is finished, the track page displays the URL to download result (`/get_result?job_id=...`)

## Usage

Expand Down
6 changes: 6 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
project(libsimple_inference_server)


file(GLOB source *.cpp)
add_library(libsimple_inference_server STATIC ${source})
target_link_libraries(libsimple_inference_server drogon magic_enum::magic_enum)
Loading