diff --git a/core/src/main/scala/sttp/tapir/EndpointIO.scala b/core/src/main/scala/sttp/tapir/EndpointIO.scala index 8bb38352c7..5671cbdaf4 100644 --- a/core/src/main/scala/sttp/tapir/EndpointIO.scala +++ b/core/src/main/scala/sttp/tapir/EndpointIO.scala @@ -534,23 +534,27 @@ case class WebSocketBodyOutput[PIPE_REQ_RESP, REQ, RESP, T, S]( def concatenateFragmentedFrames(c: Boolean): WebSocketBodyOutput[PIPE_REQ_RESP, REQ, RESP, T, S] = this.copy(concatenateFragmentedFrames = c) - /** @param i If `true`, [[WebSocketFrame.Pong]] frames will be ignored and won't be passed to the codecs for decoding. + /** Note: some interpreters ignore this setting. + * @param i If `true`, [[WebSocketFrame.Pong]] frames will be ignored and won't be passed to the codecs for decoding. * Note that only some interpreters expose ping-pong frames. */ def ignorePong(i: Boolean): WebSocketBodyOutput[PIPE_REQ_RESP, REQ, RESP, T, S] = this.copy(concatenateFragmentedFrames = i) - /** @param a If `true`, [[WebSocketFrame.Ping]] frames will cause a matching [[WebSocketFrame.Pong]] frame to be sent + /** Note: some interpreters ignore this setting. + * @param a If `true`, [[WebSocketFrame.Ping]] frames will cause a matching [[WebSocketFrame.Pong]] frame to be sent * back, and won't be passed to codecs for decoding. * Note that only some interpreters expose ping-pong frames. */ def autoPongOnPing(a: Boolean): WebSocketBodyOutput[PIPE_REQ_RESP, REQ, RESP, T, S] = this.copy(concatenateFragmentedFrames = a) - /** @param d If `true`, [[WebSocketFrame.Close]] frames won't be passed to the request codec for decoding (in server + /** Note: some interpreters ignore this setting. + * @param d If `true`, [[WebSocketFrame.Close]] frames won't be passed to the request codec for decoding (in server * interpreters). */ def decodeCloseRequests(d: Boolean): WebSocketBodyOutput[PIPE_REQ_RESP, REQ, RESP, T, S] = this.copy(decodeCloseRequests = d) - /** @param d If `true`, [[WebSocketFrame.Close]] frames won't be passed to the response codec for decoding (in client + /** Note: some interpreters ignore this setting. + * @param d If `true`, [[WebSocketFrame.Close]] frames won't be passed to the response codec for decoding (in client * interpreters). */ def decodeCloseResponses(d: Boolean): WebSocketBodyOutput[PIPE_REQ_RESP, REQ, RESP, T, S] = this.copy(decodeCloseResponses = d) diff --git a/doc/endpoint/websockets.md b/doc/endpoint/websockets.md index 54be0cf7ad..db8788941b 100644 --- a/doc/endpoint/websockets.md +++ b/doc/endpoint/websockets.md @@ -59,7 +59,9 @@ Request/response schemas can be customised through `.requestsSchema` and `.respo ## Interpreting as a sever When interpreting a web socket endpoint as a server, the [server logic](../server/logic.md) needs to provide a -streaming-specific pipe from requests to responses. E.g. in Akka's case, this will be `Flow[REQ, RESP, Any]`. +streaming-specific pipe from requests to responses. E.g. in akka's case, this will be `Flow[REQ, RESP, Any]`. + +Refer to the documentation of interpreters for more details, as not all interpreters support all settings. ## Interpreting as a client diff --git a/doc/server/akkahttp.md b/doc/server/akkahttp.md index 5a1d588694..5a3b3c5809 100644 --- a/doc/server/akkahttp.md +++ b/doc/server/akkahttp.md @@ -162,8 +162,9 @@ The capability can be added to the classpath independently of the interpreter th The interpreter supports web sockets, with pipes of type `Flow[REQ, RESP, Any]`. See [web sockets](../endpoint/websockets.md) for more details. -[Automatic pings](https://doc.akka.io/docs/akka-http/current/server-side/websocket-support.html#automatic-keep-alive-ping-support) -can be enabled through configuration. +akka-http does not expose control frames (`Ping`, `Pong` and `Close`), so any setting regarding them are discarded, and +ping/pong frames which are sent explicitly are ignored. [Automatic pings](https://doc.akka.io/docs/akka-http/current/server-side/websocket-support.html#automatic-keep-alive-ping-support) +can be instead enabled through configuration. ## Configuration diff --git a/examples/src/main/scala/sttp/tapir/examples/WebSocketAkkaServer.scala b/examples/src/main/scala/sttp/tapir/examples/WebSocketAkkaServer.scala index 4dcf9b13e5..0a30ef8ae2 100644 --- a/examples/src/main/scala/sttp/tapir/examples/WebSocketAkkaServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/WebSocketAkkaServer.scala @@ -23,19 +23,20 @@ import scala.concurrent.{Await, Future} object WebSocketAkkaServer extends App { case class Response(hello: String) - // The endpoint: GET /ping. + // The web socket endpoint: GET /ping. // We need to provide both the type & media type for the requests, and responses. Here, the requests will be // strings, and responses will be returned as json. val wsEndpoint: Endpoint[Unit, Unit, Flow[String, Response, Any], AkkaStreams with WebSockets] = endpoint.get.in("ping").out(webSocketBody[String, CodecFormat.TextPlain, Response, CodecFormat.Json](AkkaStreams)) + // Implementation of the web socket: a flow which echoes incoming messages val wsRoute: Route = wsEndpoint.toRoute(_ => Future.successful(Right(Flow.fromFunction((in: String) => Response(in))))) - // documentation + // Documentation val apiDocs = wsEndpoint.toAsyncAPI("JSON echo", "1.0", List("dev" -> Server("localhost:8080", "ws"))).toYaml println(s"Paste into https://playground.asyncapi.io/ to see the docs for this endpoint:\n$apiDocs") - // starting the server + // Starting the server implicit val actorSystem: ActorSystem = ActorSystem() import actorSystem.dispatcher @@ -43,8 +44,9 @@ object WebSocketAkkaServer extends App { .newServerAt("localhost", 8080) .bindFlow(wsRoute) .flatMap { _ => - // we could have interpreted wsEndpoint as a client, but here we are using sttp client directly + // We could have interpreted wsEndpoint as a client, but here we are using sttp client directly val backend: SttpBackend[Future, WebSockets] = AkkaHttpBackend.usingActorSystem(actorSystem) + // Client which interacts with the web socket basicRequest .response(asWebSocket { ws: WebSocket[Future] => for { diff --git a/examples/src/main/scala/sttp/tapir/examples/WebSocketHttp4sServer.scala b/examples/src/main/scala/sttp/tapir/examples/WebSocketHttp4sServer.scala index be31db706f..f260e31cf5 100644 --- a/examples/src/main/scala/sttp/tapir/examples/WebSocketHttp4sServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/WebSocketHttp4sServer.scala @@ -33,13 +33,13 @@ object WebSocketHttp4sServer extends App { case class CountResponse(received: Int) - // The endpoint: GET /count. + // The web socket endpoint: GET /count. // We need to provide both the type & media type for the requests, and responses. Here, the requests will be // byte arrays, and responses will be returned as json. val wsEndpoint: Endpoint[Unit, Unit, Pipe[IO, String, CountResponse], Fs2Streams[IO] with WebSockets] = endpoint.get.in("count").out(webSocketBody[String, CodecFormat.TextPlain, CountResponse, CodecFormat.Json](Fs2Streams[IO])) - // counts the number of bytes received each second + // A pipe which counts the number of bytes received each second val countBytes: Pipe[IO, String, CountResponse] = { in => sealed trait CountAction case class AddAction(n: Int) extends CountAction @@ -65,19 +65,21 @@ object WebSocketHttp4sServer extends App { } } - // implementing the endpoint's logic + // Implementing the endpoint's logic, by providing the web socket pipe val wsRoutes: HttpRoutes[IO] = wsEndpoint.toRoutes(_ => IO.pure(Right(countBytes))) + // Documentation val apiDocs = wsEndpoint.toAsyncAPI("Byte counter", "1.0", List("dev" -> Server("localhost:8080", "ws"))).toYaml println(s"Paste into https://playground.asyncapi.io/ to see the docs for this endpoint:\n$apiDocs") - // starting the server + // Starting the server BlazeServerBuilder[IO](ec) .bindHttp(8080, "localhost") .withHttpApp(Router("/" -> wsRoutes).orNotFound) .resource .flatMap(_ => AsyncHttpClientFs2Backend.resource[IO](blocker)) .use { backend => + // Client which interacts with the web socket basicRequest .response(asWebSocket { ws: WebSocket[IO] => for { diff --git a/generated-doc/out/endpoint/websockets.md b/generated-doc/out/endpoint/websockets.md index ddf422148a..493f9374e3 100644 --- a/generated-doc/out/endpoint/websockets.md +++ b/generated-doc/out/endpoint/websockets.md @@ -59,7 +59,9 @@ Request/response schemas can be customised through `.requestsSchema` and `.respo ## Interpreting as a sever When interpreting a web socket endpoint as a server, the [server logic](../server/logic.md) needs to provide a -streaming-specific pipe from requests to responses. E.g. in Akka's case, this will be `Flow[REQ, RESP, Any]`. +streaming-specific pipe from requests to responses. E.g. in akka's case, this will be `Flow[REQ, RESP, Any]`. + +Refer to the documentation of interpreters for more details, as not all interpreters support all settings. ## Interpreting as a client diff --git a/generated-doc/out/server/akkahttp.md b/generated-doc/out/server/akkahttp.md index d6be1e788f..7a8ebbd7df 100644 --- a/generated-doc/out/server/akkahttp.md +++ b/generated-doc/out/server/akkahttp.md @@ -162,8 +162,9 @@ The capability can be added to the classpath independently of the interpreter th The interpreter supports web sockets, with pipes of type `Flow[REQ, RESP, Any]`. See [web sockets](../endpoint/websockets.md) for more details. -[Automatic pings](https://doc.akka.io/docs/akka-http/current/server-side/websocket-support.html#automatic-keep-alive-ping-support) -can be enabled through configuration. +akka-http does not expose control frames (`Ping`, `Pong` and `Close`), so any setting regarding them are discarded, and +ping/pong frames which are sent explicitly are ignored. [Automatic pings](https://doc.akka.io/docs/akka-http/current/server-side/websocket-support.html#automatic-keep-alive-ping-support) +can be instead enabled through configuration. ## Configuration