using Invercargill; using Invercargill.DataStructures; using Astralis; namespace Statum { /** * Base class for pre-rendered Statum pages. * * A page is a static, precompressed document served at its route. Build-time * composition (templates/fragments, `pstm-res` rewriting) aside, it is just a * precompressed resource — so a generated subclass (emitted by * `statum-mkpstm`) provides the precompressed byte arrays and the * encoding/ETag accessors, and this base does lean `Accept-Encoding` * negotiation, `ETag`/`If-None-Match` handling and serving. * * No slots are resolved: pages are loaded by browser navigations, which carry * no `X-Statum-Slot` headers, so the page endpoint stays as fast as possible. */ public abstract class StatumPage : Object, Endpoint { /** Content type of the served page (normally `text/html`). */ public abstract string content_type { get; } /** Best supported encoding from the encodings this page carries. */ public abstract string get_best_encoding(Set supported); /** The bytes for `encoding`. */ public abstract unowned uint8[] get_encoding(string encoding); /** The ETag for `encoding`. */ public abstract string get_etag_for(string encoding); public async HttpResult handle_request(HttpContext http_context, RouteContext route_context) throws GLib.Error { var best = get_best_encoding(parse_accept_encoding(http_context)); var etag = get_etag_for(best); if (etag_matches(http_context, etag)) { return new HttpEmptyResult(StatusCode.NOT_MODIFIED); } var result = new HttpDataResult(Wrap.byte_array(get_encoding(best))); result.set_header("Content-Type", content_type); result.set_header("Content-Encoding", best); result.set_header("ETag", etag); return result; } private static Set parse_accept_encoding(HttpContext http_context) { var supported = new HashSet(); foreach (var header in http_context.request.headers.get_or_empty("Accept-Encoding")) { foreach (var token in header.split(",")) { var encoding = token.strip(); if (encoding.length > 0) { supported.add(encoding); } } } return supported; } private static bool etag_matches(HttpContext http_context, string etag) { foreach (var header in http_context.request.headers.get_or_empty("If-None-Match")) { if (header.contains(etag)) { return true; } } return false; } } }