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"); }