[2.x] fix: Log server response body on publish failure (#8537)

When publishing to a repository fails with an HTTP error (e.g., 403, 409), the server often includes helpful error details in the response body. Previously, sbt only showed the HTTP status code without the response body.

This reimplements the upload method.

Fixes #7423
This commit is contained in:
MkDev11 2026-01-15 00:23:31 -05:00 committed by GitHub
parent fb53925fb6
commit b2db55768c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 76 additions and 1 deletions

View File

@ -0,0 +1,75 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009, 2010 Mark Harrah
*/
package sbt.internal.librarymanagement
import java.io.{ File, FileInputStream, IOException }
import java.net.{ HttpURLConnection, URL }
import org.apache.ivy.util.url.{ BasicURLHandler, IvyAuthenticator }
import org.apache.ivy.util.{ CopyProgressListener, FileUtil, Message }
import org.apache.ivy.Ivy
import scala.io.Source
import scala.util.Using
private[librarymanagement] class ErrorLoggingURLHandler extends BasicURLHandler {
private val ErrorBodyTruncateLen = 1024
override def upload(
source: File,
dest: URL,
l: CopyProgressListener
): Unit = {
if (dest.getProtocol != "http" && dest.getProtocol != "https") {
throw new UnsupportedOperationException(
"URL repository only support HTTP PUT at the moment"
)
}
IvyAuthenticator.install()
var conn: HttpURLConnection = null
try {
val normalizedDest = normalizeToURL(dest)
conn = normalizedDest.openConnection().asInstanceOf[HttpURLConnection]
conn.setDoOutput(true)
conn.setRequestMethod("PUT")
conn.setRequestProperty("User-Agent", "Apache Ivy/" + Ivy.getIvyVersion)
conn.setRequestProperty(
"Accept",
"application/octet-stream, application/json, application/xml, */*"
)
conn.setRequestProperty("Content-type", "application/octet-stream")
conn.setRequestProperty("Content-length", source.length().toString)
conn.setInstanceFollowRedirects(true)
val in = new FileInputStream(source)
try {
val os = conn.getOutputStream
FileUtil.copy(in, os, l)
} finally {
try in.close()
catch { case _: IOException => }
}
val responseCode = conn.getResponseCode
val responseMessage = conn.getResponseMessage
val errorBody = Option(conn.getErrorStream).map { stream =>
Using.resource(stream) { s =>
val body = Source.fromInputStream(s, "UTF-8").mkString
if (body.length > ErrorBodyTruncateLen)
body.take(ErrorBodyTruncateLen) + "..."
else body
}
}
errorBody.filter(_.nonEmpty).foreach { body =>
Message.error(s"Server response body: $body")
}
validatePutStatusCode(dest, responseCode, responseMessage)
} finally {
if (conn != null) conn.disconnect()
}
}
}

View File

@ -85,7 +85,7 @@ final class IvySbt(
}
}
private lazy val basicUrlHandler: URLHandler = new BasicURLHandler
private lazy val basicUrlHandler: URLHandler = new ErrorLoggingURLHandler
private lazy val settings: IvySettings = {
val dispatcher: URLHandlerDispatcher = URLHandlerRegistry.getDefault match {