Last active
November 7, 2022 13:02
-
-
Save HansG/59ffae7e165955bdf2aad4f5949c83ca to your computer and use it in GitHub Desktop.
Modified Version of skunk:modules/example/src/main/scala-2/Http4s.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (c) 2018-2021 by Rob Norris | |
// This software is licensed under the MIT License (MIT). | |
// For more information see LICENSE or https://opensource.org/licenses/MIT | |
package example | |
import cats.effect.std.Console | |
import cats.effect._ | |
import com.comcast.ip4s.IpLiteralSyntax | |
import io.circe.Encoder | |
import io.circe.generic.semiauto.deriveEncoder | |
import org.http4s.ember.server.EmberServerBuilder | |
import org.http4s.implicits._ | |
import org.http4s.server.middleware.{RequestLogger, ResponseLogger} | |
import org.http4s.server.{Router, Server} | |
import org.http4s.{HttpApp, HttpRoutes} | |
/** | |
* A small but complete web service that serves data from the `world` database. | |
* Note that the effect `F` is abstract throughout. So run this program and then try some requests: | |
* | |
* curl -i http://localhost:8080/country/USA | |
* curl -i http://localhost:8080/country/foobar | |
* | |
*/ | |
class Http4sExample { | |
/** A data model with a Circe `Encoder` */ | |
case class Country(code: String, name: String) | |
object Country { | |
implicit val encoderCountry: Encoder[Country] = deriveEncoder | |
} | |
def httpAppFrom[F[_]: Async: Console](routes: HttpRoutes[F]): HttpApp[F] = { | |
def addLoggers(http: HttpApp[F]): HttpApp[F] = { | |
val httpReq = RequestLogger.httpApp(true, true)(http) | |
ResponseLogger.httpApp(true, true)(httpReq) | |
} | |
addLoggers(Router("/" -> routes).orNotFound) | |
} | |
/** Given an `HttpApp` we can create a running `Server` resource. */ | |
def resServer[F[_]: Async]( | |
httpApp: HttpApp[F] | |
): Resource[F, Server] = | |
EmberServerBuilder | |
.default[F] | |
.withHost(host"localhost") | |
.withPort(port"8080") | |
.withHttpApp(httpApp) | |
.build | |
/* | |
val resSession = Session.single[F]( | |
host = "localhost", | |
port = 5432, | |
user = "jimmy", | |
password = Some("banana"), | |
database = "world" | |
) | |
*/ | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (c) 2018-2021 by Rob Norris | |
// This software is licensed under the MIT License (MIT). | |
// For more information see LICENSE or https://opensource.org/licenses/MIT | |
package example | |
import cats._ | |
import cats.effect._ | |
import cats.effect.std.Console | |
import cats.syntax.all._ | |
import fs2.Stream | |
import fs2.io.net.Network | |
import io.circe.syntax._ | |
import natchez.Trace | |
import natchez.Trace.Implicits.noop | |
import org.http4s.HttpRoutes | |
import org.http4s.circe._ | |
import org.http4s.dsl.Http4sDsl | |
import skunk.codec.text.{bpchar, varchar} | |
import skunk.implicits._ | |
import skunk.{Fragment, Query, Session, Void} | |
object Http4sExampleTry1 extends Http4sExample with IOApp { | |
/** A service interface and companion factory method. */ | |
trait CountryService[F[_]] { | |
def byCode(code: String): F[Option[Country]] | |
def all: Resource[F, Stream[F, Country]] | |
} | |
/** Given a `Session` we can create a `Countries` resource with pre-prepared statements. */ | |
def resCountryService[F[_]: Monad: MonadCancel[*[_], Throwable]]( | |
resSession: Resource[F, Session[F]] | |
): Resource[F, CountryService[F]] = { | |
def countryQuery[A](where: Fragment[A]): Query[A, Country] = | |
sql"SELECT code, name FROM country $where".query((bpchar(3) ~ varchar).gmap[Country]) | |
resSession.map { sess => | |
new CountryService[F] { | |
def byCode(code: String): F[Option[Country]] = | |
sess.prepare(countryQuery(sql"WHERE code = ${bpchar(3)}")).use { psByCode => | |
psByCode.option(code) | |
} | |
def all: Resource[F, Stream[F, Country]] = | |
sess.prepare(countryQuery(Fragment.empty)).map { psAll => | |
psAll.stream(Void, 64) | |
} | |
} | |
} | |
} | |
/** Resource yielding a pool of `CountryService`, backed by a single `Blocker` and `SocketGroup`. */ | |
def resResCountryService[F[_]: Concurrent: Network: Console: Trace]: Resource[F, Resource[F, CountryService[F]]] = | |
Session | |
.pooled[F]( | |
host = "localhost", | |
port = 5432, | |
user = "jimmy", | |
password = Some("banana"), | |
database = "world", | |
max = 10, | |
commandCache = 0, | |
queryCache = 0 | |
) | |
.map(rs => resCountryService(rs)) | |
/** Given a pool of `Countries` we can create an `HttpRoutes`. */ | |
def resRoutes[F[_]: Concurrent]( | |
resService: Resource[F, CountryService[F]] | |
): Resource[F, HttpRoutes[F]] = { | |
object dsl extends Http4sDsl[F]; | |
import dsl._ | |
resService.map { countries => | |
HttpRoutes.of[F] { | |
case GET -> Root / "country" / code => | |
countries.byCode(code).flatMap { | |
case Some(c) => Ok(c.asJson) | |
case None => NotFound(s"No country has code $code.") | |
} | |
case GET -> Root / "countries" => | |
countries.all.use { st => | |
val stt = st.compile.toList.map(_.asJson) //how to use stream directly in the response? | |
Ok(stt) | |
} | |
} | |
} | |
} | |
/** Our application as a resource. */ | |
def resServer[F[_]: Async: Console: Trace]: Resource[F, Unit] = | |
for { | |
rs <- resResCountryService | |
routes <- resRoutes(rs) | |
app = httpAppFrom(routes) | |
_ <- resServer(app) | |
} yield () | |
/** Main method instantiates `F` to `IO` and `use`s our resource forever. */ | |
def run(args: List[String]): IO[ExitCode] = | |
resServer[IO].useForever | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (c) 2018-2021 by Rob Norris | |
// This software is licensed under the MIT License (MIT). | |
// For more information see LICENSE or https://opensource.org/licenses/MIT | |
package example | |
import cats._ | |
import cats.effect.std.Console | |
import cats.effect._ | |
import cats.syntax.all._ | |
import fs2.Stream | |
import fs2.io.net.Network | |
import io.circe.syntax._ | |
import org.http4s.circe._ | |
import org.http4s.dsl.Http4sDsl | |
import org.http4s.server.Server | |
import org.http4s.HttpRoutes | |
import skunk.codec.text.{bpchar, varchar} | |
import skunk.implicits._ | |
import skunk.{Fragment, Query, Session, Void} | |
import natchez.Trace | |
import natchez.Trace.Implicits.noop | |
import org.typelevel.log4cats.Logger | |
import org.typelevel.log4cats.slf4j.Slf4jLogger | |
/** | |
* A small but complete web service that serves data from the `world` database. | |
* Note that the effect `F` is abstract throughout. So run this program and then try some requests: | |
* | |
* curl -i http://localhost:8080/country/USA | |
* curl -i http://localhost:8080/country/foobar | |
* | |
*/ | |
object Http4sExample2 extends Http4sExample with IOApp { | |
/** A service interface and companion factory method. */ | |
trait CountryService[F[_]] { | |
def byCode(code: String): F[Option[Country]] | |
def all: F[Stream[F, Country]] | |
} | |
/** Given a `Session` we can create a `Countries` resource with pre-prepared statements. */ | |
def countryServiceFrom[F[_]: Monad: MonadCancel[*[_], Throwable] : Logger]( | |
resSession: Resource[F, Session[F]] | |
): CountryService[F] = { | |
def countryQuery[A](where: Fragment[A]): Query[A, Country] = | |
sql"SELECT code, name FROM country $where".query((bpchar(3) ~ varchar).gmap[Country]) | |
new CountryService[F] { | |
def byCode(code: String): F[Option[Country]] = | |
resSession.use { sess => | |
sess.prepare(countryQuery(sql"WHERE code = ${bpchar(3)}")).use { psByCode => | |
psByCode.option(code) | |
} | |
} | |
def all: F[Stream[F, Country]] = | |
resSession.use { sess => | |
sess.prepare(countryQuery(Fragment.empty)).use { prepQ => | |
Monad[F].pure( prepQ.stream(Void, 64) | |
) | |
} | |
}.onError { | |
case err => | |
//Logger[F].error( | |
Monad[F].pure(println( | |
s"Failed on: ${err.toString} \n ${err.getCause}" | |
)) | |
} | |
} | |
} | |
/** Resource yielding a pool of `CountryService`, backed by a single `Blocker` and `SocketGroup`. */ | |
def resResSession[F[_]: Concurrent: Network: Console: Trace]: Resource[F, Resource[F, Session[F]]] = | |
Session | |
.pooled[F]( | |
host = "localhost", | |
port = 5432, | |
user = "jimmy", | |
password = Some("banana"), | |
database = "world", | |
max = 10, | |
commandCache = 0, | |
queryCache = 0 | |
) | |
/** Given a pool of `Countries` we can create an `HttpRoutes`. */ | |
def routesFrom[F[_]: Concurrent]( | |
countries: CountryService[F] | |
): HttpRoutes[F] = { | |
object dsl extends Http4sDsl[F]; | |
import dsl._ | |
HttpRoutes.of[F] { | |
case GET -> Root / "country" / code => | |
countries.byCode(code).flatMap { | |
case Some(c) => Ok(c.asJson) | |
case None => NotFound(s"No country has code $code.") | |
} | |
case GET -> Root / "countries" => | |
countries.all.flatMap { st => | |
val stt = st.compile.toList.map(_.asJson) | |
Ok(stt) | |
} | |
} | |
} | |
/** Our application as a resource. */ | |
def resServer[F[_]: Async: Console: Trace : Logger]: Resource[F, Server] = | |
resResSession.map { rs => | |
val cs = countryServiceFrom(rs) | |
val r = routesFrom(cs) | |
httpAppFrom(r) | |
} flatMap { app => | |
resServer(app) | |
} | |
implicit val logger = Slf4jLogger.getLogger[IO] | |
/** Main method instantiates `F` to `IO` and `use`s our resource forever. */ | |
def run(args: List[String]): IO[ExitCode] = | |
resServer[IO].useForever | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment