Skip to content

Commit

Permalink
Merge pull request #638 from http4s/merge/to/main
Browse files Browse the repository at this point in the history
Merge series/0.5 to main
  • Loading branch information
hamnis authored Mar 12, 2024
2 parents 87ac54e + cc2a9e2 commit 0e314e8
Show file tree
Hide file tree
Showing 15 changed files with 224 additions and 32 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ jobs:

- name: Upload target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: target-${{ matrix.os }}-${{ matrix.java }}-${{ matrix.scala }}-${{ matrix.project }}
path: targets.tar
Expand Down Expand Up @@ -123,7 +123,7 @@ jobs:
run: sbt +update

- name: Download target directories (2.13, rootJVM)
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-rootJVM

Expand All @@ -133,7 +133,7 @@ jobs:
rm targets.tar
- name: Download target directories (3, rootJVM)
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: target-${{ matrix.os }}-${{ matrix.java }}-3-rootJVM

Expand Down
2 changes: 1 addition & 1 deletion .scala-steward.conf
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ updates.ignore = [
{ groupId = "org.scalameta", artifactId = "scalafmt-core" },
{ groupId = "org.scala-sbt", artifactId = "sbt" },
{ groupId = "org.http4s", artifactId = "sbt-http4s-org" },
]
]
2 changes: 1 addition & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version = 3.7.17
version = 3.8.0
runner.dialect = scala213
style = default

Expand Down
17 changes: 9 additions & 8 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import com.typesafe.tools.mima.core._

val Scala213 = "2.13.12"
val Scala213 = "2.13.13"

inThisBuild(
Seq(
Expand All @@ -12,21 +12,21 @@ inThisBuild(
licenses := Seq(License.Apache2),
tlBaseVersion := "1.0",
tlSonatypeUseLegacyHost := false,
crossScalaVersions := Seq(Scala213, "3.3.1"),
crossScalaVersions := Seq(Scala213, "3.3.3"),
ThisBuild / scalaVersion := Scala213,
githubWorkflowJavaVersions := Seq(JavaSpec.temurin("17"))
)
)

val http4sVersion = "1.0.0-M40"

val jetty = "12.0.5"
val jetty = "12.0.7"

val netty = "4.1.104.Final"
val netty = "4.1.107.Final"

val munit = "0.7.29"

val io_uring = "0.0.24.Final"
val io_uring = "0.0.25.Final"

val nativeNettyModules =
Seq(
Expand All @@ -35,6 +35,7 @@ val nativeNettyModules =
"io.netty.incubator" % "netty-incubator-transport-classes-io_uring" % io_uring,
("io.netty" % "netty-transport-native-epoll" % netty).classifier("linux-x86_64") % Runtime,
("io.netty" % "netty-transport-native-kqueue" % netty).classifier("osx-x86_64") % Runtime,
("io.netty" % "netty-transport-native-kqueue" % netty).classifier("osx-aarch_64") % Runtime,
("io.netty.incubator" % "netty-incubator-transport-native-io_uring" % io_uring)
.classifier("linux-x86_64") % Runtime
)
Expand All @@ -45,14 +46,14 @@ lazy val core = project
name := "http4s-netty-core",
libraryDependencies ++= List(
"org.log4s" %% "log4s" % "1.10.0",
"co.fs2" %% "fs2-reactive-streams" % "3.9.3",
"co.fs2" %% "fs2-reactive-streams" % "3.9.4",
("org.playframework.netty" % "netty-reactive-streams-http" % "3.0.2")
.exclude("io.netty", "netty-codec-http")
.exclude("io.netty", "netty-handler"),
"io.netty" % "netty-codec-http" % netty,
"io.netty" % "netty-handler" % netty,
"org.http4s" %% "http4s-core" % http4sVersion,
"org.typelevel" %% "cats-effect" % "3.5.2"
"org.typelevel" %% "cats-effect" % "3.5.4"
)
)

Expand Down Expand Up @@ -95,7 +96,7 @@ lazy val client = project
("com.github.monkeywie" % "proxyee" % "1.7.6" % Test)
.excludeAll("io.netty")
.excludeAll("org.bouncycastle"),
"com.github.bbottema" % "java-socks-proxy-server" % "2.0.0" % Test,
"com.github.bbottema" % "java-socks-proxy-server" % "3.0.0" % Test,
"org.scalameta" %% "munit" % munit % Test,
"ch.qos.logback" % "logback-classic" % "1.2.13" % Test,
"org.typelevel" %% "munit-cats-effect" % "2.0.0-M3" % Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import io.netty.handler.ssl.ApplicationProtocolNames
import io.netty.handler.ssl.SslHandler
import io.netty.handler.timeout.IdleStateHandler
import io.netty.util.concurrent.Future
import org.http4s.Headers
import org.http4s.HttpVersion
import org.http4s.Request
import org.http4s.Response
Expand Down Expand Up @@ -101,9 +102,9 @@ private[client] class Http4sChannelPoolMap[F[_]](

def run(request0: Request[F]): Resource[F, Response[F]] = {
val request =
if (config.http2 && request0.uri.scheme.contains(Uri.Scheme.https))
request0.withHttpVersion(HttpVersion.`HTTP/2`)
else request0
(if (config.http2 && request0.uri.scheme.contains(Uri.Scheme.https))
request0.withHttpVersion(HttpVersion.`HTTP/2`)
else request0).withHeaders(config.defaultRequestHeaders).putHeaders(request0.headers)
val key = Key(RequestKey.fromRequest(request), request.httpVersion)

for {
Expand All @@ -118,7 +119,15 @@ private[client] class Http4sChannelPoolMap[F[_]](
}
} { h =>
val pipeline = channel.pipeline()
F.delay(if (pipeline.toMap.containsKey("http4s")) void(pipeline.remove(h)) else ())
F.delay {
if (pipeline.toMap.containsKey("http4s"))
try
void(pipeline.remove(h))
catch {
case _: Throwable => ()
}
else ()
}
}
response <- handler.dispatch(request, channel, key)
} yield response
Expand Down Expand Up @@ -241,7 +250,8 @@ private[client] object Http4sChannelPoolMap {
idleTimeout: Duration,
proxy: Option[Proxy],
sslConfig: SSLContextOption,
http2: Boolean
http2: Boolean,
defaultRequestHeaders: Headers
)

private[client] def fromFuture[F[_]: Async, A](future: => Future[A]): F[A] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import java.io.IOException
import java.nio.channels.ClosedChannelException
import scala.concurrent.ExecutionContext
import scala.concurrent.Future
import scala.concurrent.TimeoutException
import scala.util.Failure
import scala.util.Success

Expand Down Expand Up @@ -200,8 +201,9 @@ private[netty] class Http4sHandler[F[_]](dispatcher: Dispatcher[F])(implicit F:
override def userEventTriggered(ctx: ChannelHandlerContext, evt: scala.Any): Unit = void {
evt match {
case _: IdleStateEvent if ctx.channel().isOpen =>
logger.trace(s"Closing connection due to idle timeout")
ctx.channel().close()
val message = s"Closing connection due to idle timeout"
logger.trace(message)
onException(ctx.channel(), new TimeoutException(message))
case _ => super.userEventTriggered(ctx, evt)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ package client
import cats.effect.Async
import cats.effect.Resource
import io.netty.bootstrap.Bootstrap
import org.http4s.Headers
import org.http4s.client.Client
import org.http4s.headers.`User-Agent`

import javax.net.ssl.SSLContext
import scala.concurrent.duration._
Expand All @@ -36,7 +38,8 @@ class NettyClientBuilder[F[_]](
sslContext: SSLContextOption,
nettyChannelOptions: NettyChannelOptions,
proxy: Option[Proxy],
http2: Boolean
http2: Boolean,
defaultRequestHeaders: Headers
)(implicit F: Async[F]) {
type Self = NettyClientBuilder[F]

Expand All @@ -51,7 +54,8 @@ class NettyClientBuilder[F[_]](
sslContext: SSLContextOption = sslContext,
nettyChannelOptions: NettyChannelOptions = nettyChannelOptions,
proxy: Option[Proxy] = proxy,
http2: Boolean = http2
http2: Boolean = http2,
defaultRequestHeaders: Headers = defaultRequestHeaders
): NettyClientBuilder[F] =
new NettyClientBuilder[F](
idleTimeout,
Expand All @@ -64,7 +68,8 @@ class NettyClientBuilder[F[_]](
sslContext,
nettyChannelOptions,
proxy,
http2
http2,
defaultRequestHeaders
)

def withNativeTransport: Self = copy(transport = NettyTransport.defaultFor(Os.get))
Expand Down Expand Up @@ -102,6 +107,12 @@ class NettyClientBuilder[F[_]](
def withHttp2: Self = copy(http2 = true)
def withoutHttp2: Self = copy(http2 = false)

def withUserAgent(useragent: `User-Agent`): NettyClientBuilder[F] =
copy(defaultRequestHeaders = defaultRequestHeaders.put(useragent))

def withDefaultRequestHeaders(headers: Headers): NettyClientBuilder[F] =
copy(defaultRequestHeaders = headers)

private def createBootstrap: Resource[F, Bootstrap] =
Resource.make(F.delay {
val bootstrap = new Bootstrap()
Expand All @@ -122,7 +133,8 @@ class NettyClientBuilder[F[_]](
idleTimeout,
proxy,
sslContext,
http2
http2,
defaultRequestHeaders
)
Client[F](new Http4sChannelPoolMap[F](bs, config).run)
}
Expand All @@ -141,6 +153,7 @@ object NettyClientBuilder {
sslContext = SSLContextOption.TryDefaultSSLContext,
nettyChannelOptions = NettyChannelOptions.empty,
proxy = Proxy.fromSystemProperties,
http2 = false
http2 = false,
defaultRequestHeaders = Headers()
)
}
86 changes: 86 additions & 0 deletions client/src/test/scala/org/http4s/netty/client/EchoHeaderTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright 2020 http4s.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.http4s.netty.client

import cats.effect.IO
import com.comcast.ip4s._
import munit.catseffect.IOFixture
import org.http4s.HttpRoutes
import org.http4s.ProductId
import org.http4s.Request
import org.http4s.Response
import org.http4s.client.Client
import org.http4s.dsl.io._
import org.http4s.ember.server.EmberServerBuilder
import org.http4s.headers.`User-Agent`
import org.http4s.implicits._
import org.http4s.server.Server

import scala.concurrent.duration._

class EchoHeaderTest extends IOSuite {
private val defaultUserAgent: `User-Agent` = `User-Agent`(ProductId("http4s-client", Some("1.0")))
val client: IOFixture[Client[IO]] =
resourceFixture(
NettyClientBuilder[IO]
.withIdleTimeout(10.seconds)
.withUserAgent(defaultUserAgent)
.resource,
"client")

val server: IOFixture[Server] = resourceFixture(
EmberServerBuilder
.default[IO]
.withPort(port"0")
.withHttpApp(
HttpRoutes
.of[IO] { case r @ GET -> Root / "echo-ua" =>
val ua = r.headers.get[`User-Agent`]
ua match {
case Some(value) => IO(Response[IO]().putHeaders(value))
case None => NotFound()
}
}
.orNotFound
)
.build,
"server"
)

test("echo useragent back") {
val s = server()

client().get(s.baseUri / "echo-ua")(res =>
res.headers.get[`User-Agent`] match {
case Some(value) => IO(assertEquals(value, defaultUserAgent))
case None => IO(fail("No user-agent header found"))
})
}

test("echo useragent back override") {
val s = server()

val overrideUA = `User-Agent`(ProductId("override"))
client()
.run(Request[IO](uri = s.baseUri / "echo-ua").putHeaders(overrideUA))
.use(res =>
res.headers.get[`User-Agent`] match {
case Some(value) => IO(assertEquals(value, overrideUA))
case None => IO(fail("No user-agent header found"))
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ import scala.compat.java8.FutureConverters._
class HttpProxyTest extends IOSuite {

val server: IOFixture[Uri] = resourceFixture(
ServerScaffold[IO](1, false, HttpRoutes.pure(Response[IO]().withEntity("Hello from origin")))
ServerScaffold[IO](
1,
secure = false,
HttpRoutes.pure(Response[IO]().withEntity("Hello from origin")))
.map(_.servers.head.uri),
"server")

Expand Down
Loading

0 comments on commit 0e314e8

Please sign in to comment.