Skip to content

modifyResponseBodyFilterFactory for some reason not executing #3670

Open
@dreamstar-enterprises

Description

@dreamstar-enterprises

Hi, I'm not sure why the rewrite function here simply won't execute.

/**
 * Gateway filter that enriches the /user-info endpoint response with session timing information.
 *
 * Process flow:
 * 1. Intercepts responses from the /user-info endpoint
 * 2. Attempts to retrieve session creation time from Redis session hash
 * 3. If successful, adds session timing information to the JSON response:
 *    - sessionCreatedAt: When the session was created
 *    - sessionExpiresAt: When the session will expire (xx mins after creation)
 *
 * Error handling:
 * - If Redis access fails: Logs error and continues without session timing info
 * - If session key not found: Continues without session timing info
 * - Non-user-info endpoints: Passes through unmodified
 *
 * The filter maintains the original response integrity even when Redis operations fail,
 * ensuring the user-info endpoint remains functional with or without the additional
 * session timing data.
 *
 * @property redisTemplate For accessing Redis session data
 * @property sessionProperties Configuration for Redis session namespacing
 * @property serverProperties Server configuration including endpoint prefixes
 * @property modifyResponseBodyFilterFactory Factory for response body modification
 */
@Component
internal class SessionInfoResponseFilter(
    private val redisTemplate: ReactiveRedisTemplate<String, Any>,
    private val sessionProperties: SessionProperties,
    private val serverProperties: ServerProperties,
    private val modifyResponseBodyFilterFactory: ModifyResponseBodyGatewayFilterFactory
) : AbstractGatewayFilterFactory<SessionInfoResponseFilter.Config>() {

    /** Configuration class for the session info response filter */
    class Config

    private val logger = LoggerFactory.getLogger(SessionInfoResponseFilter::class.java)

    /**
     * Applies the filter to add session timing information to /me responses.
     *
     * @param config Filter configuration (unused in this implementation)
     * @return GatewayFilter that enriches /user-info responses with session data
     */
    override fun apply(config: Config): GatewayFilter = GatewayFilter { exchange, chain ->

        // only modify /user-info endpoint responses
        if (exchange.request.path.value() != "${serverProperties.resourceServerPrefix}/me") {
            logger.debug("request did not match me endpoint")
            chain.filter(exchange)
        } else {
            chain.filter(exchange).then(
                exchange.session.flatMap { session ->
                    logger.debug("request did match me endpoint")
                    val sessionId = session.id
                    val sessionKey = "${sessionProperties.redis?.sessionNamespace}:sessions:$sessionId"

                    redisTemplate.opsForHash<String, Any>()
                        .get(sessionKey, "creationTime")
                        .doOnNext { creationTime ->
                            logger.debug("3. Found creation time in Redis: {}", creationTime)
                        }
                        .flatMap { creationTime ->

                            val created = Instant.ofEpochMilli(creationTime.toString().toLong())
                            logger.debug("4. Converted to Instant: {}", created)

                            // Define the RewriteFunction separately
                            val rewriteFunction = RewriteFunction<String, String> { _, originalResponse ->
                                logger.debug("Entering RewriteFunction with body: {}", originalResponse)

                                if (originalResponse.isNullOrEmpty()) {
                                    logger.warn("Response body is empty or null")
                                    return@RewriteFunction Mono.just(originalResponse)
                                }

                                return@RewriteFunction try {
                                    val jsonNode = JSONUtilities.objectMapper.readTree(originalResponse)
                                    logger.debug("Parsed JSON node: {}", jsonNode)

                                    (jsonNode as ObjectNode).apply {
                                        put("sessionCreatedAt", created.toString())
                                        put("sessionExpiresAt", created.plusSeconds(sessionProperties.timeout.toLong()).toString())
                                    }

                                    val modifiedResponse = JSONUtilities.objectMapper.writeValueAsString(jsonNode)
                                    logger.debug("Modified response body: {}", modifiedResponse)

                                    Mono.just(modifiedResponse)
                                } catch (ex: Exception) {
                                    logger.error("Failed to process response body", ex)
                                    Mono.error(ex)
                                }
                            }

                            val modifyConfig = ModifyResponseBodyGatewayFilterFactory.Config().apply {
                                logger.debug("Setting up rewrite function")
                                setInClass(String::class.java)
                                setOutClass(String::class.java)
                                setRewriteFunction(String::class.java, String::class.java, rewriteFunction)
                            }

                            // Apply modification filter first, then the chain
                            modifyResponseBodyFilterFactory.apply(modifyConfig).filter(exchange, chain)
                            
                        }
                        .onErrorResume { error ->
                            // log the Redis error but allow the chain to continue
                            logger.error("Failed to get session timing from Redis", error)
                            Mono.empty()
                        }
                }
            )
        }
    }
}

In the logs all I can see is:


2025-01-15T21:21:43.302Z DEBUG 35283 --- [BFFApplication] [ctor-http-nio-4] c.v.b.r.s.SessionInfoResponseFilter      : request did match userinfo endpoint
2025-01-15T21:21:43.316Z DEBUG 35283 --- [BFFApplication] [xecutorLoop-3-5] c.v.b.r.s.SessionInfoResponseFilter      : 3. Found creation time in Redis: 1736971305338
2025-01-15T21:21:43.317Z DEBUG 35283 --- [BFFApplication] [xecutorLoop-3-5] c.v.b.r.s.SessionInfoResponseFilter      : 4. Converted to Instant: 2025-01-15T20:01:45.338Z
2025-01-15T21:21:43.317Z DEBUG 35283 --- [BFFApplication] [xecutorLoop-3-5] c.v.b.r.s.SessionInfoResponseFilter      : Setting up rewrite function

I've spent 3 hours going over the internet trying to find a solution, but am getting stuck.

Would appreciate if anyone could help

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions