diff --git a/zio-http-testkit/src/main/scala/zio/http/TestClient.scala b/zio-http-testkit/src/main/scala/zio/http/TestClient.scala index 4c28c3fe81..f6d93d2112 100644 --- a/zio-http-testkit/src/main/scala/zio/http/TestClient.scala +++ b/zio-http-testkit/src/main/scala/zio/http/TestClient.scala @@ -91,6 +91,7 @@ final case class TestClient( headers: Headers, body: Body, sslConfig: Option[zio.http.ClientSSLConfig], + proxy: Option[Proxy], )(implicit trace: Trace): ZIO[Any, Throwable, Response] = { val notFound: PartialFunction[Request, ZIO[Any, Response, Response]] = { case _: Request => ZIO.succeed(Response.notFound) diff --git a/zio-http/src/main/scala/zio/http/ZClient.scala b/zio-http/src/main/scala/zio/http/ZClient.scala index 4a4a147762..3b0b90b77c 100644 --- a/zio-http/src/main/scala/zio/http/ZClient.scala +++ b/zio-http/src/main/scala/zio/http/ZClient.scala @@ -30,6 +30,7 @@ final case class ZClient[-Env, -In, +Err, +Out]( url: URL, headers: Headers, sslConfig: Option[ClientSSLConfig], + proxy: Option[Proxy], bodyEncoder: ZClient.BodyEncoder[Env, Err, In], bodyDecoder: ZClient.BodyDecoder[Env, Err, Out], driver: ZClient.Driver[Env, Err], @@ -162,6 +163,7 @@ final case class ZClient[-Env, -In, +Err, +Out]( self.headers ++ request.headers, request.body, sslConfig, + proxy, ), ) else @@ -177,6 +179,7 @@ final case class ZClient[-Env, -In, +Err, +Out]( self.headers ++ request.headers, body, sslConfig, + proxy, ), ), ) @@ -197,6 +200,7 @@ final case class ZClient[-Env, -In, +Err, +Out]( headers, body, sslConfig, + proxy, ) def retry[Env1 <: Env](policy: Schedule[Env1, Err, Any]): ZClient[Env1, In, Err, Out] = @@ -218,6 +222,9 @@ final case class ZClient[-Env, -In, +Err, +Out]( def ssl(ssl: ClientSSLConfig): ZClient[Env, In, Err, Out] = copy(sslConfig = Some(ssl)) + def proxy(proxy: Proxy): ZClient[Env, In, Err, Out] = + copy(proxy = Some(proxy)) + def transform[Env2, In2, Err2, Out2]( bodyEncoder: ZClient.BodyEncoder[Env2, Err2, In2], bodyDecoder: ZClient.BodyDecoder[Env2, Err2, Out2], @@ -228,6 +235,7 @@ final case class ZClient[-Env, -In, +Err, +Out]( url, headers, sslConfig, + proxy, bodyEncoder, bodyDecoder, driver, @@ -278,6 +286,7 @@ object ZClient { URL.empty, Headers.empty, None, + None, BodyEncoder.identity, BodyDecoder.identity, driver, @@ -363,7 +372,7 @@ object ZClient { trait Driver[-Env, +Err] { self => final def apply(request: Request)(implicit trace: Trace): ZIO[Env & Scope, Err, Response] = - self.request(request.version, request.method, request.url, request.headers, request.body, None) + self.request(request.version, request.method, request.url, request.headers, request.body, None, None) final def disableStreaming(implicit ev: Err <:< Throwable): Driver[Env, Throwable] = { val self0 = self.widenError[Throwable] @@ -376,8 +385,9 @@ object ZClient { headers: Headers, body: Body, sslConfig: Option[ClientSSLConfig], + proxy: Option[Proxy], )(implicit trace: Trace): ZIO[Env & Scope, Throwable, Response] = - self0.request(version, method, url, headers, body, sslConfig).flatMap { response => + self0.request(version, method, url, headers, body, sslConfig, proxy).flatMap { response => response.body.asChunk.map { chunk => response.copy(body = Body.fromChunk(chunk)) } @@ -413,8 +423,9 @@ object ZClient { headers: Headers, body: Body, sslConfig: Option[ClientSSLConfig], + proxy: Option[Proxy], )(implicit trace: Trace): ZIO[Env & Scope, Err2, Response] = - self.request(version, method, url, headers, body, sslConfig).mapError(f) + self.request(version, method, url, headers, body, sslConfig, proxy).mapError(f) override def socket[Env1 <: Env]( version: Version, @@ -443,8 +454,9 @@ object ZClient { headers: Headers, body: Body, sslConfig: Option[ClientSSLConfig], + proxy: Option[Proxy], )(implicit trace: Trace): ZIO[Env & Scope, Err2, Response] = - self.request(version, method, url, headers, body, sslConfig).refineOrDie(pf) + self.request(version, method, url, headers, body, sslConfig, proxy).refineOrDie(pf) override def socket[Env1 <: Env]( version: Version, @@ -469,10 +481,11 @@ object ZClient { headers: Headers, body: Body, sslConfig: Option[ClientSSLConfig], + proxy: Option[Proxy], )(implicit trace: Trace): ZIO[Env & Scope, Err, Response] final def request(req: Request)(implicit trace: Trace): ZIO[Env & Scope, Err, Response] = - request(req.version, req.method, req.url, req.headers, req.body, None) + request(req.version, req.method, req.url, req.headers, req.body, None, None) final def retry[Env1 <: Env, Err1 >: Err](policy: zio.Schedule[Env1, Err1, Any]) = new Driver[Env1, Err1] { @@ -483,8 +496,9 @@ object ZClient { headers: Headers, body: Body, sslConfig: Option[ClientSSLConfig], + proxy: Option[Proxy], )(implicit trace: Trace): ZIO[Env1 & Scope, Err1, Response] = - self.request(version, method, url, headers, body, sslConfig).retry(policy) + self.request(version, method, url, headers, body, sslConfig, proxy).retry(policy) override def socket[Env2 <: Env1]( version: Version, @@ -629,11 +643,11 @@ object ZClient { headers: Headers, body: Body, sslConfig: Option[ClientSSLConfig], + proxy: Option[Proxy], )(implicit trace: Trace): ZIO[Scope, Throwable, Response] = { val request = Request(version, method, url, headers, body, None) - val cfg = sslConfig.fold(config)(config.ssl) - - requestAsync(request, cfg, () => WebSocketApp.unit, None) + val cfg = config.copy(ssl = sslConfig.orElse(config.ssl), proxy = proxy.orElse(config.proxy)) + requestAsync(request, cfg, () => WebSocketApp.unit, None) } def socket[Env1]( diff --git a/zio-http/src/main/scala/zio/http/ZClientAspect.scala b/zio-http/src/main/scala/zio/http/ZClientAspect.scala index 718d2fca08..82bf529893 100644 --- a/zio-http/src/main/scala/zio/http/ZClientAspect.scala +++ b/zio-http/src/main/scala/zio/http/ZClientAspect.scala @@ -140,9 +140,10 @@ object ZClientAspect { headers: Headers, body: Body, sslConfig: Option[ClientSSLConfig], + proxy: Option[Proxy], )(implicit trace: Trace): ZIO[Env & Scope, Err, Response] = oldDriver - .request(version, method, url, headers, body, sslConfig) + .request(version, method, url, headers, body, sslConfig, proxy) .sandbox .exit .timed @@ -210,9 +211,10 @@ object ZClientAspect { headers: Headers, body: Body, sslConfig: Option[ClientSSLConfig], + proxy: Option[Proxy], )(implicit trace: Trace): ZIO[Env & Scope, Err, Response] = { oldDriver - .request(version, method, url, headers, body, sslConfig) + .request(version, method, url, headers, body, sslConfig, proxy) .sandbox .exit .timed diff --git a/zio-http/src/test/scala/zio/http/ClientProxySpec.scala b/zio-http/src/test/scala/zio/http/ClientProxySpec.scala index 2c2e00b1af..b585f246ec 100644 --- a/zio-http/src/test/scala/zio/http/ClientProxySpec.scala +++ b/zio-http/src/test/scala/zio/http/ClientProxySpec.scala @@ -21,9 +21,10 @@ import java.net.ConnectException import zio.test.Assertion._ import zio.test.TestAspect.{sequential, timeout, withLiveClock} import zio.test._ -import zio.{Scope, ZIO, ZLayer, durationInt} +import zio.{Scope, Trace, ZIO, ZLayer, durationInt} import zio.http.internal.{DynamicServer, HttpRunnableSpec, serverTestLayer} +import zio.http.ZClient.{Config, DriverLive} import zio.http.netty.NettyConfig import zio.http.netty.client.NettyClientDriver @@ -49,6 +50,24 @@ object ClientProxySpec extends HttpRunnableSpec { } yield out assertZIO(res.either)(isLeft(isSubtype[ConnectException](anything))) }, + test("ZClient proxy respond Ok") { + val res = + for { + port <- ZIO.environmentWithZIO[DynamicServer](_.get.port) + url <- ZIO.fromEither(URL.decode(s"http://localhost:$port")) + id <- DynamicServer.deploy(Handler.ok.toHttpApp) + proxy = Proxy.empty.url(url).headers(Headers(DynamicServer.APP_ID, id)) + zclient <- ZIO.serviceWith[Client](_.copy(proxy = Some(proxy))) + out <- zclient + .request( + Request.get(url = url), + ) + .provide( + Scope.default, + ) + } yield out + assertZIO(res.either)(isRight) + }, test("proxy respond Ok") { val res = for { @@ -68,6 +87,7 @@ object ClientProxySpec extends HttpRunnableSpec { ZLayer.succeed(NettyConfig.default), Scope.default, ) + _ = println(out) } yield out assertZIO(res.either)(isRight) }, @@ -110,4 +130,5 @@ object ClientProxySpec extends HttpRunnableSpec { override def spec: Spec[TestEnvironment with Scope, Any] = suite("ClientProxy") { serve.as(List(clientProxySpec)) }.provideShared(DynamicServer.live, serverTestLayer) @@ sequential @@ withLiveClock + timeout(5 seconds) @@ sequential @@ withLiveClock }