| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160 |
- using Invercargill;
- using Invercargill.DataStructures;
- using Invercargill.Expressions;
- using Inversion;
- using Astralis;
- namespace Statum {
- /**
- * Outcome of {@link StatumPage.select_variant}: either the index of the
- * pre-rendered variant to serve, or a guard failure (optional redirect or an
- * error page at a status).
- */
- public class StatumVariantSelection : Object {
- public bool guard_failed { get; private set; }
- public string? redirect { get; private set; }
- public StatusCode status { get; private set; default = StatusCode.FORBIDDEN; }
- public int variant_index { get; private set; }
- /** Selects the variant at `index`. */
- public static StatumVariantSelection variant(int index) {
- var selection = new StatumVariantSelection();
- selection.variant_index = index;
- return selection;
- }
- /** Signals a guard failure that should redirect to `redirect` (HTTP 302). */
- public static StatumVariantSelection redirect_to(string redirect) {
- var selection = new StatumVariantSelection();
- selection.guard_failed = true;
- selection.redirect = redirect;
- return selection;
- }
- /** Signals a guard failure that should serve an error page at `status`. */
- public static StatumVariantSelection failure(StatusCode status = StatusCode.FORBIDDEN) {
- var selection = new StatumVariantSelection();
- selection.guard_failed = true;
- selection.status = status;
- return selection;
- }
- }
- /**
- * Base class for pre-rendered Statum pages.
- *
- * A generated subclass (emitted by `statum-mkpstm`) extends this, providing
- * the precompressed variant byte arrays and a compiled {@link select_variant}
- * that evaluates `pstm-guard`s and the `pstm-if` matrix against the request's
- * held slots. This base supplies header parsing → held slots, encoding
- * negotiation, ETag handling and guard redirect/error-page serving.
- */
- public abstract class StatumPage : Object, Endpoint {
- protected HeldSlotResolver resolver = inject<HeldSlotResolver>();
- /** Content type of the served page (normally `text/html`). */
- public abstract string content_type { get; }
- /**
- * Evaluates the page's guards and `pstm-if` matrix against the held
- * slots, returning the variant to serve or a guard failure.
- */
- public abstract StatumVariantSelection select_variant(ReadOnlyAssociative<string, HeldSlot> held) throws GLib.Error;
- /** Best supported encoding for the chosen variant. */
- public abstract string get_best_encoding(int variant, Set<string> supported);
- /** The bytes for `variant` under `encoding`. */
- public abstract unowned uint8[] get_encoding(int variant, string encoding);
- /** The ETag for `variant` under `encoding`. */
- public abstract string get_etag_for(int variant, string encoding);
- public async HttpResult handle_request(HttpContext http_context, RouteContext route_context) throws GLib.Error {
- var held = resolver.resolve(http_context).held;
- StatumVariantSelection selection;
- try {
- selection = select_variant(held);
- } catch (GLib.Error e) {
- return new HttpStringResult(@"Internal error: $(e.message)", StatusCode.INTERNAL_SERVER_ERROR);
- }
- if (selection.guard_failed) {
- return serve_guard_failure(selection);
- }
- var accepts = parse_accept_encoding(http_context);
- var best = get_best_encoding(selection.variant_index, accepts);
- var etag = get_etag_for(selection.variant_index, best);
- if (etag_matches(http_context, etag)) {
- return new HttpEmptyResult(StatusCode.NOT_MODIFIED);
- }
- var bytes = get_encoding(selection.variant_index, best);
- var result = new HttpDataResult(Wrap.byte_array(bytes));
- result.set_header("Content-Type", content_type);
- result.set_header("Content-Encoding", best);
- result.set_header("ETag", etag);
- return result;
- }
- private static HttpResult serve_guard_failure(StatumVariantSelection selection) {
- if (selection.redirect != null) {
- var result = new HttpEmptyResult((StatusCode) 302);
- result.set_header("Location", (!)selection.redirect);
- return result;
- }
- return new HttpStringResult("Forbidden", selection.status);
- }
- private static Set<string> parse_accept_encoding(HttpContext http_context) {
- var supported = new HashSet<string>();
- 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;
- }
- }
- /**
- * Registers a {@link PropertiesPropertyAccessor} for any {@link Element}
- * wrapping a {@link Properties} value, so that `pstm-if`/`pstm-guard`
- * predicate evaluation (the runtime-evaluator fallback) can read members off
- * a slot's public/private data — e.g. `auth.role` or `auth.private.score`.
- *
- * Registered once by {@link StatumModule}.
- */
- public class StatumPropertiesAccessorFactory : Object, PropertyAccessorFactory {
- public PropertyAccessor? create(Element element) {
- Properties properties;
- if (element.try_get_as<Properties>(out properties)) {
- return new PropertiesPropertyAccessor(properties);
- }
- return null;
- }
- }
- }
|