Firefly v4.8.0 is released

30 Mar 2018, Alvin Qiu

Firefly v4.8.0 adds RedisSessionHandler, AsyncStaticFileHandler, and fixes the AsyncTransactionalManager JDBC connection early closed exception.

Usually, we develop the HTTP service that is stateless, so we store the user session state in the external DB system. The Redis fits well with that situation. In this version, we provide the RedisSessionHandler that stores the session state in the Redis.

The first we set the Redis configuration when the RedisSessionHandler is initialized.

@Component("redisSessionHandler")
class RedisSessionHandler : Handler {

    private val handler: RedisHTTPSessionHandler

    init {
        val store = RedisSessionStore()
        val client = Redisson.createReactive()
        store.client = client
        store.keyPrefix = "com:fireflysource"
        store.sessionKey = "test_session"
        handler = RedisHTTPSessionHandler(store)
    }

    override fun handle(ctx: RoutingContext?) = handler.handle(ctx)
}

And then, set the handler to the router. In this way, we can store user state in the Redis session using the session APIs in the RoutingContext.

@Inject
private lateinit var redisHTTPSessionHandler: RedisSessionHandler


// session handler
router {
    path = "*"
    asyncHandler { redisHTTPSessionHandler.handle(this) }
}

// the user login
private suspend fun verifyPasswordRequest(ctx: RoutingContext) {
    val username = ctx.getParameter("username")
    val password = ctx.getParameter("password")

    Assert.hasText(username, "The username is required")
    Assert.hasText(password, "The password is required")

    try {
        val user = userService.getByName(username)
        Assert.isTrue(user.password == password, "The password is incorrect")

        val session = ctx.session.await()
        val userInfo = UserInfo(user.id ?: 0, user.name)
        session.attributes[config.loginUserKey] = userInfo
        session.maxInactiveInterval = config.sessionMaxInactiveInterval
        ctx.updateSession(session).await()
        ctx.setAttribute(config.loginUserKey, userInfo)
        ctx.redirect(getBackURL(ctx))
        log.info("user $userInfo login success!")
    } catch (e: RecordNotFound) {
        throw IllegalArgumentException("The username is incorrect")
    }
}

The Firefly v4.8.0 adds the AsyncStaticFileHandler that reads the static file using the AsynchronousFileChannel. It is not blocking the Firefly network thread when the user gets the static file from the Firefly HTTP server. We can use the AsyncStaticFileHandler in this way:

@Component("staticResourceHandler")
class StaticResourceHandler : AsyncHandler {

    val staticResources = listOf("/favicon.ico", "/static/*")

    companion object {
        private val log = KtLogger.getLogger { }
        private val staticFileHandler = createStaticFileHandler()

        private fun createStaticFileHandler(): AsyncStaticFileHandler {
            try {
                val path = Paths.get(SystemRouter::class.java.getResource("/").toURI())
                return AsyncStaticFileHandler(path.toAbsolutePath().toString(), 16 * 1024, true)
            } catch (e: Exception) {
                throw CommonRuntimeException(e)
            }
        }
    }

    override suspend fun handle(ctx: RoutingContext) {
        log.info("static file request ${ctx.uri}")
        ctx.put(HttpHeader.CACHE_CONTROL, "max-age=86400")
        staticFileHandler.handle(ctx)
    }

}

Set the router:

// static file handler
router {
    httpMethod = HttpMethod.GET
    paths = staticResourceHandler.staticResources
    asyncHandler(staticResourceHandler)
}

Update log:

  1. Add RedisSessionHandler.
  2. Add AsyncStaticFileHandler.
  3. Fix the AsyncTransactionalManager JDBC connection early closed exception.