spring-启动 TCP 服务器:在回答“多行”请求之前等待“空行”

spring-boot TCP server: wait for `empty line` before answering a `multi-line` request

我想为带有 spring-boot 的后缀编写自定义 check_policy_service (http://www.postfix.org/SMTPD_POLICY_README.html)。

简短:postfix 发送多行,如:

foo=bar
me=you
year=123
[empty line]

空行表示请求已完成,现在 spring-boot 应用程序应该处理数据并且 return 类似 action=ok

问题: 在我当前的设置中,应用程序尝试立即处理第 1 行 foo=bar,而不是等到 [empty line] 被发送。如何让应用等待空行?

当前设置: spring-启动 2.6.3

@Configuration
class TcpServerConfig {
//    @Value("${tcp.server.port}")
    private val port = 6676
    @Bean
    fun serverConnectionFactory(): AbstractServerConnectionFactory {
        val serverConnectionFactory = TcpNioServerConnectionFactory(port)
        serverConnectionFactory.setUsingDirectBuffers(true)
        serverConnectionFactory.isSingleUse = false // reuse socket
        return serverConnectionFactory
    }
    @Bean
    fun inboundChannel(): MessageChannel {
        return DirectChannel()
    }

    @Bean
    fun inboundGateway(
        serverConnectionFactory: AbstractServerConnectionFactory,
        inboundChannel: MessageChannel
    ): TcpInboundGateway {
        val tcpInboundGateway = TcpInboundGateway()
        tcpInboundGateway.setConnectionFactory(serverConnectionFactory)
        tcpInboundGateway.setRequestChannel(inboundChannel)
        return tcpInboundGateway
    }
}
@MessageEndpoint
class TcpServerEndpoint {

    @ServiceActivator(inputChannel = "inboundChannel")
    fun process(message: ByteArray): ByteArray {
        
        // I would need to have `message` contain every line until empty line - not a single line        
        println(String(message))
        
        // TODO handle message accordingly
        return "action=ok\n".toByteArray()
    }
}

我不确定在哪里挂钩。也许 DirectChannel 需要别的东西?

我写了一个快速的非 spring-boot 实现,它可以工作,所以基本上我需要 spring-boot-ified 版本:


fun main(args: Array<String>) {
    val server = ServerSocket(9999)
    println("Server is running on port ${server.localPort}")

    while (true) {
        val client = server.accept()
        println("Client connected: ${client.inetAddress.hostAddress}")

        // Run client in it's own thread.
        thread { ClientHandler(client).run() }
    }

}

class ClientHandler(client: Socket) {
    private val client: Socket = client
    private val reader: BufferedReader = BufferedReader(InputStreamReader(client.getInputStream()))
    private val writer: PrintWriter = PrintWriter(client.getOutputStream(), true)
    private var running: Boolean = false

    fun run() {
        running = true
        while (running) {
            try {
                do {
                    val line = reader.readLine() ?: break
                    // TODO collect all lines in a list and handle
                } while (line.isNotEmpty())

                write("action=ok")

            } catch (ex: Exception) {
                // TODO: Implement exception handling
                ex.printStackTrace()
                shutdown()
            } finally {

            }
        }
    }

    private fun write(message: String) {
        writer.write((message + "\n\n"))
        writer.flush()
    }
    private fun shutdown() {
        running = false
        client.close()
        println("${client.inetAddress.hostAddress} closed the connection")
    }
}

神奇的部分是读取所有行直到行为空,然后处理请求

您需要设置 Deserializer 以便 Spring 知道何时将读取的内容(到目前为止)释放给处理程序,如 the docs 中所述。

在您的情况下,您应该编写一个实现接口 org.springframework.core.serializer.Deserializer 的自定义反序列化程序,一旦检测到连续两个换行符,它就会将内容释放给处理程序。使用 Postfix 发送的行使其 return 成为 List<String>

class DoubleNewLineDeserializer: Deserializer<List<String>> {

    companion object {
        const val UNIX_NEWLINE = "\n\n"
        const val WINDOWS_NEWLINE = "\r\n\r\n"
        val CRLFRegex = Regex("[\r\n]+")
    }

    override fun deserialize(inputStream: InputStream): List<String> {
        val builder = StringBuilder()
        while(!builder.endsWith(UNIX_NEWLINE) && !builder.endsWith(WINDOWS_NEWLINE))
            builder.append(inputStream.read().toChar())
        return builder.toString().split(CRLFRegex).filter { it.isNotEmpty() }
    }

}

这个反序列化器读取输入直到它找到一个双新行(这里是 \n\n\r\n\r\n,你可以根据需要修改它)此时它删除消息定界符然后 return将消息作为行列表。

最后,在 ServerConnectionFactory 上设置新的反序列化器:

serverConnectionFactory.deserializer = DoubleNewLineDeserializer()

TCP 套接字输入:

foo=bar
me=you
year=123
[empty line]
[empty line]

处理程序的输入:

[foo=bar, me=you, year=123]