All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.fireflysource.net.http.server.impl.content.handler.AsyncMultiPart.kt Maven / Gradle / Ivy

There is a newer version: 5.0.2
Show newest version
package com.fireflysource.net.http.server.impl.content.handler

import com.fireflysource.common.io.BufferUtils
import com.fireflysource.common.io.deleteIfExistsAsync
import com.fireflysource.common.io.flipToFill
import com.fireflysource.common.io.flipToFlush
import com.fireflysource.common.sys.Result
import com.fireflysource.common.sys.SystemLogger
import com.fireflysource.net.http.common.content.handler.AbstractByteBufferContentHandler
import com.fireflysource.net.http.common.content.handler.AbstractFileContentHandler
import com.fireflysource.net.http.common.content.provider.AbstractByteBufferContentProvider
import com.fireflysource.net.http.common.content.provider.AbstractFileContentProvider
import com.fireflysource.net.http.common.exception.BadMessageException
import com.fireflysource.net.http.common.model.HttpFields
import com.fireflysource.net.http.common.model.HttpHeader
import com.fireflysource.net.http.common.model.HttpStatus
import com.fireflysource.net.http.server.MultiPart
import kotlinx.coroutines.future.asCompletableFuture
import kotlinx.coroutines.future.await
import java.nio.ByteBuffer
import java.nio.charset.Charset
import java.nio.file.Path
import java.nio.file.StandardOpenOption
import java.util.concurrent.CompletableFuture

class AsyncMultiPart(
    private val maxFileSize: Long,
    private val fileSizeThreshold: Int,
    private val path: Path
) : MultiPart {

    companion object {
        private val log = SystemLogger.create(AsyncMultiPart::class.java)
    }

    private val byteBufferHandler = object : AbstractByteBufferContentHandler() {}
    private var fileHandler: AbstractFileContentHandler? = null
    private var fileHandlerFuture: CompletableFuture? = null
    private val fileProvider: AbstractFileContentProvider by lazy {
        object : AbstractFileContentProvider(path, StandardOpenOption.READ) {}
    }
    private val byteBufferProvider: AbstractByteBufferContentProvider by lazy {
        val size = byteBufferHandler.getByteBuffers().sumBy { it.remaining() }
        val buf = BufferUtils.allocate(size)
        val pos = buf.flipToFill()
        byteBufferHandler.getByteBuffers().forEach { BufferUtils.put(it, buf) }
        buf.flipToFlush(pos)
        object : AbstractByteBufferContentProvider(buf) {}
    }
    private val httpFields = HttpFields()
    private var size: Long = 0
    private var name: String = ""
    private var fileName: String = ""
    private var exceededFileThreshold = false

    override fun getName(): String = name

    fun setName(name: String) {
        this.name = name
    }

    override fun getFileName(): String = fileName

    fun setFileName(fileName: String) {
        this.fileName = fileName
    }

    override fun getHttpFields(): HttpFields = httpFields

    override fun getSize(): Long = size

    fun accept(item: ByteBuffer, last: Boolean) {
        size += item.remaining()
        log.debug { "Multi part accepts data. name: $name size: ${item.remaining()}, last: $last" }
        if (size > maxFileSize) {
            throw BadMessageException(HttpStatus.PAYLOAD_TOO_LARGE_413)
        }

        if (size <= fileSizeThreshold) {
            if (item.hasRemaining()) byteBufferHandler.accept(item, null)
        } else {
            val fileHandler = this.fileHandler
            if (fileHandler != null) {
                if (item.hasRemaining()) fileHandler.accept(item, null)
            } else {
                exceededFileThreshold = true
                val newFileHandler = object : AbstractFileContentHandler(
                    path,
                    StandardOpenOption.WRITE,
                    StandardOpenOption.CREATE_NEW
                ) {}
                this.fileHandler = newFileHandler
                byteBufferHandler.getByteBuffers().forEach { newFileHandler.accept(it, null) }
                if (item.hasRemaining()) newFileHandler.accept(item, null)
            }

            if (last) {
                fileHandlerFuture = this.fileHandler?.closeAsync()
            }
        }
    }

    suspend fun closeFileHandler() {
        fileHandler?.closeAsync()?.await()
    }

    override fun getContentType(): String = httpFields[HttpHeader.CONTENT_TYPE]

    override fun read(byteBuffer: ByteBuffer): CompletableFuture {
        return getProvider().read(byteBuffer)
    }

    override fun getStringBody(charset: Charset): String {
        return if (exceededFileThreshold) ""
        else {
            val buffer = byteBufferProvider.toByteBuffer()
            BufferUtils.toString(buffer, charset)
        }
    }

    override fun isOpen(): Boolean {
        return getProvider().isOpen
    }

    override fun close() {
        closeAsync()
    }

    override fun closeAsync(): CompletableFuture {
        return getProvider().closeAsync()
            .thenCompose { deleteIfExistsAsync(path).asCompletableFuture() }
            .thenCompose { Result.DONE }
    }

    private fun getProvider() = if (exceededFileThreshold) fileProvider else byteBufferProvider

    override fun toString(): String {
        return "AsyncMultiPart(maxFileSize=$maxFileSize, fileSizeThreshold=$fileSizeThreshold, path=$path, httpFields=${httpFields.size()}, size=$size, name='$name', fileName='$fileName', exceededFileThreshold=$exceededFileThreshold)"
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy