HeadersAndCookies.vala 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. using Astralis;
  2. using Invercargill;
  3. using Invercargill.DataStructures;
  4. /**
  5. * HeadersAndCookies Example
  6. *
  7. * Demonstrates how to access and manipulate HTTP headers and cookies.
  8. * Uses Invercargill Dictionary and Enumerable for header/cookie processing.
  9. */
  10. // Display all request headers
  11. class HeadersEndpoint : Object, Endpoint {
  12. public string route { get { return "/headers"; } }
  13. public Method[] methods { owned get { return { Method.GET }; } }
  14. public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
  15. var parts = new Series<string>();
  16. parts.add("Request Headers:\n");
  17. parts.add("================\n\n");
  18. http_context.request.headers.to_immutable_buffer()
  19. .iterate((grouping) => {
  20. grouping.iterate((value) => {
  21. parts.add(@"$(grouping.key): $value\n");
  22. });
  23. });
  24. var result = parts.to_immutable_buffer()
  25. .aggregate<string>("", (acc, s) => acc + s);
  26. return new HttpStringResult(result);
  27. }
  28. }
  29. // Check for user-agent header
  30. class UserAgentEndpoint : Object, Endpoint {
  31. public string route { get { return "/user-agent"; } }
  32. public Method[] methods { owned get { return { Method.GET }; } }
  33. public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
  34. var user_agent = http_context.request.user_agent ?? "Unknown";
  35. return new HttpStringResult(@"Your User-Agent is: $user_agent");
  36. }
  37. }
  38. // Check content type and content length
  39. class ContentInfoEndpoint : Object, Endpoint {
  40. public string route { get { return "/content-info"; } }
  41. public Method[] methods { owned get { return { Method.GET }; } }
  42. public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
  43. var parts = new Series<string>();
  44. parts.add("Content Information:\n");
  45. parts.add("===================\n\n");
  46. parts.add(@"Content-Type: $(http_context.request.content_type)\n");
  47. parts.add(@"Content-Length: $(http_context.request.content_length)\n");
  48. parts.add(@"Content-Encoding: $(http_context.request.content_encoding ?? "none")\n");
  49. var result = parts.to_immutable_buffer()
  50. .aggregate<string>("", (acc, s) => acc + s);
  51. return new HttpStringResult(result);
  52. }
  53. }
  54. // Check if header exists
  55. class CheckHeaderEndpoint : Object, Endpoint {
  56. public string route { get { return "/check-header"; } }
  57. public Method[] methods { owned get { return { Method.GET }; } }
  58. public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
  59. var header_name = http_context.request.get_query_or_default("name", "Accept");
  60. var exists = http_context.request.has_header(header_name);
  61. var value = http_context.request.get_header(header_name);
  62. return new HttpStringResult(@"Header '$header_name': $(exists ? "EXISTS" : "NOT FOUND")\nValue: $(value ?? "N/A")");
  63. }
  64. }
  65. // Set custom response headers
  66. class CustomHeadersEndpoint : Object, Endpoint {
  67. public string route { get { return "/custom-headers"; } }
  68. public Method[] methods { owned get { return { Method.GET }; } }
  69. public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
  70. return new HttpStringResult("Response with custom headers!")
  71. .set_header("X-Custom-Header", "Custom-Value")
  72. .set_header("X-Request-Id", generate_request_id())
  73. .set_header("X-Powered-By", "Astralis")
  74. .set_header("X-Server-Time", new DateTime.now_local().format_iso8601());
  75. }
  76. }
  77. // Display all cookies
  78. class CookiesEndpoint : Object, Endpoint {
  79. public string route { get { return "/cookies"; } }
  80. public Method[] methods { owned get { return { Method.GET }; } }
  81. public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
  82. var parts = new Series<string>();
  83. parts.add("Request Cookies:\n");
  84. parts.add("================\n\n");
  85. if (http_context.request.cookies.to_immutable_buffer().count() == 0) {
  86. parts.add("(No cookies sent)\n");
  87. parts.add("\nTry setting a cookie first: /set-cookie?name=test&value=123\n");
  88. } else {
  89. http_context.request.cookies.to_immutable_buffer()
  90. .iterate((grouping) => {
  91. grouping.iterate((value) => {
  92. parts.add(@"$(grouping.key): $value\n");
  93. });
  94. });
  95. }
  96. var result = parts.to_immutable_buffer()
  97. .aggregate<string>("", (acc, s) => acc + s);
  98. return new HttpStringResult(result);
  99. }
  100. }
  101. // Get specific cookie
  102. class GetCookieEndpoint : Object, Endpoint {
  103. public string route { get { return "/get-cookie"; } }
  104. public Method[] methods { owned get { return { Method.GET }; } }
  105. public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
  106. var name = http_context.request.get_query_or_default("name", "session");
  107. var value = http_context.request.get_cookie(name);
  108. if (value == null) {
  109. return new HttpStringResult(@"Cookie '$name' not found");
  110. }
  111. return new HttpStringResult(@"Cookie '$name' = '$value'");
  112. }
  113. }
  114. // Set a cookie (via Set-Cookie header)
  115. class SetCookieEndpoint : Object, Endpoint {
  116. public string route { get { return "/set-cookie"; } }
  117. public Method[] methods { owned get { return { Method.GET }; } }
  118. public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
  119. var name = http_context.request.get_query_or_default("name", "test");
  120. var value = http_context.request.get_query_or_default("value", "123");
  121. var max_age = http_context.request.get_query_or_default("max_age", "3600");
  122. return new HttpStringResult(@"Cookie '$name' set to '$value'")
  123. .set_header("Set-Cookie", @"$name=$value; Max-Age=$max_age; Path=/");
  124. }
  125. }
  126. // Set multiple cookies
  127. class SetCookiesEndpoint : Object, Endpoint {
  128. public string route { get { return "/set-cookies"; } }
  129. public Method[] methods { owned get { return { Method.GET }; } }
  130. public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
  131. // Note: Setting multiple cookies with the same header name requires
  132. // special handling - this example shows the approach
  133. return new HttpStringResult("Multiple cookies set!")
  134. .set_header("Set-Cookie", "user=john; Max-Age=3600; Path=/");
  135. // Additional cookies would need to be set via multiple Set-Cookie headers
  136. // which requires extending the HttpResult API or using a different approach
  137. }
  138. }
  139. // Delete a cookie
  140. class DeleteCookieEndpoint : Object, Endpoint {
  141. public string route { get { return "/delete-cookie"; } }
  142. public Method[] methods { owned get { return { Method.GET }; } }
  143. public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
  144. var name = http_context.request.get_query_or_default("name", "test");
  145. return new HttpStringResult(@"Cookie '$name' deleted")
  146. .set_header("Set-Cookie", @"$name=; Max-Age=0; Path=/");
  147. }
  148. }
  149. // Check if cookie exists
  150. class HasCookieEndpoint : Object, Endpoint {
  151. public string route { get { return "/has-cookie"; } }
  152. public Method[] methods { owned get { return { Method.GET }; } }
  153. public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
  154. var name = http_context.request.get_query_or_default("name", "session");
  155. var exists = http_context.request.has_cookie(name);
  156. return new HttpStringResult(@"Cookie '$name': $(exists ? "EXISTS" : "NOT FOUND")");
  157. }
  158. }
  159. // Cookie-based session simulation
  160. class SessionEndpoint : Object, Endpoint {
  161. public string route { get { return "/session"; } }
  162. public Method[] methods { owned get { return { Method.GET }; } }
  163. public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
  164. var session_id = http_context.request.get_cookie("session_id");
  165. if (session_id == null) {
  166. // Create new session
  167. var new_session_id = generate_session_id();
  168. return new HttpStringResult(@"New session created!
  169. Session ID: $new_session_id
  170. Your session will expire in 1 hour.")
  171. .set_header("Set-Cookie", @"session_id=$new_session_id; Max-Age=3600; Path=/; HttpOnly");
  172. }
  173. // Existing session
  174. return new HttpStringResult(@"Welcome back!
  175. Session ID: $session_id
  176. Your session is active.");
  177. }
  178. }
  179. // CORS headers example
  180. class CorsEndpoint : Object, Endpoint {
  181. public string route { get { return "/cors"; } }
  182. public Method[] methods { owned get { return { Method.GET }; } }
  183. public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
  184. var origin = http_context.request.get_header("Origin") ?? "*";
  185. return new HttpStringResult(@"CORS enabled for origin: $origin")
  186. .set_header("Access-Control-Allow-Origin", origin)
  187. .set_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
  188. .set_header("Access-Control-Allow-Headers", "Content-Type, Authorization")
  189. .set_header("Access-Control-Max-Age", "86400");
  190. }
  191. }
  192. // Cache control headers
  193. class CacheEndpoint : Object, Endpoint {
  194. public string route { get { return "/cache"; } }
  195. public Method[] methods { owned get { return { Method.GET }; } }
  196. public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
  197. var cache_type = http_context.request.get_query_or_default("type", "public");
  198. return new HttpStringResult(@"This response is cached ($cache_type cache, 1 hour)")
  199. .set_header("Cache-Control", @"$cache_type, max-age=3600")
  200. .set_header("Expires", get_expires_header(3600))
  201. .set_header("ETag", generate_etag());
  202. }
  203. }
  204. // Content negotiation
  205. class NegotiateEndpoint : Object, Endpoint {
  206. public string route { get { return "/negotiate"; } }
  207. public Method[] methods { owned get { return { Method.GET }; } }
  208. public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
  209. var accept = http_context.request.get_header("Accept") ?? "*/*";
  210. if (accept.contains("application/json")) {
  211. return new HttpStringResult(@"{ \"message\": \"JSON response\", \"format\": \"json\" }")
  212. .set_header("Content-Type", "application/json");
  213. } else if (accept.contains("text/xml")) {
  214. return new HttpStringResult(@"<?xml version=\"1.0\"?><response><message>XML response</message><format>xml</format></response>")
  215. .set_header("Content-Type", "text/xml");
  216. } else {
  217. return new HttpStringResult("Plain text response")
  218. .set_header("Content-Type", "text/plain");
  219. }
  220. }
  221. }
  222. // Security headers
  223. class SecureEndpoint : Object, Endpoint {
  224. public string route { get { return "/secure"; } }
  225. public Method[] methods { owned get { return { Method.GET }; } }
  226. public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
  227. return new HttpStringResult("Response with security headers!")
  228. .set_header("X-Content-Type-Options", "nosniff")
  229. .set_header("X-Frame-Options", "DENY")
  230. .set_header("X-XSS-Protection", "1; mode=block")
  231. .set_header("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
  232. .set_header("Content-Security-Policy", "default-src 'self'")
  233. .set_header("Referrer-Policy", "strict-origin-when-cross-origin");
  234. }
  235. }
  236. void main() {
  237. var router = new EndpointRouter()
  238. .add_endpoint(new HeadersEndpoint())
  239. .add_endpoint(new UserAgentEndpoint())
  240. .add_endpoint(new ContentInfoEndpoint())
  241. .add_endpoint(new CheckHeaderEndpoint())
  242. .add_endpoint(new CustomHeadersEndpoint())
  243. .add_endpoint(new CookiesEndpoint())
  244. .add_endpoint(new GetCookieEndpoint())
  245. .add_endpoint(new SetCookieEndpoint())
  246. .add_endpoint(new SetCookiesEndpoint())
  247. .add_endpoint(new DeleteCookieEndpoint())
  248. .add_endpoint(new HasCookieEndpoint())
  249. .add_endpoint(new SessionEndpoint())
  250. .add_endpoint(new CorsEndpoint())
  251. .add_endpoint(new CacheEndpoint())
  252. .add_endpoint(new NegotiateEndpoint())
  253. .add_endpoint(new SecureEndpoint());
  254. var pipeline = new Pipeline()
  255. .add_component(router);
  256. var server = new Server(8083, pipeline);
  257. print("Headers and Cookies Example Server running on port 8083\n");
  258. print("Try these endpoints:\n");
  259. print(" - http://localhost:8083/headers\n");
  260. print(" - http://localhost:8083/user-agent\n");
  261. print(" - http://localhost:8083/cookies\n");
  262. print(" - http://localhost:8083/set-cookie?name=test&value=hello\n");
  263. print(" - http://localhost:8083/set-cookies\n");
  264. print(" - http://localhost:8083/session\n");
  265. print(" - http://localhost:8083/cors\n");
  266. print(" - http://localhost:8083/cache\n");
  267. print(" - http://localhost:8083/negotiate\n");
  268. print(" - http://localhost:8083/secure\n");
  269. server.run();
  270. }
  271. // Helper functions
  272. string generate_request_id() {
  273. var timestamp = new DateTime.now_local().to_unix();
  274. var random = Random.next_int();
  275. return @"req-$timestamp-$random";
  276. }
  277. string generate_session_id() {
  278. var timestamp = new DateTime.now_local().to_unix();
  279. var random = Random.next_int();
  280. return @"sess-$timestamp-$random";
  281. }
  282. string generate_etag() {
  283. var timestamp = new DateTime.now_local().to_unix();
  284. return @"\"$timestamp\"";
  285. }
  286. string get_expires_header(int seconds) {
  287. var now = new DateTime.now_local();
  288. var expires = now.add_seconds(seconds);
  289. return expires.format("%a, %d %b %Y %H:%M:%S GMT");
  290. }