This commit is contained in:
@@ -39,7 +39,6 @@ allprojects { subproject ->
|
|||||||
modularity.inferModulePath = true
|
modularity.inferModulePath = true
|
||||||
toolchain {
|
toolchain {
|
||||||
languageVersion = JavaLanguageVersion.of(21)
|
languageVersion = JavaLanguageVersion.of(21)
|
||||||
vendor = JvmVendorSpec.GRAAL_VM
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -91,6 +91,10 @@ Provider<EnvelopeJarTask> envelopeJarTaskProvider = tasks.named(EnvelopePlugin.E
|
|||||||
}
|
}
|
||||||
|
|
||||||
tasks.named(NativeImagePlugin.CONFIGURE_NATIVE_IMAGE_TASK_NAME, NativeImageConfigurationTask) {
|
tasks.named(NativeImagePlugin.CONFIGURE_NATIVE_IMAGE_TASK_NAME, NativeImageConfigurationTask) {
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(21)
|
||||||
|
vendor = JvmVendorSpec.GRAAL_VM
|
||||||
|
}
|
||||||
mainClass = "net.woggioni.rbcs.cli.graal.GraalNativeImageConfiguration"
|
mainClass = "net.woggioni.rbcs.cli.graal.GraalNativeImageConfiguration"
|
||||||
classpath = project.files(
|
classpath = project.files(
|
||||||
configurations.configureNativeImageRuntimeClasspath,
|
configurations.configureNativeImageRuntimeClasspath,
|
||||||
|
@@ -56,7 +56,6 @@ import net.woggioni.rbcs.server.configuration.Serializer
|
|||||||
import net.woggioni.rbcs.server.exception.ExceptionHandler
|
import net.woggioni.rbcs.server.exception.ExceptionHandler
|
||||||
import net.woggioni.rbcs.server.handler.MaxRequestSizeHandler
|
import net.woggioni.rbcs.server.handler.MaxRequestSizeHandler
|
||||||
import net.woggioni.rbcs.server.handler.ServerHandler
|
import net.woggioni.rbcs.server.handler.ServerHandler
|
||||||
import net.woggioni.rbcs.server.handler.TraceHandler
|
|
||||||
import net.woggioni.rbcs.server.throttling.BucketManager
|
import net.woggioni.rbcs.server.throttling.BucketManager
|
||||||
import net.woggioni.rbcs.server.throttling.ThrottlingHandler
|
import net.woggioni.rbcs.server.throttling.ThrottlingHandler
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
@@ -355,13 +354,12 @@ class RemoteBuildCacheServer(private val cfg: Configuration) {
|
|||||||
|
|
||||||
val serverHandler = let {
|
val serverHandler = let {
|
||||||
val prefix = Path.of("/").resolve(Path.of(cfg.serverPath ?: "/"))
|
val prefix = Path.of("/").resolve(Path.of(cfg.serverPath ?: "/"))
|
||||||
ServerHandler(prefix)
|
ServerHandler(prefix) {
|
||||||
|
cacheHandlerFactory.newHandler(cfg, ch.eventLoop(), channelFactory, datagramChannelFactory)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pipeline.addLast(eventExecutorGroup, ServerHandler.NAME, serverHandler)
|
pipeline.addLast(eventExecutorGroup, ServerHandler.NAME, serverHandler)
|
||||||
|
pipeline.addLast(ExceptionHandler.NAME, ExceptionHandler)
|
||||||
pipeline.addLast(cacheHandlerFactory.newHandler(cfg, ch.eventLoop(), channelFactory, datagramChannelFactory))
|
|
||||||
pipeline.addLast(TraceHandler)
|
|
||||||
pipeline.addLast(ExceptionHandler)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun asyncClose() = cacheHandlerFactory.asyncClose()
|
override fun asyncClose() = cacheHandlerFactory.asyncClose()
|
||||||
|
@@ -0,0 +1,4 @@
|
|||||||
|
package net.woggioni.rbcs.server.event
|
||||||
|
|
||||||
|
class RequestCompletedEvent {
|
||||||
|
}
|
@@ -27,6 +27,9 @@ import javax.net.ssl.SSLPeerUnverifiedException
|
|||||||
|
|
||||||
@Sharable
|
@Sharable
|
||||||
object ExceptionHandler : ChannelDuplexHandler() {
|
object ExceptionHandler : ChannelDuplexHandler() {
|
||||||
|
|
||||||
|
val NAME : String = this::class.java.name
|
||||||
|
|
||||||
private val log = contextLogger()
|
private val log = contextLogger()
|
||||||
|
|
||||||
private val NOT_AUTHORIZED: FullHttpResponse = DefaultFullHttpResponse(
|
private val NOT_AUTHORIZED: FullHttpResponse = DefaultFullHttpResponse(
|
||||||
|
@@ -1,28 +1,79 @@
|
|||||||
package net.woggioni.rbcs.server.handler
|
package net.woggioni.rbcs.server.handler
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelHandler
|
||||||
import io.netty.channel.ChannelHandler.Sharable
|
import io.netty.channel.ChannelHandler.Sharable
|
||||||
import io.netty.channel.ChannelHandlerContext
|
import io.netty.channel.ChannelHandlerContext
|
||||||
|
import io.netty.channel.ChannelOutboundHandler
|
||||||
|
import io.netty.channel.ChannelPromise
|
||||||
import io.netty.channel.SimpleChannelInboundHandler
|
import io.netty.channel.SimpleChannelInboundHandler
|
||||||
import io.netty.handler.codec.http.HttpContent
|
import io.netty.handler.codec.http.HttpContent
|
||||||
import io.netty.handler.codec.http.LastHttpContent
|
import io.netty.handler.codec.http.LastHttpContent
|
||||||
|
import net.woggioni.rbcs.api.message.CacheMessage.CacheValueNotFoundResponse
|
||||||
import net.woggioni.rbcs.api.message.CacheMessage.CacheContent
|
import net.woggioni.rbcs.api.message.CacheMessage.CacheContent
|
||||||
|
import net.woggioni.rbcs.api.message.CacheMessage.CachePutResponse
|
||||||
import net.woggioni.rbcs.api.message.CacheMessage.LastCacheContent
|
import net.woggioni.rbcs.api.message.CacheMessage.LastCacheContent
|
||||||
|
import java.net.SocketAddress
|
||||||
|
|
||||||
@Sharable
|
class CacheContentHandler(private val pairedHandler : ChannelHandler) : SimpleChannelInboundHandler<HttpContent>(), ChannelOutboundHandler {
|
||||||
object CacheContentHandler : SimpleChannelInboundHandler<HttpContent>() {
|
private var requestFinished = false
|
||||||
val NAME = this::class.java.name
|
|
||||||
|
|
||||||
override fun channelRead0(ctx: ChannelHandlerContext, msg: HttpContent) {
|
override fun channelRead0(ctx: ChannelHandlerContext, msg: HttpContent) {
|
||||||
|
if(requestFinished) {
|
||||||
|
ctx.fireChannelRead(msg.retain())
|
||||||
|
} else {
|
||||||
when (msg) {
|
when (msg) {
|
||||||
is LastHttpContent -> {
|
is LastHttpContent -> {
|
||||||
ctx.fireChannelRead(LastCacheContent(msg.content().retain()))
|
ctx.fireChannelRead(LastCacheContent(msg.content().retain()))
|
||||||
ctx.pipeline().remove(this)
|
requestFinished = true
|
||||||
}
|
}
|
||||||
else -> ctx.fireChannelRead(CacheContent(msg.content().retain()))
|
else -> ctx.fireChannelRead(CacheContent(msg.content().retain()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
|
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
|
||||||
super.exceptionCaught(ctx, cause)
|
super.exceptionCaught(ctx, cause)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun bind(ctx: ChannelHandlerContext, localAddress: SocketAddress, promise: ChannelPromise) {
|
||||||
|
ctx.bind(localAddress, promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun connect(
|
||||||
|
ctx: ChannelHandlerContext,
|
||||||
|
remoteAddress: SocketAddress,
|
||||||
|
localAddress: SocketAddress,
|
||||||
|
promise: ChannelPromise
|
||||||
|
) {
|
||||||
|
ctx.connect(remoteAddress, localAddress, promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun disconnect(ctx: ChannelHandlerContext, promise: ChannelPromise) {
|
||||||
|
ctx.disconnect(promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close(ctx: ChannelHandlerContext, promise: ChannelPromise) {
|
||||||
|
ctx.close(promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deregister(ctx: ChannelHandlerContext, promise: ChannelPromise) {
|
||||||
|
ctx.deregister(promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(ctx: ChannelHandlerContext) {
|
||||||
|
ctx.read()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun flush(ctx: ChannelHandlerContext) {
|
||||||
|
ctx.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(ctx: ChannelHandlerContext, msg: Any, promise: ChannelPromise) {
|
||||||
|
ctx.write(msg, promise)
|
||||||
|
if(msg is LastCacheContent || msg is CachePutResponse || msg is CacheValueNotFoundResponse || msg is LastHttpContent) {
|
||||||
|
ctx.pipeline().remove(pairedHandler)
|
||||||
|
ctx.pipeline().remove(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@@ -0,0 +1,56 @@
|
|||||||
|
package net.woggioni.rbcs.server.handler
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelHandlerContext
|
||||||
|
import io.netty.channel.ChannelInboundHandlerAdapter
|
||||||
|
import io.netty.channel.ChannelOutboundHandler
|
||||||
|
import io.netty.channel.ChannelPromise
|
||||||
|
import net.woggioni.rbcs.server.event.RequestCompletedEvent
|
||||||
|
import java.net.SocketAddress
|
||||||
|
|
||||||
|
class ResponseCapHandler : ChannelInboundHandlerAdapter(), ChannelOutboundHandler {
|
||||||
|
val bufferedMessages = mutableListOf<Any>()
|
||||||
|
|
||||||
|
override fun bind(ctx: ChannelHandlerContext, localAddress: SocketAddress, promise: ChannelPromise) {
|
||||||
|
ctx.bind(localAddress, promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun connect(
|
||||||
|
ctx: ChannelHandlerContext,
|
||||||
|
remoteAddress: SocketAddress,
|
||||||
|
localAddress: SocketAddress,
|
||||||
|
promise: ChannelPromise
|
||||||
|
) {
|
||||||
|
ctx.connect(remoteAddress, localAddress, promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun disconnect(ctx: ChannelHandlerContext, promise: ChannelPromise) {
|
||||||
|
ctx.disconnect(promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close(ctx: ChannelHandlerContext, promise: ChannelPromise) {
|
||||||
|
ctx.close(promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deregister(ctx: ChannelHandlerContext, promise: ChannelPromise) {
|
||||||
|
ctx.deregister(promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(ctx: ChannelHandlerContext) {
|
||||||
|
ctx.read()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(ctx: ChannelHandlerContext, msg: Any, promise: ChannelPromise) {
|
||||||
|
bufferedMessages.add(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun flush(ctx: ChannelHandlerContext) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) {
|
||||||
|
if(evt is RequestCompletedEvent) {
|
||||||
|
for(msg in bufferedMessages) ctx.write(msg)
|
||||||
|
ctx.flush()
|
||||||
|
ctx.pipeline().remove(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
package net.woggioni.rbcs.server.handler
|
package net.woggioni.rbcs.server.handler
|
||||||
|
|
||||||
import io.netty.channel.ChannelDuplexHandler
|
import io.netty.channel.ChannelDuplexHandler
|
||||||
|
import io.netty.channel.ChannelHandler
|
||||||
import io.netty.channel.ChannelHandlerContext
|
import io.netty.channel.ChannelHandlerContext
|
||||||
import io.netty.channel.ChannelPromise
|
import io.netty.channel.ChannelPromise
|
||||||
import io.netty.handler.codec.http.DefaultFullHttpResponse
|
import io.netty.handler.codec.http.DefaultFullHttpResponse
|
||||||
@@ -15,6 +16,8 @@ import io.netty.handler.codec.http.HttpRequest
|
|||||||
import io.netty.handler.codec.http.HttpResponseStatus
|
import io.netty.handler.codec.http.HttpResponseStatus
|
||||||
import io.netty.handler.codec.http.HttpUtil
|
import io.netty.handler.codec.http.HttpUtil
|
||||||
import io.netty.handler.codec.http.HttpVersion
|
import io.netty.handler.codec.http.HttpVersion
|
||||||
|
import io.netty.handler.codec.http.LastHttpContent
|
||||||
|
import net.woggioni.rbcs.api.CacheHandlerFactory
|
||||||
import net.woggioni.rbcs.api.CacheValueMetadata
|
import net.woggioni.rbcs.api.CacheValueMetadata
|
||||||
import net.woggioni.rbcs.api.message.CacheMessage
|
import net.woggioni.rbcs.api.message.CacheMessage
|
||||||
import net.woggioni.rbcs.api.message.CacheMessage.CacheContent
|
import net.woggioni.rbcs.api.message.CacheMessage.CacheContent
|
||||||
@@ -27,19 +30,22 @@ import net.woggioni.rbcs.api.message.CacheMessage.LastCacheContent
|
|||||||
import net.woggioni.rbcs.common.createLogger
|
import net.woggioni.rbcs.common.createLogger
|
||||||
import net.woggioni.rbcs.common.debug
|
import net.woggioni.rbcs.common.debug
|
||||||
import net.woggioni.rbcs.common.warn
|
import net.woggioni.rbcs.common.warn
|
||||||
|
import net.woggioni.rbcs.server.event.RequestCompletedEvent
|
||||||
|
import net.woggioni.rbcs.server.exception.ExceptionHandler
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
class ServerHandler(private val serverPrefix: Path) :
|
class ServerHandler(private val serverPrefix: Path, private val cacheHandlerSupplier : () -> ChannelHandler) :
|
||||||
ChannelDuplexHandler() {
|
ChannelDuplexHandler() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val log = createLogger<ServerHandler>()
|
private val log = createLogger<ServerHandler>()
|
||||||
val NAME = this::class.java.name
|
val NAME = ServerHandler::class.java.name
|
||||||
}
|
}
|
||||||
|
|
||||||
private var httpVersion = HttpVersion.HTTP_1_1
|
private var httpVersion = HttpVersion.HTTP_1_1
|
||||||
private var keepAlive = true
|
private var keepAlive = true
|
||||||
|
private var pipelinedRequests = 0
|
||||||
|
|
||||||
private fun resetRequestMetadata() {
|
private fun resetRequestMetadata() {
|
||||||
httpVersion = HttpVersion.HTTP_1_1
|
httpVersion = HttpVersion.HTTP_1_1
|
||||||
@@ -73,6 +79,8 @@ class ServerHandler(private val serverPrefix: Path) :
|
|||||||
try {
|
try {
|
||||||
when (msg) {
|
when (msg) {
|
||||||
is CachePutResponse -> {
|
is CachePutResponse -> {
|
||||||
|
pipelinedRequests -= 1
|
||||||
|
ctx.fireUserEventTriggered(RequestCompletedEvent())
|
||||||
val response = DefaultFullHttpResponse(httpVersion, HttpResponseStatus.CREATED)
|
val response = DefaultFullHttpResponse(httpVersion, HttpResponseStatus.CREATED)
|
||||||
val keyBytes = msg.key.toByteArray(Charsets.UTF_8)
|
val keyBytes = msg.key.toByteArray(Charsets.UTF_8)
|
||||||
response.headers().apply {
|
response.headers().apply {
|
||||||
@@ -88,6 +96,8 @@ class ServerHandler(private val serverPrefix: Path) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
is CacheValueNotFoundResponse -> {
|
is CacheValueNotFoundResponse -> {
|
||||||
|
pipelinedRequests -= 1
|
||||||
|
ctx.fireUserEventTriggered(RequestCompletedEvent())
|
||||||
val response = DefaultFullHttpResponse(httpVersion, HttpResponseStatus.NOT_FOUND)
|
val response = DefaultFullHttpResponse(httpVersion, HttpResponseStatus.NOT_FOUND)
|
||||||
response.headers()[HttpHeaderNames.CONTENT_LENGTH] = 0
|
response.headers()[HttpHeaderNames.CONTENT_LENGTH] = 0
|
||||||
setKeepAliveHeader(response.headers())
|
setKeepAliveHeader(response.headers())
|
||||||
@@ -108,6 +118,8 @@ class ServerHandler(private val serverPrefix: Path) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
is LastCacheContent -> {
|
is LastCacheContent -> {
|
||||||
|
pipelinedRequests -= 1
|
||||||
|
ctx.fireUserEventTriggered(RequestCompletedEvent())
|
||||||
ctx.writeAndFlush(DefaultLastHttpContent(msg.content()))
|
ctx.writeAndFlush(DefaultLastHttpContent(msg.content()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,6 +139,10 @@ class ServerHandler(private val serverPrefix: Path) :
|
|||||||
} finally {
|
} finally {
|
||||||
resetRequestMetadata()
|
resetRequestMetadata()
|
||||||
}
|
}
|
||||||
|
} else if(msg is LastHttpContent) {
|
||||||
|
pipelinedRequests -= 1
|
||||||
|
ctx.fireUserEventTriggered(RequestCompletedEvent())
|
||||||
|
ctx.write(msg, promise)
|
||||||
} else super.write(ctx, msg, promise)
|
} else super.write(ctx, msg, promise)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,7 +155,13 @@ class ServerHandler(private val serverPrefix: Path) :
|
|||||||
if (path.startsWith(serverPrefix)) {
|
if (path.startsWith(serverPrefix)) {
|
||||||
val relativePath = serverPrefix.relativize(path)
|
val relativePath = serverPrefix.relativize(path)
|
||||||
val key = relativePath.toString()
|
val key = relativePath.toString()
|
||||||
ctx.pipeline().addAfter(NAME, CacheContentHandler.NAME, CacheContentHandler)
|
if(pipelinedRequests > 0) {
|
||||||
|
ctx.pipeline().addBefore(ExceptionHandler.NAME, null, ResponseCapHandler())
|
||||||
|
}
|
||||||
|
val cacheHandler = cacheHandlerSupplier()
|
||||||
|
ctx.pipeline().addBefore(ExceptionHandler.NAME, null, CacheContentHandler(cacheHandler))
|
||||||
|
ctx.pipeline().addBefore(ExceptionHandler.NAME, null, cacheHandler)
|
||||||
|
pipelinedRequests += 1
|
||||||
key.let(::CacheGetRequest)
|
key.let(::CacheGetRequest)
|
||||||
.let(ctx::fireChannelRead)
|
.let(ctx::fireChannelRead)
|
||||||
?: ctx.channel().write(CacheValueNotFoundResponse())
|
?: ctx.channel().write(CacheValueNotFoundResponse())
|
||||||
@@ -159,7 +181,14 @@ class ServerHandler(private val serverPrefix: Path) :
|
|||||||
log.debug(ctx) {
|
log.debug(ctx) {
|
||||||
"Added value for key '$key' to build cache"
|
"Added value for key '$key' to build cache"
|
||||||
}
|
}
|
||||||
ctx.pipeline().addAfter(NAME, CacheContentHandler.NAME, CacheContentHandler)
|
if(pipelinedRequests > 0) {
|
||||||
|
ctx.pipeline().addBefore(ExceptionHandler.NAME, null, ResponseCapHandler())
|
||||||
|
}
|
||||||
|
val cacheHandler = cacheHandlerSupplier()
|
||||||
|
ctx.pipeline().addBefore(ExceptionHandler.NAME, null, CacheContentHandler(cacheHandler))
|
||||||
|
ctx.pipeline().addBefore(ExceptionHandler.NAME, null, cacheHandler)
|
||||||
|
pipelinedRequests += 1
|
||||||
|
|
||||||
path.fileName?.toString()
|
path.fileName?.toString()
|
||||||
?.let {
|
?.let {
|
||||||
val mimeType = HttpUtil.getMimeType(msg)?.toString()
|
val mimeType = HttpUtil.getMimeType(msg)?.toString()
|
||||||
@@ -176,6 +205,11 @@ class ServerHandler(private val serverPrefix: Path) :
|
|||||||
ctx.writeAndFlush(response)
|
ctx.writeAndFlush(response)
|
||||||
}
|
}
|
||||||
} else if (method == HttpMethod.TRACE) {
|
} else if (method == HttpMethod.TRACE) {
|
||||||
|
if(pipelinedRequests > 0) {
|
||||||
|
ctx.pipeline().addBefore(ExceptionHandler.NAME, null, ResponseCapHandler())
|
||||||
|
}
|
||||||
|
ctx.pipeline().addBefore(ExceptionHandler.NAME, null, TraceHandler)
|
||||||
|
pipelinedRequests += 1
|
||||||
super.channelRead(ctx, msg)
|
super.channelRead(ctx, msg)
|
||||||
} else {
|
} else {
|
||||||
log.warn(ctx) {
|
log.warn(ctx) {
|
||||||
@@ -187,42 +221,6 @@ class ServerHandler(private val serverPrefix: Path) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
data class ContentDisposition(val type: Type?, val fileName: String?) {
|
|
||||||
enum class Type {
|
|
||||||
attachment, `inline`;
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
@JvmStatic
|
|
||||||
fun parse(maybeString: String?) = maybeString.let { s ->
|
|
||||||
try {
|
|
||||||
java.lang.Enum.valueOf(Type::class.java, s)
|
|
||||||
} catch (ex: IllegalArgumentException) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
@JvmStatic
|
|
||||||
fun parse(contentDisposition: String) : ContentDisposition {
|
|
||||||
val parts = contentDisposition.split(";").dropLastWhile { it.isEmpty() }.toTypedArray()
|
|
||||||
val dispositionType = parts[0].trim { it <= ' ' }.let(Type::parse) // Get the type (e.g., attachment)
|
|
||||||
|
|
||||||
var filename: String? = null
|
|
||||||
for (i in 1..<parts.size) {
|
|
||||||
val part = parts[i].trim { it <= ' ' }
|
|
||||||
if (part.lowercase(Locale.getDefault()).startsWith("filename=")) {
|
|
||||||
filename = part.substring("filename=".length).trim { it <= ' ' }.replace("\"", "")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ContentDisposition(dispositionType, filename)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
|
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
|
||||||
super.exceptionCaught(ctx, cause)
|
super.exceptionCaught(ctx, cause)
|
||||||
}
|
}
|
||||||
|
@@ -42,6 +42,7 @@ object TraceHandler : ChannelInboundHandlerAdapter() {
|
|||||||
}
|
}
|
||||||
is LastHttpContent -> {
|
is LastHttpContent -> {
|
||||||
ctx.writeAndFlush(msg)
|
ctx.writeAndFlush(msg)
|
||||||
|
ctx.pipeline().remove(this)
|
||||||
}
|
}
|
||||||
is HttpContent -> ctx.writeAndFlush(msg)
|
is HttpContent -> ctx.writeAndFlush(msg)
|
||||||
else -> super.channelRead(ctx, msg)
|
else -> super.channelRead(ctx, msg)
|
||||||
|
Reference in New Issue
Block a user