app.d 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  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 memcached_test : memcached_test; // memcached example
  12. import std.string;
  13. import std.array;
  14. import std.algorithm;
  15. import std.variant : Variant;
  16. import std.datetime : SysTime, Clock;
  17. //import core.sync.mutex : Mutex;
  18. import std.concurrency : spawn;
  19. import vibe.core.concurrency;
  20. import vibe.core.core : Task, sleep;
  21. import core.time : Duration, dur;
  22. /+
  23. Tid user_state_pid; // with dlang message passing like in Erlang -- err here - do not tick every 30 sec
  24. struct UserState2{
  25. string client_id;
  26. SysTime last_online_at;
  27. }
  28. alias UserStateMap2 = UserState2[string];
  29. UserStateMap2 user_states;
  30. struct Msg1{
  31. uint8 command;
  32. }
  33. /*
  34. struct Msg2{
  35. Tid sender;
  36. string client_id;
  37. }
  38. */
  39. struct Msg21{
  40. uint8 command;
  41. string client_id;
  42. }
  43. struct Msg3{
  44. Tid sender;
  45. uint8 command;
  46. string client_id;
  47. }
  48. +/
  49. /*
  50. struct UserState{ // with mutex
  51. string client_id;
  52. SysTime last_online_at;
  53. this(string client_id, SysTime last_online_at) pure nothrow @safe{
  54. this.client_id = client_id;
  55. this.last_online_at = last_online_at;
  56. }
  57. }
  58. alias UserStateMap = UserState[string];
  59. class UserStateManager{ // with mutex
  60. private UserStateMap states;
  61. private Object lockObj;
  62. this(){
  63. lockObj = new Object();
  64. }
  65. bool contains(string client_id){
  66. synchronized(lockObj){
  67. return (client_id in states) !is null;
  68. }
  69. }
  70. void addOrUpdate(string client_id){
  71. synchronized(lockObj){
  72. auto now = Clock.currTime();
  73. states.remove(client_id);
  74. states[client_id] = UserState(client_id, now);
  75. }
  76. }
  77. void cleanup(){
  78. synchronized(lockObj){
  79. auto now = Clock.currTime();
  80. auto toRemove = states.byKey
  81. .filter!(k => (now - states[k].last_online_at).total!"seconds" > 30)
  82. .array;
  83. foreach(client_id; toRemove){
  84. states.remove(client_id);
  85. }
  86. }
  87. }
  88. size_t length() const{
  89. synchronized(lockObj){
  90. return states.length;
  91. }
  92. }
  93. }
  94. __gshared UserStateManager userStateManager;
  95. */
  96. import std.conv : to;
  97. import tr;
  98. import mustache;
  99. alias MustacheEngine!(string) Mustache;
  100. import vibe.db.postgresql;
  101. import vibe.data.bson;
  102. //import vibe.data.json;
  103. // https://github.com/vibe-d/vibe.d/blob/master/source/vibe/vibe.d
  104. PostgresClient[] clients;
  105. import settings_toml; // get settings from settings.toml, settings keys, settings validation etc
  106. /* test unproper arguments order */
  107. /* do not */
  108. /*
  109. alias test_key1 = int;
  110. alias test_key2 = int;
  111. string test_args_types_mismash(test_key1 x, test_key2 y){
  112. return ( to!string(x) ~ " + " ~ to!string(y) ~ " = " ~ to!string( x + y ) );
  113. }
  114. */
  115. /* do not */
  116. /*
  117. import std.typecons : Typedef;
  118. alias test_key5 = Typedef!int;
  119. alias test_key6 = Typedef!int;
  120. string test_args_types_mismash(test_key5 x, test_key6 y){
  121. return ( to!string(x) ~ " + " ~ to!string(y) ~ " = " ~ to!string( x + y ) );
  122. }
  123. */
  124. /* do like */
  125. import std.typecons : Typedef;
  126. alias test_key5 = Typedef!(int, int.init, "key5");
  127. alias test_key6 = Typedef!(int, int.init, "key6");
  128. string test_args_types_mismash(test_key5 x, test_key6 y){
  129. return ( to!string(x) ~ " + " ~ to!string(y) ~ " = " ~ to!string( x + y ) );
  130. }
  131. /* or - do like */
  132. struct test_key3 { int v; }
  133. struct test_key4 { int v; }
  134. string test_args_types_mismash2(test_key3 x, test_key4 y){
  135. return ( to!string(x.v) ~ " + " ~ to!string(y.v) ~ " = " ~ to!string( x.v + y.v ) );
  136. }
  137. /*
  138. void startCleanupTask(){ // with mutex
  139. while(true){
  140. writeln("startCleanupTask();");
  141. userStateManager.cleanup();
  142. //sleep(dur!"hours"(1)); // every 1 hour = 3600 sec
  143. sleep(dur!"seconds"(30)); // every 30 sec
  144. }
  145. }
  146. */
  147. /+
  148. void startCleanupTask2(){ // with message passing (actors model like in Erlang); worker -- err here - do not tick every 30 sec
  149. writeln("startCleanupTask2();");
  150. while(true){
  151. /*
  152. auto msg = receiveOnly!(Tid, uint8, string)();
  153. writeln("int: ", msg[1]); // got int
  154. writeln("string: ", msg[2]); // got string
  155. msg[0].send(thisTid); // send message back
  156. */
  157. receive(
  158. (Msg1 msg){
  159. if(msg.command == 0){ // 0 for delete inactive clients
  160. writeln("221 ", user_states.keys); // show all keys
  161. auto now = Clock.currTime();
  162. auto toRemove = user_states.byKey
  163. .filter!(k => (now - user_states[k].last_online_at).total!"seconds" > 30) // clean every 30 seconds
  164. .array;
  165. foreach(client_id; toRemove){
  166. user_states.remove(client_id);
  167. }
  168. }
  169. return true;
  170. },
  171. /*
  172. (Msg2 msg){
  173. msg.sender
  174. msg.client_id
  175. },
  176. */
  177. (Msg21 msg){
  178. if(msg.command == 2){ // 2 for add_or_upd client
  179. //auto now = Clock.currTime();
  180. user_states[msg.client_id] = UserState2( client_id : msg.client_id, last_online_at : Clock.currTime() );
  181. //writeln("user_states.length = ", user_states.length);
  182. //writeln(user_states.keys); // show all keys
  183. //writeln(user_states.values); // show all values
  184. }else if(msg.command == 3){ // 3 for delete client
  185. user_states.remove(msg.client_id);
  186. }
  187. return true;
  188. },
  189. (Msg3 msg){
  190. if(msg.command == 1){ // 1 for check is new client
  191. //msg.sender.send( (msg.client_id in user_states) !is null ); // is client exists
  192. msg.sender.send( (msg.client_id in user_states) is null ); // is new client
  193. }
  194. //}else if(msg.command == 4){ // 4 for get length
  195. // msg.sender.send(user_states.length);
  196. //}
  197. return true;
  198. },
  199. (Variant v){
  200. writeln("got unexpected Variant v: ", v);
  201. return true;
  202. }
  203. );
  204. }
  205. }
  206. void startCleanupTask21(){ // clean daemon
  207. while(true){
  208. writeln("do clean!");
  209. writeln("273", user_states.keys); // show all keys
  210. user_state_pid.send(Msg1(0)); // 0 for delete inactive clients
  211. writeln("275", user_states.keys); // show all keys
  212. sleep(dur!"seconds"(30)); // clean every 30 seconds
  213. }
  214. }
  215. +/
  216. void main(){
  217. read_settings_toml(); // read settings, settings validation
  218. uint conn_num = cast(uint) toml_s[s_toml_db][s_toml_db_conn_num].integer();
  219. uint8 i = cast(uint8) conn_num;
  220. // https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING
  221. // https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS
  222. //client = new PostgresClient("host=localhost port=5432 dbname=mydb user=username password=pass connect_timeout=5", 100);
  223. while(i > 0){
  224. //writeln("i = ", i);
  225. clients ~= new PostgresClient( "host=" ~ toml_s[s_toml_db][s_toml_db_host].str() ~
  226. " port=" ~ toml_s[s_toml_db][s_toml_db_port].str() ~
  227. " dbname=" ~ toml_s[s_toml_db][s_toml_db_name].str() ~
  228. " user=" ~ toml_s[s_toml_db][s_toml_db_user].str() ~
  229. " password=" ~ toml_s[s_toml_db][s_toml_db_pass].str() ~
  230. " connect_timeout=" ~ toml_s[s_toml_db][s_toml_db_conn_timeout].str(),
  231. conn_num,
  232. (scope Connection conn){
  233. conn.prepareEx("get_city_by_id", "SELECT id, name, population FROM test WHERE id = $1");
  234. } );
  235. i--;
  236. }
  237. /*
  238. userStateManager = new UserStateManager(); // with mutex
  239. userStateManager.addOrUpdate("123");
  240. */
  241. auto settings = new HTTPServerSettings;
  242. //settings.port = 8080;
  243. //settings.bindAddresses = ["::1", "127.0.0.1"];
  244. settings.port = cast(ushort)toml_s[s_toml_http][s_toml_http_port].integer();
  245. settings.bindAddresses = [ toml_s[s_toml_http][s_toml_http_host].str().idup ];
  246. //auto fsettings = new HTTPFileServerSettings;
  247. //fsettings.serverPathPrefix = "/static";
  248. auto router = new URLRouter;
  249. //router.get("/", &index);
  250. ////router.get("static/*", serverStaticFiles("public/", fsettings) );
  251. //router.get("/", staticTemplate!"index.html");
  252. router.get("/", serveStaticFile("public/index.html") ); // static html + ws echo example
  253. router.get("/ws", handleWebSockets(&ws_handle) ); // static html + ws echo example
  254. router.get("/test", &test); // Mustache template + postgresql pool example
  255. 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
  256. router.get("/login_test", &login_test); // login - logged - logout -- via ws with bert
  257. router.get("/memcached_test", &memcached_test); // memcached example
  258. router.get("*", serveStaticFiles("public/"));
  259. //auto listener = listenHTTP(settings, &hello);
  260. auto listener = listenHTTP(settings, router);
  261. scope (exit){
  262. listener.stopListening();
  263. }
  264. //writeln("userStateManager.states is null? ", userStateManager.states is null);
  265. //sleep(dur!"seconds"(1));
  266. //spawn(&startCleanupTask); // clean inactive user state -- with mutex
  267. /*
  268. user_state_pid = spawn(&startCleanupTask2); // worker - clean inactive user state -- with message passing (actors model like in Erlang) -- err here - do not tick every 30 sec
  269. spawn(&startCleanupTask21); // daemon for worker
  270. //auto clean_daemon_pid = spawn(&startCleanupTask21, thisTid);
  271. sleep(dur!"seconds"(5));
  272. */
  273. logInfo("Please open http://127.0.0.1:8080/ in your browser.");
  274. runApplication();
  275. }
  276. void ws_handle(scope WebSocket sock){
  277. // simple echo server + :)
  278. //writeln("sock = ", sock); // vibe.http.websockets.WebSocket
  279. //writeln("sock.request = ", sock.request); // GET /ws?client_id=YaHoAnZo3JPYOwX7yn35 HTTP/1.1
  280. //writeln("sock.request.requestPath = ", sock.request.requestPath); // /ws
  281. //writeln("sock.request.queryString = ", sock.request.queryString); // client_id=YaHoAnZo3JPYOwX7yn35
  282. //writeln("sock.request.query = ", sock.request.query); // ["client_id": "YaHoAnZo3JPYOwX7yn35"]
  283. string client_id = "";
  284. if("client_id" in sock.request.query){
  285. client_id = sock.request.query["client_id"];
  286. //writeln("found client_id = ", client_id);
  287. }else{
  288. //writeln("client_id not found");
  289. throw new StringException("client_id not found");
  290. }
  291. /*
  292. bool is_new_client = true;
  293. if(userStateManager.contains(client_id)){ // used with mutex
  294. is_new_client = false;
  295. writeln("Reconnection from existing client: ", client_id);
  296. }else{
  297. writeln("New client connected: ", client_id);
  298. }
  299. userStateManager.addOrUpdate(client_id);
  300. */
  301. /*
  302. user_state_pid.send(Msg3(thisTid, 1, client_id)); // 1 for check is client exists -- err here - do not tick every 30 sec
  303. //enforce(receiveOnly!Tid() == tid);
  304. //auto response = receiveOnly!(Tid, bool)(); // response[0] = Tid of worker; response[1] = bool result
  305. bool is_new_client = receiveOnly!bool();
  306. if(is_new_client){
  307. writeln("New client connected: ", client_id);
  308. }else{
  309. writeln("Reconnection from existing client: ", client_id);
  310. }
  311. user_state_pid.send(Msg21(2, client_id));
  312. */
  313. /*
  314. try{
  315. while(sock.connected){
  316. auto msg = sock.receiveText();
  317. sock.send(msg ~ " :)");
  318. }
  319. }catch(Exception ex){
  320. writeln("disconnected client_id = ", client_id);
  321. writeln("Error: ", ex.msg); // Error: Connection closed while reading message.
  322. }
  323. */
  324. while(sock.waitForData()){
  325. auto msg = sock.receiveText();
  326. sock.send(msg ~ " :)");
  327. }
  328. writeln("after disconnect"); // now shows
  329. }
  330. /*
  331. void index(HTTPServerRequest req, HTTPServerResponse res){
  332. res.writeBody("Hello, World!");
  333. }
  334. */
  335. /*
  336. void hello(HTTPServerRequest req, HTTPServerResponse res){
  337. res.writeBody("Hello, World!");
  338. }
  339. */
  340. /*
  341. void test_pg_conn_driver(){ // https://github.com/denizzzka/vibe.d.db.postgresql/blob/master/example/example.d#L13
  342. client.pickConnection( (scope conn){
  343. immutable result = conn.exec(
  344. "SELECT 123 as first_num, 567 as second_num, 'abc'::text as third_text " ~
  345. "UNION ALL " ~
  346. "SELECT 890, 233, 'fgh'::text as third_text",
  347. ValueFormat.BINARY
  348. );
  349. assert(result[0]["second_num"].as!PGinteger == 567);
  350. assert(result[1]["third_text"].as!PGtext == "fgh");
  351. foreach (val; rangify(result[0])){
  352. writeln("Found entry: ", val.as!Bson.toJson);
  353. }
  354. } );
  355. }
  356. */
  357. string get_all_cities(){
  358. return "SELECT id, name, population FROM test ORDER BY id";
  359. }
  360. void test_pg_conn_driver_queries(){
  361. /*
  362. client.pickConnection( (scope conn){
  363. QueryParams params; // https://github.com/denizzzka/dpq2/blob/master/src/dpq2/args.d#L15
  364. params.preparedStatementName = "get_city_by_id";
  365. 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
  366. conn.prepareEx(params.preparedStatementName, "SELECT id, name, population FROM test WHERE id = $1"); // get_city_by_id
  367. auto result1 = conn.execPrepared(params);
  368. writeln("id: ", result1[0]["id"].as!PGinteger);
  369. writeln("name: ", result1[0]["name"].as!PGtext);
  370. writeln("population: ", result1[0]["population"].as!PGinteger);
  371. //conn.prepareEx("q1", "UPDATE test SET name = $1, population = $2 WHERE id = $3"); // update_city_by_id
  372. //immutable result1 = conn.execPrepared("", ValueFormat.BINARY);
  373. destroy(conn);
  374. } );
  375. */
  376. //auto conn = clients[0].lockConnection(); // todo think is this correct to take index 0 here
  377. clients[0].pickConnection( (scope conn){ // todo think is this correct to take index 0 here
  378. QueryParams params; // https://github.com/denizzzka/dpq2/blob/master/src/dpq2/args.d#L15
  379. params.preparedStatementName = "get_city_by_id";
  380. 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
  381. //conn.prepareEx(params.preparedStatementName, "SELECT id, name, population FROM test WHERE id = $1"); // get_city_by_id
  382. auto result1 = conn.execPrepared(params);
  383. writeln("id: ", result1[0]["id"].as!PGinteger);
  384. writeln("name: ", result1[0]["name"].as!PGtext);
  385. writeln("population: ", result1[0]["population"].as!PGinteger);
  386. //conn.prepareEx("q1", "UPDATE test SET name = $1, population = $2 WHERE id = $3"); // update_city_by_id
  387. //immutable result1 = conn.execPrepared("", ValueFormat.BINARY);
  388. destroy(conn);
  389. } );
  390. }
  391. /*
  392. "SELECT id, name, population FROM test ORDER BY id"
  393. "UPDATE test SET name = $1, population = $2 WHERE id = $3", [City_Name, City_Pop, City_Id]
  394. "INSERT INTO test (name, population) VALUES ($1, $2)", [City_Name, City_Pop]
  395. "INSERT INTO test (name, population) VALUES ($1, $2) RETURNING id", [City_Name, City_Pop]
  396. "DELETE FROM test WHERE id = $1", [City_Id]
  397. */
  398. void test(HTTPServerRequest req, HTTPServerResponse res){
  399. /* test unproper arguments order */
  400. /* do not */
  401. /*
  402. test_key1 x = 5;
  403. test_key2 y = 2;
  404. //string r1 = test_args_types_mismash(x, y); // proper arguments order
  405. 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..
  406. writeln("r1 = ", r1);
  407. */
  408. /* do not */
  409. /*
  410. test_key5 x = 5;
  411. test_key6 y = 2;
  412. //string r3 = test_args_types_mismash(x, y); // proper arguments order
  413. 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..
  414. writeln("r3 = ", r3);
  415. */
  416. /* do like */
  417. test_key5 x = 5;
  418. test_key6 y = 2;
  419. string r3 = test_args_types_mismash(x, y); // proper arguments order
  420. //string r3 = test_args_types_mismash(y, x); // unproper - this not compiles
  421. writeln("r3 = ", r3);
  422. /* or - do like */
  423. test_key3 x2 = test_key3(5);
  424. test_key4 y2 = test_key4(2);
  425. string r2 = test_args_types_mismash2(x2, y2); // proper arguments order
  426. //string r2 = test_args_types_mismash2(y2, x2); // unproper - this not compiles
  427. writeln("r2 = ", r2);
  428. // i18n example
  429. Language Lang = Language.uk;
  430. writeln("tr 1 = ", Tr(Lang, TKey.hello));
  431. writeln("tr 2 = ", Tr(Lang, TKey.welcome, ["username"], 0) );
  432. writeln("tr 3 = ", Tr(Lang, TKey.apples, [], 1) );
  433. writeln("tr 3 = ", Tr(Lang, TKey.apples, [], 2) ) ;
  434. writeln("tr 3 = ", Tr(Lang, TKey.apples, [], 5) );
  435. writeln("tr 4 = ", Tr(Lang, TKey.apples_n_oranges, ["6", "7"], 0) );
  436. //test_pg_conn_driver();
  437. test_pg_conn_driver_queries();
  438. Mustache mustache2;
  439. auto context2 = new Mustache.Context;
  440. mustache2.path = "priv/folder2";
  441. mustache2.ext = "dtl";
  442. context2["param2"] = "blah blah blah ";
  443. Mustache mustache;
  444. auto context = new Mustache.Context;
  445. mustache.path = "priv";
  446. mustache.ext = "dtl";
  447. //context.useSection("boolean");
  448. //assert(mustache.renderString(" {{#boolean}}YES{{/boolean}}\n {{#boolean}}GOOD{{/boolean}}\n", context) == " YES\n GOOD\n");
  449. //{{escaped_html_tags}}
  450. //{{{not_escaped_html_tags}}}
  451. //{{#repo}}<b>{{name}}</b>{{/repo}}
  452. //{{^repo}}No repos :({{/repo}}
  453. // to
  454. //No repos :(
  455. context["lang"] = "en";
  456. context["number1"] = 42;
  457. context.useSection("maybe1");
  458. context["part1"] = mustache2.render("part1", context2);
  459. context["result1"] = "Hello, World!\n";
  460. res.headers["Content-Type"] = "text/html; charset=utf-8";
  461. //res.writeBody("Hello, World!\n" ~ result);
  462. res.writeBody( mustache.render("main", context) );
  463. }