Cleaned up the code a bit, added some testing code for UDAs for path handler stuff.
This commit is contained in:
parent
b7da823ddb
commit
d9310d5979
|
|
@ -152,6 +152,9 @@ private void map(
|
||||||
handler.addMapping(method, API_PATH ~ subPath, HttpRequestHandler.of(fn));
|
handler.addMapping(method, API_PATH ~ subPath, HttpRequestHandler.of(fn));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A filter that adds CORS response headers.
|
||||||
|
*/
|
||||||
private class CorsFilter : HttpRequestFilter {
|
private class CorsFilter : HttpRequestFilter {
|
||||||
private string webOrigin;
|
private string webOrigin;
|
||||||
|
|
||||||
|
|
@ -168,6 +171,10 @@ private class CorsFilter : HttpRequestFilter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A filter that rejects requests with a body that's too large, to avoid issues
|
||||||
|
* later with handling such large objects in memory.
|
||||||
|
*/
|
||||||
private class ContentLengthFilter : HttpRequestFilter {
|
private class ContentLengthFilter : HttpRequestFilter {
|
||||||
const MAX_LENGTH = 1024 * 1024 * 20; // 2MB limit
|
const MAX_LENGTH = 1024 * 1024 * 20; // 2MB limit
|
||||||
|
|
||||||
|
|
@ -192,6 +199,10 @@ private class ContentLengthFilter : HttpRequestFilter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A filter that catches any exception thrown by the filter chain, and nicely
|
||||||
|
* formats the response status and message.
|
||||||
|
*/
|
||||||
private class ExceptionHandlingFilter : HttpRequestFilter {
|
private class ExceptionHandlingFilter : HttpRequestFilter {
|
||||||
void doFilter(ref ServerHttpRequest request, ref ServerHttpResponse response, FilterChain filterChain) {
|
void doFilter(ref ServerHttpRequest request, ref ServerHttpResponse response, FilterChain filterChain) {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,73 @@
|
||||||
import handy_http_transport;
|
import handy_http_transport;
|
||||||
import slf4d;
|
import slf4d;
|
||||||
import slf4d.default_provider;
|
import slf4d.default_provider;
|
||||||
import scheduled;
|
|
||||||
import std.datetime;
|
|
||||||
|
|
||||||
import api_mapping;
|
import api_mapping;
|
||||||
import util.config;
|
import util.config;
|
||||||
import analytics;
|
import scheduled_jobs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the Finnow API.
|
||||||
|
*/
|
||||||
void main() {
|
void main() {
|
||||||
|
// testUDA();
|
||||||
|
|
||||||
const config = readConfig();
|
const config = readConfig();
|
||||||
|
configureSlf4d(config);
|
||||||
|
startScheduledJobs();
|
||||||
|
startWebServer(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
void configureSlf4d(in AppConfig config) {
|
||||||
Level logLevel = getConfiguredLoggingLevel(config);
|
Level logLevel = getConfiguredLoggingLevel(config);
|
||||||
auto provider = new DefaultProvider(logLevel);
|
auto provider = new DefaultProvider(logLevel);
|
||||||
configureLoggingProvider(provider);
|
configureLoggingProvider(provider);
|
||||||
infoF!"Loaded app config: port = %d, webOrigin = %s"(config.port, config.webOrigin);
|
}
|
||||||
|
|
||||||
// Start scheduled tasks in a separate thread:
|
|
||||||
JobSchedule analyticsSchedule = new FixedIntervalSchedule(
|
|
||||||
hours(1),
|
|
||||||
Clock.currTime(UTC()) + seconds(10)
|
|
||||||
);
|
|
||||||
|
|
||||||
JobScheduler jobScheduler = new TaskPoolScheduler();
|
|
||||||
jobScheduler.addJob(() {
|
|
||||||
info("Computing account balance time series analytics for all users...");
|
|
||||||
doForAllUserProfiles(&computeAccountBalanceTimeSeries);
|
|
||||||
doForAllUserProfiles(&computeCategorySpendTimeSeries);
|
|
||||||
info("Done computing analytics!");
|
|
||||||
}, analyticsSchedule);
|
|
||||||
jobScheduler.start();
|
|
||||||
|
|
||||||
|
void startWebServer(in AppConfig config) {
|
||||||
Http1TransportConfig transportConfig = defaultConfig();
|
Http1TransportConfig transportConfig = defaultConfig();
|
||||||
transportConfig.port = config.port;
|
transportConfig.port = config.port;
|
||||||
HttpTransport transport = new TaskPoolHttp1Transport(mapApiHandlers(config.webOrigin), transportConfig);
|
HttpTransport transport = new TaskPoolHttp1Transport(mapApiHandlers(config.webOrigin), transportConfig);
|
||||||
transport.start();
|
transport.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// void testUDA() {
|
||||||
|
// import std.traits : getSymbolsByUDA;
|
||||||
|
// import std.stdio;
|
||||||
|
// static assert(getSymbolsByUDA!(app, Attr).length == 2);
|
||||||
|
// Attr a;
|
||||||
|
// static foreach(symbol; getSymbolsByUDA!(app, Attr)) {
|
||||||
|
// pragma(msg, symbol);
|
||||||
|
// pragma(msg, __traits(identifier, symbol));
|
||||||
|
// pragma(msg, "------------------");
|
||||||
|
// static foreach(attr; __traits(getAttributes, symbol)) {
|
||||||
|
// pragma(msg, "Attribute:");
|
||||||
|
// pragma(msg, attr);
|
||||||
|
// pragma(msg, __traits(identifier, attr));
|
||||||
|
// static if (is(typeof(attr) == Attr)) {
|
||||||
|
// pragma(msg, "Found target attribute!");
|
||||||
|
// pragma(msg, attr.val);
|
||||||
|
// a = attr;
|
||||||
|
// writefln!"Function %s has attr val = %d"((__traits(identifier, symbol)), a.val);
|
||||||
|
// } else {
|
||||||
|
// pragma(msg, "Other attribute :(");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// struct Attr {
|
||||||
|
// int val;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// enum OtherAttr;
|
||||||
|
|
||||||
|
// @Attr(5) @OtherAttr
|
||||||
|
// void testMethod1() {
|
||||||
|
// int x = 5;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @Attr(42)
|
||||||
|
// void testMethod2() {
|
||||||
|
// int y = 5;
|
||||||
|
// }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
module scheduled_jobs;
|
||||||
|
|
||||||
|
import scheduled;
|
||||||
|
import std.datetime;
|
||||||
|
import slf4d;
|
||||||
|
|
||||||
|
import analytics;
|
||||||
|
|
||||||
|
void startScheduledJobs() {
|
||||||
|
JobSchedule analyticsSchedule = new FixedIntervalSchedule(
|
||||||
|
hours(1),
|
||||||
|
Clock.currTime(UTC()) + seconds(5)
|
||||||
|
);
|
||||||
|
|
||||||
|
JobScheduler jobScheduler = new TaskPoolScheduler();
|
||||||
|
jobScheduler.addJob(() {
|
||||||
|
info("Computing account balance time series analytics for all users...");
|
||||||
|
doForAllUserProfiles(&computeAccountBalanceTimeSeries);
|
||||||
|
doForAllUserProfiles(&computeCategorySpendTimeSeries);
|
||||||
|
info("Done computing analytics!");
|
||||||
|
}, analyticsSchedule);
|
||||||
|
jobScheduler.start();
|
||||||
|
}
|
||||||
|
|
@ -2,12 +2,34 @@ module util.config;
|
||||||
|
|
||||||
import std.stdio;
|
import std.stdio;
|
||||||
import slf4d;
|
import slf4d;
|
||||||
|
import asdf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The path of the config file to attempt to load.
|
||||||
|
*/
|
||||||
private const CONFIG_FILE = "finnow-api-config.json";
|
private const CONFIG_FILE = "finnow-api-config.json";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finnow API configuration struct, which contains properties used to
|
||||||
|
* configure various parts of the API.
|
||||||
|
*/
|
||||||
struct AppConfig {
|
struct AppConfig {
|
||||||
|
/**
|
||||||
|
* The port on which the HTTP server will accept requests.
|
||||||
|
*/
|
||||||
ushort port;
|
ushort port;
|
||||||
|
/**
|
||||||
|
* The address at which the corresponding Finnow website is available to
|
||||||
|
* clients. In development, this will be another localhost address, but in
|
||||||
|
* deployed environments, it'll be the actual website with a legitimate
|
||||||
|
* domain name. This is used to supply the Access-Control-Allow-Origin
|
||||||
|
* CORS header, which can help to prevent cross-site-scripting attacks.
|
||||||
|
*/
|
||||||
string webOrigin;
|
string webOrigin;
|
||||||
|
/**
|
||||||
|
* The logging level to use globally in the application. See SLF4D for more:
|
||||||
|
* <a>https://andrewlalis.github.io/slf4d/guide/using-slf4d.html#logging-levels</a>
|
||||||
|
*/
|
||||||
string logLevel;
|
string logLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,6 +53,8 @@ AppConfig readConfig() {
|
||||||
);
|
);
|
||||||
// Local dev environment if no config is given.
|
// Local dev environment if no config is given.
|
||||||
if (!exists(CONFIG_FILE)) {
|
if (!exists(CONFIG_FILE)) {
|
||||||
|
const defaultConfigJson = serializeToJson(defaultConfig);
|
||||||
|
infoF!"No config file \"%s\" found. Using defaults: %s."(CONFIG_FILE, defaultConfigJson);
|
||||||
return defaultConfig;
|
return defaultConfig;
|
||||||
}
|
}
|
||||||
JSONValue obj;
|
JSONValue obj;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue