From 25f36e65a7b266c35c0f6a9ca2e318bf1bca63a4 Mon Sep 17 00:00:00 2001 From: Steve Waldman Date: Fri, 28 Jun 2019 23:30:10 -0700 Subject: [PATCH 1/4] Capture and report any error message in the response body when uploads fail. --- .../ivyint/GigahorseUrlHandler.scala | 61 ++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/ivy/src/main/scala/sbt/internal/librarymanagement/ivyint/GigahorseUrlHandler.scala b/ivy/src/main/scala/sbt/internal/librarymanagement/ivyint/GigahorseUrlHandler.scala index 72c7c58b4..943bab93a 100644 --- a/ivy/src/main/scala/sbt/internal/librarymanagement/ivyint/GigahorseUrlHandler.scala +++ b/ivy/src/main/scala/sbt/internal/librarymanagement/ivyint/GigahorseUrlHandler.scala @@ -195,12 +195,71 @@ class GigahorseUrlHandler(http: OkHttpClient) extends AbstractURLHandler { if (l != null) { l.end(new CopyProgressEvent(EmptyBuffer, source.length())) } - validatePutStatusCode(dest, response.code(), response.message()) + validatePutStatusCode(dest, response) } finally { response.close() } } + private val ErrorBodyTruncateLen = 512 // in case some bad service returns files rather than messages in error bodies + private val DefaultErrorCharset = java.nio.charset.StandardCharsets.UTF_8 + + // this is perhaps overly cautious, but oh well + private def readTruncated(is: InputStream): Option[(Array[Byte], Boolean)] = { + val os = new ByteArrayOutputStream(ErrorBodyTruncateLen) + var count = 0 + var b = is.read() + while (b >= 0 && count < ErrorBodyTruncateLen) { + os.write(b) + count += 1 + b = is.read() + } + if (count > 0) { + Some((os.toByteArray, count >= ErrorBodyTruncateLen)) + } else { + None + } + } + + /* + * Supplements the IOException emitted on a bad status code by our inherited validatePutStatusCode(...) + * method with any message that might be present in an error response body. + * + * after calling this method, the object given as the response parameter must be reliably closed. + */ + private def validatePutStatusCode(dest: URL, response: Response): Unit = { + try { + validatePutStatusCode(dest, response.code(), response.message()) + } catch { + case ioe: IOException => { + val mbBodyMessage = { + for { + body <- Option(response.body()) + is <- Option(body.byteStream) + (bytes, truncated) <- readTruncated(is) + charset <- Option(body.contentType()).map(_.charset(DefaultErrorCharset)) orElse Some( + DefaultErrorCharset + ) + } yield { + val raw = new String(bytes, charset) + if (truncated) raw + "..." else raw + } + } + + mbBodyMessage match { + case Some(bodyMessage) => { // reconstruct the IOException + val newMessage = ioe.getMessage() + s"; Response Body: ${bodyMessage}" + val reconstructed = new IOException(newMessage, ioe.getCause()) + reconstructed.setStackTrace(ioe.getStackTrace()) + throw reconstructed + } + case None => { + throw ioe + } + } + } + } + } } object GigahorseUrlHandler { From 1b21f200b5da906eda7040b5119cae5518f22c90 Mon Sep 17 00:00:00 2001 From: Steve Waldman Date: Fri, 28 Jun 2019 23:31:36 -0700 Subject: [PATCH 2/4] Update scalafmt version. --- .scalafmt.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 718ce5aed..8392aa694 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = 2.0.0-RC6 +version = 2.0.0-RC8 maxColumn = 100 project.git = true project.excludeFilters = [ /sbt-test/, /input_sources/, /contraband-scala/ ] From 20ca549612bafd386ff991130227cfb50c28ddab Mon Sep 17 00:00:00 2001 From: Steve Waldman Date: Sat, 29 Jun 2019 00:08:44 -0700 Subject: [PATCH 3/4] Fix detection of truncation (so messages exactly max length aren't flagged truncated) --- .../ivyint/GigahorseUrlHandler.scala | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/ivy/src/main/scala/sbt/internal/librarymanagement/ivyint/GigahorseUrlHandler.scala b/ivy/src/main/scala/sbt/internal/librarymanagement/ivyint/GigahorseUrlHandler.scala index 943bab93a..1941de252 100644 --- a/ivy/src/main/scala/sbt/internal/librarymanagement/ivyint/GigahorseUrlHandler.scala +++ b/ivy/src/main/scala/sbt/internal/librarymanagement/ivyint/GigahorseUrlHandler.scala @@ -209,13 +209,18 @@ class GigahorseUrlHandler(http: OkHttpClient) extends AbstractURLHandler { val os = new ByteArrayOutputStream(ErrorBodyTruncateLen) var count = 0 var b = is.read() - while (b >= 0 && count < ErrorBodyTruncateLen) { - os.write(b) - count += 1 - b = is.read() + var truncated = false + while (!truncated && b >= 0) { + if (count >= ErrorBodyTruncateLen) { + truncated = true + } else { + os.write(b) + count += 1 + b = is.read() + } } if (count > 0) { - Some((os.toByteArray, count >= ErrorBodyTruncateLen)) + Some((os.toByteArray, truncated)) } else { None } From 315fbec370a8648d2b1d649f5f635cdca30f7947 Mon Sep 17 00:00:00 2001 From: Steve Waldman Date: Sat, 29 Jun 2019 12:41:26 -0700 Subject: [PATCH 4/4] Add more neurotic resource management, close()ing of streams. --- .../ivyint/GigahorseUrlHandler.scala | 59 +++++++++++++------ 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/ivy/src/main/scala/sbt/internal/librarymanagement/ivyint/GigahorseUrlHandler.scala b/ivy/src/main/scala/sbt/internal/librarymanagement/ivyint/GigahorseUrlHandler.scala index 1941de252..87ed352b1 100644 --- a/ivy/src/main/scala/sbt/internal/librarymanagement/ivyint/GigahorseUrlHandler.scala +++ b/ivy/src/main/scala/sbt/internal/librarymanagement/ivyint/GigahorseUrlHandler.scala @@ -204,25 +204,50 @@ class GigahorseUrlHandler(http: OkHttpClient) extends AbstractURLHandler { private val ErrorBodyTruncateLen = 512 // in case some bad service returns files rather than messages in error bodies private val DefaultErrorCharset = java.nio.charset.StandardCharsets.UTF_8 - // this is perhaps overly cautious, but oh well - private def readTruncated(is: InputStream): Option[(Array[Byte], Boolean)] = { - val os = new ByteArrayOutputStream(ErrorBodyTruncateLen) - var count = 0 - var b = is.read() - var truncated = false - while (!truncated && b >= 0) { - if (count >= ErrorBodyTruncateLen) { - truncated = true - } else { - os.write(b) - count += 1 - b = is.read() + // neurotic resource managemement... + // we could use this elsewhere in the class too + private def borrow[S <: AutoCloseable, T](rsrc: => S)(op: S => T): T = { + val r = rsrc + val out = { + try { + op(r) + } catch { + case NonFatal(t) => { + try { + r.close() + } catch { + case NonFatal(ct) => t.addSuppressed(ct) + } + throw t + } } } - if (count > 0) { - Some((os.toByteArray, truncated)) - } else { - None + r.close() + out + } + + // this is perhaps overly cautious, but oh well + private def readTruncated(byteStream: InputStream): Option[(Array[Byte], Boolean)] = { + borrow(byteStream) { is => + borrow(new ByteArrayOutputStream(ErrorBodyTruncateLen)) { os => + var count = 0 + var b = is.read() + var truncated = false + while (!truncated && b >= 0) { + if (count >= ErrorBodyTruncateLen) { + truncated = true + } else { + os.write(b) + count += 1 + b = is.read() + } + } + if (count > 0) { + Some((os.toByteArray, truncated)) + } else { + None + } + } } }