Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[forward-port] All backends write headers as UTF-8 #2115

Merged
merged 9 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@ jobs:
matrix:
scala-version: [ "2.12", "2.13", "3" ]
target-platform: [ "JVM", "JS", "Native" ]
exclude:
- scala-version: "2.11"
target-platform: "JS"
- scala-version: "2.11"
target-platform: "Native"
steps:
- name: Checkout
uses: actions/checkout@v2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package sttp.client4.asynchttpclient.cats

import cats.effect.IO
import sttp.client4._
import sttp.client4.asynchttpclient.AsyncHttpClientHttpTest
import sttp.client4.impl.cats.CatsTestBase
import sttp.client4.testing.HttpTest

class AsyncHttpClientCatsHttpTest extends HttpTest[IO] with CatsTestBase {
class AsyncHttpClientCatsHttpTest extends AsyncHttpClientHttpTest[IO] with CatsTestBase {
override val backend: Backend[IO] = AsyncHttpClientCatsBackend[IO]().unsafeRunSync()

"illegal url exceptions" - {
Expand All @@ -15,8 +15,4 @@ class AsyncHttpClientCatsHttpTest extends HttpTest[IO] with CatsTestBase {
}
}
}

override def throwsExceptionOnUnsupportedEncoding = false
override def supportsAutoDecompressionDisabling = false
override def supportsResponseAsInputStream = false
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package sttp.client4.asynchttpclient.cats

import cats.effect.IO
import sttp.client4._
import sttp.client4.asynchttpclient.AsyncHttpClientHttpTest
import sttp.client4.impl.cats.{CatsRetryTest, CatsTestBase}
import sttp.client4.testing.HttpTest

class AsyncHttpClientCatsHttpTest extends HttpTest[IO] with CatsRetryTest with CatsTestBase {
class AsyncHttpClientCatsHttpTest extends AsyncHttpClientHttpTest[IO] with CatsRetryTest with CatsTestBase {
override val backend: Backend[IO] = AsyncHttpClientCatsBackend[IO]().unsafeRunSync()

"illegal url exceptions" - {
Expand All @@ -15,8 +15,4 @@ class AsyncHttpClientCatsHttpTest extends HttpTest[IO] with CatsRetryTest with C
}
}
}

override def throwsExceptionOnUnsupportedEncoding = false
override def supportsAutoDecompressionDisabling = false
override def supportsResponseAsInputStream = false
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,12 @@ package sttp.client4.asynchttpclient.fs2

import cats.effect.{Blocker, IO}
import sttp.client4.Backend
import sttp.client4.asynchttpclient.AsyncHttpClientHttpTest
import sttp.client4.impl.cats.CatsTestBase
import sttp.client4.testing.HttpTest

import scala.concurrent.ExecutionContext.global

class AsyncHttpClientFs2HttpTest extends HttpTest[IO] with CatsTestBase {
class AsyncHttpClientFs2HttpTest extends AsyncHttpClientHttpTest[IO] with CatsTestBase {
override val backend: Backend[IO] =
AsyncHttpClientFs2Backend[IO](Blocker.liftExecutionContext(global)).unsafeRunSync()

override def throwsExceptionOnUnsupportedEncoding = false
override def supportsAutoDecompressionDisabling = false
override def supportsResponseAsInputStream = false
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@ package sttp.client4.asynchttpclient.fs2

import cats.effect.IO
import sttp.client4.Backend
import sttp.client4.asynchttpclient.AsyncHttpClientHttpTest
import sttp.client4.impl.cats.{CatsTestBase, TestIODispatcher}
import sttp.client4.testing.HttpTest

class AsyncHttpClientFs2HttpTest extends HttpTest[IO] with TestIODispatcher with CatsTestBase {
class AsyncHttpClientFs2HttpTest extends AsyncHttpClientHttpTest[IO] with TestIODispatcher with CatsTestBase {
override val backend: Backend[IO] =
AsyncHttpClientFs2Backend[IO](dispatcher = dispatcher).unsafeRunSync()

override def throwsExceptionOnUnsupportedEncoding = false
// for some unknown reason this single test fails using the fs2 implementation
override def supportsConnectionRefusedTest = false
override def supportsAutoDecompressionDisabling = false
override def supportsResponseAsInputStream = false
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
package sttp.client4.asynchttpclient.future

import sttp.client4.Backend
import sttp.client4.testing.{ConvertToFuture, HttpTest}
import sttp.client4.asynchttpclient.AsyncHttpClientHttpTest
import sttp.client4.testing.ConvertToFuture

import scala.concurrent.Future

class AsyncHttpClientFutureHttpTest extends HttpTest[Future] {
class AsyncHttpClientFutureHttpTest extends AsyncHttpClientHttpTest[Future] {

override val backend: Backend[Future] = AsyncHttpClientFutureBackend()
override implicit val convertToFuture: ConvertToFuture[Future] = ConvertToFuture.future

override def throwsExceptionOnUnsupportedEncoding = false

override def supportsCancellation: Boolean = false
override def timeoutToNone[T](t: Future[T], timeoutMillis: Int): Future[Option[T]] = t.map(Some(_))
override def supportsAutoDecompressionDisabling = false
override def supportsResponseAsInputStream = false
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package sttp.client4.asynchttpclient.monix

import java.util.concurrent.TimeoutException

import monix.eval.Task
import monix.execution.Scheduler.Implicits.global
import sttp.client4._
import sttp.client4.asynchttpclient.AsyncHttpClientHttpTest
import sttp.client4.impl.monix.convertMonixTaskToFuture
import sttp.client4.testing.{ConvertToFuture, HttpTest}
import monix.execution.Scheduler.Implicits.global
import sttp.client4.testing.ConvertToFuture

import java.util.concurrent.TimeoutException
import scala.concurrent.duration._

class AsyncHttpClientMonixHttpTest extends HttpTest[Task] {
class AsyncHttpClientMonixHttpTest extends AsyncHttpClientHttpTest[Task] {
override val backend: Backend[Task] = AsyncHttpClientMonixBackend().runSyncUnsafe()
override implicit val convertToFuture: ConvertToFuture[Task] = convertMonixTaskToFuture

Expand All @@ -20,8 +20,4 @@ class AsyncHttpClientMonixHttpTest extends HttpTest[Task] {
.onErrorRecover { case _: TimeoutException =>
None
}

override def throwsExceptionOnUnsupportedEncoding = false
override def supportsAutoDecompressionDisabling = false
override def supportsResponseAsInputStream = false
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,15 @@ package sttp.client4.asynchttpclient.scalaz

import scalaz.concurrent.Task
import sttp.client4.Backend
import sttp.client4.asynchttpclient.AsyncHttpClientHttpTest
import sttp.client4.impl.scalaz.convertScalazTaskToFuture
import sttp.client4.testing.{ConvertToFuture, HttpTest}
import sttp.client4.testing.ConvertToFuture

class AsyncHttpClientScalazHttpTest extends HttpTest[Task] {
class AsyncHttpClientScalazHttpTest extends AsyncHttpClientHttpTest[Task] {

override val backend: Backend[Task] = AsyncHttpClientScalazBackend().unsafePerformSync
override implicit val convertToFuture: ConvertToFuture[Task] = convertScalazTaskToFuture

override def throwsExceptionOnUnsupportedEncoding = false

override def supportsCancellation: Boolean = false
override def timeoutToNone[T](t: Task[T], timeoutMillis: Int): Task[Option[T]] = t.map(Some(_))
override def supportsAutoDecompressionDisabling = false
override def supportsResponseAsInputStream = false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package sttp.client4.asynchttpclient

import sttp.client4.testing.HttpTest

abstract class AsyncHttpClientHttpTest[F[_]] extends HttpTest[F] {
override protected def throwsExceptionOnUnsupportedEncoding = false
override protected def supportsAutoDecompressionDisabling = false
override protected def supportsNonAsciiHeaderValues = false
override protected def supportsResponseAsInputStream = false
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
package sttp.client4.asynchttpclient.zio

import sttp.client4._
import sttp.client4.asynchttpclient.AsyncHttpClientHttpTest
import sttp.client4.impl.zio.ZioTestBase
import sttp.client4.testing.{ConvertToFuture, HttpTest}
import zio.{Task, ZIO}

class AsyncHttpClientZioHttpTest extends HttpTest[Task] with ZioTestBase {
class AsyncHttpClientZioHttpTest extends AsyncHttpClientHttpTest[Task] with ZioTestBase {

override val backend: Backend[Task] =
unsafeRunSyncOrThrow(AsyncHttpClientZioBackend())
override implicit val convertToFuture: ConvertToFuture[Task] = convertZioTaskToFuture

override def throwsExceptionOnUnsupportedEncoding = false
override def supportsAutoDecompressionDisabling = false
override def supportsResponseAsInputStream = false

"throw an exception instead of ZIO defect if the header value is invalid" in {

val r = basicRequest
Expand Down
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,7 @@ def okhttpBackendProject(proj: String, includeDotty: Boolean) =
.settings(testServerSettings)
.settings(name := s"okhttp-backend-$proj")
.jvmPlatform(scalaVersions = scala2 ++ (if (includeDotty) scala3 else Nil))
.dependsOn(okhttpBackend)
.dependsOn(okhttpBackend % compileAndTest)

lazy val okhttpMonixBackend =
okhttpBackendProject("monix", includeDotty = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ class HttpURLConnectionBackend private (
case FileBody(b, _) => Some(b.toFile.length())
}

val headersLen = headers.getBytes(Iso88591).length
val headersLen = headers.getBytes(Utf8).length

bodyLen.map(bl => dashesLen + boundaryLen + crLfLen + headersLen + crLfLen + crLfLen + bl + crLfLen)
}
Expand All @@ -214,8 +214,9 @@ class HttpURLConnectionBackend private (

val os = c.getOutputStream
def writeMeta(s: String): Unit = {
os.write(s.getBytes(Iso88591))
total += s.getBytes(Iso88591).length.toLong
val utf8Bytes = s.getBytes(Utf8)
os.write(utf8Bytes)
total += utf8Bytes.length.toLong
}

partsWithHeaders.foreach { case (headers, p) =>
Expand Down
8 changes: 8 additions & 0 deletions core/src/test/scala/sttp/client4/testing/HttpTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ trait HttpTest[F[_]]
protected def supportsAutoDecompressionDisabling = true
protected def supportsDeflateWrapperChecking = true
protected def supportsEmptyContentEncoding = true
protected def supportsNonAsciiHeaderValues = true

"request parsing" - {
"Inf timeout should not throw exception" in {
Expand Down Expand Up @@ -472,6 +473,13 @@ trait HttpTest[F[_]]
req.send(backend).toFuture().map(resp => resp.body should be("p1=v1 (f1), p2=v2 (f2)"))
}

if(supportsNonAsciiHeaderValues) {
"send a multipart message with non-ascii filenames" in {
val req = mp.multipartBody(multipart("p1", "v1").fileName("fó1"), multipart("p2", "v2").fileName("fó2"))
req.send(backend).toFuture().map { resp => resp.body should be("p1=v1 (fó1), p2=v2 (fó2)") }
}
}

"send a multipart message with binary data and filename" in {
val binaryPart =
multipart("p1", "v1".getBytes)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
package sttp.client4.okhttp.monix

import java.util.concurrent.TimeoutException

import monix.eval.Task
import monix.execution.Scheduler.Implicits.global
import sttp.capabilities.monix.MonixStreams
import sttp.client4.StreamBackend
import sttp.client4.impl.monix.convertMonixTaskToFuture
import sttp.client4.testing.{ConvertToFuture, HttpTest}
import sttp.client4.okhttp.OkHttpHttpTest
import sttp.client4.testing.ConvertToFuture

import scala.concurrent.duration.DurationInt
import java.util.concurrent.TimeoutException
import scala.concurrent.duration._

class OkHttpMonixHttpTest extends HttpTest[Task] {
class OkHttpMonixHttpTest extends OkHttpHttpTest[Task] {

override val backend: StreamBackend[Task, MonixStreams] = OkHttpMonixBackend().runSyncUnsafe()
override implicit val convertToFuture: ConvertToFuture[Task] = convertMonixTaskToFuture

override def supportsDeflateWrapperChecking = false

override def timeoutToNone[T](t: Task[T], timeoutMillis: Int): Task[Option[T]] =
t.map(Some(_))
.timeout(timeoutMillis.milliseconds)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@ import sttp.client4.testing.{ConvertToFuture, HttpTest}

import scala.concurrent.Future

class OkHttpFutureHttpTest extends HttpTest[Future] {
class OkHttpFutureHttpTest extends OkHttpHttpTest[Future] {

override val backend: WebSocketBackend[Future] = OkHttpFutureBackend()
override implicit val convertToFuture: ConvertToFuture[Future] = ConvertToFuture.future

override def supportsCancellation: Boolean = false
override def timeoutToNone[T](t: Future[T], timeoutMillis: Int): Future[Option[T]] = t.map(Some(_))
override def supportsDeflateWrapperChecking = false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package sttp.client4.okhttp

import sttp.client4.testing.HttpTest

abstract class OkHttpHttpTest[F[_]] extends HttpTest[F] {
override protected def supportsDeflateWrapperChecking = false
override protected def supportsNonAsciiHeaderValues = false
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ package sttp.client4.okhttp
import sttp.client4.testing.{ConvertToFuture, HttpTest}
import sttp.client4.{Identity, WebSocketBackend}

class OkHttpSyncHttpTest extends HttpTest[Identity] {
class OkHttpSyncHttpTest extends OkHttpHttpTest[Identity] {
override val backend: WebSocketBackend[Identity] = OkHttpSyncBackend()

override implicit val convertToFuture: ConvertToFuture[Identity] = ConvertToFuture.id

override def supportsCancellation: Boolean = false
override def timeoutToNone[T](t: Identity[T], timeoutMillis: Int): Identity[Option[T]] = Some(t)
override def supportsDeflateWrapperChecking = false
}
Loading