diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f377d9b..25cdf702 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,11 +29,9 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - scala: [2.12, 2.13, 3] + scala: [2.13, 3] java: [temurin@11, temurin@17] exclude: - - scala: 2.12 - java: temurin@17 - scala: 3 java: temurin@17 runs-on: ${{ matrix.os }} @@ -100,15 +98,15 @@ jobs: run: sbt '++ ${{ matrix.scala }}' unusedCompileDependenciesTest - name: Make target directories - if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/series/0.9') + if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') run: mkdir -p core/target project/target - name: Compress target directories - if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/series/0.9') + if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') run: tar cf targets.tar core/target project/target - name: Upload target directories - if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/series/0.9') + if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') uses: actions/upload-artifact@v4 with: name: target-${{ matrix.os }}-${{ matrix.java }}-${{ matrix.scala }} @@ -117,7 +115,7 @@ jobs: publish: name: Publish Artifacts needs: [build] - if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/series/0.9') + if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') strategy: matrix: os: [ubuntu-latest] @@ -158,16 +156,6 @@ jobs: if: matrix.java == 'temurin@17' && steps.setup-java-temurin-17.outputs.cache-hit == 'false' run: sbt +update - - name: Download target directories (2.12) - uses: actions/download-artifact@v4 - with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.12 - - - name: Inflate target directories (2.12) - run: | - tar xf targets.tar - rm targets.tar - - name: Download target directories (2.13) uses: actions/download-artifact@v4 with: @@ -258,7 +246,7 @@ jobs: - name: Submit Dependencies uses: scalacenter/sbt-dependency-submission@v2 with: - modules-ignore: root_2.12 root_2.13 root_3 docs_2.12 docs_2.13 docs_3 sbt-http4s-org-scalafix-internal_2.12 sbt-http4s-org-scalafix-internal_2.13 sbt-http4s-org-scalafix-internal_3 + modules-ignore: root_2.13 root_3 docs_2.13 docs_3 sbt-http4s-org-scalafix-internal_2.13 sbt-http4s-org-scalafix-internal_3 configs-ignore: test scala-tool scala-doc-tool test-internal site: @@ -307,7 +295,7 @@ jobs: run: sbt docs/tlSite - name: Publish site - if: github.event_name != 'pull_request' && github.ref == 'refs/heads/series/0.9' + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' uses: peaceiris/actions-gh-pages@v4.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.mergify.yml b/.mergify.yml index d97e0ec2..3a3cb2e3 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -10,7 +10,6 @@ pull_request_rules: conditions: - author=http4s-steward[bot] - body~=labels:.*early-semver-patch - - status-success=Build and Test (ubuntu-latest, 2.12, temurin@11) - status-success=Build and Test (ubuntu-latest, 2.13, temurin@11) - status-success=Build and Test (ubuntu-latest, 2.13, temurin@17) - status-success=Build and Test (ubuntu-latest, 3, temurin@11) diff --git a/.scalafmt.conf b/.scalafmt.conf index ac610336..4943dbe8 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,6 +1,6 @@ version=3.8.3 -runner.dialect = scala212 +runner.dialect = scala213 style = default maxColumn = 100 diff --git a/CHANGELOG.md b/CHANGELOG.md index a3564655..0396092a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,15 +4,7 @@ This file summarizes **notable** changes for each release, but does not describe ---- -## v0.9.1 (2023-05-13) - -### Dependency updates - -* cats-effect-3.5.0 -* fs2-3.7.0 -* http4s-0.23.19 - -## v0.9.0 (2023-02-08) +## v1.0.0-M9 (2023-02-08) ### "Breaking" changes @@ -22,8 +14,15 @@ This file summarizes **notable** changes for each release, but does not describe * scala-3.2.2 * fs2-3.6.0 +* http4s-1.0.0-M39 + +## v1.0.0-M8 (2023-01-05) + +### Dependency updates -## v0.8.0 (2022-11-20) +* **http4s-1.0.0-M38** + +## v1.0.0-M7 (2022-11-20) ### Breaking changes @@ -34,37 +33,120 @@ This file summarizes **notable** changes for each release, but does not describe * cats-effect-3.4.1 * fs2-3.4.0 -## v0.7.0 (2022-03-23) +## v1.0.0-M6 (2022-09-20) + +### Dependency updates + +* **http4s-1.0.0-M37** + +## v1.0.0-M5 (2022-08-27) + +### Dependency updates + +* **http4s-1.0.0-M36** + +## v1.0.0-M4 (2022-07-29) + +### Dependency updates + +* **http4s-1.0.0-M35** + +## v1.0.0-M3 (2022-07-07) + +### Dependency updates + +* cats-2.8.0 +* **http4s-1.0.0-M34** + +## v1.0.0-M2 (2022-05-31) + +### Improvements + +* Optimizations via new entity model. [#644](https://github.com/http4s/http4s-jdk-http-client/pull/644) +* Use Cats Effect EC as `HttpClient` executor. [#641](https://github.com/http4s/http4s-jdk-http-client/pull/641) -This is the spiritual successor to v0.5.0. +### Dependency updates + +* cats-effect-3.3.12 +* fs2-3.2.7 +* **http4s-1.0.0-M33** + +## v1.0.0-M1 (2022-03-23) + +This is the spiritual successor to v0.6.0-M7. ### Breaking changes -* We now use the upstream WebSocket model made public in http4s-0.23.11. +* We now use the upstream WebSocket model made public in http4s-1.0.0-M32. ### Dependency updates -* scala-2.12.15, scala-2.13.8, scala-3.1.1 -* cats-2.7.0 +* scala-2.13.8, scala-3.1.1 * cats-effect-3.3.8 * fs2-3.2.5 -* http4s-0.23.11 +* http4s-1.0.0-M32 -## v0.5.0 (2021-07-31) +## v0.6.0-M7 (2021-12-09) -This release is considered stable. +### Dependency updates + +* **http4s-1.0.0-M30** +* **scala-3.1.0** +* scala-2.13.7 +* cats-2.7.0 +* cats-effect-3.3.0 +* fs2-3.2.3 +* scodec-bits-1.1.30 +* case-insensitive-1.2.0 + +## v0.6.0-M6 (2021-10-13) + +### Dependency updates + +* **http4s-1.0.0-M29** +* fs2-3.1.5 + +## v0.6.0-M5 (2021-10-07) + +### Dependency updates + +* **http4s-1.0.0-M28** +* fs2-3.1.4 +* scodec-1.1.29 + +## v0.6.0-M4 (2021-09-21) + +### Dependency updates + +* **http4s-1.0.0-M27** +* scala-2.12.15 +* cats-effect-3.2.9 +* vault-3.1.0 + +## v0.6.0-M3 (2021-09-15) + +### Dependency updates + +* **http4s-1.0.0-M25** +* scala-3.0.2 +* cats-effect-3.2.8 +* fs2-3.1.2 +* scodec-bits-1.1.28 + +## v0.6.0-M2 (2021-08-08) ### Dependency updates -* **http4s-0.23.0** +* **http4s-1.0.0-M24** * scala-2.12.14 * scala-3.0.1 -* cats-effect-3.2.1 -* fs2-3.0.6 +* cats-effect-3.2.2 +* fs2-3.1.0 +* vault-3.0.4 -## v0.5.0-RC1 (2021-05-28) +## v0.6.0-M1 (2021-05-28) -The v0.5.x releases will now track http4s-0.23.x. +This is the moral successor to v0.5.0-M4, tracking the 1.x releases of http4s. ### Breaking changes @@ -73,7 +155,7 @@ The v0.5.x releases will now track http4s-0.23.x. ### Dependency updates * **scala-3.0.0** -* **http4s-0.23.0-RC1** +* **http4s-1.0.0-M23** * scala-2.13.6 * cats-2.6.1 * cats-effect-3.1.1 diff --git a/build.sbt b/build.sbt index 01e59985..5f878b6c 100644 --- a/build.sbt +++ b/build.sbt @@ -38,7 +38,8 @@ val catsV = "2.12.0" val catsEffectV = "3.5.5" val fs2V = "3.11.0" val scodecV = "1.2.1" -val http4sV = "0.23.29" +val http4sV = "1.0.0-M43" +val log4catsV = "2.7.0" val reactiveStreamsV = "1.0.4" val vaultV = "3.6.0" val caseInsensitiveV = "1.4.2" @@ -48,7 +49,8 @@ val munitCatsEffectV = "2.0.0" val emberServer = Seq( "org.http4s" %% "http4s-ember-server" % http4sV, - "org.http4s" %% "http4s-dsl" % http4sV + "org.http4s" %% "http4s-dsl" % http4sV, + "org.typelevel" %% "log4cats-noop" % log4catsV ) val coreDeps = Seq( @@ -69,9 +71,9 @@ val coreDeps = Seq( )).map(_ % Test) val scala213 = "2.13.15" -ThisBuild / crossScalaVersions := Seq("2.12.20", scala213, "3.3.4") +ThisBuild / crossScalaVersions := Seq(scala213, "3.3.4") ThisBuild / scalaVersion := scala213 -ThisBuild / tlBaseVersion := "0.9" +ThisBuild / tlBaseVersion := "1.0" ThisBuild / startYear := Some(2019) ThisBuild / developers := List( tlGitHubDev("ChristopherDavenport", "Christopher Davenport"), @@ -81,8 +83,8 @@ ThisBuild / developers := List( ThisBuild / tlJdkRelease := Some(11) ThisBuild / githubWorkflowJavaVersions := Seq("11", "17").map(JavaSpec.temurin(_)) -ThisBuild / tlCiReleaseBranches := Seq("series/0.9") -ThisBuild / tlSitePublishBranch := Some("series/0.9") +ThisBuild / tlCiReleaseBranches := Seq("main") +ThisBuild / tlSitePublishBranch := Some("main") lazy val docsSettings = Seq( @@ -93,8 +95,9 @@ lazy val docsSettings = import laika.config._ tlSiteHelium.value.site.versions( Versions - .forCurrentVersion(Version("0.9.x", "0.9")) + .forCurrentVersion(Version("1.x", "1.x")) .withOlderVersions( + Version("0.9.x", "0.9"), Version("0.8.x", "0.8"), Version("0.7.x", "0.7"), Version("0.6.x", "0.6.0-M7"), diff --git a/core/src/main/scala/org/http4s/jdkhttpclient/JdkHttpClient.scala b/core/src/main/scala/org/http4s/jdkhttpclient/JdkHttpClient.scala index 1c0472d7..1a23c924 100644 --- a/core/src/main/scala/org/http4s/jdkhttpclient/JdkHttpClient.scala +++ b/core/src/main/scala/org/http4s/jdkhttpclient/JdkHttpClient.scala @@ -24,6 +24,7 @@ import fs2.Chunk import fs2.Stream import fs2.concurrent.SignallingRef import fs2.interop.flow +import org.http4s.Entity import org.http4s.Header import org.http4s.Headers import org.http4s.HttpVersion @@ -31,12 +32,12 @@ import org.http4s.Request import org.http4s.Response import org.http4s.Status import org.http4s.client.Client -import org.http4s.internal.CollectionCompat.CollectionConverters._ import org.typelevel.ci.CIString import java.net.URI import java.net.http.HttpClient import java.net.http.HttpRequest +import java.net.http.HttpRequest.BodyPublisher import java.net.http.HttpRequest.BodyPublishers import java.net.http.HttpResponse import java.net.http.HttpResponse.BodyHandlers @@ -44,6 +45,7 @@ import java.nio.ByteBuffer import java.time.Duration import java.util import java.util.concurrent.Flow +import scala.jdk.CollectionConverters._ object JdkHttpClient { @@ -60,12 +62,16 @@ object JdkHttpClient { jdkHttpClient: HttpClient, ignoredHeaders: Set[CIString] = restrictedHeaders )(implicit F: Async[F]): Client[F] = { - def convertRequest(req: Request[F]): Resource[F, HttpRequest] = - flow.toPublisher(req.body.chunks.map(_.toByteBuffer)).evalMap { publisher => - convertHttpVersionFromHttp4s[F](req.httpVersion).map { version => - val rb = HttpRequest.newBuilder - .method( - req.method.name, + def convertRequest(req: Request[F]): Resource[F, HttpRequest] = for { + version <- Resource.eval(convertHttpVersionFromHttp4s[F](req.httpVersion)) + bodyPublisher <- req.entity match { + case Entity.Empty => Resource.pure[F, BodyPublisher](BodyPublishers.noBody()) + case Entity.Strict(bytes) => + Resource.pure[F, BodyPublisher](BodyPublishers.ofInputStream(() => bytes.toInputStream)) + case Entity.Streamed(body, _) => + flow + .toPublisher(body.chunks.map(_.toByteBuffer)) + .map { publisher => if (req.isChunked) BodyPublishers.fromPublisher(publisher) else @@ -74,16 +80,17 @@ object JdkHttpClient { BodyPublishers.fromPublisher(publisher, length) case _ => BodyPublishers.noBody } - ) - .uri(URI.create(req.uri.renderString)) - .version(version) - val headers = req.headers.headers.iterator - .filterNot(h => ignoredHeaders.contains(h.name)) - .flatMap(h => Iterator(h.name.toString, h.value)) - .toArray - (if (headers.isEmpty) rb else rb.headers(headers: _*)).build - } + } } + rb = HttpRequest.newBuilder + .method(req.method.name, bodyPublisher) + .uri(URI.create(req.uri.renderString)) + .version(version) + headers = req.headers.headers.iterator + .filterNot(h => ignoredHeaders.contains(h.name)) + .flatMap(h => Iterator(h.name.toString, h.value)) + .toArray + } yield (if (headers.isEmpty) rb else rb.headers(headers: _*)).build // Convert the JDK HttpResponse into a http4s Response value. // @@ -217,12 +224,14 @@ object JdkHttpClient { case HttpClient.Version.HTTP_1_1 => HttpVersion.`HTTP/1.1` case HttpClient.Version.HTTP_2 => HttpVersion.`HTTP/2` }, - body = body - .interruptWhen(signal) - .flatMap(bs => - Stream.fromIterator(bs.iterator.asScala.map(Chunk.byteBuffer), 1) - ) - .flatMap(Stream.chunk) + entity = Entity.stream( + body + .interruptWhen(signal) + .flatMap(bs => + Stream.fromIterator(bs.iterator.asScala.map(Chunk.byteBuffer), 1) + ) + .flatMap(Stream.chunk) + ) ) -> signal.set(true) } ) diff --git a/core/src/test/scala/org/http4s/jdkhttpclient/BodyLeakExample.scala b/core/src/test/scala/org/http4s/jdkhttpclient/BodyLeakExample.scala index 7da879a8..584f5734 100644 --- a/core/src/test/scala/org/http4s/jdkhttpclient/BodyLeakExample.scala +++ b/core/src/test/scala/org/http4s/jdkhttpclient/BodyLeakExample.scala @@ -24,11 +24,15 @@ import org.http4s._ import org.http4s.client._ import org.http4s.ember.server.EmberServerBuilder import org.http4s.syntax.all._ +import org.typelevel.log4cats.LoggerFactory +import org.typelevel.log4cats.noop.NoOpFactory // This is a *manual* test for the body leak fixed in #335 // Run e.g. with `bloop run core-test --args -J-Xmx200M` object BodyLeakExample extends IOApp { + implicit val loggerFactory: LoggerFactory[IO] = NoOpFactory[IO] + val app: HttpApp[IO] = Kleisli((_: Request[IO]) => IO.pure(Response[IO]().withEntity("Hello, HTTP"))) diff --git a/core/src/test/scala/org/http4s/jdkhttpclient/CompletableFutureTerminationTest.scala b/core/src/test/scala/org/http4s/jdkhttpclient/CompletableFutureTerminationTest.scala index a1b79923..18ae0afa 100644 --- a/core/src/test/scala/org/http4s/jdkhttpclient/CompletableFutureTerminationTest.scala +++ b/core/src/test/scala/org/http4s/jdkhttpclient/CompletableFutureTerminationTest.scala @@ -26,6 +26,8 @@ import munit.CatsEffectSuite import org.http4s._ import org.http4s.ember.server._ import org.http4s.server._ +import org.typelevel.log4cats.LoggerFactory +import org.typelevel.log4cats.noop.NoOpFactory import java.net.URI import java.net.http.HttpClient @@ -38,6 +40,8 @@ import scala.concurrent.duration._ final class CompletableFutureTerminationTest extends CatsEffectSuite { import CompletableFutureTerminationTest._ + implicit val loggerFactory: LoggerFactory[IO] = NoOpFactory[IO] + private val duration: FiniteDuration = FiniteDuration(50L, TimeUnit.MILLISECONDS) @@ -172,7 +176,7 @@ object CompletableFutureTerminationTest { * ensure the server has received the request. This permit is acquired ''before'' one is * acquired from `semaphore`. */ - private def stallingServerR[F[_]: Network]( + private def stallingServerR[F[_]: Network: LoggerFactory]( semaphore: Semaphore[F], gotRequest: Semaphore[F] )(implicit F: Async[F]): Resource[F, Server] = diff --git a/core/src/test/scala/org/http4s/jdkhttpclient/JdkWSClientSpec.scala b/core/src/test/scala/org/http4s/jdkhttpclient/JdkWSClientSpec.scala index d81e63de..0e49af62 100644 --- a/core/src/test/scala/org/http4s/jdkhttpclient/JdkWSClientSpec.scala +++ b/core/src/test/scala/org/http4s/jdkhttpclient/JdkWSClientSpec.scala @@ -29,12 +29,16 @@ import org.http4s.ember.server.EmberServerBuilder import org.http4s.implicits._ import org.http4s.websocket.WebSocketFrame import org.typelevel.ci._ +import org.typelevel.log4cats.LoggerFactory +import org.typelevel.log4cats.noop.NoOpFactory import scodec.bits.ByteVector import scala.concurrent.duration._ class JdkWSClientSpec extends CatsEffectSuite { + implicit val loggerFactory: LoggerFactory[IO] = NoOpFactory[IO] + val webSocket: IOFixture[WSClient[IO]] = ResourceSuiteLocalFixture("webSocket", Resource.eval(JdkWSClient.simple[IO])) val echoServerUri: IOFixture[Uri] = diff --git a/docs/README.md b/docs/README.md index 987fccb7..721bcf2e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -189,6 +189,11 @@ import org.http4s.dsl.io._ import org.http4s.implicits._ import org.http4s.ember.server.EmberServerBuilder import com.comcast.ip4s._ +import org.typelevel.log4cats.LoggerFactory +import org.typelevel.log4cats.noop.NoOpFactory + +implicit val loggerFactory: LoggerFactory[IO] = NoOpFactory[IO] + val echoServer = EmberServerBuilder.default[IO] .withPort(port"0") .withHttpWebSocketApp(wsb => HttpRoutes.of[IO] {