mirror of
https://github.com/sbt/sbt.git
synced 2026-09-07 11:01:08 +02:00
Consolidate and optimize input stream json reading
We had similar code for reading json frames from an input stream in NetworkChannel and ServerConnection. I reworked and consolidated this logic into a shared method in ReadJsonFromInputStream. This commit also removes the ObjectMessage reporting methods that weren't doing anything.
This commit is contained in:
@@ -9,10 +9,13 @@ package sbt
|
||||
package internal
|
||||
package client
|
||||
|
||||
import java.net.{ SocketTimeoutException, Socket }
|
||||
import java.io.IOException
|
||||
import java.net.{ Socket, SocketTimeoutException }
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
import sbt.protocol._
|
||||
import sbt.internal.protocol._
|
||||
import sbt.internal.util.ReadJsonFromInputStream
|
||||
|
||||
abstract class ServerConnection(connection: Socket) {
|
||||
|
||||
@@ -25,69 +28,29 @@ abstract class ServerConnection(connection: Socket) {
|
||||
val thread = new Thread(s"sbt-serverconnection-${connection.getPort}") {
|
||||
override def run(): Unit = {
|
||||
try {
|
||||
val readBuffer = new Array[Byte](4096)
|
||||
val in = connection.getInputStream
|
||||
connection.setSoTimeout(5000)
|
||||
var buffer: Vector[Byte] = Vector.empty
|
||||
def readFrame: Vector[Byte] = {
|
||||
def getContentLength: Int = {
|
||||
readLine.drop(16).toInt
|
||||
}
|
||||
val l = getContentLength
|
||||
readLine
|
||||
readLine
|
||||
readContentLength(l)
|
||||
}
|
||||
|
||||
def readLine: String = {
|
||||
if (buffer.isEmpty) {
|
||||
val bytesRead = in.read(readBuffer)
|
||||
if (bytesRead > 0) {
|
||||
buffer = buffer ++ readBuffer.toVector.take(bytesRead)
|
||||
}
|
||||
}
|
||||
val delimPos = buffer.indexOf(delimiter)
|
||||
if (delimPos > 0) {
|
||||
val chunk0 = buffer.take(delimPos)
|
||||
buffer = buffer.drop(delimPos + 1)
|
||||
// remove \r at the end of line.
|
||||
val chunk1 = if (chunk0.lastOption contains retByte) chunk0.dropRight(1) else chunk0
|
||||
new String(chunk1.toArray, "utf-8")
|
||||
} else readLine
|
||||
}
|
||||
|
||||
def readContentLength(length: Int): Vector[Byte] = {
|
||||
if (buffer.size < length) {
|
||||
val bytesRead = in.read(readBuffer)
|
||||
if (bytesRead > 0) {
|
||||
buffer = buffer ++ readBuffer.toVector.take(bytesRead)
|
||||
} else ()
|
||||
} else ()
|
||||
if (length <= buffer.size) {
|
||||
val chunk = buffer.take(length)
|
||||
buffer = buffer.drop(length)
|
||||
chunk
|
||||
} else readContentLength(length)
|
||||
}
|
||||
|
||||
while (running.get) {
|
||||
try {
|
||||
val frame = readFrame
|
||||
Serialization
|
||||
.deserializeJsonMessage(frame)
|
||||
.fold(
|
||||
{ errorDesc =>
|
||||
val s = frame.mkString("") // new String(: Array[Byte], "UTF-8")
|
||||
println(s"Got invalid chunk from server: $s \n" + errorDesc)
|
||||
},
|
||||
_ match {
|
||||
case msg: JsonRpcRequestMessage => onRequest(msg)
|
||||
case msg: JsonRpcResponseMessage => onResponse(msg)
|
||||
case msg: JsonRpcNotificationMessage => onNotification(msg)
|
||||
}
|
||||
)
|
||||
val frame = ReadJsonFromInputStream(in, running, None)
|
||||
if (running.get) {
|
||||
Serialization
|
||||
.deserializeJsonMessage(frame)
|
||||
.fold(
|
||||
{ errorDesc =>
|
||||
val s = frame.mkString("") // new String(: Array[Byte], "UTF-8")
|
||||
println(s"Got invalid chunk from server: $s \n" + errorDesc)
|
||||
},
|
||||
_ match {
|
||||
case msg: JsonRpcRequestMessage => onRequest(msg)
|
||||
case msg: JsonRpcResponseMessage => onResponse(msg)
|
||||
case msg: JsonRpcNotificationMessage => onNotification(msg)
|
||||
}
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
case _: SocketTimeoutException => // its ok
|
||||
case e: IOException => running.set(false)
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
import java.io.InputStream
|
||||
import java.nio.channels.ClosedChannelException
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import scala.collection.mutable
|
||||
import scala.util.Try
|
||||
|
||||
private[sbt] object ReadJsonFromInputStream {
|
||||
def apply(
|
||||
inputStream: InputStream,
|
||||
running: AtomicBoolean,
|
||||
onHeader: Option[String => Unit]
|
||||
): Seq[Byte] = {
|
||||
val newline = '\n'.toInt
|
||||
val carriageReturn = '\r'.toInt
|
||||
val contentLength = "Content-Length: "
|
||||
var bytes = new mutable.ArrayBuffer[Byte]
|
||||
def getLine(): String = {
|
||||
val line = new String(bytes.toArray, "UTF-8")
|
||||
bytes = new mutable.ArrayBuffer[Byte]
|
||||
onHeader.foreach(oh => oh(line))
|
||||
line
|
||||
}
|
||||
var content: Seq[Byte] = Seq.empty[Byte]
|
||||
var consecutiveLineEndings = 0
|
||||
var onCarriageReturn = false
|
||||
do {
|
||||
val byte = inputStream.read
|
||||
byte match {
|
||||
case `newline` =>
|
||||
val line = getLine()
|
||||
if (onCarriageReturn) consecutiveLineEndings += 1
|
||||
onCarriageReturn = false
|
||||
if (line.startsWith(contentLength)) {
|
||||
Try(line.drop(contentLength.length).toInt) foreach { len =>
|
||||
def drainHeaders(): Unit =
|
||||
do {
|
||||
inputStream.read match {
|
||||
case `newline` if onCarriageReturn =>
|
||||
getLine()
|
||||
onCarriageReturn = false
|
||||
consecutiveLineEndings += 1
|
||||
case `carriageReturn` => onCarriageReturn = true
|
||||
case c =>
|
||||
if (c == newline) getLine()
|
||||
else bytes += c.toByte
|
||||
onCarriageReturn = false
|
||||
consecutiveLineEndings = 0
|
||||
}
|
||||
} while (consecutiveLineEndings < 2)
|
||||
drainHeaders()
|
||||
val buf = new Array[Byte](len)
|
||||
var offset = 0
|
||||
do {
|
||||
offset += inputStream.read(buf, offset, len - offset)
|
||||
} while (offset < len)
|
||||
content = buf.toSeq
|
||||
}
|
||||
} else if (line.startsWith("{")) {
|
||||
// Assume this is a json object with no headers
|
||||
content = line.getBytes.toSeq
|
||||
}
|
||||
case i if i < 0 =>
|
||||
running.set(false)
|
||||
throw new ClosedChannelException
|
||||
case `carriageReturn` => onCarriageReturn = true
|
||||
case c =>
|
||||
onCarriageReturn = false
|
||||
bytes += c.toByte
|
||||
|
||||
}
|
||||
} while (content.isEmpty && running.get)
|
||||
content
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user