130 lines
4.6 KiB
D
130 lines
4.6 KiB
D
import handy_httpd;
|
|
import handy_httpd.handlers.path_delegating_handler;
|
|
import handy_httpd.handlers.file_resolving_handler;
|
|
import slf4d;
|
|
import slf4d.default_provider;
|
|
import std.path;
|
|
import std.file;
|
|
import std.json;
|
|
|
|
const EXTRACTS_DIR = "extracts";
|
|
const EXTRACT_FILENAME = "__EXTRACT__.json";
|
|
const EXTRACT_COMMAND = ["java", "-jar", "materials-extractor-v1.0.0.jar"];
|
|
|
|
void main() {
|
|
auto provider = new shared DefaultProvider(true, Levels.INFO);
|
|
configureLoggingProvider(provider);
|
|
|
|
ServerConfig config = ServerConfig.defaultValues();
|
|
config.workerPoolSize = 3;
|
|
config.connectionQueueSize = 10;
|
|
config.port = 8100;
|
|
|
|
PathDelegatingHandler handler = new PathDelegatingHandler();
|
|
handler.addMapping("POST", "/extracts", &handleExtract);
|
|
handler.addMapping("GET", "/extracts/{extractId}", &getExtract);
|
|
|
|
FileResolvingHandler fileHandler = new FileResolvingHandler("site", DirectoryResolutionStrategies.serveIndexFiles);
|
|
handler.addMapping("/**", fileHandler);
|
|
new HttpServer(handler, config).start();
|
|
}
|
|
|
|
void handleExtract(ref HttpRequestContext ctx) {
|
|
import std.json;
|
|
import std.uuid;
|
|
import std.process;
|
|
import std.stdio;
|
|
|
|
immutable UUID extractId = randomUUID();
|
|
MultipartFormData data = ctx.request.readBodyAsMultipartFormData();
|
|
if (!validateExtractRequest(data, ctx.response)) return;
|
|
const extractDir = buildPath(EXTRACTS_DIR, extractId.toString());
|
|
if (!exists(extractDir)) {
|
|
mkdirRecurse(extractDir);
|
|
}
|
|
string[] filenames;
|
|
uint[] counts;
|
|
foreach (MultipartElement element; data.elements) {
|
|
if (element.name == "schematics") {
|
|
const filePath = buildPath(extractDir, element.filename.get());
|
|
std.file.write(filePath, element.content);
|
|
filenames ~= filePath;
|
|
} else if (element.name == "counts") {
|
|
import std.conv;
|
|
immutable uint count = element.content.to!uint;
|
|
counts ~= count;
|
|
}
|
|
}
|
|
|
|
infoF!"Running extract process on files: %s"(filenames);
|
|
auto extractionResult = execute(EXTRACT_COMMAND ~ filenames);
|
|
immutable int exitCode = extractionResult.status;
|
|
infoF!"Exit code: %d"(exitCode);
|
|
rmdirRecurse(extractDir);
|
|
if (exitCode != 0) {
|
|
ctx.response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR);
|
|
ctx.response.writeBodyString(extractionResult.output);
|
|
} else {
|
|
const extractJsonPath = extractDir ~ ".json";
|
|
// It was successful, so add __COUNT__ to each object in the extract data.
|
|
JSONValue extractData = parseJSON(extractionResult.output);
|
|
for (uint i = 0; i < extractData.array.length; i++) {
|
|
extractData.array[i].object["__COUNT__"] = JSONValue(counts[i]);
|
|
}
|
|
std.file.write(extractJsonPath, extractData.toPrettyString());
|
|
|
|
JSONValue result = JSONValue.emptyObject;
|
|
result.object["extractId"] = JSONValue(extractId.toString());
|
|
ctx.response.writeBodyString(result.toJSON(), "application/json");
|
|
}
|
|
}
|
|
|
|
private bool validateExtractRequest(ref MultipartFormData data, ref HttpResponse response) {
|
|
if (data.elements.length < 2) {
|
|
response.setStatus(HttpStatus.BAD_REQUEST);
|
|
response.writeBodyString("Requires at least 2 form data elements (schematic file and count).");
|
|
return false;
|
|
}
|
|
uint nextElementIdx = 0;
|
|
while (nextElementIdx < data.elements.length) {
|
|
MultipartElement element = data.elements[nextElementIdx++];
|
|
if (element.name == "schematics") {
|
|
if (element.filename.isNull || element.filename.get().length < 5 || element.content.length < 10) {
|
|
response.setStatus(HttpStatus.BAD_REQUEST);
|
|
response.writeBodyString("Invalid or missing schematic file.");
|
|
return false;
|
|
}
|
|
const string filename = element.filename.get();
|
|
if (nextElementIdx == data.elements.length) {
|
|
response.setStatus(HttpStatus.BAD_REQUEST);
|
|
response.writeBodyString("Missing count element for schematic: " ~ filename);
|
|
return false;
|
|
}
|
|
MultipartElement countElement = data.elements[nextElementIdx++];
|
|
import std.conv;
|
|
try {
|
|
immutable uint count = countElement.content.to!uint;
|
|
if (count < 1 || count > 1000) throw new Exception("out of range: count should be between 1 and 1000, inclusive.");
|
|
} catch (Exception e) {
|
|
response.setStatus(HttpStatus.BAD_REQUEST);
|
|
response.writeBodyString("Invalid count element: " ~ e.msg);
|
|
return false;
|
|
}
|
|
} else if (element.name == "processing-terminal") {
|
|
// TODO: Check processing-terminal format.
|
|
} else {
|
|
response.setStatus(HttpStatus.BAD_REQUEST);
|
|
response.writeBodyString("Unknown element: " ~ element.name);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void getExtract(ref HttpRequestContext ctx) {
|
|
string extractId = ctx.request.getPathParamAs!string("extractId");
|
|
const extractFile = buildPath(EXTRACTS_DIR, extractId ~ ".json");
|
|
fileResponse(ctx.response, extractFile, "application/json");
|
|
}
|