app.d 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. alias uint8 = ubyte;
  2. //import vibe.vibe;
  3. // https://github.com/vibe-d/vibe.d/blob/master/source/vibe/vibe.d
  4. import vibe.core.core;
  5. import vibe.http.router;
  6. import vibe.http.server;
  7. import vibe.http.fileserver;
  8. import vibe.http.websockets;
  9. import vibe.core.log;
  10. import ws_bert_login : ws_bert_handle, login_test; // login - logged - logout -- via ws with bert ++ memcached + postgresql for sessions
  11. import std.string;
  12. import std.array;
  13. import std.algorithm;
  14. import std.datetime : SysTime, Clock;
  15. //import core.sync.mutex : Mutex;
  16. import std.concurrency : spawn;
  17. import vibe.core.concurrency;
  18. import vibe.core.core : Task, sleep;
  19. import core.time : Duration, dur;
  20. /*
  21. struct UserState{ // with mutex
  22. string client_id;
  23. SysTime last_online_at;
  24. this(string client_id, SysTime last_online_at) pure nothrow @safe{
  25. this.client_id = client_id;
  26. this.last_online_at = last_online_at;
  27. }
  28. }
  29. alias UserStateMap = UserState[string];
  30. class UserStateManager{ // with mutex
  31. private UserStateMap states;
  32. private Object lockObj;
  33. this(){
  34. lockObj = new Object();
  35. }
  36. bool contains(string client_id){
  37. synchronized(lockObj){
  38. return (client_id in states) !is null;
  39. }
  40. }
  41. void addOrUpdate(string client_id){
  42. synchronized(lockObj){
  43. auto now = Clock.currTime();
  44. states.remove(client_id);
  45. states[client_id] = UserState(client_id, now);
  46. }
  47. }
  48. void cleanup(){
  49. synchronized(lockObj){
  50. auto now = Clock.currTime();
  51. auto toRemove = states.byKey
  52. .filter!(k => (now - states[k].last_online_at).total!"seconds" > 30)
  53. .array;
  54. foreach(client_id; toRemove){
  55. states.remove(client_id);
  56. }
  57. }
  58. }
  59. size_t length() const{
  60. synchronized(lockObj){
  61. return states.length;
  62. }
  63. }
  64. }
  65. __gshared UserStateManager userStateManager;
  66. */
  67. import memcached4d;
  68. import std.conv : to;
  69. import tr;
  70. import mustache;
  71. alias MustacheEngine!(string) Mustache;
  72. import vibe.db.postgresql;
  73. import vibe.data.bson;
  74. //import vibe.data.json;
  75. // https://github.com/vibe-d/vibe.d/blob/master/source/vibe/vibe.d
  76. PostgresClient[] clients;
  77. import settings_toml; // get settings from settings.toml, settings keys, settings validation etc
  78. /* test unproper arguments order */
  79. /* do not */
  80. /*
  81. alias test_key1 = int;
  82. alias test_key2 = int;
  83. string test_args_types_mismash(test_key1 x, test_key2 y){
  84. return ( to!string(x) ~ " + " ~ to!string(y) ~ " = " ~ to!string( x + y ) );
  85. }
  86. */
  87. /* do not */
  88. /*
  89. import std.typecons : Typedef;
  90. alias test_key5 = Typedef!int;
  91. alias test_key6 = Typedef!int;
  92. string test_args_types_mismash(test_key5 x, test_key6 y){
  93. return ( to!string(x) ~ " + " ~ to!string(y) ~ " = " ~ to!string( x + y ) );
  94. }
  95. */
  96. /* do like */
  97. import std.typecons : Typedef;
  98. alias test_key5 = Typedef!(int, int.init, "key5");
  99. alias test_key6 = Typedef!(int, int.init, "key6");
  100. string test_args_types_mismash(test_key5 x, test_key6 y){
  101. return ( to!string(x) ~ " + " ~ to!string(y) ~ " = " ~ to!string( x + y ) );
  102. }
  103. /* or - do like */
  104. struct test_key3 { int v; }
  105. struct test_key4 { int v; }
  106. string test_args_types_mismash2(test_key3 x, test_key4 y){
  107. return ( to!string(x.v) ~ " + " ~ to!string(y.v) ~ " = " ~ to!string( x.v + y.v ) );
  108. }
  109. /*
  110. void startCleanupTask(){ // with mutex
  111. while(true){
  112. writeln("startCleanupTask();");
  113. userStateManager.cleanup();
  114. //sleep(dur!"hours"(1)); // every 1 hour = 3600 sec
  115. sleep(dur!"seconds"(30)); // every 30 sec
  116. }
  117. }
  118. */
  119. void main(){
  120. read_settings_toml(); // read settings, settings validation
  121. uint conn_num = cast(uint) toml_s[s_toml_db][s_toml_db_conn_num].integer();
  122. uint8 i = cast(uint8) conn_num;
  123. // https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING
  124. // https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS
  125. //client = new PostgresClient("host=localhost port=5432 dbname=mydb user=username password=pass connect_timeout=5", 100);
  126. while(i > 0){
  127. //writeln("i = ", i);
  128. clients ~= new PostgresClient( "host=" ~ toml_s[s_toml_db][s_toml_db_host].str() ~
  129. " port=" ~ toml_s[s_toml_db][s_toml_db_port].str() ~
  130. " dbname=" ~ toml_s[s_toml_db][s_toml_db_name].str() ~
  131. " user=" ~ toml_s[s_toml_db][s_toml_db_user].str() ~
  132. " password=" ~ toml_s[s_toml_db][s_toml_db_pass].str() ~
  133. " connect_timeout=" ~ toml_s[s_toml_db][s_toml_db_conn_timeout].str(),
  134. conn_num,
  135. (scope Connection conn){
  136. conn.prepareEx("get_city_by_id", "SELECT id, name, population FROM test WHERE id = $1");
  137. } );
  138. i--;
  139. }
  140. /*
  141. userStateManager = new UserStateManager(); // with mutex
  142. userStateManager.addOrUpdate("123");
  143. */
  144. auto settings = new HTTPServerSettings;
  145. //settings.port = 8080;
  146. //settings.bindAddresses = ["::1", "127.0.0.1"];
  147. settings.port = cast(ushort)toml_s[s_toml_http][s_toml_http_port].integer();
  148. settings.bindAddresses = [ toml_s[s_toml_http][s_toml_http_host].str().idup ];
  149. //auto fsettings = new HTTPFileServerSettings;
  150. //fsettings.serverPathPrefix = "/static";
  151. auto router = new URLRouter;
  152. //router.get("/", &index);
  153. ////router.get("static/*", serverStaticFiles("public/", fsettings) );
  154. //router.get("/", staticTemplate!"index.html");
  155. router.get("/", serveStaticFile("public/index.html") ); // static html + ws echo example
  156. router.get("/ws", handleWebSockets(&ws_handle) ); // static html + ws echo example
  157. router.get("/test", &test); // Mustache template + memcached + postgresql pool example
  158. router.get("/ws_login_test", handleWebSockets(&ws_bert_handle) ); // ws handler begins from "ws_" and next same http page path // login - logged - logout -- via ws with bert
  159. router.get("/login_test", &login_test); // login - logged - logout -- via ws with bert
  160. router.get("*", serveStaticFiles("public/"));
  161. //auto listener = listenHTTP(settings, &hello);
  162. auto listener = listenHTTP(settings, router);
  163. scope (exit){
  164. listener.stopListening();
  165. }
  166. //writeln("userStateManager.states is null? ", userStateManager.states is null);
  167. //sleep(dur!"seconds"(1));
  168. //spawn(&startCleanupTask); // clean inactive user state -- with mutex
  169. logInfo("Please open http://127.0.0.1:8080/ in your browser.");
  170. runApplication();
  171. }
  172. void ws_handle(scope WebSocket sock){
  173. // simple echo server + :)
  174. //writeln("sock = ", sock); // vibe.http.websockets.WebSocket
  175. //writeln("sock.request = ", sock.request); // GET /ws?client_id=YaHoAnZo3JPYOwX7yn35 HTTP/1.1
  176. //writeln("sock.request.requestPath = ", sock.request.requestPath); // /ws
  177. //writeln("sock.request.queryString = ", sock.request.queryString); // client_id=YaHoAnZo3JPYOwX7yn35
  178. //writeln("sock.request.query = ", sock.request.query); // ["client_id": "YaHoAnZo3JPYOwX7yn35"]
  179. string client_id = "";
  180. if("client_id" in sock.request.query){
  181. client_id = sock.request.query["client_id"];
  182. //writeln("found client_id = ", client_id);
  183. }else{
  184. //writeln("client_id not found");
  185. throw new StringException("client_id not found");
  186. }
  187. /*
  188. bool is_new_client = true;
  189. if(userStateManager.contains(client_id)){ // used with mutex
  190. is_new_client = false;
  191. writeln("Reconnection from existing client: ", client_id);
  192. }else{
  193. writeln("New client connected: ", client_id);
  194. }
  195. userStateManager.addOrUpdate(client_id);
  196. */
  197. /*
  198. try{
  199. while(sock.connected){
  200. auto msg = sock.receiveText();
  201. sock.send(msg ~ " :)");
  202. }
  203. }catch(Exception ex){
  204. writeln("disconnected client_id = ", client_id);
  205. writeln("Error: ", ex.msg); // Error: Connection closed while reading message.
  206. }
  207. */
  208. while(sock.waitForData()){
  209. auto msg = sock.receiveText();
  210. sock.send(msg ~ " :)");
  211. }
  212. writeln("after disconnect"); // now shows
  213. }
  214. /*
  215. void index(HTTPServerRequest req, HTTPServerResponse res){
  216. res.writeBody("Hello, World!");
  217. }
  218. */
  219. /*
  220. void hello(HTTPServerRequest req, HTTPServerResponse res){
  221. res.writeBody("Hello, World!");
  222. }
  223. */
  224. /*
  225. void test_pg_conn_driver(){ // https://github.com/denizzzka/vibe.d.db.postgresql/blob/master/example/example.d#L13
  226. client.pickConnection( (scope conn){
  227. immutable result = conn.exec(
  228. "SELECT 123 as first_num, 567 as second_num, 'abc'::text as third_text " ~
  229. "UNION ALL " ~
  230. "SELECT 890, 233, 'fgh'::text as third_text",
  231. ValueFormat.BINARY
  232. );
  233. assert(result[0]["second_num"].as!PGinteger == 567);
  234. assert(result[1]["third_text"].as!PGtext == "fgh");
  235. foreach (val; rangify(result[0])){
  236. writeln("Found entry: ", val.as!Bson.toJson);
  237. }
  238. } );
  239. }
  240. */
  241. string get_all_cities(){
  242. return "SELECT id, name, population FROM test ORDER BY id";
  243. }
  244. void test_pg_conn_driver_queries(){
  245. /*
  246. client.pickConnection( (scope conn){
  247. QueryParams params; // https://github.com/denizzzka/dpq2/blob/master/src/dpq2/args.d#L15
  248. params.preparedStatementName = "get_city_by_id";
  249. params.argsVariadic(3); // https://github.com/denizzzka/dpq2/blob/master/example/example.d#L42 // https://github.com/denizzzka/dpq2/blob/master/src/dpq2/query.d#L336 // https://github.com/denizzzka/vibe.d.db.postgresql/blob/master/source/vibe/db/postgresql/package.d#L423
  250. conn.prepareEx(params.preparedStatementName, "SELECT id, name, population FROM test WHERE id = $1"); // get_city_by_id
  251. auto result1 = conn.execPrepared(params);
  252. writeln("id: ", result1[0]["id"].as!PGinteger);
  253. writeln("name: ", result1[0]["name"].as!PGtext);
  254. writeln("population: ", result1[0]["population"].as!PGinteger);
  255. //conn.prepareEx("q1", "UPDATE test SET name = $1, population = $2 WHERE id = $3"); // update_city_by_id
  256. //immutable result1 = conn.execPrepared("", ValueFormat.BINARY);
  257. destroy(conn);
  258. } );
  259. */
  260. //auto conn = clients[0].lockConnection(); // todo think is this correct to take index 0 here
  261. clients[0].pickConnection( (scope conn){ // todo think is this correct to take index 0 here
  262. QueryParams params; // https://github.com/denizzzka/dpq2/blob/master/src/dpq2/args.d#L15
  263. params.preparedStatementName = "get_city_by_id";
  264. params.argsVariadic(3); // https://github.com/denizzzka/dpq2/blob/master/example/example.d#L42 // https://github.com/denizzzka/dpq2/blob/master/src/dpq2/query.d#L336 // https://github.com/denizzzka/vibe.d.db.postgresql/blob/master/source/vibe/db/postgresql/package.d#L423
  265. //conn.prepareEx(params.preparedStatementName, "SELECT id, name, population FROM test WHERE id = $1"); // get_city_by_id
  266. auto result1 = conn.execPrepared(params);
  267. writeln("id: ", result1[0]["id"].as!PGinteger);
  268. writeln("name: ", result1[0]["name"].as!PGtext);
  269. writeln("population: ", result1[0]["population"].as!PGinteger);
  270. //conn.prepareEx("q1", "UPDATE test SET name = $1, population = $2 WHERE id = $3"); // update_city_by_id
  271. //immutable result1 = conn.execPrepared("", ValueFormat.BINARY);
  272. destroy(conn);
  273. } );
  274. }
  275. /*
  276. "SELECT id, name, population FROM test ORDER BY id"
  277. "UPDATE test SET name = $1, population = $2 WHERE id = $3", [City_Name, City_Pop, City_Id]
  278. "INSERT INTO test (name, population) VALUES ($1, $2)", [City_Name, City_Pop]
  279. "INSERT INTO test (name, population) VALUES ($1, $2) RETURNING id", [City_Name, City_Pop]
  280. "DELETE FROM test WHERE id = $1", [City_Id]
  281. */
  282. void test(HTTPServerRequest req, HTTPServerResponse res){
  283. auto cache = memcachedConnect("127.0.0.1:11211");
  284. /* test unproper arguments order */
  285. /* do not */
  286. /*
  287. test_key1 x = 5;
  288. test_key2 y = 2;
  289. //string r1 = test_args_types_mismash(x, y); // proper arguments order
  290. string r1 = test_args_types_mismash(y, x); // unproper - this compiles but we got logic error bug in runtime.. :( use struct for compiler check this..
  291. writeln("r1 = ", r1);
  292. */
  293. /* do not */
  294. /*
  295. test_key5 x = 5;
  296. test_key6 y = 2;
  297. //string r3 = test_args_types_mismash(x, y); // proper arguments order
  298. string r3 = test_args_types_mismash(y, x); // unproper - this compiles but we got logic error bug in runtime.. :( use struct for compiler check this..
  299. writeln("r3 = ", r3);
  300. */
  301. /* do like */
  302. test_key5 x = 5;
  303. test_key6 y = 2;
  304. string r3 = test_args_types_mismash(x, y); // proper arguments order
  305. //string r3 = test_args_types_mismash(y, x); // unproper - this not compiles
  306. writeln("r3 = ", r3);
  307. /* or - do like */
  308. test_key3 x2 = test_key3(5);
  309. test_key4 y2 = test_key4(2);
  310. string r2 = test_args_types_mismash2(x2, y2); // proper arguments order
  311. //string r2 = test_args_types_mismash2(y2, x2); // unproper - this not compiles
  312. writeln("r2 = ", r2);
  313. Language Lang = Language.uk;
  314. writeln("tr 1 = ", Tr(Lang, TKey.hello));
  315. writeln("tr 2 = ", Tr(Lang, TKey.welcome, ["username"], 0) );
  316. writeln("tr 3 = ", Tr(Lang, TKey.apples, [], 1) );
  317. writeln("tr 3 = ", Tr(Lang, TKey.apples, [], 2) ) ;
  318. writeln("tr 3 = ", Tr(Lang, TKey.apples, [], 5) );
  319. writeln("tr 4 = ", Tr(Lang, TKey.apples_n_oranges, ["6", "7"], 0) );
  320. writeln("get test1 = ", cache.get!string("test1"));
  321. string v1 = "value1 = 🔥🦀";
  322. if(cache.store("test1", v1) == RETURN_STATE.SUCCESS ){
  323. writeln("stored successfully");
  324. writeln("get stored: ", cache.get!string("test1") );
  325. }else{
  326. writeln("not stored");
  327. }
  328. string result = cache.get!string("test1");
  329. writeln("get test1 = ", result);
  330. writeln(cache.del("test1"));
  331. //test_pg_conn_driver();
  332. test_pg_conn_driver_queries();
  333. Mustache mustache2;
  334. auto context2 = new Mustache.Context;
  335. mustache2.path = "priv/folder2";
  336. mustache2.ext = "dtl";
  337. context2["param2"] = "blah blah blah ";
  338. Mustache mustache;
  339. auto context = new Mustache.Context;
  340. mustache.path = "priv";
  341. mustache.ext = "dtl";
  342. //context.useSection("boolean");
  343. //assert(mustache.renderString(" {{#boolean}}YES{{/boolean}}\n {{#boolean}}GOOD{{/boolean}}\n", context) == " YES\n GOOD\n");
  344. //{{escaped_html_tags}}
  345. //{{{not_escaped_html_tags}}}
  346. //{{#repo}}<b>{{name}}</b>{{/repo}}
  347. //{{^repo}}No repos :({{/repo}}
  348. // to
  349. //No repos :(
  350. context["lang"] = "en";
  351. context["number1"] = 42;
  352. context.useSection("maybe1");
  353. context["part1"] = mustache2.render("part1", context2);
  354. context["result1"] = "Hello, World!\n" ~ result;
  355. res.headers["Content-Type"] = "text/html; charset=utf-8";
  356. //res.writeBody("Hello, World!\n" ~ result);
  357. res.writeBody( mustache.render("main", context) );
  358. }