sessions.pp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  1. (* Feel free to use this example code in any way
  2. you see fit (Public Domain) *)
  3. // Original example: https://gnunet.org/svn/libmicrohttpd/doc/examples/sessions.c
  4. program sessions;
  5. {$mode objfpc}{$H+}
  6. uses
  7. SysUtils, BaseUnix, cmem, cutils, libmicrohttpd;
  8. const
  9. (**
  10. * Invalid method page.
  11. *)
  12. METHOD_ERROR = '<html><head><title>Illegal request</title></head><body>Go away.</body></html>';
  13. (**
  14. * Invalid URL page.
  15. *)
  16. NOT_FOUND_ERROR = '<html><head><title>Not found</title></head><body>Go away.</body></html>';
  17. (**
  18. * Front page. (/)
  19. *)
  20. MAIN_PAGE = '<html><head><title>Welcome</title></head><body><form action="/2" method="post">What is your name? <input type="text" name="v1" value="%s" /><input type="submit" value="Next" /></body></html>';
  21. (**
  22. * Second page. (/2)
  23. *)
  24. SECOND_PAGE = '<html><head><title>Tell me more</title></head><body><a href="/">previous</a> <form action="/S" method="post">%s, what is your job? <input type="text" name="v2" value="%s" /><input type="submit" value="Next" /></body></html>';
  25. (**
  26. * Second page (/S)
  27. *)
  28. SUBMIT_PAGE = '<html><head><title>Ready to submit?</title></head><body><form action="/F" method="post"><a href="/2">previous </a> <input type="hidden" name="DONE" value="yes" /><input type="submit" value="Submit" /></body></html>';
  29. (**
  30. * Last page.
  31. *)
  32. LAST_PAGE = '<html><head><title>Thank you</title></head><body>Thank you.</body></html>';
  33. (**
  34. * Name of our cookie.
  35. *)
  36. COOKIE_NAME = 'session';
  37. type
  38. (**
  39. * State we keep for each user/session/browser.
  40. *)
  41. PSession = ^TSession;
  42. TSession = packed record
  43. (**
  44. * We keep all sessions in a linked list.
  45. *)
  46. next: PSession;
  47. (**
  48. * Unique ID for this session.
  49. *)
  50. sid: array[0..33] of Char;
  51. (**
  52. * Reference counter giving the number of connections
  53. * currently using this session.
  54. *)
  55. rc: cint;
  56. (**
  57. * Time when this session was last active.
  58. *)
  59. start: time_t;
  60. (**
  61. * String submitted via form.
  62. *)
  63. value_1: array[0..64] of Char;
  64. (**
  65. * Another value submitted via form.
  66. *)
  67. value_2: array[0..64] of Char;
  68. end;
  69. (**
  70. * Data kept per request.
  71. *)
  72. TRequest = packed record
  73. (**
  74. * Associated session.
  75. *)
  76. session: PSession;
  77. (**
  78. * Post processor handling form data (IF this is
  79. * a POST request).
  80. *)
  81. pp: PMHD_PostProcessor;
  82. (**
  83. * URL to serve in response to this POST (if this request
  84. * was a 'POST')
  85. *)
  86. post_url: pcchar;
  87. end;
  88. PRequest = ^TRequest;
  89. var
  90. (**
  91. * Linked list of all active sessions. Yes, O(n) but a
  92. * hash table would be overkill for a simple example...
  93. *)
  94. _sessions: PSession;
  95. (**
  96. * Return the session handle for this connection, or
  97. * create one if this is a new user.
  98. *)
  99. function get_session(connection: PMHD_Connection): PSession;
  100. var
  101. ret: PSession;
  102. cookie: pcchar;
  103. begin
  104. cookie := MHD_lookup_connection_value(connection, MHD_COOKIE_KIND, COOKIE_NAME);
  105. if cookie <> nil then
  106. begin
  107. (* find existing session *)
  108. ret := _sessions;
  109. while nil <> ret do
  110. begin
  111. if StrComp(cookie, ret^.sid) = 0 then
  112. Break;
  113. ret := ret^.next;
  114. end;
  115. if nil <> ret then
  116. begin
  117. Inc(ret^.rc);
  118. Exit(ret);
  119. end;
  120. end;
  121. (* create fresh session *)
  122. ret := CAlloc(1, SizeOf(TSession));
  123. if nil = ret then
  124. begin
  125. WriteLn(stderr, 'calloc error: ', strerror(errno^));
  126. Exit(nil);
  127. end;
  128. (* not a super-secure way to generate a random session ID,
  129. but should do for a simple example... *)
  130. snprintf(ret^.sid, SizeOf(ret^.sid), '%X%X%X%X', Cardinal(rand),
  131. Cardinal(rand), Cardinal(rand), Cardinal(rand));
  132. Inc(ret^.rc);
  133. ret^.start := FpTime;
  134. ret^.next := _sessions;
  135. _sessions := ret;
  136. Result := ret;
  137. end;
  138. (**
  139. * Type of handler that generates a reply.
  140. *
  141. * @param cls content for the page (handler-specific)
  142. * @param mime mime type to use
  143. * @param session session information
  144. * @param connection connection to process
  145. * @param MHD_YES on success, MHD_NO on failure
  146. *)
  147. type
  148. TPageHandler = function(cls: Pointer; mime: Pcchar; session: PSession;
  149. connection: PMHD_Connection): LongInt; cdecl;
  150. (**
  151. * Entry we generate for each page served.
  152. *)
  153. { TPage }
  154. TPage = packed record
  155. (**
  156. * Acceptable URL for this page.
  157. *)
  158. url: Pcchar;
  159. (**
  160. * Mime type to set for the page.
  161. *)
  162. mime: Pcchar;
  163. (**
  164. * Handler to call to generate response.
  165. *)
  166. handler: TPageHandler;
  167. (**
  168. * Extra argument to handler.
  169. *)
  170. handler_cls: Pcchar;
  171. end;
  172. (**
  173. * Add header to response to set a session cookie.
  174. *
  175. * @param session session to use
  176. * @param response response to modify
  177. *)
  178. procedure add_session_cookie(session: PSession; response: PMHD_Response);
  179. var
  180. cstr: array[0..256] of Char;
  181. begin
  182. snprintf(cstr, SizeOf(cstr), '%s=%s', COOKIE_NAME, session^.sid);
  183. if MHD_NO =
  184. MHD_add_response_header(response, MHD_HTTP_HEADER_SET_COOKIE, cstr) then
  185. WriteLn(stderr, 'Failed to set session cookie header!');
  186. end;
  187. (**
  188. * Handler that returns a simple static HTTP page that
  189. * is passed in via 'cls'.
  190. *
  191. * @param cls a 'const char *' with the HTML webpage to return
  192. * @param mime mime type to use
  193. * @param session session handle
  194. * @param connection connection to use
  195. *)
  196. function serve_simple_form(cls: Pointer; mime: Pcchar; session: PSession;
  197. connection: PMHD_Connection): cint; cdecl;
  198. var
  199. ret: cint;
  200. form: Pcchar;
  201. response: PMHD_Response;
  202. begin
  203. form := cls;
  204. (* return static form *)
  205. response := MHD_create_response_from_buffer(Length(form), Pointer(form),
  206. MHD_RESPMEM_PERSISTENT);
  207. add_session_cookie(session, response);
  208. MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_ENCODING, mime);
  209. ret := MHD_queue_response(connection, MHD_HTTP_OK, response);
  210. MHD_destroy_response(response);
  211. Result := ret;
  212. end;
  213. (**
  214. * Handler that adds the 'v1' value to the given HTML code.
  215. *
  216. * @param cls a 'const char *' with the HTML webpage to return
  217. * @param mime mime type to use
  218. * @param session session handle
  219. * @param connection connection to use
  220. *)
  221. function fill_v1_form(cls: Pointer; mime: Pcchar; session: PSession;
  222. connection: PMHD_Connection): cint; cdecl;
  223. var
  224. ret: cint;
  225. form: Pcchar;
  226. reply: Pcchar;
  227. response: PMHD_Response;
  228. begin
  229. form := cls;
  230. if asprintf(@reply, form, session^.value_1) = -1 then
  231. (* oops *)
  232. Exit(MHD_NO);
  233. (* return static form *)
  234. response := MHD_create_response_from_buffer(Length(reply), Pointer(reply),
  235. MHD_RESPMEM_MUST_FREE);
  236. add_session_cookie(session, response);
  237. MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_ENCODING, mime);
  238. ret := MHD_queue_response(connection, MHD_HTTP_OK, response);
  239. MHD_destroy_response(response);
  240. Result := ret;
  241. end;
  242. (**
  243. * Handler that adds the 'v1' and 'v2' values to the given HTML code.
  244. *
  245. * @param cls a 'const char *' with the HTML webpage to return
  246. * @param mime mime type to use
  247. * @param session session handle
  248. * @param connection connection to use
  249. *)
  250. function fill_v1_v2_form(cls: Pointer; mime: Pcchar; session: PSession;
  251. connection: PMHD_Connection): cint; cdecl;
  252. var
  253. ret: cint;
  254. form: Pcchar;
  255. reply: Pcchar;
  256. response: PMHD_Response;
  257. begin
  258. form := cls;
  259. if asprintf(@reply, form, session^.value_1, session^.value_2) = -1 then
  260. (* oops *)
  261. Exit(MHD_NO);
  262. (* return static form *)
  263. response := MHD_create_response_from_buffer(Length(reply), Pointer(reply),
  264. MHD_RESPMEM_MUST_FREE);
  265. add_session_cookie(session, response);
  266. MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_ENCODING, mime);
  267. ret := MHD_queue_response(connection, MHD_HTTP_OK, response);
  268. MHD_destroy_response(response);
  269. Result := ret;
  270. end;
  271. (**
  272. * Handler used to generate a 404 reply.
  273. *
  274. * @param cls a 'const char *' with the HTML webpage to return
  275. * @param mime mime type to use
  276. * @param session session handle
  277. * @param connection connection to use
  278. *)
  279. function not_found_page(cls: Pointer; mime: Pcchar; session: PSession;
  280. connection: PMHD_Connection): cint; cdecl;
  281. var
  282. ret: cint;
  283. response: PMHD_Response;
  284. begin
  285. (* unsupported HTTP method *)
  286. response := MHD_create_response_from_buffer(Length(NOT_FOUND_ERROR),
  287. Pcchar(NOT_FOUND_ERROR), MHD_RESPMEM_PERSISTENT);
  288. ret := MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response);
  289. MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_ENCODING, mime);
  290. MHD_destroy_response(response);
  291. Result := ret;
  292. end;
  293. const
  294. (**
  295. * List of all pages served by this HTTP server.
  296. *)
  297. pages: array[0..4] of TPage = (
  298. (url: '/'; mime: 'text/html'; handler: @fill_v1_form; handler_cls: MAIN_PAGE),
  299. (url: '/2'; mime: 'text/html'; handler: @fill_v1_v2_form; handler_cls: SECOND_PAGE),
  300. (url: '/S'; mime: 'text/html'; handler: @serve_simple_form; handler_cls: SUBMIT_PAGE),
  301. (url: '/F'; mime: 'text/html'; handler: @serve_simple_form; handler_cls: LAST_PAGE),
  302. (url: nil; mime: nil; handler: @not_found_page; handler_cls: nil) (* 404 *)
  303. );
  304. (**
  305. * Iterator over key-value pairs where the value
  306. * maybe made available in increments and/or may
  307. * not be zero-terminated. Used for processing
  308. * POST data.
  309. *
  310. * @param cls user-specified closure
  311. * @param kind type of the value
  312. * @param key 0-terminated key for the value
  313. * @param filename name of the uploaded file, NULL if not known
  314. * @param content_type mime-type of the data, NULL if not known
  315. * @param transfer_encoding encoding of the data, NULL if not known
  316. * @param data pointer to size bytes of data at the
  317. * specified offset
  318. * @param off offset of data in the overall value
  319. * @param size number of bytes in data available
  320. * @return MHD_YES to continue iterating,
  321. * MHD_NO to abort the iteration
  322. *)
  323. function post_iterator(cls: Pointer; kind: MHD_ValueKind; key: Pcchar;
  324. filename: Pcchar; content_type: Pcchar; transfer_encoding: Pcchar;
  325. data: Pcchar; off: cuint64; size: size_t): cint; cdecl;
  326. var
  327. request: PRequest;
  328. session: PSession;
  329. begin
  330. request := cls;
  331. session := request^.session;
  332. if StrComp('DONE', key) = 0 then
  333. begin
  334. WriteLn(stdout, Format('Session `%s'' submitted `%s'', `%s''', [
  335. session^.sid, session^.value_1, session^.value_2]));
  336. Exit(MHD_YES);
  337. end;
  338. if StrComp('v1', key) = 0 then
  339. begin
  340. if (size + off) > SizeOf(session^.value_1) then
  341. size := SizeOf(session^.value_1) - off;
  342. Move(data^, session^.value_1[off], size);
  343. if (size + off) < SizeOf(session^.value_1) then
  344. session^.value_1[size + off] := #0;
  345. Exit(MHD_YES);
  346. end;
  347. if StrComp('v2', key) = 0 then
  348. begin
  349. if (size + off) > SizeOf(session^.value_2) then
  350. size := SizeOf(session^.value_2) - off;
  351. Move(data^, session^.value_2[off], size);
  352. if (size + off) < SizeOf(session^.value_2) then
  353. session^.value_2[size + off] := #0;
  354. Exit(MHD_YES);
  355. end;
  356. WriteLn(stderr, Format('Unsupported form value `%s''', [key]));
  357. Result := MHD_YES;
  358. end;
  359. (**
  360. * Main MHD callback for handling requests.
  361. *
  362. *
  363. * @param cls argument given together with the function
  364. * pointer when the handler was registered with MHD
  365. * @param connection handle to connection which is being processed
  366. * @param url the requested url
  367. * @param method the HTTP method used ("GET", "PUT", etc.)
  368. * @param version the HTTP version string (i.e. "HTTP/1.1")
  369. * @param upload_data the data being uploaded (excluding HEADERS,
  370. * for a POST that fits into memory and that is encoded
  371. * with a supported encoding, the POST data will NOT be
  372. * given in upload_data and is instead available as
  373. * part of MHD_get_connection_values; very large POST
  374. * data *will* be made available incrementally in
  375. * upload_data)
  376. * @param upload_data_size set initially to the size of the
  377. * upload_data provided; the method must update this
  378. * value to the number of bytes NOT processed;
  379. * @param ptr pointer that the callback can set to some
  380. * address and that will be preserved by MHD for future
  381. * calls for this request; since the access handler may
  382. * be called many times (i.e., for a PUT/POST operation
  383. * with plenty of upload data) this allows the application
  384. * to easily associate some request-specific state.
  385. * If necessary, this state can be cleaned up in the
  386. * global "MHD_RequestCompleted" callback (which
  387. * can be set with the MHD_OPTION_NOTIFY_COMPLETED).
  388. * Initially, <tt>*con_cls</tt> will be NULL.
  389. * @return MHS_YES if the connection was handled successfully,
  390. * MHS_NO if the socket must be closed due to a serios
  391. * error while handling the request
  392. *)
  393. function create_response(cls: Pointer; connection: PMHD_Connection;
  394. url: Pcchar; method: Pcchar; version: Pcchar; upload_data: Pcchar;
  395. upload_data_size: Psize_t; ptr: PPointer): cint; cdecl;
  396. var
  397. response: PMHD_Response;
  398. request: PRequest;
  399. session: PSession;
  400. ret: cint;
  401. i: Cardinal;
  402. begin
  403. request := ptr^;
  404. if nil = request then
  405. begin
  406. request := CAlloc(1, SizeOf(TRequest));
  407. if nil = request then
  408. begin
  409. WriteLn(stderr, 'calloc error: ', strerror(errno^));
  410. Exit(MHD_NO);
  411. end;
  412. ptr^ := request;
  413. if StrComp(method, MHD_HTTP_METHOD_POST) = 0 then
  414. begin
  415. request^.pp := MHD_create_post_processor(connection, 1024,
  416. @post_iterator, request);
  417. if nil = request^.pp then
  418. begin
  419. WriteLn(stderr, Format('Failed to setup post processor for `%s''',
  420. [url]));
  421. Exit(MHD_NO); (* internal error *)
  422. end;
  423. end;
  424. Exit(MHD_YES);
  425. end;
  426. if nil = request^.session then
  427. begin
  428. request^.session := get_session(connection);
  429. if nil = request^.session then
  430. begin
  431. WriteLn(stderr, Format('Failed to setup session for `%s''', [url]));
  432. Exit(MHD_NO); (* internal error *)
  433. end;
  434. end;
  435. session := request^.session;
  436. session^.start := FpTime;
  437. if StrComp(method, MHD_HTTP_METHOD_POST) = 0 then
  438. begin
  439. (* evaluate POST data *)
  440. MHD_post_process(request^.pp, upload_data, upload_data_size^);
  441. if upload_data_size^ <> 0 then
  442. begin
  443. upload_data_size^ := 0;
  444. Exit(MHD_YES);
  445. end;
  446. (* done with POST data, serve response *)
  447. MHD_destroy_post_processor(request^.pp);
  448. request^.pp := nil;
  449. method := MHD_HTTP_METHOD_GET; (* fake 'GET' *)
  450. if nil <> request^.post_url then
  451. url := request^.post_url;
  452. end;
  453. if (StrComp(method, MHD_HTTP_METHOD_GET) = 0) or
  454. (StrComp(method, MHD_HTTP_METHOD_HEAD) = 0) then
  455. begin
  456. (* find out which page to serve *)
  457. i := 0;
  458. while (pages[i].url <> nil) and (StrComp(pages[i].url, url) <> 0) do
  459. Inc(i);
  460. ret := pages[i].handler(pages[i].handler_cls, pages[i].mime, session,
  461. connection);
  462. if ret <> MHD_YES then
  463. WriteLn(stderr, Format('Failed to create page for `%s''', [url]));
  464. Exit(ret);
  465. end;
  466. (* unsupported HTTP method *)
  467. response := MHD_create_response_from_buffer(Length(METHOD_ERROR),
  468. Pcchar(METHOD_ERROR), MHD_RESPMEM_PERSISTENT);
  469. ret := MHD_queue_response(connection, MHD_HTTP_NOT_ACCEPTABLE, response);
  470. MHD_destroy_response(response);
  471. Result := ret;
  472. end;
  473. (**
  474. * Callback called upon completion of a request.
  475. * Decrements session reference counter.
  476. *
  477. * @param cls not used
  478. * @param connection connection that completed
  479. * @param con_cls session handle
  480. * @param toe status code
  481. *)
  482. procedure request_completed_callback(cls: Pointer; connection: PMHD_Connection;
  483. con_cls: PPointer; toe: MHD_RequestTerminationCode);
  484. var
  485. request: PRequest;
  486. begin
  487. request := con_cls^;
  488. if nil = request then
  489. Exit;
  490. if nil <> request^.session then
  491. Dec(request^.session^.rc);
  492. if nil <> request^.pp then
  493. MHD_destroy_post_processor(request^.pp);
  494. Free(request);
  495. end;
  496. (**
  497. * Clean up handles of sessions that have been idle for
  498. * too long.
  499. *)
  500. procedure expire_sessions;
  501. var
  502. pos: PSession;
  503. prev: PSession;
  504. next: PSession;
  505. now: time_t;
  506. begin
  507. now := FpTime;
  508. prev := nil;
  509. pos := _sessions;
  510. while nil <> pos do
  511. begin
  512. next := pos^.next;
  513. if (now - pos^.start) > (60 * 60) then
  514. begin
  515. (* expire sessions after 1h *)
  516. if nil = prev then
  517. _sessions := pos^.next
  518. else
  519. prev^.next := next;
  520. Free(pos);
  521. end
  522. else
  523. prev := pos;
  524. pos := next;
  525. end;
  526. end;
  527. (**
  528. * Call with the port number as the only argument.
  529. * Never terminates (other than by signals, such as CTRL-C).
  530. *)
  531. var
  532. d: PMHD_Daemon;
  533. tv: timeval;
  534. tvp: ptimeval;
  535. rs: TFDSet;
  536. ws: TFDSet;
  537. es: TFDSet;
  538. max: cint;
  539. mhd_timeout: MHD_UNSIGNED_LONG_LONG;
  540. begin
  541. if argc <> 2 then
  542. begin
  543. WriteLn(argv[0], ' PORT');
  544. Halt(1);
  545. end;
  546. (* initialize PRNG *)
  547. Randomize;
  548. d := MHD_start_daemon(MHD_USE_DEBUG, StrToInt(argv[1]), nil, nil,
  549. @create_response, nil, MHD_OPTION_CONNECTION_TIMEOUT, cuint(15),
  550. MHD_OPTION_NOTIFY_COMPLETED, @request_completed_callback, nil, MHD_OPTION_END);
  551. if nil = d then
  552. Halt(1);
  553. while True do
  554. begin
  555. expire_sessions;
  556. max := 0;
  557. fpFD_ZERO(rs);
  558. fpFD_ZERO(ws);
  559. fpFD_ZERO(es);
  560. if MHD_YES <> MHD_get_fdset(d, @rs, @ws, @es, @max) then
  561. Break; (* fatal internal error *)
  562. if MHD_get_timeout(d, @mhd_timeout) = MHD_YES then
  563. begin
  564. tv.tv_sec := mhd_timeout div 1000;
  565. tv.tv_usec := (mhd_timeout - (tv.tv_sec * 1000)) * 1000;
  566. tvp := @tv;
  567. end
  568. else
  569. tvp := nil;
  570. if -1 = fpSelect(max + 1, @rs, @ws, @es, tvp) then
  571. begin
  572. if (ESysEINTR <> errno^) then
  573. WriteLn(stderr, 'Aborting due to error during select: ', strerror(errno^));
  574. Break;
  575. end;
  576. MHD_run(d);
  577. end;
  578. MHD_stop_daemon(d);
  579. end.