StatumPage.vala 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
  1. using Invercargill;
  2. using Invercargill.DataStructures;
  3. using Astralis;
  4. namespace Statum {
  5. /**
  6. * Base class for pre-rendered Statum pages.
  7. *
  8. * A page is a static, precompressed document served at its route. Build-time
  9. * composition (templates/fragments, `pstm-res` rewriting) aside, it is just a
  10. * precompressed resource — so a generated subclass (emitted by
  11. * `statum-mkpstm`) provides the precompressed byte arrays and the
  12. * encoding/ETag accessors, and this base does lean `Accept-Encoding`
  13. * negotiation, `ETag`/`If-None-Match` handling and serving.
  14. *
  15. * No slots are resolved: pages are loaded by browser navigations, which carry
  16. * no `X-Statum-Slot` headers, so the page endpoint stays as fast as possible.
  17. */
  18. public abstract class StatumPage : Object, Endpoint {
  19. /** Content type of the served page (normally `text/html`). */
  20. public abstract string content_type { get; }
  21. /** Best supported encoding from the encodings this page carries. */
  22. public abstract string get_best_encoding(Set<string> supported);
  23. /** The bytes for `encoding`. */
  24. public abstract unowned uint8[] get_encoding(string encoding);
  25. /** The ETag for `encoding`. */
  26. public abstract string get_etag_for(string encoding);
  27. public async HttpResult handle_request(HttpContext http_context, RouteContext route_context) throws GLib.Error {
  28. var best = get_best_encoding(parse_accept_encoding(http_context));
  29. var etag = get_etag_for(best);
  30. if (etag_matches(http_context, etag)) {
  31. return new HttpEmptyResult(StatusCode.NOT_MODIFIED);
  32. }
  33. var result = new HttpDataResult(Wrap.byte_array(get_encoding(best)));
  34. result.set_header("Content-Type", content_type);
  35. result.set_header("Content-Encoding", best);
  36. result.set_header("ETag", etag);
  37. return result;
  38. }
  39. private static Set<string> parse_accept_encoding(HttpContext http_context) {
  40. var supported = new HashSet<string>();
  41. foreach (var header in http_context.request.headers.get_or_empty("Accept-Encoding")) {
  42. foreach (var token in header.split(",")) {
  43. var encoding = token.strip();
  44. if (encoding.length > 0) {
  45. supported.add(encoding);
  46. }
  47. }
  48. }
  49. return supported;
  50. }
  51. private static bool etag_matches(HttpContext http_context, string etag) {
  52. foreach (var header in http_context.request.headers.get_or_empty("If-None-Match")) {
  53. if (header.contains(etag)) {
  54. return true;
  55. }
  56. }
  57. return false;
  58. }
  59. }
  60. }