Skip to content

Commit 7833377

Browse files
committed
refactor: switches interceptor routing to be global to all routes.
1 parent ced5431 commit 7833377

File tree

19 files changed

+208
-116
lines changed

19 files changed

+208
-116
lines changed

adapter/awslambda/src/main/java/io/gatehill/imposter/awslambda/impl/LambdaServer.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ abstract class LambdaServer<Request, Response>(
8787
response.setStatusCode(HttpUtil.HTTP_NOT_FOUND)
8888

8989
} else {
90-
matched.forEach { route ->
90+
for (route in matched) {
9191
val request = buildRequest(event, route)
9292
val exchange = LambdaHttpExchange(router, route, request, response, attributes)
9393
val handler = route.handler ?: throw IllegalStateException("No route handler set for: $route")
@@ -100,6 +100,12 @@ abstract class LambdaServer<Request, Response>(
100100
exchange.failureCause?.let { cause ->
101101
throw RuntimeException("Route failed: $route", cause)
102102
}
103+
if (exchange.shouldTriggerNextHandler) {
104+
logger.trace("Continuing to next handler (if exists) for: ${describeRequestShort(event)}")
105+
} else {
106+
logger.trace("Stopping processing for: ${describeRequestShort(event)}")
107+
break
108+
}
103109
}
104110
}
105111

adapter/awslambda/src/main/java/io/gatehill/imposter/awslambda/impl/model/LambdaHttpExchange.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,12 @@
4444
package io.gatehill.imposter.awslambda.impl.model
4545

4646
import com.google.common.base.Strings
47-
import io.gatehill.imposter.http.*
47+
import io.gatehill.imposter.http.ExchangePhase
48+
import io.gatehill.imposter.http.HttpExchange
49+
import io.gatehill.imposter.http.HttpRequest
50+
import io.gatehill.imposter.http.HttpResponse
51+
import io.gatehill.imposter.http.HttpRoute
52+
import io.gatehill.imposter.http.HttpRouter
4853
import io.gatehill.imposter.util.HttpUtil
4954
import io.vertx.core.buffer.Buffer
5055

@@ -68,6 +73,9 @@ class LambdaHttpExchange(
6873
override var failureCause: Throwable? = null
6974
private set
7075

76+
var shouldTriggerNextHandler = false
77+
private set
78+
7179
override val response: HttpResponse = object : HttpResponse by response {
7280
override fun end() {
7381
router.invokeBeforeEndHandlers(this@LambdaHttpExchange)
@@ -114,4 +122,8 @@ class LambdaHttpExchange(
114122
override fun put(key: String, value: Any) {
115123
attributes[key] = value
116124
}
125+
126+
override fun next() {
127+
shouldTriggerNextHandler = true
128+
}
117129
}

adapter/vertxweb/src/main/java/io/gatehill/imposter/server/vertxweb/impl/VertxHttpExchange.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,8 @@ class VertxHttpExchange(
100100
override fun put(key: String, value: Any) {
101101
routingContext.put(key, value)
102102
}
103+
104+
override fun next() {
105+
routingContext.next()
106+
}
103107
}

core/api/src/main/java/io/gatehill/imposter/plugin/config/ConfigurablePlugin.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,6 @@ import io.gatehill.imposter.config.LoadedConfig
4848
* @author Pete Cornish
4949
*/
5050
interface ConfigurablePlugin<C : PluginConfig> {
51-
fun loadConfiguration(loadedConfigs: List<LoadedConfig>)
5251
val configs: List<C>
52+
fun loadConfiguration(loadedConfigs: List<LoadedConfig>)
5353
}

core/api/src/main/java/io/gatehill/imposter/service/HandlerService.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import io.gatehill.imposter.http.HttpExchangeHandler
4949
import io.gatehill.imposter.http.HttpRouter
5050
import io.gatehill.imposter.http.ResourceMatcher
5151
import io.gatehill.imposter.plugin.config.PluginConfig
52+
import io.gatehill.imposter.plugin.config.resource.BasicResourceConfig
5253
import io.gatehill.imposter.server.ServerFactory
5354

5455
/**
@@ -113,6 +114,13 @@ interface HandlerService {
113114
httpExchangeHandler: HttpExchangeFutureHandler,
114115
): HttpExchangeFutureHandler
115116

117+
fun build(
118+
imposterConfig: ImposterConfig,
119+
pluginConfig: PluginConfig,
120+
resourceConfig: BasicResourceConfig,
121+
httpExchangeHandler: HttpExchangeFutureHandler
122+
): HttpExchangeFutureHandler
123+
116124
/**
117125
* Same as [build] but wraps [httpExchangeHandler] in a future.
118126
*/

core/api/src/main/java/io/gatehill/imposter/service/InterceptorService.kt

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,22 +43,17 @@
4343

4444
package io.gatehill.imposter.service
4545

46-
import io.gatehill.imposter.http.HttpExchange
46+
import io.gatehill.imposter.http.HttpRouter
4747
import io.gatehill.imposter.plugin.config.PluginConfig
4848
import io.gatehill.imposter.plugin.config.resource.BasicResourceConfig
49-
import java.util.concurrent.CompletableFuture
5049

5150
/**
5251
* Executes request interceptors.
5352
*/
5453
interface InterceptorService {
55-
/**
56-
* Execute request interceptors, indicating whether the exchange
57-
* has been handled.
58-
*/
59-
fun executeInterceptors(
54+
fun configureInterceptorRoute(
55+
router: HttpRouter,
6056
pluginConfig: PluginConfig,
61-
interceptors: List<BasicResourceConfig>,
62-
httpExchange: HttpExchange,
63-
): CompletableFuture<Boolean>
57+
interceptor: BasicResourceConfig
58+
)
6459
}

core/engine/src/main/java/io/gatehill/imposter/model/steps/http/RemoteHttpExchange.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,8 @@ class RemoteHttpExchange(
9999
override fun put(key: String, value: Any) {
100100
initiatingExchange.put(key, value)
101101
}
102+
103+
override fun next() {
104+
throw UnsupportedOperationException()
105+
}
102106
}

core/engine/src/main/java/io/gatehill/imposter/plugin/config/ConfiguredPlugin.kt

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,11 @@ package io.gatehill.imposter.plugin.config
4545
import io.gatehill.imposter.ImposterConfig
4646
import io.gatehill.imposter.config.LoadedConfig
4747
import io.gatehill.imposter.config.util.ConfigUtil
48+
import io.gatehill.imposter.http.HttpRouter
4849
import io.gatehill.imposter.http.UniqueRoute
4950
import io.gatehill.imposter.plugin.RoutablePlugin
5051
import io.gatehill.imposter.plugin.config.resource.PassthroughResourceConfig
52+
import io.gatehill.imposter.service.InterceptorService
5153
import io.gatehill.imposter.util.ResourceUtil
5254
import io.vertx.core.Vertx
5355
import org.apache.logging.log4j.LogManager
@@ -60,7 +62,8 @@ import javax.inject.Inject
6062
*/
6163
abstract class ConfiguredPlugin<T : BasicPluginConfig> @Inject constructor(
6264
protected val vertx: Vertx,
63-
protected val imposterConfig: ImposterConfig
65+
protected val imposterConfig: ImposterConfig,
66+
private val interceptorService: InterceptorService,
6467
) : RoutablePlugin, ConfigurablePlugin<T> {
6568

6669
private val logger: Logger = LogManager.getLogger(ConfiguredPlugin::class.java)
@@ -111,6 +114,23 @@ abstract class ConfiguredPlugin<T : BasicPluginConfig> @Inject constructor(
111114
/* no op */
112115
}
113116

117+
override fun configureRoutes(router: HttpRouter) {
118+
configureInterceptorRoutes(router)
119+
configureResourceRoutes(router)
120+
}
121+
122+
fun configureInterceptorRoutes(router: HttpRouter) {
123+
configs.forEach { config ->
124+
if (config is InterceptorsHolder<*>) {
125+
config.interceptors?.forEach { interceptor ->
126+
interceptorService.configureInterceptorRoute(router, config, interceptor)
127+
}
128+
}
129+
}
130+
}
131+
132+
protected abstract fun configureResourceRoutes(router: HttpRouter)
133+
114134
/**
115135
* Iterates over [configs] and subresources to find unique route combinations
116136
* of path and HTTP method. For each combination found, only

core/engine/src/main/java/io/gatehill/imposter/service/HandlerServiceImpl.kt

Lines changed: 74 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ import io.gatehill.imposter.config.util.EnvVars
4949
import io.gatehill.imposter.http.*
5050
import io.gatehill.imposter.lifecycle.SecurityLifecycleHooks
5151
import io.gatehill.imposter.lifecycle.SecurityLifecycleListener
52-
import io.gatehill.imposter.plugin.config.InterceptorsHolder
5352
import io.gatehill.imposter.plugin.config.PluginConfig
5453
import io.gatehill.imposter.plugin.config.ResourcesHolder
5554
import io.gatehill.imposter.plugin.config.resource.BasicResourceConfig
@@ -68,7 +67,6 @@ import org.apache.logging.log4j.Level
6867
import org.apache.logging.log4j.LogManager
6968
import java.io.File
7069
import java.util.*
71-
import java.util.concurrent.CompletableFuture
7270
import java.util.regex.Pattern
7371
import javax.inject.Inject
7472

@@ -78,7 +76,6 @@ import javax.inject.Inject
7876
class HandlerServiceImpl @Inject constructor(
7977
private val securityService: SecurityService,
8078
private val securityLifecycle: SecurityLifecycleHooks,
81-
private val interceptorService: InterceptorService,
8279
private val responseService: ResponseService,
8380
private val upstreamService: UpstreamService,
8481
) : HandlerService, CoroutineScope by supervisedDefaultCoroutineScope {
@@ -103,16 +100,36 @@ class HandlerServiceImpl @Inject constructor(
103100
httpExchangeHandler: HttpExchangeFutureHandler,
104101
): HttpExchangeFutureHandler {
105102
val resolvedResourceConfigs = resolveResourceConfigs(pluginConfig)
106-
val resolvedInterceptorConfigs = resolveInterceptorConfigs(pluginConfig)
107103
return { httpExchange: HttpExchange ->
108-
handle(
109-
pluginConfig,
110-
httpExchangeHandler,
111-
httpExchange,
112-
resolvedResourceConfigs,
113-
resolvedInterceptorConfigs,
114-
resourceMatcher
115-
)
104+
future {
105+
handle(
106+
pluginConfig,
107+
httpExchangeHandler,
108+
httpExchange,
109+
resolvedResourceConfigs,
110+
resourceMatcher
111+
)
112+
}
113+
}
114+
}
115+
116+
override fun build(
117+
imposterConfig: ImposterConfig,
118+
pluginConfig: PluginConfig,
119+
resourceConfig: BasicResourceConfig,
120+
httpExchangeHandler: HttpExchangeFutureHandler,
121+
): HttpExchangeFutureHandler {
122+
val resolvedResourceConfigs = resolveResourceConfigs(pluginConfig)
123+
return { httpExchange: HttpExchange ->
124+
future {
125+
handle(
126+
pluginConfig,
127+
httpExchangeHandler,
128+
httpExchange,
129+
resolvedResourceConfigs,
130+
resourceConfig,
131+
)
132+
}
116133
}
117134
}
118135

@@ -221,18 +238,6 @@ class HandlerServiceImpl @Inject constructor(
221238
} ?: emptyList()
222239
}
223240

224-
/**
225-
* Extract the interceptor configurations from the plugin configuration, if present.
226-
*
227-
* @param pluginConfig the plugin configuration
228-
* @return the interceptor configurations
229-
*/
230-
private fun resolveInterceptorConfigs(pluginConfig: PluginConfig): List<ResolvedResourceConfig> {
231-
return (pluginConfig as? InterceptorsHolder<*>)?.interceptors?.map { config ->
232-
ResolvedResourceConfig.parse(config)
233-
} ?: emptyList()
234-
}
235-
236241
private fun logAppropriatelyForPath(httpExchange: HttpExchange, description: String) {
237242
val level = determineLogLevel(httpExchange)
238243
LOGGER.log(
@@ -248,65 +253,83 @@ class HandlerServiceImpl @Inject constructor(
248253
IGNORED_ERROR_PATHS.any { p: Pattern -> p.matcher(path).matches() }
249254
}?.let { Level.TRACE } ?: if (httpExchange.response.statusCode >= 500) Level.ERROR else Level.WARN
250255

251-
} catch (ignored: Exception) {
256+
} catch (_: Exception) {
252257
Level.ERROR
253258
}
254259
}
255260

256-
private fun handle(
261+
private suspend fun handle(
257262
pluginConfig: PluginConfig,
258263
httpExchangeHandler: HttpExchangeFutureHandler,
259264
httpExchange: HttpExchange,
260265
resourceConfigs: List<ResolvedResourceConfig>,
261-
interceptorConfigs: List<ResolvedResourceConfig>,
262266
resourceMatcher: ResourceMatcher,
263-
): CompletableFuture<Unit> = future {
267+
) {
264268
try {
265-
httpExchange.put(LogUtil.KEY_REQUEST_START, System.nanoTime())
269+
val rootResourceConfig = pluginConfig as BasicResourceConfig
270+
val resourceConfig = resourceMatcher.matchSingleResourceConfig(pluginConfig, resourceConfigs, httpExchange)
271+
?: rootResourceConfig
266272

267-
// every request has a unique ID
268-
val requestId = UUID.randomUUID().toString()
269-
httpExchange.put(ResourceUtil.RC_REQUEST_ID_KEY, requestId)
273+
handle(
274+
pluginConfig,
275+
httpExchangeHandler,
276+
httpExchange,
277+
resourceConfigs,
278+
resourceConfig
279+
)
280+
281+
} catch (e: Exception) {
282+
httpExchange.fail(
283+
RuntimeException("Unhandled exception processing request ${describeRequest(httpExchange)}", e)
284+
)
285+
}
286+
}
270287

288+
private suspend fun handle(
289+
pluginConfig: PluginConfig,
290+
httpExchangeHandler: HttpExchangeFutureHandler,
291+
httpExchange: HttpExchange,
292+
resourceConfigs: List<ResolvedResourceConfig>,
293+
resourceConfig: BasicResourceConfig,
294+
) {
295+
try {
296+
val rootResourceConfig = pluginConfig as BasicResourceConfig
271297
val response = httpExchange.response
272298

299+
httpExchange.putIfAbsent(LogUtil.KEY_REQUEST_START) {
300+
return@putIfAbsent System.nanoTime()
301+
}
302+
303+
// every request has a unique ID
304+
val requestId = httpExchange.getOrPut(ResourceUtil.RC_REQUEST_ID_KEY) {
305+
return@getOrPut UUID.randomUUID().toString()
306+
}
307+
273308
if (shouldAddEngineResponseHeaders) {
274309
response.putHeader("X-Imposter-Request", requestId)
275310
response.putHeader("Server", "imposter")
276311
}
277312

278-
val matchedInterceptors = resourceMatcher.matchAllResourceConfigs(pluginConfig, interceptorConfigs, httpExchange)
279-
val rootResourceConfig = pluginConfig as BasicResourceConfig
280-
val resourceConfig = resourceMatcher.matchSingleResourceConfig(pluginConfig, resourceConfigs, httpExchange)
281-
?: rootResourceConfig
282-
283-
// allows plugins to customise behaviour
313+
// note: may overwrite existing value from previous handler
284314
httpExchange.put(ResourceUtil.RESOURCE_CONFIG_KEY, resourceConfig)
285315

286316
if (isRequestPermitted(rootResourceConfig, resourceConfig, resourceConfigs, httpExchange)) {
287317
// set before actual dispatch to avoid race condition where
288318
// a response is sent before the phase is set
289319
httpExchange.phase = ExchangePhase.REQUEST_DISPATCHED
290320

291-
val handled = interceptorService.executeInterceptors(
292-
pluginConfig,
293-
matchedInterceptors,
294-
httpExchange
295-
).await()
296-
297-
if (!handled) {
298-
if (shouldForwardToUpstream(pluginConfig, resourceConfig, httpExchange)) {
299-
forwardToUpstream(pluginConfig, resourceConfig, httpExchange).await()
300-
} else {
301-
httpExchangeHandler(httpExchange).await()
302-
}
321+
if (shouldForwardToUpstream(pluginConfig, resourceConfig, httpExchange)) {
322+
forwardToUpstream(pluginConfig, resourceConfig, httpExchange).await()
323+
} else {
324+
httpExchangeHandler(httpExchange).await()
303325
}
326+
327+
// note: this will log after every matching handler
304328
LogUtil.logCompletion(httpExchange)
305329

306330
} else {
307331
LOGGER.trace("Request {} was not permitted to continue", describeRequest(httpExchange, requestId))
308332
}
309-
310333
} catch (e: Exception) {
311334
httpExchange.fail(
312335
RuntimeException("Unhandled exception processing request ${describeRequest(httpExchange)}", e)

0 commit comments

Comments
 (0)