Contenu connexe Plus de Knoldus Inc. (20) Http4s 2. Lack of etiquette and manners is a huge turn off.
KnolX Etiquettes
Punctuality
Join the session 5 minutes prior to
the session start time. We start on
time and conclude on time!
Feedback
Make sure to submit a constructive
feedback for all sessions as it is
very helpful for the presenter.
Silent Mode
Keep your mobile devices in silent
mode, feel free to move out of
session in case you need to attend
an urgent call.
Avoid Disturbance
Avoid unwanted chit chat during
the session.
3. Agenda
Introduction to http4s
http4s DSL
http4s Streaming Nature
http4s Support for Circe
Request -
Response
Model
Creating
Endpoint
Creating
Server
Matching
Requests
Sending
RAW
JSON
Extracting
Requests
Receiving
RAW
JSON
EntityBody[
F]
4. http4s - What makes it different?
Define
Values
Light-weight
Product
Quality
Typeful
Pure
Functional
Performant
Streaming
Cross -
Platform
5. http4s - Request and Response
type HttpRoutes = Kleisli[OptionT[F, *], Request, Response],
Request -> Response
Request -> Option[Response]
Request -> F[Option[Response]]
Kleisli[OptionT[F, *], Request, Response]
6. http4s DSL
Defining a simple endpoint
implicit val runtime: IORuntime = cats.effect.unsafe.IORuntime.global
val route: HttpRoutes[IO] = HttpRoutes.of[IO] {
case GET -> Root / "length" / str => Ok(str.length.toString)
}
Request Type Endpoint Behavior
GET (/POST/PUT) /length/{str} str.length.toString
7. http4s DSL
Running a Server
BlazeServerBuilder[IO]
.bindHttp(8080, "localhost")
.withHttpApp(app)
.resource
.useForever
.as(ExitCode.Success)
val app =
Router(
“/” -> route,
“/api” -> servicesAPI
)
.orNotFound
8. http4s DSL
Matching & Extracting Requests
HttpRoutes.of[IO] {
case GET -> Root / "hello" / name => Ok(s"Hello, ${name}!")
}
HttpRoutes.of[IO] {
case GET -> "hello" /: rest => Ok(s"""Hello, ${rest.segments.mkString(" and ")}!""")
}
HttpRoutes.of[IO] {
case GET -> Root / file ~ "json" => Ok(s"""{"response": "You asked for $file"}""")
}
● The -> Object and Matching Paths
9. http4s DSL
Matching & Extracting Requests
def getUserName(userId: Int): IO[String] = ???
val usersService = HttpRoutes.of[IO] {
case GET -> Root / "users" / IntVar(userId) =>
Ok(getUserName(userId))
}
object LocalDateVar {
def unapply(str: String): Option[LocalDate] = {
if (!str.isEmpty)
Try(LocalDate.parse(str)).toOption
else
None
}
}
def getTemperatureForecast(date: LocalDate): IO[Double] =
IO(42.23)
val dailyWeatherService = HttpRoutes.of[IO] {
case GET -> Root / "weather" / "temperature" /
LocalDateVar(localDate) =>
Ok(getTemperatureForecast(localDate)
.map(s"The temperature on $localDate will be: " +
_))
}
// /weather/temperature/2016-11-05
● Handling Path Parameters
10. object FullNameAndIDExtractor extends MatrixVar("name", List("first", "last", "id"))
val greetingWithIdService = HttpRoutes.of[IO] {
case GET -> Root / "hello" / FullNameAndIDExtractor(first, last, IntVar(id)) / "greeting" =>
Ok(s"Hello, $first $last. Your User ID is $id.")
}
// /hello/name;first=john;last=doe;id=123/greeting
● Handling Matrix Path Parameters
http4s DSL
Matching & Extracting Requests
11. object CountryQueryParamMatcher extends QueryParamDecoderMatcher[String]("country")
implicit val yearQueryParamDecoder: QueryParamDecoder[Year] =
QueryParamDecoder[Int].map(Year.of)
object YearQueryParamMatcher extends QueryParamDecoderMatcher[Year]("year")
val averageTemperatureService = HttpRoutes.of[IO] {
case GET -> Root / "weather" / "temperature" :? CountryQueryParamMatcher(country) +& YearQueryParamMatcher(year) =>
Ok(getAverageTemperatureForCountryAndYear(country, year)
.map(s"Average temperature for $country in $year was: " + _))
}
● Handling Query Parameters - QueryParamDecoderMatcher
http4s DSL
Matching & Extracting Requests
12. implicit val yearQueryParamDecoder: QueryParamDecoder[Year] = QueryParamDecoder[Int].map(Year.of)
object OptionalYearQueryParamMatcher extends OptionalQueryParamDecoderMatcher[Year]("year")
val routes = HttpRoutes.of[IO] {
case GET -> Root / "temperature" :? OptionalYearQueryParamMatcher(maybeYear) =>
maybeYear match {
case None =>
Ok(getAverageTemperatureForCurrentYear)
case Some(year) =>
Ok(getAverageTemperatureForYear(year))
}
}
● Handling Query Parameters - OptionalQueryParamDecoderMatcher
http4s DSL
Matching & Extracting Requests
13. implicit val yearQueryParamDecoder: QueryParamDecoder[Year] = QueryParamDecoder[Int]
.emap(i => Try(Year.of(i))
.toEither
.leftMap(t =>
ParseFailure(t.getMessage, t.getMessage)
)
)
object YearQueryParamMatcher extends ValidatingQueryParamDecoderMatcher[Year]("year")
val routes = HttpRoutes.of[IO] {
case GET -> Root / "temperature" :? YearQueryParamMatcher(yearValidated) =>
yearValidated.fold(
parseFailures => BadRequest("unable to parse argument year"),
year => Ok(getAverageTemperatureForYear(year))
)
}
● Handling Query Parameters - ValidatingQueryParamDecoderMatcher
http4s DSL
Matching & Extracting Requests
14. object LongParamMatcher extends OptionalValidatingQueryParamDecoderMatcher[Long]("long")
val routes = HttpRoutes.of[IO] {
case GET -> Root / "number" :? LongParamMatcher(maybeNumber) =>
maybeNumber match {
case Some(n) =>
n.fold(
parseFailures => BadRequest("unable to parse argument 'long'"),
year => Ok(n.toString)
)
case None => BadRequest("missing number")
}
}
● Handling Query Parameters - OptionalValidatingQueryParamDecoderMatcher
http4s DSL
Matching & Extracting Requests
15. http4s Streaming Nature
Request[F] / R esponse[F]
EntityBody[F]
Stream[F, Byte]
EntityEncoder / EntityDecoder
Raw Data
Types:
String, File,
InputStream
JSON
Support:
curve,
json4s-native
XML
Support:
scala-xml
16. http4s JSON Codec - Circe
import io.circe.syntax._
case class Hello(name: String)
implicit val HelloEncoder: Encoder[Hello] =
Encoder.instance { (hello: Hello) =>
json"""{"hello": ${hello.name}}"""
}
Hello("Alice").asJson
// res3: Json = JObject(value = object[hello -> "Alice"])
import io.circe.generic.auto._
case class User(name: String)
User("Alice").asJson
// res4: Json = JObject(value = object[name -> "Alice"])
Ok(Hello("Alice").asJson).unsafeRunSync()
POST(User("Bob").asJson, uri"/hello")
● Sending Raw JSON
17. http4s JSON Codec - Circe
Ok("""{"name":"Alice"}""").flatMap(_.as[Json]).unsafeRunS
ync()
// res8: Json = JObject(value = object[name -> "Alice"])
POST("""{"name":"Bob"}""",
uri"/hello").as[Json].unsafeRunSync()
// res9: Json = JObject(value = object[name -> "Bob"])
case class User(name: String)
implicit val userDecoder = jsonOf[IO, User]
Ok("""{"name":"Alice"}""").flatMap(_.as[User]).unsafeRunS
ync()
// res10: User = User(name = "Alice")
POST("""{"name":"Bob"}""",
uri"/hello").as[User].unsafeRunSync()
// res11: User = User(name = "Bob")
● Receiving Raw JSON
18. http4s JSON Codec - Circe
// Server
import io.circe.generic.auto._
case class User(name: String)
case class Hello(greeting: String)
implicit val decoder = jsonOf[IO, User]
val jsonApp = HttpRoutes.of[IO] {
case req @ POST -> Root / "hello" =>
for {
user <- req.as[User] // Decode
resp <- Ok(Hello(user.name).asJson) // Encode
} yield (resp)
}.orNotFound
● Combining Encoder and Decoder
// Client
import io.circe.generic.auto._
def helloClient(name: String): IO[Hello] = {
val req = POST(User(name).asJson,
uri"http://localhost:8080/hello")
EmberClientBuilder.default[IO].build.use {
httpClient =>
httpClient.expect(req)(jsonOf[IO, Hello])
}
}
helloClient("Alice").unsafeRunSync()