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]
我想为带有 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]