From 20fd5e5dfbd48d6b65df12b460b488ea4715a16c Mon Sep 17 00:00:00 2001 From: Christophe Roudet Date: Fri, 12 Apr 2024 09:34:00 -0400 Subject: [PATCH] Add support for logback-access v2 --- pom.xml | 29 +- .../logback/access/LogbackAccessContext.kt | 2 +- .../boot/logback/access/LogbackAccessEvent.kt | 10 +- .../access/LogbackAccessEventSource.kt | 6 +- .../logback/access/LogbackAccessProperties.kt | 2 +- .../jetty/LogbackAccessJettyEventSource.kt | 71 ++-- .../jetty/LogbackAccessJettyRequestLog.kt | 7 +- ...ckAccessJettyWebServerFactoryCustomizer.kt | 6 +- .../logback/access/jetty/RequestWrapper.kt | 356 ++++++++++++++++++ .../joran/LogbackAccessJoranConfigurator.kt | 2 +- ...backAccessTeeServletFilterConfiguration.kt | 6 +- .../tomcat/LogbackAccessTomcatEventSource.kt | 12 +- .../LogbackAccessUndertowEventSource.kt | 28 +- .../value/LogbackAccessLocalPortStrategy.kt | 2 +- .../logback/access/SecurityAttributesTest.kt | 7 +- .../access/test/assertion/Assertions.kt | 19 +- ...TestContextClassLoaderCustomizerFactory.kt | 2 +- .../access/test/mock/MockEventFilter.kt | 6 +- .../access/test/mock/MockServletController.kt | 2 +- 19 files changed, 483 insertions(+), 92 deletions(-) create mode 100644 src/main/kotlin/dev/akkinoc/spring/boot/logback/access/jetty/RequestWrapper.kt diff --git a/pom.xml b/pom.xml index 93f40e8c..fd845f87 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ org.springframework.boot spring-boot-starter-parent - 3.1.6 + 3.2.4 @@ -127,8 +127,25 @@ true - ch.qos.logback - logback-access + ch.qos.logback.access + common + 2.0.1 + + + ch.qos.logback.access + tomcat + 2.0.1 + + + ch.qos.logback.access + jetty12 + 2.0.1 + + + org.eclipse.jetty.ee10.websocket + jetty-ee10-websocket-jakarta-server + 12.0.8 + test org.springframework.boot @@ -138,7 +155,7 @@ jakarta.servlet jakarta.servlet-api - 5.0.0 + 6.0.0 test @@ -282,12 +299,12 @@ io.gitlab.arturbosch.detekt detekt-cli - 1.23.5 + 1.23.6 io.gitlab.arturbosch.detekt detekt-formatting - 1.23.5 + 1.23.6 diff --git a/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/LogbackAccessContext.kt b/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/LogbackAccessContext.kt index 28cb3acc..73342012 100644 --- a/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/LogbackAccessContext.kt +++ b/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/LogbackAccessContext.kt @@ -1,6 +1,6 @@ package dev.akkinoc.spring.boot.logback.access -import ch.qos.logback.access.spi.AccessContext +import ch.qos.logback.access.common.spi.AccessContext import ch.qos.logback.core.spi.FilterReply import ch.qos.logback.core.status.Status import dev.akkinoc.spring.boot.logback.access.LogbackAccessProperties.Companion.DEFAULT_CONFIGS diff --git a/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/LogbackAccessEvent.kt b/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/LogbackAccessEvent.kt index c2c16217..32cdecae 100644 --- a/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/LogbackAccessEvent.kt +++ b/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/LogbackAccessEvent.kt @@ -1,9 +1,9 @@ package dev.akkinoc.spring.boot.logback.access -import ch.qos.logback.access.spi.IAccessEvent -import ch.qos.logback.access.spi.IAccessEvent.NA -import ch.qos.logback.access.spi.IAccessEvent.SENTINEL -import ch.qos.logback.access.spi.ServerAdapter +import ch.qos.logback.access.common.spi.IAccessEvent +import ch.qos.logback.access.common.spi.IAccessEvent.NA +import ch.qos.logback.access.common.spi.IAccessEvent.SENTINEL +import ch.qos.logback.access.common.spi.ServerAdapter import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse import java.io.IOException @@ -17,7 +17,7 @@ import java.util.concurrent.TimeUnit.MILLISECONDS * The Logback-access event. * * @property source The Logback-access event source. - * @see ch.qos.logback.access.spi.AccessEvent + * @see ch.qos.logback.access.common.spi.AccessEvent */ class LogbackAccessEvent(private var source: LogbackAccessEventSource) : IAccessEvent, Serializable { diff --git a/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/LogbackAccessEventSource.kt b/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/LogbackAccessEventSource.kt index 538697e8..f3053288 100644 --- a/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/LogbackAccessEventSource.kt +++ b/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/LogbackAccessEventSource.kt @@ -1,7 +1,7 @@ package dev.akkinoc.spring.boot.logback.access -import ch.qos.logback.access.spi.IAccessEvent -import ch.qos.logback.access.spi.ServerAdapter +import ch.qos.logback.access.common.spi.IAccessEvent +import ch.qos.logback.access.common.spi.ServerAdapter import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse import java.io.Serializable @@ -11,7 +11,7 @@ import java.io.Serializable * Represents the attributes of [IAccessEvent] by Kotlin properties, * which helps to implement subclasses with Kotlin delegated properties (especially [lazy]). * - * @see ch.qos.logback.access.spi.IAccessEvent + * @see ch.qos.logback.access.common.spi.IAccessEvent */ abstract class LogbackAccessEventSource { diff --git a/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/LogbackAccessProperties.kt b/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/LogbackAccessProperties.kt index 1b7498bb..1b5e0f93 100644 --- a/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/LogbackAccessProperties.kt +++ b/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/LogbackAccessProperties.kt @@ -1,6 +1,6 @@ package dev.akkinoc.spring.boot.logback.access -import ch.qos.logback.access.spi.IAccessEvent +import ch.qos.logback.access.common.spi.IAccessEvent import dev.akkinoc.spring.boot.logback.access.value.LogbackAccessLocalPortStrategy import io.undertow.UndertowOptions import org.apache.catalina.valves.RemoteIpValve diff --git a/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/jetty/LogbackAccessJettyEventSource.kt b/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/jetty/LogbackAccessJettyEventSource.kt index 3873a278..ac7cacd4 100644 --- a/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/jetty/LogbackAccessJettyEventSource.kt +++ b/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/jetty/LogbackAccessJettyEventSource.kt @@ -1,22 +1,24 @@ package dev.akkinoc.spring.boot.logback.access.jetty -import ch.qos.logback.access.AccessConstants.LB_INPUT_BUFFER -import ch.qos.logback.access.AccessConstants.LB_OUTPUT_BUFFER +import ch.qos.logback.access.common.AccessConstants.LB_INPUT_BUFFER +import ch.qos.logback.access.common.AccessConstants.LB_OUTPUT_BUFFER +import ch.qos.logback.access.common.servlet.Util.isFormUrlEncoded +import ch.qos.logback.access.jetty.JettyModernServerAdapter import ch.qos.logback.access.jetty.JettyServerAdapter -import ch.qos.logback.access.servlet.Util.isFormUrlEncoded -import ch.qos.logback.access.servlet.Util.isImageResponse +import ch.qos.logback.access.jetty.ResponseWrapper import dev.akkinoc.spring.boot.logback.access.LogbackAccessContext import dev.akkinoc.spring.boot.logback.access.LogbackAccessEventSource import dev.akkinoc.spring.boot.logback.access.LogbackAccessHackyLoggingOverrides.overriddenRequestBody import dev.akkinoc.spring.boot.logback.access.LogbackAccessHackyLoggingOverrides.overriddenResponseBody import dev.akkinoc.spring.boot.logback.access.security.LogbackAccessSecurityServletFilter.Companion.REMOTE_USER_ATTRIBUTE import dev.akkinoc.spring.boot.logback.access.value.LogbackAccessLocalPortStrategy +import org.eclipse.jetty.http.HttpHeader import org.eclipse.jetty.server.Request import org.eclipse.jetty.server.Response +import org.eclipse.jetty.session.DefaultSessionIdManager import java.lang.System.currentTimeMillis import java.lang.Thread.currentThread import java.net.URLEncoder.encode -import java.util.Collections.unmodifiableList import java.util.Collections.unmodifiableMap import kotlin.text.Charsets.UTF_8 @@ -24,64 +26,66 @@ import kotlin.text.Charsets.UTF_8 * The Logback-access event source for the Jetty web server. * * @property logbackAccessContext The Logback-access context. - * @see ch.qos.logback.access.spi.AccessEvent + * @see ch.qos.logback.access.common.spi.AccessEvent * @see ch.qos.logback.access.jetty.JettyServerAdapter - * @see ch.qos.logback.access.PatternLayout + * @see ch.qos.logback.access.common.PatternLayout * @see org.eclipse.jetty.server.CustomRequestLog */ class LogbackAccessJettyEventSource( private val logbackAccessContext: LogbackAccessContext, - override val request: Request, - override val response: Response, + private val rawRequest: Request, + private val rawResponse: Response, + override val request: RequestWrapper, + override val response: ResponseWrapper ) : LogbackAccessEventSource() { - override val serverAdapter: JettyServerAdapter = JettyServerAdapter(request, response) + override val serverAdapter: JettyServerAdapter = JettyModernServerAdapter(rawRequest, rawResponse) override val timeStamp: Long = currentTimeMillis() - override val elapsedTime: Long = timeStamp - request.timeStamp + override val elapsedTime: Long = timeStamp - Request.getTimeStamp(rawRequest) override val sequenceNumber: Long? = logbackAccessContext.raw.sequenceNumberGenerator?.nextSequenceNumber() override val threadName: String = currentThread().name override val serverName: String by lazy(LazyThreadSafetyMode.NONE) { - request.serverName + Request.getServerName(rawRequest) } override val localPort: Int by lazy(LazyThreadSafetyMode.NONE) { when (logbackAccessContext.properties.localPortStrategy) { - LogbackAccessLocalPortStrategy.LOCAL -> request.localPort - LogbackAccessLocalPortStrategy.SERVER -> request.serverPort + LogbackAccessLocalPortStrategy.LOCAL -> Request.getLocalPort(rawRequest) + LogbackAccessLocalPortStrategy.SERVER -> Request.getServerPort(rawRequest) } } override val remoteAddr: String by lazy(LazyThreadSafetyMode.NONE) { - request.remoteAddr + Request.getRemoteAddr(rawRequest) } override val remoteHost: String by lazy(LazyThreadSafetyMode.NONE) { - request.remoteHost + Request.getRemoteAddr(rawRequest) } override val remoteUser: String? by lazy(LazyThreadSafetyMode.NONE) { - request.getAttribute(REMOTE_USER_ATTRIBUTE) as String? ?: request.remoteUser + rawRequest.getAttribute(REMOTE_USER_ATTRIBUTE) as String? } override val protocol: String by lazy(LazyThreadSafetyMode.NONE) { - request.protocol + rawRequest.connectionMetaData.protocol } override val method: String by lazy(LazyThreadSafetyMode.NONE) { - request.method + rawRequest.method } override val requestURI: String? by lazy(LazyThreadSafetyMode.NONE) { - request.requestURI + rawRequest.httpURI.path } override val queryString: String by lazy(LazyThreadSafetyMode.NONE) { - request.queryString?.let { "?$it" }.orEmpty() + rawRequest.httpURI.query?.let { "?$it" }.orEmpty() } override val requestURL: String by lazy(LazyThreadSafetyMode.NONE) { @@ -90,37 +94,37 @@ class LogbackAccessJettyEventSource( override val requestHeaderMap: Map by lazy(LazyThreadSafetyMode.NONE) { val headers = sortedMapOf(String.CASE_INSENSITIVE_ORDER) - request.headerNames.asSequence().associateWithTo(headers) { request.getHeader(it) } + rawRequest.headers.fieldNamesCollection.associateByTo(headers, { it }, { rawRequest.headers[it] }) unmodifiableMap(headers) } override val cookieMap: Map by lazy(LazyThreadSafetyMode.NONE) { val cookies = linkedMapOf() - request.cookies.orEmpty().associateTo(cookies) { it.name to it.value } + Request.getCookies(rawRequest).associateByTo(cookies, { it.name }, { it.value }) unmodifiableMap(cookies) } override val requestParameterMap: Map> by lazy(LazyThreadSafetyMode.NONE) { val params = linkedMapOf>() - request.parameterMap.mapValuesTo(params) { unmodifiableList(it.value.asList()) } + Request.getParameters(rawRequest).associateByTo(params, { it.name }, { it.values }) unmodifiableMap(params) } override val attributeMap: Map by lazy(LazyThreadSafetyMode.NONE) { val attrs = linkedMapOf() - request.attributeNames.asSequence() + rawRequest.attributeNameSet.asSequence() .filter { it !in setOf(LB_INPUT_BUFFER, LB_OUTPUT_BUFFER) } - .associateWithTo(attrs) { "${request.getAttribute(it)}" } + .associateWithTo(attrs) { "${rawRequest.getAttribute(it)}" } unmodifiableMap(attrs) } override val sessionID: String? by lazy(LazyThreadSafetyMode.NONE) { - request.getSession(false)?.id + rawRequest.getAttribute(DefaultSessionIdManager.__NEW_SESSION_ID)?.toString() } override val requestContent: String? by lazy(LazyThreadSafetyMode.NONE) { overriddenRequestBody(request)?.also { return@lazy it } - val bytes = request.getAttribute(LB_INPUT_BUFFER) as ByteArray? + val bytes = rawRequest.getAttribute(LB_INPUT_BUFFER) as ByteArray? if (bytes == null && isFormUrlEncoded(request)) { return@lazy requestParameterMap.asSequence() .flatMap { (key, values) -> values.asSequence().map { key to it } } @@ -131,23 +135,24 @@ class LogbackAccessJettyEventSource( } override val statusCode: Int by lazy(LazyThreadSafetyMode.NONE) { - response.status + rawResponse.status } override val responseHeaderMap: Map by lazy(LazyThreadSafetyMode.NONE) { val headers = sortedMapOf(String.CASE_INSENSITIVE_ORDER) - response.headerNames.associateWithTo(headers) { response.getHeader(it) } + rawResponse.headers.fieldNamesCollection.associateByTo(headers, { it }, { rawResponse.headers[it] }) unmodifiableMap(headers) } override val contentLength: Long by lazy(LazyThreadSafetyMode.NONE) { - response.contentCount + Response.getContentBytesWritten(rawResponse) } override val responseContent: String? by lazy(LazyThreadSafetyMode.NONE) { overriddenResponseBody(request, response)?.also { return@lazy it } - if (isImageResponse(response)) return@lazy "[IMAGE CONTENTS SUPPRESSED]" - val bytes = request.getAttribute(LB_OUTPUT_BUFFER) as ByteArray? + val contentType = rawResponse.headers[HttpHeader.CONTENT_TYPE] + if (contentType != null && contentType.startsWith("image/")) return@lazy "[IMAGE CONTENTS SUPPRESSED]" + val bytes = rawRequest.getAttribute(LB_OUTPUT_BUFFER) as ByteArray? bytes?.let { String(it, UTF_8) } } diff --git a/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/jetty/LogbackAccessJettyRequestLog.kt b/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/jetty/LogbackAccessJettyRequestLog.kt index 047c062a..219a393b 100644 --- a/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/jetty/LogbackAccessJettyRequestLog.kt +++ b/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/jetty/LogbackAccessJettyRequestLog.kt @@ -1,5 +1,6 @@ package dev.akkinoc.spring.boot.logback.access.jetty +import ch.qos.logback.access.jetty.ResponseWrapper import dev.akkinoc.spring.boot.logback.access.LogbackAccessContext import dev.akkinoc.spring.boot.logback.access.LogbackAccessEvent import org.eclipse.jetty.server.Request @@ -30,8 +31,10 @@ class LogbackAccessJettyRequestLog( ) val source = LogbackAccessJettyEventSource( logbackAccessContext = logbackAccessContext, - request = request, - response = response, + rawRequest = request, + rawResponse = response, + request = RequestWrapper(request), + response = ResponseWrapper(response), ) val event = LogbackAccessEvent(source) logbackAccessContext.emit(event) diff --git a/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/jetty/LogbackAccessJettyWebServerFactoryCustomizer.kt b/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/jetty/LogbackAccessJettyWebServerFactoryCustomizer.kt index 987d4fa6..933bf009 100644 --- a/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/jetty/LogbackAccessJettyWebServerFactoryCustomizer.kt +++ b/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/jetty/LogbackAccessJettyWebServerFactoryCustomizer.kt @@ -2,7 +2,6 @@ package dev.akkinoc.spring.boot.logback.access.jetty import dev.akkinoc.spring.boot.logback.access.LogbackAccessContext import org.eclipse.jetty.server.Server -import org.eclipse.jetty.server.handler.RequestLogHandler import org.slf4j.Logger import org.slf4j.LoggerFactory.getLogger import org.springframework.boot.web.embedded.jetty.ConfigurableJettyWebServerFactory @@ -34,10 +33,7 @@ class LogbackAccessJettyWebServerFactoryCustomizer( * @param server The [Server]. */ private fun customize(server: Server) { - val handler = RequestLogHandler().apply { - requestLog = LogbackAccessJettyRequestLog(logbackAccessContext) - } - server.insertHandler(handler) + server.setRequestLog(LogbackAccessJettyRequestLog(logbackAccessContext)) log.debug( "Customized the {}: {} @{}", Server::class.simpleName, diff --git a/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/jetty/RequestWrapper.kt b/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/jetty/RequestWrapper.kt new file mode 100644 index 00000000..8fb858a3 --- /dev/null +++ b/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/jetty/RequestWrapper.kt @@ -0,0 +1,356 @@ +package dev.akkinoc.spring.boot.logback.access.jetty + +import ch.qos.logback.access.common.spi.IAccessEvent.NA +import ch.qos.logback.access.common.spi.WrappedHttpRequest +import jakarta.servlet.AsyncContext +import jakarta.servlet.DispatcherType +import jakarta.servlet.RequestDispatcher +import jakarta.servlet.ServletConnection +import jakarta.servlet.ServletContext +import jakarta.servlet.ServletException +import jakarta.servlet.ServletInputStream +import jakarta.servlet.ServletRequest +import jakarta.servlet.ServletResponse +import jakarta.servlet.http.Cookie +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import jakarta.servlet.http.HttpSession +import jakarta.servlet.http.HttpUpgradeHandler +import jakarta.servlet.http.Part +import org.eclipse.jetty.http.HttpHeader +import org.eclipse.jetty.http.HttpScheme +import org.eclipse.jetty.http.HttpURI +import org.eclipse.jetty.http.HttpVersion +import org.eclipse.jetty.server.Request +import org.eclipse.jetty.server.Session +import java.io.BufferedReader +import java.io.IOException +import java.io.UnsupportedEncodingException +import java.nio.charset.StandardCharsets +import java.security.Principal +import java.util.Collections +import java.util.Enumeration +import java.util.Locale +import java.util.TreeMap + +/** + * Request wrapper that implements Servlet API. + */ +class RequestWrapper(private val request: Request) : HttpServletRequest, WrappedHttpRequest { + private var requestURL: StringBuffer? = null + + override fun getAuthType(): String? { + return null + } + + override fun getCookies(): Array { + return arrayOfNulls(0) + } + + override fun getDateHeader(name: String?): Long { + return 0 + } + + override fun getHeader(name: String?): String? { + return null + } + + override fun getHeaders(name: String?): Enumeration? { + return null + } + + override fun getHeaderNames(): Enumeration? { + return Collections.enumeration(request.headers.fieldNamesCollection) + } + + override fun buildRequestHeaderMap(): Map { + val requestHeaderMap: MutableMap = TreeMap(java.lang.String.CASE_INSENSITIVE_ORDER) + request.headers.associateByTo(requestHeaderMap, { it.name }, { it.value }) + return requestHeaderMap + } + + override fun getIntHeader(name: String?): Int { + return 0 + } + + override fun getMethod(): String { + return request.method + } + + override fun getPathInfo(): String? { + return null + } + + override fun getPathTranslated(): String? { + return null + } + + override fun getContextPath(): String? { + return null + } + + override fun getQueryString(): String { + return request.httpURI.query + } + + override fun getRemoteUser(): String? { + return null + } + + override fun isUserInRole(role: String?): Boolean { + return false + } + + override fun getUserPrincipal(): Principal? { + return null + } + + override fun getRequestedSessionId(): String? { + return null + } + + override fun getRequestURI(): String { + val mutable: HttpURI.Mutable = HttpURI.build(request.httpURI) + mutable.query(null) + return mutable.asString() + } + + override fun getRequestURL(): StringBuffer { + if (requestURL == null) { + val result: String = request.httpURI.asString() + requestURL = StringBuffer(result) + } + return requestURL!! + } + + override fun getServletPath(): String? { + return null + } + + override fun getSessionID(): String { + val session: Session = request.getSession(false) + return if (session == null) { + NA + } else { + session.id + } + } + + override fun getSession(create: Boolean): HttpSession { + throw UnsupportedOperationException() + } + + override fun getSession(): HttpSession { + throw UnsupportedOperationException() + } + + override fun changeSessionId(): String { + throw UnsupportedOperationException() + } + + override fun isRequestedSessionIdValid(): Boolean { + throw UnsupportedOperationException() + } + + override fun isRequestedSessionIdFromCookie(): Boolean { + return false + } + + override fun isRequestedSessionIdFromURL(): Boolean { + throw UnsupportedOperationException() + } + + @Throws(IOException::class, ServletException::class) + override fun authenticate(response: HttpServletResponse?): Boolean { + return false + } + + @Throws(ServletException::class) + override fun login(username: String?, password: String?) = Unit + + @Throws(ServletException::class) + override fun logout() = Unit + + @Throws(IOException::class, ServletException::class) + override fun getParts(): Collection? { + return null + } + + @Throws(IOException::class, ServletException::class) + override fun getPart(name: String?): Part? { + return null + } + + @Throws(IOException::class, ServletException::class) + override fun upgrade(handlerClass: Class?): T? { + return null + } + + override fun getAttribute(name: String?): Any { + return request.getAttribute(name) + } + + override fun getAttributeNames(): Enumeration { + val attributeNamesSet: Set = request.attributeNameSet + return Collections.enumeration(attributeNamesSet) + } + + override fun getCharacterEncoding(): String? { + return null + } + + @Throws(UnsupportedEncodingException::class) + override fun setCharacterEncoding(env: String?) = Unit + + override fun getContentLength(): Int { + return 0 + } + + override fun getContentLengthLong(): Long { + return 0L + } + + override fun getContentType(): String? { + return request.headers[HttpHeader.CONTENT_TYPE] + } + + @Throws(IOException::class) + override fun getInputStream(): ServletInputStream? { + return null + } + + override fun buildRequestParameterMap(): Map> { + val results: MutableMap> = HashMap() + val allParameters = Request.extractQueryParameters(request, StandardCharsets.UTF_8) + allParameters.associateByTo(results, { it.name }, { it.values.toTypedArray() }) + return results + } + + override fun getParameter(name: String?): String { + throw UnsupportedOperationException() + } + + override fun getParameterNames(): Enumeration { + throw UnsupportedOperationException() + } + + override fun getParameterValues(name: String?): Array { + throw UnsupportedOperationException() + } + + override fun getParameterMap(): Map> { + throw UnsupportedOperationException() + } + + override fun getProtocol(): String { + return request.connectionMetaData.protocol + } + + override fun getScheme(): String { + return request.httpURI.scheme + } + + override fun getServerName(): String { + return Request.getServerName(request) + } + + override fun getServerPort(): Int { + return Request.getServerPort(request) + } + + @Throws(IOException::class) + override fun getReader(): BufferedReader? { + return null + } + + override fun getRemoteAddr(): String { + return Request.getRemoteAddr(request) + } + + override fun getRemoteHost(): String { + return Request.getRemoteAddr(request) + } + + override fun setAttribute(name: String?, o: Any?) = Unit + + override fun removeAttribute(name: String?) = Unit + + override fun getLocale(): Locale { + return Request.getLocales(request)[0] + } + + override fun getLocales(): Enumeration { + return Collections.enumeration(Request.getLocales(request)) + } + + override fun isSecure(): Boolean { + return HttpScheme.HTTPS.`is`(request.httpURI.scheme) + } + + override fun getRequestDispatcher(path: String?): RequestDispatcher? { + return null + } + + override fun getRemotePort(): Int { + return 0 + } + + override fun getLocalName(): String? { + return null + } + + override fun getLocalAddr(): String? { + return null + } + + override fun getLocalPort(): Int { + return 0 + } + + override fun getServletContext(): ServletContext? { + return null + } + + @Throws(IllegalStateException::class) + override fun startAsync(): AsyncContext? { + return null + } + + @Throws(IllegalStateException::class) + override fun startAsync(servletRequest: ServletRequest?, servletResponse: ServletResponse?): AsyncContext? { + return null + } + + override fun isAsyncStarted(): Boolean { + return false + } + + override fun isAsyncSupported(): Boolean { + return false + } + + override fun getAsyncContext(): AsyncContext? { + return null + } + + override fun getDispatcherType(): DispatcherType? { + return null + } + + override fun getRequestId(): String { + return request.connectionMetaData.id + "#" + request.id + } + + override fun getProtocolRequestId(): String { + val httpVersion: HttpVersion = request.connectionMetaData.httpVersion + return if (httpVersion === HttpVersion.HTTP_2 || httpVersion === (HttpVersion.HTTP_3)) { + request.id + } else { + NA + } + } + + override fun getServletConnection(): ServletConnection? { + return null + } +} diff --git a/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/joran/LogbackAccessJoranConfigurator.kt b/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/joran/LogbackAccessJoranConfigurator.kt index 1610dc64..671afa32 100644 --- a/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/joran/LogbackAccessJoranConfigurator.kt +++ b/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/joran/LogbackAccessJoranConfigurator.kt @@ -1,6 +1,6 @@ package dev.akkinoc.spring.boot.logback.access.joran -import ch.qos.logback.access.joran.JoranConfigurator +import ch.qos.logback.access.common.joran.JoranConfigurator import ch.qos.logback.core.joran.spi.ElementSelector import ch.qos.logback.core.joran.spi.RuleStore import ch.qos.logback.core.model.processor.DefaultProcessor diff --git a/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/tee/LogbackAccessTeeServletFilterConfiguration.kt b/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/tee/LogbackAccessTeeServletFilterConfiguration.kt index 427f2218..b4cb65a5 100644 --- a/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/tee/LogbackAccessTeeServletFilterConfiguration.kt +++ b/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/tee/LogbackAccessTeeServletFilterConfiguration.kt @@ -1,8 +1,8 @@ package dev.akkinoc.spring.boot.logback.access.tee -import ch.qos.logback.access.AccessConstants.TEE_FILTER_EXCLUDES_PARAM -import ch.qos.logback.access.AccessConstants.TEE_FILTER_INCLUDES_PARAM -import ch.qos.logback.access.servlet.TeeFilter +import ch.qos.logback.access.common.AccessConstants.TEE_FILTER_EXCLUDES_PARAM +import ch.qos.logback.access.common.AccessConstants.TEE_FILTER_INCLUDES_PARAM +import ch.qos.logback.access.common.servlet.TeeFilter import dev.akkinoc.spring.boot.logback.access.LogbackAccessProperties import org.slf4j.Logger import org.slf4j.LoggerFactory.getLogger diff --git a/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/tomcat/LogbackAccessTomcatEventSource.kt b/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/tomcat/LogbackAccessTomcatEventSource.kt index 465b58a4..38f0f439 100644 --- a/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/tomcat/LogbackAccessTomcatEventSource.kt +++ b/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/tomcat/LogbackAccessTomcatEventSource.kt @@ -1,9 +1,9 @@ package dev.akkinoc.spring.boot.logback.access.tomcat -import ch.qos.logback.access.AccessConstants.LB_INPUT_BUFFER -import ch.qos.logback.access.AccessConstants.LB_OUTPUT_BUFFER -import ch.qos.logback.access.servlet.Util.isFormUrlEncoded -import ch.qos.logback.access.servlet.Util.isImageResponse +import ch.qos.logback.access.common.AccessConstants.LB_INPUT_BUFFER +import ch.qos.logback.access.common.AccessConstants.LB_OUTPUT_BUFFER +import ch.qos.logback.access.common.servlet.Util.isFormUrlEncoded +import ch.qos.logback.access.common.servlet.Util.isImageResponse import ch.qos.logback.access.tomcat.TomcatServerAdapter import dev.akkinoc.spring.boot.logback.access.LogbackAccessContext import dev.akkinoc.spring.boot.logback.access.LogbackAccessEventSource @@ -31,9 +31,9 @@ import kotlin.text.Charsets.UTF_8 * * @property logbackAccessContext The Logback-access context. * @property requestAttributesEnabled Whether to enable the request attributes to work with [RemoteIpValve]. - * @see ch.qos.logback.access.spi.AccessEvent + * @see ch.qos.logback.access.common.spi.AccessEvent * @see ch.qos.logback.access.tomcat.TomcatServerAdapter - * @see ch.qos.logback.access.PatternLayout + * @see ch.qos.logback.access.common.PatternLayout * @see org.apache.catalina.valves.AccessLogValve * @see org.apache.catalina.valves.AbstractAccessLogValve * @see org.apache.catalina.valves.AbstractAccessLogValve.AccessLogElement diff --git a/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/undertow/LogbackAccessUndertowEventSource.kt b/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/undertow/LogbackAccessUndertowEventSource.kt index ae72a82e..0308c165 100644 --- a/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/undertow/LogbackAccessUndertowEventSource.kt +++ b/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/undertow/LogbackAccessUndertowEventSource.kt @@ -1,10 +1,10 @@ package dev.akkinoc.spring.boot.logback.access.undertow -import ch.qos.logback.access.AccessConstants.LB_INPUT_BUFFER -import ch.qos.logback.access.AccessConstants.LB_OUTPUT_BUFFER -import ch.qos.logback.access.servlet.Util.isFormUrlEncoded -import ch.qos.logback.access.servlet.Util.isImageResponse -import ch.qos.logback.access.spi.ServerAdapter +import ch.qos.logback.access.common.AccessConstants.LB_INPUT_BUFFER +import ch.qos.logback.access.common.AccessConstants.LB_OUTPUT_BUFFER +import ch.qos.logback.access.common.servlet.Util.isFormUrlEncoded +import ch.qos.logback.access.common.servlet.Util.isImageResponse +import ch.qos.logback.access.common.spi.ServerAdapter import dev.akkinoc.spring.boot.logback.access.LogbackAccessContext import dev.akkinoc.spring.boot.logback.access.LogbackAccessEventSource import dev.akkinoc.spring.boot.logback.access.LogbackAccessHackyLoggingOverrides.overriddenRequestBody @@ -31,8 +31,8 @@ import kotlin.text.Charsets.UTF_8 * * @property logbackAccessContext The Logback-access context. * @property exchange The request/response exchange. - * @see ch.qos.logback.access.spi.AccessEvent - * @see ch.qos.logback.access.PatternLayout + * @see ch.qos.logback.access.common.spi.AccessEvent + * @see ch.qos.logback.access.common.PatternLayout * @see io.undertow.servlet.spec.HttpServletRequestImpl * @see io.undertow.server.handlers.accesslog.AccessLogHandler * @see io.undertow.attribute.ExchangeAttribute @@ -117,7 +117,10 @@ class LogbackAccessUndertowEventSource( override val requestHeaderMap: Map by lazy(LazyThreadSafetyMode.NONE) { val headers = sortedMapOf(String.CASE_INSENSITIVE_ORDER) - exchange.requestHeaders.associateTo(headers) { "${it.headerName}" to it.first } + exchange.requestHeaders.associateTo(headers) { + "${it.headerName}" to it.first() + + } unmodifiableMap(headers) } @@ -139,11 +142,8 @@ class LogbackAccessUndertowEventSource( override val attributeMap: Map by lazy(LazyThreadSafetyMode.NONE) { val attrs = linkedMapOf() - if (request != null) { - request.attributeNames.asSequence() - .filter { it !in setOf(LB_INPUT_BUFFER, LB_OUTPUT_BUFFER) } - .associateWithTo(attrs) { "${request.getAttribute(it)}" } - } + request?.attributeNames?.asSequence()?.filter { it !in setOf(LB_INPUT_BUFFER, LB_OUTPUT_BUFFER) } + ?.associateWithTo(attrs) { "${request.getAttribute(it)}" } unmodifiableMap(attrs) } @@ -169,7 +169,7 @@ class LogbackAccessUndertowEventSource( override val responseHeaderMap: Map by lazy(LazyThreadSafetyMode.NONE) { val headers = sortedMapOf(String.CASE_INSENSITIVE_ORDER) - exchange.responseHeaders.associateTo(headers) { "${it.headerName}" to it.first } + exchange.responseHeaders.associateTo(headers) { "${it.headerName}" to it.first() } unmodifiableMap(headers) } diff --git a/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/value/LogbackAccessLocalPortStrategy.kt b/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/value/LogbackAccessLocalPortStrategy.kt index d1cbb838..e4b61293 100644 --- a/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/value/LogbackAccessLocalPortStrategy.kt +++ b/src/main/kotlin/dev/akkinoc/spring/boot/logback/access/value/LogbackAccessLocalPortStrategy.kt @@ -1,6 +1,6 @@ package dev.akkinoc.spring.boot.logback.access.value -import ch.qos.logback.access.spi.IAccessEvent +import ch.qos.logback.access.common.spi.IAccessEvent import jakarta.servlet.ServletRequest /** diff --git a/src/test/kotlin/dev/akkinoc/spring/boot/logback/access/SecurityAttributesTest.kt b/src/test/kotlin/dev/akkinoc/spring/boot/logback/access/SecurityAttributesTest.kt index 7db2c24b..9e6ea930 100644 --- a/src/test/kotlin/dev/akkinoc/spring/boot/logback/access/SecurityAttributesTest.kt +++ b/src/test/kotlin/dev/akkinoc/spring/boot/logback/access/SecurityAttributesTest.kt @@ -15,6 +15,7 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration +import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration import org.springframework.boot.test.web.client.TestRestTemplate import org.springframework.boot.test.web.client.exchange @@ -28,7 +29,11 @@ import org.springframework.test.context.TestPropertySource * @property supportsRemoteUsers Whether to support remote users. */ @ExtendWith(EventsCaptureExtension::class) -@Import(SecurityAutoConfiguration::class, ReactiveSecurityAutoConfiguration::class) +@Import( + ReactiveUserDetailsServiceAutoConfiguration::class, + SecurityAutoConfiguration::class, + ReactiveSecurityAutoConfiguration::class +) @TestPropertySource( properties = [ "spring.security.user.name=test-user", diff --git a/src/test/kotlin/dev/akkinoc/spring/boot/logback/access/test/assertion/Assertions.kt b/src/test/kotlin/dev/akkinoc/spring/boot/logback/access/test/assertion/Assertions.kt index 2a4418b3..bbd591c6 100644 --- a/src/test/kotlin/dev/akkinoc/spring/boot/logback/access/test/assertion/Assertions.kt +++ b/src/test/kotlin/dev/akkinoc/spring/boot/logback/access/test/assertion/Assertions.kt @@ -1,8 +1,9 @@ package dev.akkinoc.spring.boot.logback.access.test.assertion -import io.kotest.assertions.timing.continually -import io.kotest.assertions.timing.eventually -import io.kotest.assertions.until.fixed +import io.kotest.assertions.nondeterministic.continually +import io.kotest.assertions.nondeterministic.continuallyConfig +import io.kotest.assertions.nondeterministic.eventually +import io.kotest.assertions.nondeterministic.eventuallyConfig import kotlinx.coroutines.runBlocking import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds @@ -21,8 +22,12 @@ object Assertions { * @return The return value of the assertion function. */ fun assertLogbackAccessEventsEventually(assert: () -> T): T { + val config = eventuallyConfig { + duration = 1.seconds + interval = 100.milliseconds + } return runBlocking { - eventually(duration = 1.seconds, interval = 100.milliseconds.fixed()) { assert() } + eventually(config) { assert() } } } @@ -35,8 +40,12 @@ object Assertions { * @return The return value of the assertion function. */ fun assertLogbackAccessEventsContinually(assert: () -> T): T? { + val config = continuallyConfig { + duration = 1.seconds + interval = 100.milliseconds + } return runBlocking { - continually(duration = 1.seconds, interval = 100.milliseconds.fixed()) { assert() } + continually(config) { assert() } } } diff --git a/src/test/kotlin/dev/akkinoc/spring/boot/logback/access/test/configuration/TestContextClassLoaderCustomizerFactory.kt b/src/test/kotlin/dev/akkinoc/spring/boot/logback/access/test/configuration/TestContextClassLoaderCustomizerFactory.kt index c1dc71f5..62076c2e 100644 --- a/src/test/kotlin/dev/akkinoc/spring/boot/logback/access/test/configuration/TestContextClassLoaderCustomizerFactory.kt +++ b/src/test/kotlin/dev/akkinoc/spring/boot/logback/access/test/configuration/TestContextClassLoaderCustomizerFactory.kt @@ -19,8 +19,8 @@ import org.springframework.util.ClassUtils.convertClassNameToResourcePath import java.net.URL import io.undertow.websockets.jsr.Bootstrap as UndertowBootstrap import org.apache.tomcat.websocket.server.WsSci as TomcatWsSci +import org.eclipse.jetty.ee10.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer as JettyJakartaWebSocketServletContainerInitializer import org.eclipse.jetty.server.Server as JettyServer -import org.eclipse.jetty.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer as JettyJakartaWebSocketServletContainerInitializer import reactor.netty.http.server.HttpServer as NettyHttpServer /** diff --git a/src/test/kotlin/dev/akkinoc/spring/boot/logback/access/test/mock/MockEventFilter.kt b/src/test/kotlin/dev/akkinoc/spring/boot/logback/access/test/mock/MockEventFilter.kt index 8dea9565..923b604c 100644 --- a/src/test/kotlin/dev/akkinoc/spring/boot/logback/access/test/mock/MockEventFilter.kt +++ b/src/test/kotlin/dev/akkinoc/spring/boot/logback/access/test/mock/MockEventFilter.kt @@ -1,6 +1,6 @@ package dev.akkinoc.spring.boot.logback.access.test.mock -import ch.qos.logback.access.spi.IAccessEvent +import ch.qos.logback.access.common.spi.IAccessEvent import ch.qos.logback.core.filter.Filter import ch.qos.logback.core.spi.FilterReply @@ -13,11 +13,11 @@ class MockEventFilter : Filter() { /** * The request header name for overriding filter replies. */ - var requestHeaderName: String = "mock-event-filter-reply" + private var requestHeaderName: String = "mock-event-filter-reply" override fun decide(event: IAccessEvent): FilterReply { val value = event.getRequestHeader(requestHeaderName) - return FilterReply.values().find { it.name.equals(value, ignoreCase = true) } ?: FilterReply.NEUTRAL + return FilterReply.entries.find { it.name.equals(value, ignoreCase = true) } ?: FilterReply.NEUTRAL } } diff --git a/src/test/kotlin/dev/akkinoc/spring/boot/logback/access/test/mock/MockServletController.kt b/src/test/kotlin/dev/akkinoc/spring/boot/logback/access/test/mock/MockServletController.kt index 99f3555e..6040adfc 100644 --- a/src/test/kotlin/dev/akkinoc/spring/boot/logback/access/test/mock/MockServletController.kt +++ b/src/test/kotlin/dev/akkinoc/spring/boot/logback/access/test/mock/MockServletController.kt @@ -42,7 +42,7 @@ class MockServletController { @GetMapping("/text-with-session") fun getTextWithSession(session: HttpSession): String { val response = "mock-text" - log.debug("Getting a text with a session: {}; {}", response, session) + log.debug("Getting a text with a session: {}; {}", response, session.id) return response }