From 0e7f3d8ea33e0b3be9a1b163f0cab4bb0e158b1b Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Wed, 8 May 2024 10:05:17 +0100 Subject: [PATCH] refactor: allows script engines to customise request object. --- .../imposter/script/ExecutionContext.kt | 116 +------------ .../gatehill/imposter/script/ScriptRequest.kt | 82 +++++++++ .../imposter/service/ScriptService.kt | 7 + .../model/script/LazyScriptRequest.kt | 157 ++++++++++++++++++ .../model/script/SimpleScriptRequest.kt | 90 ++++++++++ .../io/gatehill/imposter/script/ScriptUtil.kt | 56 ++----- .../script/EmbeddedScriptServiceImpl.kt | 3 + .../service/script/InlineScriptService.kt | 2 +- .../script/ScriptedResponseServiceImpl.kt | 8 +- .../service/DelegatingJsScriptServiceImpl.kt | 4 + .../graalvm/{ => model}/RequestProxy.kt | 38 +++-- .../service/GraalvmCompatScriptServiceImpl.kt | 3 + .../service/GraalvmScriptServiceImpl.kt | 13 +- .../groovy/service/GroovyScriptServiceImpl.kt | 3 + .../service/NashornScriptServiceImpl.kt | 3 + .../scripting/AbstractBaseScriptTest.kt | 2 +- 16 files changed, 406 insertions(+), 181 deletions(-) create mode 100644 core/api/src/main/java/io/gatehill/imposter/script/ScriptRequest.kt create mode 100644 core/engine/src/main/java/io/gatehill/imposter/model/script/LazyScriptRequest.kt create mode 100644 core/engine/src/main/java/io/gatehill/imposter/model/script/SimpleScriptRequest.kt rename scripting/graalvm/src/main/java/io/gatehill/imposter/scripting/graalvm/{ => model}/RequestProxy.kt (75%) diff --git a/core/api/src/main/java/io/gatehill/imposter/script/ExecutionContext.kt b/core/api/src/main/java/io/gatehill/imposter/script/ExecutionContext.kt index 11f128a19..9e5e2ac7e 100644 --- a/core/api/src/main/java/io/gatehill/imposter/script/ExecutionContext.kt +++ b/core/api/src/main/java/io/gatehill/imposter/script/ExecutionContext.kt @@ -42,16 +42,13 @@ */ package io.gatehill.imposter.script -import io.gatehill.imposter.util.CollectionUtil -import java.util.function.Supplier - /** * Wrapper for context variables available during script execution. * * @author Pete Cornish */ class ExecutionContext( - request: Request + request: ScriptRequest ) : HashMap() { init { @@ -73,115 +70,4 @@ class ExecutionContext( return super.get(key) } - - interface Request { - val path: String - val method: String - val uri: String - val headers: Map - - /** - * @return the request path parameters - */ - val pathParams: Map - - /** - * @return the request query parameters - */ - val queryParams: Map - - /** - * @return the request form parameters - */ - val formParams: Map - - /** - * @return the request body - */ - val body: String? - - /** - * @return the [headers] map, but with all keys in lowercase - */ - val normalisedHeaders: Map - - /** - * Legacy property removed. - */ - @get:Deprecated("Use queryParams instead.", ReplaceWith("queryParams")) - val params: Map - } - - /** - * Representation of the request, supporting lazily-initialised collections for params and headers. - */ - class RequestImpl( - override val path: String, - override val method: String, - override val uri: String, - private val headersSupplier: Supplier>, - private val pathParamsSupplier: Supplier>, - private val queryParamsSupplier: Supplier>, - private val formParamsSupplier: Supplier>, - private val bodySupplier: Supplier, - ) : Request { - override val headers: Map by lazy { - headersSupplier.get() - } - - /** - * @return the request path parameters - */ - override val pathParams: Map get() { - return pathParamsSupplier.get() - } - - /** - * @return the request query parameters - */ - override val queryParams: Map by lazy { - queryParamsSupplier.get() - } - - /** - * @return the request form parameters - */ - override val formParams: Map by lazy { - formParamsSupplier.get() - } - - /** - * @return the request body - */ - override val body: String? by lazy { - bodySupplier.get() - } - - /** - * @return the [headers] map, but with all keys in lowercase - */ - override val normalisedHeaders: Map - get() = CollectionUtil.convertKeysToLowerCase(headers) - - /** - * Legacy property removed. - */ - @get:Deprecated("Use queryParams instead.", ReplaceWith("queryParams")) - override val params: Map - get() = throw UnsupportedOperationException( - "Error: the deprecated 'context.request.params' property was removed. Use 'context.request.queryParams' or 'context.request.pathParams' instead." - ) - - override fun toString(): String { - return "Request{" + - "path='" + path + '\'' + - ", method='" + method + '\'' + - ", uri='" + uri + '\'' + - ", pathParams=" + pathParams + - ", queryParams=" + queryParams + - ", headers=" + headers + - ", body=<" + (body?.let { "${it.length} bytes" } ?: "null") + '>' + - '}' - } - } } diff --git a/core/api/src/main/java/io/gatehill/imposter/script/ScriptRequest.kt b/core/api/src/main/java/io/gatehill/imposter/script/ScriptRequest.kt new file mode 100644 index 000000000..e41f75644 --- /dev/null +++ b/core/api/src/main/java/io/gatehill/imposter/script/ScriptRequest.kt @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2024. + * + * This file is part of Imposter. + * + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, as + * defined below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of rights + * under the License will not include, and the License does not grant to + * you, the right to Sell the Software. + * + * For purposes of the foregoing, "Sell" means practicing any or all of + * the rights granted to you under the License to provide to third parties, + * for a fee or other consideration (including without limitation fees for + * hosting or consulting/support services related to the Software), a + * product or service whose value derives, entirely or substantially, from + * the functionality of the Software. Any license notice or attribution + * required by the License must also include this Commons Clause License + * Condition notice. + * + * Software: Imposter + * + * License: GNU Lesser General Public License version 3 + * + * Licensor: Peter Cornish + * + * Imposter is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Imposter is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Imposter. If not, see . + */ + +package io.gatehill.imposter.script + +interface ScriptRequest { + val path: String + val method: String + val uri: String + val headers: Map + + /** + * @return the request path parameters + */ + val pathParams: Map + + /** + * @return the request query parameters + */ + val queryParams: Map + + /** + * @return the request form parameters + */ + val formParams: Map + + /** + * @return the request body + */ + val body: String? + + /** + * @return the [headers] map, but with all keys in lowercase + */ + val normalisedHeaders: Map + + /** + * Legacy property removed. + */ + @get:Deprecated("Use queryParams instead.", ReplaceWith("queryParams")) + val params: Map +} diff --git a/core/api/src/main/java/io/gatehill/imposter/service/ScriptService.kt b/core/api/src/main/java/io/gatehill/imposter/service/ScriptService.kt index 5e937f79d..9a9fc5abb 100644 --- a/core/api/src/main/java/io/gatehill/imposter/service/ScriptService.kt +++ b/core/api/src/main/java/io/gatehill/imposter/service/ScriptService.kt @@ -42,13 +42,17 @@ */ package io.gatehill.imposter.service +import io.gatehill.imposter.http.HttpRequest import io.gatehill.imposter.script.ReadWriteResponseBehaviour import io.gatehill.imposter.script.RuntimeContext +import io.gatehill.imposter.script.ScriptRequest /** * @author Pete Cornish */ interface ScriptService { + val requestBuilder: ScriptRequestBuilder + fun initScript(script: ScriptSource) { // no op } @@ -67,4 +71,7 @@ interface ScriptService { fun evalInlineScript(scriptId: String, scriptCode: String, runtimeContext: RuntimeContext): Boolean = throw NotImplementedError() + } + +typealias ScriptRequestBuilder = (request: HttpRequest) -> ScriptRequest diff --git a/core/engine/src/main/java/io/gatehill/imposter/model/script/LazyScriptRequest.kt b/core/engine/src/main/java/io/gatehill/imposter/model/script/LazyScriptRequest.kt new file mode 100644 index 000000000..4315c2091 --- /dev/null +++ b/core/engine/src/main/java/io/gatehill/imposter/model/script/LazyScriptRequest.kt @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2024. + * + * This file is part of Imposter. + * + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, as + * defined below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of rights + * under the License will not include, and the License does not grant to + * you, the right to Sell the Software. + * + * For purposes of the foregoing, "Sell" means practicing any or all of + * the rights granted to you under the License to provide to third parties, + * for a fee or other consideration (including without limitation fees for + * hosting or consulting/support services related to the Software), a + * product or service whose value derives, entirely or substantially, from + * the functionality of the Software. Any license notice or attribution + * required by the License must also include this Commons Clause License + * Condition notice. + * + * Software: Imposter + * + * License: GNU Lesser General Public License version 3 + * + * Licensor: Peter Cornish + * + * Imposter is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Imposter is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Imposter. If not, see . + */ + +package io.gatehill.imposter.model.script + +import io.gatehill.imposter.script.ScriptRequest +import io.gatehill.imposter.script.ScriptUtil +import io.gatehill.imposter.service.ScriptRequestBuilder +import io.gatehill.imposter.util.CollectionUtil +import java.util.function.Supplier + +/** + * Representation of the request, supporting lazily-initialised collections for params and headers. + */ +class LazyScriptRequest( + override val path: String, + override val method: String, + override val uri: String, + private val headersSupplier: Supplier>, + private val pathParamsSupplier: Supplier>, + private val queryParamsSupplier: Supplier>, + private val formParamsSupplier: Supplier>, + private val bodySupplier: Supplier, +) : ScriptRequest { + override val headers: Map by lazy { + headersSupplier.get() + } + + /** + * @return the request path parameters + */ + override val pathParams: Map + get() { + return pathParamsSupplier.get() + } + + /** + * @return the request query parameters + */ + override val queryParams: Map by lazy { + queryParamsSupplier.get() + } + + /** + * @return the request form parameters + */ + override val formParams: Map by lazy { + formParamsSupplier.get() + } + + /** + * @return the request body + */ + override val body: String? by lazy { + bodySupplier.get() + } + + /** + * @return the [headers] map, but with all keys in lowercase + */ + override val normalisedHeaders: Map + get() = CollectionUtil.convertKeysToLowerCase(headers) + + /** + * Legacy property removed. + */ + @get:Deprecated("Use queryParams instead.", ReplaceWith("queryParams")) + override val params: Map + get() = throw UnsupportedOperationException( + "Error: the deprecated 'context.request.params' property was removed. Use 'context.request.queryParams' or 'context.request.pathParams' instead." + ) + + override fun toString(): String { + return "Request{" + + "path='" + path + '\'' + + ", method='" + method + '\'' + + ", uri='" + uri + '\'' + + ", pathParams=" + pathParams + + ", queryParams=" + queryParams + + ", headers=" + headers + + ", body=<" + (body?.let { "${it.length} bytes" } ?: "null") + '>' + + '}' + } +} + +/** + * Constructs a [LazyScriptRequest]. + */ +val lazyScriptRequestBuilder : ScriptRequestBuilder = { request -> + val headersSupplier: () -> Map = { + ScriptUtil.caseHeaders(request) + } + + val pathParamsSupplier: () -> Map = { + request.pathParams + } + val queryParamsSupplier: () -> Map = { + request.queryParams + } + val formParamsSupplier: () -> Map = { + request.formParams + } + val bodySupplier: () -> String? = { + request.bodyAsString + } + + LazyScriptRequest( + path = request.path, + method = request.method.name, + uri = request.absoluteUri, + headersSupplier, + pathParamsSupplier, + queryParamsSupplier, + formParamsSupplier, + bodySupplier + ) +} diff --git a/core/engine/src/main/java/io/gatehill/imposter/model/script/SimpleScriptRequest.kt b/core/engine/src/main/java/io/gatehill/imposter/model/script/SimpleScriptRequest.kt new file mode 100644 index 000000000..7e60260c3 --- /dev/null +++ b/core/engine/src/main/java/io/gatehill/imposter/model/script/SimpleScriptRequest.kt @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2024. + * + * This file is part of Imposter. + * + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, as + * defined below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of rights + * under the License will not include, and the License does not grant to + * you, the right to Sell the Software. + * + * For purposes of the foregoing, "Sell" means practicing any or all of + * the rights granted to you under the License to provide to third parties, + * for a fee or other consideration (including without limitation fees for + * hosting or consulting/support services related to the Software), a + * product or service whose value derives, entirely or substantially, from + * the functionality of the Software. Any license notice or attribution + * required by the License must also include this Commons Clause License + * Condition notice. + * + * Software: Imposter + * + * License: GNU Lesser General Public License version 3 + * + * Licensor: Peter Cornish + * + * Imposter is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Imposter is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Imposter. If not, see . + */ + +package io.gatehill.imposter.model.script + +import io.gatehill.imposter.http.HttpRequest +import io.gatehill.imposter.script.ScriptRequest +import io.gatehill.imposter.script.ScriptUtil +import io.gatehill.imposter.util.CollectionUtil + +/** + * Adapter for [HttpRequest] to [ScriptRequest]. + * + * This implementation doesn't perform any caching. + */ +open class SimpleScriptRequest( + private val request: HttpRequest, +) : ScriptRequest { + override val path: String + get() = request.path + + override val method: String + get() = request.method.name + + override val uri: String + get() = request.absoluteUri + + override val headers: Map + get() = ScriptUtil.caseHeaders(request) + + override val pathParams: Map + get() = request.pathParams + + override val queryParams: Map + get() = request.queryParams + + override val formParams: Map + get() = request.formParams + + override val body: String? + get() = request.bodyAsString + + override val normalisedHeaders: Map + get() = CollectionUtil.convertKeysToLowerCase(headers) + + override val params: Map + get() = throw UnsupportedOperationException( + "Error: the deprecated 'context.request.params' property was removed. Use 'context.request.queryParams' or 'context.request.pathParams' instead." + ) +} diff --git a/core/engine/src/main/java/io/gatehill/imposter/script/ScriptUtil.kt b/core/engine/src/main/java/io/gatehill/imposter/script/ScriptUtil.kt index 8700ae3b7..dd30b467c 100644 --- a/core/engine/src/main/java/io/gatehill/imposter/script/ScriptUtil.kt +++ b/core/engine/src/main/java/io/gatehill/imposter/script/ScriptUtil.kt @@ -45,7 +45,9 @@ package io.gatehill.imposter.script import io.gatehill.imposter.config.util.EnvVars import io.gatehill.imposter.http.HttpExchange +import io.gatehill.imposter.http.HttpRequest import io.gatehill.imposter.plugin.config.PluginConfig +import io.gatehill.imposter.service.ScriptRequestBuilder import java.nio.file.Path import java.nio.file.Paths @@ -64,49 +66,18 @@ object ScriptUtil { EnvVars.getEnv("IMPOSTER_NORMALISE_HEADER_KEYS")?.toBoolean() != false /** - * Build the {@code context}, containing lazily-evaluated values. + * Build an appropriate [ExecutionContext]. * * @param httpExchange * @param additionalContext * @return the context */ - @JvmStatic - fun buildContext(httpExchange: HttpExchange, additionalContext: Map?): ExecutionContext { - val internalRequest = httpExchange.request - - val headersSupplier: () -> Map = { - val entries = internalRequest.headers - if (forceHeaderKeyNormalisation) { - LowercaseKeysMap(entries) - } else { - entries - } - } - - val pathParamsSupplier: () -> Map = { - internalRequest.pathParams - } - val queryParamsSupplier: () -> Map = { - internalRequest.queryParams - } - val formParamsSupplier: () -> Map = { - internalRequest.formParams - } - val bodySupplier: () -> String? = { - internalRequest.bodyAsString - } - - // request information - val request = ExecutionContext.RequestImpl( - path = internalRequest.path, - method = internalRequest.method.name, - uri = internalRequest.absoluteUri, - headersSupplier, - pathParamsSupplier, - queryParamsSupplier, - formParamsSupplier, - bodySupplier - ) + fun buildContext( + requestBuilder: ScriptRequestBuilder, + httpExchange: HttpExchange, + additionalContext: Map?, + ): ExecutionContext { + val request = requestBuilder(httpExchange.request) // root context val executionContext = ExecutionContext(request) @@ -119,4 +90,13 @@ object ScriptUtil { fun resolveScriptPath(pluginConfig: PluginConfig, scriptFile: String?): Path = Paths.get(pluginConfig.dir.absolutePath, scriptFile!!) + + fun caseHeaders(request: HttpRequest): Map { + val entries = request.headers + return if (forceHeaderKeyNormalisation) { + LowercaseKeysMap(entries) + } else { + entries + } + } } diff --git a/core/engine/src/main/java/io/gatehill/imposter/service/script/EmbeddedScriptServiceImpl.kt b/core/engine/src/main/java/io/gatehill/imposter/service/script/EmbeddedScriptServiceImpl.kt index 61c9a9344..76d5e9f4a 100644 --- a/core/engine/src/main/java/io/gatehill/imposter/service/script/EmbeddedScriptServiceImpl.kt +++ b/core/engine/src/main/java/io/gatehill/imposter/service/script/EmbeddedScriptServiceImpl.kt @@ -42,6 +42,7 @@ */ package io.gatehill.imposter.service.script +import io.gatehill.imposter.model.script.lazyScriptRequestBuilder import io.gatehill.imposter.script.ReadWriteResponseBehaviour import io.gatehill.imposter.script.ReadWriteResponseBehaviourImpl import io.gatehill.imposter.script.RuntimeContext @@ -54,6 +55,8 @@ import io.gatehill.imposter.service.ScriptSource class EmbeddedScriptServiceImpl : EmbeddedScriptService { private var listener: ScriptListener? = null + override val requestBuilder = lazyScriptRequestBuilder + override fun executeScript( script: ScriptSource, runtimeContext: RuntimeContext diff --git a/core/engine/src/main/java/io/gatehill/imposter/service/script/InlineScriptService.kt b/core/engine/src/main/java/io/gatehill/imposter/service/script/InlineScriptService.kt index 847957e8d..9e9ef03a8 100644 --- a/core/engine/src/main/java/io/gatehill/imposter/service/script/InlineScriptService.kt +++ b/core/engine/src/main/java/io/gatehill/imposter/service/script/InlineScriptService.kt @@ -37,7 +37,7 @@ class InlineScriptService { val scriptId = config.resourceId try { - val executionContext = ScriptUtil.buildContext(httpExchange, emptyMap()) + val executionContext = ScriptUtil.buildContext(jsScriptService.requestBuilder, httpExchange, emptyMap()) val runtimeContext = RuntimeContext( emptyMap(), logger, diff --git a/core/engine/src/main/java/io/gatehill/imposter/service/script/ScriptedResponseServiceImpl.kt b/core/engine/src/main/java/io/gatehill/imposter/service/script/ScriptedResponseServiceImpl.kt index ad54072f2..d0a0a5645 100644 --- a/core/engine/src/main/java/io/gatehill/imposter/service/script/ScriptedResponseServiceImpl.kt +++ b/core/engine/src/main/java/io/gatehill/imposter/service/script/ScriptedResponseServiceImpl.kt @@ -185,7 +185,11 @@ class ScriptedResponseServiceImpl @Inject constructor( script, LogUtil.describeRequestShort(httpExchange) ) - val executionContext = ScriptUtil.buildContext(httpExchange, additionalContext) + + // execute the script using an appropriate implementation and read response behaviour + val scriptService = scriptServiceFactory.fetchScriptService(script.source) + + val executionContext = ScriptUtil.buildContext(scriptService.requestBuilder, httpExchange, additionalContext) LOGGER.trace("Context for request: {}", Supplier { executionContext }) val additionalBindings = getAdditionalBindings(httpExchange, executionContext) @@ -199,8 +203,6 @@ class ScriptedResponseServiceImpl @Inject constructor( executionContext ) - // execute the script using an appropriate implementation and read response behaviour - val scriptService = scriptServiceFactory.fetchScriptService(script.source) val responseBehaviour = scriptService.executeScript(script, runtimeContext) // fire post execution hooks diff --git a/scripting/common/src/main/java/io/gatehill/imposter/scripting/common/service/DelegatingJsScriptServiceImpl.kt b/scripting/common/src/main/java/io/gatehill/imposter/scripting/common/service/DelegatingJsScriptServiceImpl.kt index 398c18bc5..53c3d314f 100644 --- a/scripting/common/src/main/java/io/gatehill/imposter/scripting/common/service/DelegatingJsScriptServiceImpl.kt +++ b/scripting/common/src/main/java/io/gatehill/imposter/scripting/common/service/DelegatingJsScriptServiceImpl.kt @@ -46,6 +46,7 @@ import io.gatehill.imposter.plugin.Plugin import io.gatehill.imposter.plugin.PluginManager import io.gatehill.imposter.script.RuntimeContext import io.gatehill.imposter.scripting.common.util.JavaScriptUtil +import io.gatehill.imposter.service.ScriptRequestBuilder import io.gatehill.imposter.service.ScriptService import io.gatehill.imposter.service.ScriptSource import org.apache.logging.log4j.LogManager @@ -83,6 +84,9 @@ class DelegatingJsScriptServiceImpl @Inject constructor( } } + override val requestBuilder: ScriptRequestBuilder + get() = impl.requestBuilder + override fun initScript(script: ScriptSource) = impl.initScript(script) override fun initInlineScript(scriptId: String, scriptCode: String) = impl.initInlineScript(scriptId, scriptCode) diff --git a/scripting/graalvm/src/main/java/io/gatehill/imposter/scripting/graalvm/RequestProxy.kt b/scripting/graalvm/src/main/java/io/gatehill/imposter/scripting/graalvm/model/RequestProxy.kt similarity index 75% rename from scripting/graalvm/src/main/java/io/gatehill/imposter/scripting/graalvm/RequestProxy.kt rename to scripting/graalvm/src/main/java/io/gatehill/imposter/scripting/graalvm/model/RequestProxy.kt index a541751cd..01041c190 100644 --- a/scripting/graalvm/src/main/java/io/gatehill/imposter/scripting/graalvm/RequestProxy.kt +++ b/scripting/graalvm/src/main/java/io/gatehill/imposter/scripting/graalvm/model/RequestProxy.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024. + * Copyright (c) 2024-2024. * * This file is part of Imposter. * @@ -41,18 +41,26 @@ * along with Imposter. If not, see . */ -package io.gatehill.imposter.scripting.graalvm +package io.gatehill.imposter.scripting.graalvm.model -import io.gatehill.imposter.script.ExecutionContext +import io.gatehill.imposter.http.HttpRequest +import io.gatehill.imposter.model.script.SimpleScriptRequest +import io.gatehill.imposter.script.ScriptRequest +import io.gatehill.imposter.service.ScriptRequestBuilder import org.graalvm.polyglot.Value import org.graalvm.polyglot.proxy.ProxyObject +// wrap request to allow property access syntactic sugar +val objectProxyRequestBuilder : ScriptRequestBuilder = { request -> + RequestProxy(request) +} + /** - * Graal polyglot object proxy for [ExecutionContext.Request]. + * Graal polyglot object proxy for [ScriptRequest]. */ class RequestProxy( - private val request: ExecutionContext.Request -) : ProxyObject { + request: HttpRequest +) : SimpleScriptRequest(request), ProxyObject { private val properties = arrayOf( "path", "method", @@ -66,15 +74,15 @@ class RequestProxy( ) override fun getMember(key: String?): Any? = when (key) { - "path" -> request.path - "method" -> request.method - "uri" -> request.uri - "headers" -> MapObjectProxy(request.headers) - "pathParams" -> MapObjectProxy(request.pathParams) - "queryParams" -> MapObjectProxy(request.queryParams) - "formParams" -> MapObjectProxy(request.formParams) - "body" -> request.body - "normalisedHeaders" -> MapObjectProxy(request.normalisedHeaders) + "path" -> path + "method" -> method + "uri" -> uri + "headers" -> MapObjectProxy(headers) + "pathParams" -> MapObjectProxy(pathParams) + "queryParams" -> MapObjectProxy(queryParams) + "formParams" -> MapObjectProxy(formParams) + "body" -> body + "normalisedHeaders" -> MapObjectProxy(normalisedHeaders) else -> null } diff --git a/scripting/graalvm/src/main/java/io/gatehill/imposter/scripting/graalvm/service/GraalvmCompatScriptServiceImpl.kt b/scripting/graalvm/src/main/java/io/gatehill/imposter/scripting/graalvm/service/GraalvmCompatScriptServiceImpl.kt index 7d77df100..0ef446f4f 100644 --- a/scripting/graalvm/src/main/java/io/gatehill/imposter/scripting/graalvm/service/GraalvmCompatScriptServiceImpl.kt +++ b/scripting/graalvm/src/main/java/io/gatehill/imposter/scripting/graalvm/service/GraalvmCompatScriptServiceImpl.kt @@ -43,6 +43,7 @@ package io.gatehill.imposter.scripting.graalvm.service import com.oracle.truffle.js.scriptengine.GraalJSEngineFactory +import io.gatehill.imposter.model.script.lazyScriptRequestBuilder import io.gatehill.imposter.plugin.Plugin import io.gatehill.imposter.plugin.PluginInfo import io.gatehill.imposter.plugin.RequireModules @@ -69,6 +70,8 @@ import javax.script.SimpleBindings class GraalvmCompatScriptServiceImpl : ScriptService, Plugin { private val scriptEngine: ScriptEngine + override val requestBuilder = lazyScriptRequestBuilder + init { // quieten interpreter mode warning until native graal compiler included in module path - see: // https://www.graalvm.org/reference-manual/js/RunOnJDK/ diff --git a/scripting/graalvm/src/main/java/io/gatehill/imposter/scripting/graalvm/service/GraalvmScriptServiceImpl.kt b/scripting/graalvm/src/main/java/io/gatehill/imposter/scripting/graalvm/service/GraalvmScriptServiceImpl.kt index 3739f5ef5..65642e663 100644 --- a/scripting/graalvm/src/main/java/io/gatehill/imposter/scripting/graalvm/service/GraalvmScriptServiceImpl.kt +++ b/scripting/graalvm/src/main/java/io/gatehill/imposter/scripting/graalvm/service/GraalvmScriptServiceImpl.kt @@ -45,13 +45,13 @@ package io.gatehill.imposter.scripting.graalvm.service import io.gatehill.imposter.plugin.Plugin import io.gatehill.imposter.plugin.PluginInfo import io.gatehill.imposter.plugin.RequireModules -import io.gatehill.imposter.script.ExecutionContext import io.gatehill.imposter.script.ReadWriteResponseBehaviour import io.gatehill.imposter.script.RuntimeContext import io.gatehill.imposter.script.dsl.Dsl import io.gatehill.imposter.scripting.common.util.JavaScriptUtil import io.gatehill.imposter.scripting.graalvm.GraalvmScriptingModule -import io.gatehill.imposter.scripting.graalvm.RequestProxy +import io.gatehill.imposter.scripting.graalvm.model.objectProxyRequestBuilder +import io.gatehill.imposter.service.ScriptRequestBuilder import io.gatehill.imposter.service.ScriptService import io.gatehill.imposter.service.ScriptSource import org.apache.logging.log4j.LogManager @@ -71,6 +71,9 @@ import org.graalvm.polyglot.HostAccess class GraalvmScriptServiceImpl : ScriptService, Plugin { private val engine: Engine + override val requestBuilder: ScriptRequestBuilder + get() = objectProxyRequestBuilder + init { // quieten interpreter mode warning until native graal compiler included in module path - see: // https://www.graalvm.org/reference-manual/js/RunOnJDK/ @@ -93,12 +96,6 @@ class GraalvmScriptServiceImpl : ScriptService, Plugin { .allowHostClassLookup { _ -> true } .build() .use { context -> - // wrap request to allow property access syntactic sugar - val executionContext = runtimeContext.executionContext - executionContext["request"] = RequestProxy( - executionContext["request"] as ExecutionContext.Request - ) - val bindings = context.getBindings(JS_LANG_ID) JavaScriptUtil.transformRuntimeMap( runtimeContext, diff --git a/scripting/groovy/src/main/java/io/gatehill/imposter/scripting/groovy/service/GroovyScriptServiceImpl.kt b/scripting/groovy/src/main/java/io/gatehill/imposter/scripting/groovy/service/GroovyScriptServiceImpl.kt index 6c3dbc101..70e93c86c 100644 --- a/scripting/groovy/src/main/java/io/gatehill/imposter/scripting/groovy/service/GroovyScriptServiceImpl.kt +++ b/scripting/groovy/src/main/java/io/gatehill/imposter/scripting/groovy/service/GroovyScriptServiceImpl.kt @@ -46,6 +46,7 @@ import com.google.common.cache.CacheBuilder import groovy.lang.Binding import groovy.lang.GroovyClassLoader import io.gatehill.imposter.config.util.EnvVars +import io.gatehill.imposter.model.script.lazyScriptRequestBuilder import io.gatehill.imposter.script.ReadWriteResponseBehaviour import io.gatehill.imposter.script.RuntimeContext import io.gatehill.imposter.script.ScriptUtil @@ -73,6 +74,8 @@ class GroovyScriptServiceImpl : ScriptService { .maximumSize(EnvVars.getEnv(ScriptUtil.ENV_SCRIPT_CACHE_ENTRIES)?.toLong() ?: ScriptUtil.DEFAULT_SCRIPT_CACHE_ENTRIES) .build>() + override val requestBuilder = lazyScriptRequestBuilder + init { val compilerConfig = CompilerConfiguration() compilerConfig.scriptBaseClass = GroovyDsl::class.java.canonicalName diff --git a/scripting/nashorn/src/main/java/io/gatehill/imposter/scripting/nashorn/service/NashornScriptServiceImpl.kt b/scripting/nashorn/src/main/java/io/gatehill/imposter/scripting/nashorn/service/NashornScriptServiceImpl.kt index 4ed27a49c..bbd808327 100644 --- a/scripting/nashorn/src/main/java/io/gatehill/imposter/scripting/nashorn/service/NashornScriptServiceImpl.kt +++ b/scripting/nashorn/src/main/java/io/gatehill/imposter/scripting/nashorn/service/NashornScriptServiceImpl.kt @@ -44,6 +44,7 @@ package io.gatehill.imposter.scripting.nashorn.service import com.google.common.cache.CacheBuilder import io.gatehill.imposter.config.util.EnvVars.Companion.getEnv +import io.gatehill.imposter.model.script.lazyScriptRequestBuilder import io.gatehill.imposter.plugin.Plugin import io.gatehill.imposter.plugin.PluginInfo import io.gatehill.imposter.plugin.RequireModules @@ -85,6 +86,8 @@ class NashornScriptServiceImpl : ScriptService, Plugin { .maximumSize(getEnv(ScriptUtil.ENV_SCRIPT_CACHE_ENTRIES)?.toLong() ?: ScriptUtil.DEFAULT_SCRIPT_CACHE_ENTRIES) .build>() + override val requestBuilder = lazyScriptRequestBuilder + init { if (getJvmVersion() < 11) { throw UnsupportedOperationException("Standalone Nashorn JavaScript plugin is only supported on Java 11+.") diff --git a/test/test-utils/src/main/java/io/gatehill/imposter/scripting/AbstractBaseScriptTest.kt b/test/test-utils/src/main/java/io/gatehill/imposter/scripting/AbstractBaseScriptTest.kt index 06d14c5fa..8d4c452a0 100644 --- a/test/test-utils/src/main/java/io/gatehill/imposter/scripting/AbstractBaseScriptTest.kt +++ b/test/test-utils/src/main/java/io/gatehill/imposter/scripting/AbstractBaseScriptTest.kt @@ -135,7 +135,7 @@ abstract class AbstractBaseScriptTest { When(mockHttpExchange.request).thenReturn(mockRequest) val pluginConfig = mock(PluginConfig::class.java) - val executionContext = ScriptUtil.buildContext(mockHttpExchange, null) + val executionContext = ScriptUtil.buildContext(getService().requestBuilder, mockHttpExchange, null) return RuntimeContext(env, logger, pluginConfig, additionalBindings, executionContext) }