|
@@ -1,118 +1,51 @@
|
|
|
using Invercargill;
|
|
using Invercargill;
|
|
|
using Invercargill.DataStructures;
|
|
using Invercargill.DataStructures;
|
|
|
-using Invercargill.Expressions;
|
|
|
|
|
-using Inversion;
|
|
|
|
|
using Astralis;
|
|
using Astralis;
|
|
|
|
|
|
|
|
namespace Statum {
|
|
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.
|
|
* 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.
|
|
|
|
|
|
|
+ * 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 {
|
|
public abstract class StatumPage : Object, Endpoint {
|
|
|
|
|
|
|
|
- protected HeldSlotResolver resolver = inject<HeldSlotResolver>();
|
|
|
|
|
-
|
|
|
|
|
/** Content type of the served page (normally `text/html`). */
|
|
/** Content type of the served page (normally `text/html`). */
|
|
|
public abstract string content_type { get; }
|
|
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);
|
|
|
|
|
|
|
+ /** Best supported encoding from the encodings this page carries. */
|
|
|
|
|
+ public abstract string get_best_encoding(Set<string> supported);
|
|
|
|
|
|
|
|
- /** The bytes for `variant` under `encoding`. */
|
|
|
|
|
- public abstract unowned uint8[] get_encoding(int variant, string encoding);
|
|
|
|
|
|
|
+ /** The bytes for `encoding`. */
|
|
|
|
|
+ public abstract unowned uint8[] get_encoding(string encoding);
|
|
|
|
|
|
|
|
- /** The ETag for `variant` under `encoding`. */
|
|
|
|
|
- public abstract string get_etag_for(int variant, 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 {
|
|
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);
|
|
|
|
|
|
|
+ var best = get_best_encoding(parse_accept_encoding(http_context));
|
|
|
|
|
+ var etag = get_etag_for(best);
|
|
|
|
|
|
|
|
if (etag_matches(http_context, etag)) {
|
|
if (etag_matches(http_context, etag)) {
|
|
|
return new HttpEmptyResult(StatusCode.NOT_MODIFIED);
|
|
return new HttpEmptyResult(StatusCode.NOT_MODIFIED);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- var bytes = get_encoding(selection.variant_index, best);
|
|
|
|
|
- var result = new HttpDataResult(Wrap.byte_array(bytes));
|
|
|
|
|
|
|
+ var result = new HttpDataResult(Wrap.byte_array(get_encoding(best)));
|
|
|
result.set_header("Content-Type", content_type);
|
|
result.set_header("Content-Type", content_type);
|
|
|
result.set_header("Content-Encoding", best);
|
|
result.set_header("Content-Encoding", best);
|
|
|
result.set_header("ETag", etag);
|
|
result.set_header("ETag", etag);
|
|
|
return result;
|
|
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) {
|
|
private static Set<string> parse_accept_encoding(HttpContext http_context) {
|
|
|
var supported = new HashSet<string>();
|
|
var supported = new HashSet<string>();
|
|
|
foreach (var header in http_context.request.headers.get_or_empty("Accept-Encoding")) {
|
|
foreach (var header in http_context.request.headers.get_or_empty("Accept-Encoding")) {
|
|
@@ -134,27 +67,5 @@ namespace Statum {
|
|
|
}
|
|
}
|
|
|
return false;
|
|
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;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
}
|
|
}
|