Running as a Play server¶
To expose endpoint as a play-server first add the following dependencies:
"com.softwaremill.sttp.tapir" %% "tapir-play-server" % "0.16.7"
and
"com.typesafe.play" %% "play-akka-http-server" % "2.8.1"
or
"com.typesafe.play" %% "play-netty-http-server" % "2.8.1"
depending on whether you want to use netty or akka based http-server under the hood.
Then import the package:
import sttp.tapir.server.play._
This adds two extension methods to the Endpoint
type: toRoutes
and toRoutesRecoverErrors
. This first requires the
logic of the endpoint to be given as a function of type:
I => Future[Either[E, O]]
The second recovers errors from failed effects, and hence requires that E
is
a subclass of Throwable
(an exception); it expects a function of type I => Future[O]
. For example:
import sttp.tapir._
import sttp.tapir.server.play._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import play.api.routing.Router.Routes
def countCharacters(s: String): Future[Either[Unit, Int]] =
Future(Right[Unit, Int](s.length))
val countCharactersEndpoint: Endpoint[String, Unit, Int, Nothing] =
endpoint.in(stringBody).out(plainBody[Int])
val countCharactersRoutes: Routes =
countCharactersEndpoint.toRoutes(countCharacters _)
Note that these functions take one argument, which is a tuple of type I
. This means that functions which take multiple
arguments need to be converted to a function using a single argument using .tupled
:
def logic(s: String, i: Int): Future[Either[Unit, String]] = ???
val anEndpoint: Endpoint[(String, Int), Unit, String, Nothing] = ???
val aRoute: Routes = anEndpoint.toRoute((logic _).tupled)
In practice, the routes are put in a class taking an endpoints.play.server.PlayComponents
parameter.
An HTTP server can then be started as in the following example:
object Main {
// JVM entry point that starts the HTTP server
def main(args: Array[String]): Unit = {
val playConfig = ServerConfig(port =
sys.props.get("http.port").map(_.toInt).orElse(Some(9000))
)
NettyServer.fromRouterWithComponents(playConfig) { components =>
val playComponents = PlayComponents.fromBuiltInComponents(components)
new CounterServer(playComponents).routes orElse new DocumentationServer(
playComponents
).routes
}
}
}
Configuration¶
The interpreter can be configured by providing an implicit PlayServerOptions
value and status mappers, see
server options for details.
Defining an endpoint together with the server logic¶
It’s also possible to define an endpoint together with the server logic in a single, more concise step. See server logic for details.