diff --git a/lib_web/src/main/x/web.x b/lib_web/src/main/x/web.x index 8135c93dc..5cdd21e1d 100644 --- a/lib_web/src/main/x/web.x +++ b/lib_web/src/main/x/web.x @@ -84,6 +84,9 @@ module web.xtclang.org { mixin Consumes(MediaType|MediaType[] consumes) into Class | Class | Endpoint; + mixin SessionOptional + into Class | Class | Endpoint; + /** * This annotation, `@LoginRequired`, is used to mark a web service call -- or any containing * class thereof, up to the level of the web module itself -- as requiring authentication. diff --git a/lib_xenia/src/main/x/xenia/Catalog.x b/lib_xenia/src/main/x/xenia/Catalog.x index 6c2c04576..5885fa5e3 100644 --- a/lib_xenia/src/main/x/xenia/Catalog.x +++ b/lib_xenia/src/main/x/xenia/Catalog.x @@ -117,16 +117,30 @@ const Catalog(WebApp webApp, String systemPath, WebServiceInfo[] services, Class : new UriTemplate(templateString); // check if the template matches UriParam's in the method - Int requiredParamCount = 0; + Int requiredParamCount = 0; + Boolean requiredSessionParam = False; + Boolean hasBodyParam = False; for (Parameter param : method.params) { // well-known types are Session and RequestIn (see ChainBundle.ensureCallChain) - if (param.ParamType.is(Type) || - param.ParamType == RequestIn || + if (param.ParamType == RequestIn || param.is(QueryParam) || - param.is(BodyParam) || param.defaultValue()) { continue; } + if (param.is(BodyParam)) { + if (hasBodyParam) { + throw new IllegalState($|The template for method "{method}" has more than \ + |one "@BodyParam" annotated parameter + ); + } + hasBodyParam = True; + continue; + } + + if (param.ParamType.is(Type)) { + requiredSessionParam = True; + continue; + } assert String name := param.hasName(); if (param.is(UriParam)) { @@ -134,9 +148,8 @@ const Catalog(WebApp webApp, String systemPath, WebServiceInfo[] services, Class } if (!template.vars.contains(name)) { throw new IllegalState($|The template for method "{method}" is missing \ - |a variable name "{name}": \ - |"{templateString}" - ); + |a variable name "{name}": "{templateString}" + ); } requiredParamCount++; } @@ -146,6 +159,13 @@ const Catalog(WebApp webApp, String systemPath, WebServiceInfo[] services, Class ? !method.is(HttpsOptional) : method.is(HttpsRequired); + this.requiresSession = !method.is(SessionOptional); + if (requiredSessionParam && !requiresSession) { + throw new IllegalState($|Invalid "@SessionOptional" annotation for endpoint \ + |"{method}"; parameters require a session + ); + } + this.requiredTrust = switch (method.is(_)) { case LoginRequired: TrustLevel.maxOf(serviceTrust, method.security); case LoginOptional: None; // explicitly optional overrides service trust level @@ -213,6 +233,11 @@ const Catalog(WebApp webApp, String systemPath, WebServiceInfo[] services, Class */ Boolean requiresTls; + /** + * Indicates if this endpoint requires an http session. + */ + Boolean requiresSession; + /** * [TrustLevel] of security that is required by the this endpoint. */ diff --git a/lib_xenia/src/main/x/xenia/Dispatcher.x b/lib_xenia/src/main/x/xenia/Dispatcher.x index b1c808d9c..ae3baa5c2 100644 --- a/lib_xenia/src/main/x/xenia/Dispatcher.x +++ b/lib_xenia/src/main/x/xenia/Dispatcher.x @@ -241,7 +241,7 @@ service Dispatcher { requestInfo.clientAddress); } - if (redirect) { + if (redirect && endpoint.requiresSession) { Int|HttpStatus redirectResult = session.prepareRedirect_(requestInfo); if (redirectResult.is(HttpStatus)) { RequestIn request = new Http1Request(requestInfo, []);