@@ -6,24 +6,30 @@ import java.nio.ByteBuffer
66import cats .effect ._
77import cats .effect .implicits ._
88import cats .implicits ._
9- import fs2 .{Chunk , Stream }
9+ import fs2 .concurrent .InspectableQueue
10+ import fs2 .{Chunk , Pipe , Stream }
1011import fs2 .interop .reactivestreams ._
1112import io .netty .buffer .{ByteBuf , Unpooled }
1213import org .asynchttpclient .{Request => _ , Response => _ , _ }
1314import org .reactivestreams .Publisher
14- import sttp .client .asynchttpclient .{AsyncHttpClientBackend , WebSocketHandler }
15+ import sttp .client .asynchttpclient .{AsyncHttpClientBackend , BodyFromAHC , BodyToAHC }
1516import sttp .client .impl .cats .CatsMonadAsyncError
16- import sttp .client .impl .fs2 .Fs2Streams
17+ import sttp .client .impl .fs2 .{ Fs2AsyncQueue , Fs2Streams , Fs2WebSockets }
1718import sttp .client .internal ._
19+ import sttp .client .monad .MonadAsyncError
1820import sttp .client .testing .SttpBackendStub
21+ import sttp .client .ws .WebSocket
22+ import sttp .client .ws .internal .AsyncQueue
1923import sttp .client .{FollowRedirectsBackend , SttpBackend , SttpBackendOptions , _ }
24+ import sttp .model .ws .WebSocketFrame
2025
2126import scala .concurrent .ExecutionContext
2227
2328class AsyncHttpClientFs2Backend [F [_]: ConcurrentEffect : ContextShift ] private (
2429 asyncHttpClient : AsyncHttpClient ,
2530 closeClient : Boolean ,
26- customizeRequest : BoundRequestBuilder => BoundRequestBuilder
31+ customizeRequest : BoundRequestBuilder => BoundRequestBuilder ,
32+ webSocketBufferCapacity : Option [Int ]
2733) extends AsyncHttpClientBackend [F , Fs2Streams [F ], Fs2Streams [F ] with WebSockets ](
2834 asyncHttpClient,
2935 new CatsMonadAsyncError ,
@@ -33,89 +39,124 @@ class AsyncHttpClientFs2Backend[F[_]: ConcurrentEffect: ContextShift] private (
3339
3440 override val streams : Fs2Streams [F ] = Fs2Streams [F ]
3541
36- override def send [T , R >: Fs2Streams [F ] with sttp.client.Effect [F ]](r : Request [T , R ]): F [Response [T ]] = {
42+ override def send [T , R >: sttp.client.Effect [F ] with Fs2Streams [F ] with WebSockets ](
43+ r : Request [T , R ]
44+ ): F [Response [T ]] = {
3745 super .send(r).guarantee(implicitly[ContextShift [F ]].shift)
3846 }
3947
40- override def openWebsocket [T , WS_RESULT , R >: Fs2Streams [F ] with sttp.client.Effect [F ]](
41- r : Request [T , R ],
42- handler : WebSocketHandler [WS_RESULT ]
43- ): F [WebSocketResponse [WS_RESULT ]] = super .openWebsocket(r, handler).guarantee(ContextShift [F ].shift)
44-
45- override protected def streamBodyToPublisher (s : Stream [F , Byte ]): Publisher [ByteBuf ] =
46- s.chunks.map(c => Unpooled .wrappedBuffer(c.toArray)).toUnicastPublisher
47-
48- override protected def publisherToStreamBody (p : Publisher [ByteBuffer ]): Stream [F , Byte ] =
49- p.toStream[F ].flatMap(buf => Stream .chunk(Chunk .byteBuffer(buf)))
50-
51- override protected def publisherToBytes (p : Publisher [ByteBuffer ]): F [Array [Byte ]] = {
52- p.toStream[F ]
53- .compile
54- .fold(ByteBuffer .allocate(0 ))(concatByteBuffers)
55- .map(_.array())
48+ override protected val bodyFromAHC : BodyFromAHC [F , Fs2Streams [F ]] =
49+ new BodyFromAHC [F , Fs2Streams [F ]] {
50+ override val streams : Fs2Streams [F ] = Fs2Streams [F ]
51+ override implicit val monad : MonadAsyncError [F ] = new CatsMonadAsyncError
52+
53+ override def publisherToStream (p : Publisher [ByteBuffer ]): Stream [F , Byte ] =
54+ p.toStream[F ].flatMap(buf => Stream .chunk(Chunk .byteBuffer(buf)))
55+
56+ override def publisherToBytes (p : Publisher [ByteBuffer ]): F [Array [Byte ]] = {
57+ p.toStream[F ]
58+ .compile
59+ .fold(ByteBuffer .allocate(0 ))(concatByteBuffers)
60+ .map(_.array())
61+ }
62+
63+ override def publisherToFile (p : Publisher [ByteBuffer ], f : File ): F [Unit ] = {
64+ p.toStream[F ]
65+ .flatMap(b => Stream .emits(b.array()))
66+ .through(fs2.io.file.writeAll(f.toPath, Blocker .liftExecutionContext(ExecutionContext .global)))
67+ .compile
68+ .drain
69+ }
70+
71+ override def compileWebSocketPipe (
72+ ws : WebSocket [F ],
73+ pipe : Pipe [F , WebSocketFrame .Data [_], WebSocketFrame ]
74+ ): F [Unit ] = Fs2WebSockets .handleThroughPipe(ws)(pipe)
75+ }
76+
77+ override protected val bodyToAHC : BodyToAHC [F , Fs2Streams [F ]] = new BodyToAHC [F , Fs2Streams [F ]] {
78+ override val streams : Fs2Streams [F ] = Fs2Streams [F ]
79+
80+ override protected def streamToPublisher (s : Stream [F , Byte ]): Publisher [ByteBuf ] =
81+ s.chunks.map(c => Unpooled .wrappedBuffer(c.toArray)).toUnicastPublisher
5682 }
5783
58- override protected def publisherToFile (p : Publisher [ByteBuffer ], f : File ): F [Unit ] = {
59- p.toStream[F ]
60- .flatMap(b => Stream .emits(b.array()))
61- .through(fs2.io.file.writeAll(f.toPath, Blocker .liftExecutionContext(ExecutionContext .global)))
62- .compile
63- .drain
64- }
84+ override protected def createAsyncQueue [T ]: F [AsyncQueue [F , T ]] =
85+ webSocketBufferCapacity
86+ .fold(InspectableQueue .unbounded[F , T ])(InspectableQueue .bounded)
87+ .map(new Fs2AsyncQueue (_))
6588}
6689
6790object AsyncHttpClientFs2Backend {
6891 private def apply [F [_]: ConcurrentEffect : ContextShift ](
6992 asyncHttpClient : AsyncHttpClient ,
7093 closeClient : Boolean ,
71- customizeRequest : BoundRequestBuilder => BoundRequestBuilder
94+ customizeRequest : BoundRequestBuilder => BoundRequestBuilder ,
95+ webSocketBufferCapacity : Option [Int ]
7296 ): SttpBackend [F , Fs2Streams [F ] with WebSockets ] =
73- new FollowRedirectsBackend (new AsyncHttpClientFs2Backend (asyncHttpClient, closeClient, customizeRequest))
97+ new FollowRedirectsBackend (
98+ new AsyncHttpClientFs2Backend (asyncHttpClient, closeClient, customizeRequest, webSocketBufferCapacity)
99+ )
74100
75101 def apply [F [_]: ConcurrentEffect : ContextShift ](
76102 options : SttpBackendOptions = SttpBackendOptions .Default ,
77- customizeRequest : BoundRequestBuilder => BoundRequestBuilder = identity
103+ customizeRequest : BoundRequestBuilder => BoundRequestBuilder = identity,
104+ webSocketBufferCapacity : Option [Int ] = AsyncHttpClientBackend .DefaultWebSocketBufferCapacity
78105 ): F [SttpBackend [F , Fs2Streams [F ] with WebSockets ]] =
79106 implicitly[Sync [F ]]
80- .delay(apply[F ](AsyncHttpClientBackend .defaultClient(options), closeClient = true , customizeRequest))
107+ .delay(
108+ apply[F ](
109+ AsyncHttpClientBackend .defaultClient(options),
110+ closeClient = true ,
111+ customizeRequest,
112+ webSocketBufferCapacity
113+ )
114+ )
81115
82116 /**
83117 * Makes sure the backend is closed after usage.
84118 */
85119 def resource [F [_]: ConcurrentEffect : ContextShift ](
86120 options : SttpBackendOptions = SttpBackendOptions .Default ,
87- customizeRequest : BoundRequestBuilder => BoundRequestBuilder = identity
121+ customizeRequest : BoundRequestBuilder => BoundRequestBuilder = identity,
122+ webSocketBufferCapacity : Option [Int ] = AsyncHttpClientBackend .DefaultWebSocketBufferCapacity
88123 ): Resource [F , SttpBackend [F , Fs2Streams [F ] with WebSockets ]] =
89- Resource .make(apply(options, customizeRequest))(_.close())
124+ Resource .make(apply(options, customizeRequest, webSocketBufferCapacity ))(_.close())
90125
91126 def usingConfig [F [_]: ConcurrentEffect : ContextShift ](
92127 cfg : AsyncHttpClientConfig ,
93- customizeRequest : BoundRequestBuilder => BoundRequestBuilder = identity
128+ customizeRequest : BoundRequestBuilder => BoundRequestBuilder = identity,
129+ webSocketBufferCapacity : Option [Int ] = AsyncHttpClientBackend .DefaultWebSocketBufferCapacity
94130 ): F [SttpBackend [F , Fs2Streams [F ] with WebSockets ]] =
95- implicitly[Sync [F ]].delay(apply[F ](new DefaultAsyncHttpClient (cfg), closeClient = true , customizeRequest))
131+ implicitly[Sync [F ]].delay(
132+ apply[F ](new DefaultAsyncHttpClient (cfg), closeClient = true , customizeRequest, webSocketBufferCapacity)
133+ )
96134
97135 /**
98136 * Makes sure the backend is closed after usage.
99137 */
100138 def resourceUsingConfig [F [_]: ConcurrentEffect : ContextShift ](
101139 cfg : AsyncHttpClientConfig ,
102- customizeRequest : BoundRequestBuilder => BoundRequestBuilder = identity
140+ customizeRequest : BoundRequestBuilder => BoundRequestBuilder = identity,
141+ webSocketBufferCapacity : Option [Int ] = AsyncHttpClientBackend .DefaultWebSocketBufferCapacity
103142 ): Resource [F , SttpBackend [F , Fs2Streams [F ] with WebSockets ]] =
104- Resource .make(usingConfig(cfg, customizeRequest))(_.close())
143+ Resource .make(usingConfig(cfg, customizeRequest, webSocketBufferCapacity ))(_.close())
105144
106145 /**
107146 * @param updateConfig A function which updates the default configuration (created basing on `options`).
108147 */
109148 def usingConfigBuilder [F [_]: ConcurrentEffect : ContextShift ](
110149 updateConfig : DefaultAsyncHttpClientConfig .Builder => DefaultAsyncHttpClientConfig .Builder ,
111150 options : SttpBackendOptions = SttpBackendOptions .Default ,
112- customizeRequest : BoundRequestBuilder => BoundRequestBuilder = identity
151+ customizeRequest : BoundRequestBuilder => BoundRequestBuilder = identity,
152+ webSocketBufferCapacity : Option [Int ] = AsyncHttpClientBackend .DefaultWebSocketBufferCapacity
113153 ): F [SttpBackend [F , Fs2Streams [F ] with WebSockets ]] =
114154 implicitly[Sync [F ]].delay(
115155 AsyncHttpClientFs2Backend [F ](
116156 AsyncHttpClientBackend .clientWithModifiedOptions(options, updateConfig),
117157 closeClient = true ,
118- customizeRequest
158+ customizeRequest,
159+ webSocketBufferCapacity
119160 )
120161 )
121162
@@ -126,15 +167,17 @@ object AsyncHttpClientFs2Backend {
126167 def resourceUsingConfigBuilder [F [_]: ConcurrentEffect : ContextShift ](
127168 updateConfig : DefaultAsyncHttpClientConfig .Builder => DefaultAsyncHttpClientConfig .Builder ,
128169 options : SttpBackendOptions = SttpBackendOptions .Default ,
129- customizeRequest : BoundRequestBuilder => BoundRequestBuilder = identity
170+ customizeRequest : BoundRequestBuilder => BoundRequestBuilder = identity,
171+ webSocketBufferCapacity : Option [Int ] = AsyncHttpClientBackend .DefaultWebSocketBufferCapacity
130172 ): Resource [F , SttpBackend [F , Fs2Streams [F ] with WebSockets ]] =
131- Resource .make(usingConfigBuilder(updateConfig, options, customizeRequest))(_.close())
173+ Resource .make(usingConfigBuilder(updateConfig, options, customizeRequest, webSocketBufferCapacity ))(_.close())
132174
133175 def usingClient [F [_]: ConcurrentEffect : ContextShift ](
134176 client : AsyncHttpClient ,
135- customizeRequest : BoundRequestBuilder => BoundRequestBuilder = identity
177+ customizeRequest : BoundRequestBuilder => BoundRequestBuilder = identity,
178+ webSocketBufferCapacity : Option [Int ] = AsyncHttpClientBackend .DefaultWebSocketBufferCapacity
136179 ): SttpBackend [F , Fs2Streams [F ] with WebSockets ] =
137- apply[F ](client, closeClient = false , customizeRequest)
180+ apply[F ](client, closeClient = false , customizeRequest, webSocketBufferCapacity )
138181
139182 /**
140183 * Create a stub backend for testing, which uses the `F` response wrapper, and supports `Stream[F, ByteBuffer]`
0 commit comments