org.http4s.HttpRoutes Scala Examples

The following examples show how to use org.http4s.HttpRoutes. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example.
Example 1
Source File: NatchezHttp4sModule.scala    From skunk   with MIT License 8 votes vote down vote up
// Copyright (c) 2018-2020 by Rob Norris
// This software is licensed under the MIT License (MIT).
// For more information see LICENSE or https://opensource.org/licenses/MIT

package natchez.http4s

import cats.~>
import cats.data.{ Kleisli, OptionT }
import cats.effect.Bracket
import cats.implicits._
import natchez.{ EntryPoint, Kernel, Span }
import org.http4s.HttpRoutes
import natchez.Trace
import natchez.Tags
import scala.util.control.NonFatal
import org.http4s.Response
import cats.effect.Resource
import cats.Defer
import natchez.TraceValue
import cats.Monad

object implicits {

  // Given an entry point and HTTP Routes in Kleisli[F, Span[F], ?] return routes in F. A new span
  // is created with the URI path as the name, either as a continuation of the incoming trace, if
  // any, or as a new root. This can likely be simplified, I just did what the types were saying
  // and it works so :shrug:
  private def liftT[F[_]: Bracket[?[_], Throwable]](
    entryPoint: EntryPoint[F])(
    routes:     HttpRoutes[Kleisli[F, Span[F], ?]]
  ): HttpRoutes[F] =
    Kleisli { req =>
      type G[A]  = Kleisli[F, Span[F], A]
      val lift   = λ[F ~> G](fa => Kleisli(_ => fa))
      val kernel = Kernel(req.headers.toList.map(h => (h.name.value -> h.value)).toMap)
      val spanR  = entryPoint.continueOrElseRoot(req.uri.path, kernel)
      OptionT {
        spanR.use { span =>
          val lower = λ[G ~> F](_(span))
          routes.run(req.mapK(lift)).mapK(lower).map(_.mapK(lower)).value
        }
      }
    }

  implicit class EntryPointOps[F[_]](self: EntryPoint[F]) {

    private def dummySpan(
      implicit ev: Monad[F]
    ): Span[F] =
      new Span[F] {
        val kernel: F[Kernel] = Kernel(Map.empty).pure[F]
        def put(fields: (String, TraceValue)*): F[Unit] = Monad[F].unit
        def span(name: String): Resource[F, Span[F]] = Monad[Resource[F, ?]].pure(this)
      }

    def liftT(routes: HttpRoutes[Kleisli[F, Span[F], ?]])(
      implicit ev: Bracket[F, Throwable]
    ): HttpRoutes[F] =
      implicits.liftT(self)(routes)

    
  def natchezMiddleware[F[_]: Bracket[?[_], Throwable]: Trace](routes: HttpRoutes[F]): HttpRoutes[F] =
    Kleisli { req =>

      val addRequestFields: F[Unit] =
        Trace[F].put(
          Tags.http.method(req.method.name),
          Tags.http.url(req.uri.renderString)
        )

      def addResponseFields(res: Response[F]): F[Unit] =
        Trace[F].put(
          Tags.http.status_code(res.status.code.toString)
        )

      def addErrorFields(e: Throwable): F[Unit] =
        Trace[F].put(
          Tags.error(true),
          "error.message"    -> e.getMessage,
          "error.stacktrace" -> e.getStackTrace.mkString("\n"),
        )

      OptionT {
        routes(req).onError {
          case NonFatal(e)   => OptionT.liftF(addRequestFields *> addErrorFields(e))
        } .value.flatMap {
          case Some(handler) => addRequestFields *> addResponseFields(handler).as(handler.some)
          case None          => Option.empty[Response[F]].pure[F]
        }
      }
    }

} 
Example 2
Source File: MockHttpServer.scala    From cornichon   with Apache License 2.0 6 votes vote down vote up
package com.github.agourlay.cornichon.http.server

import java.net.NetworkInterface

import com.github.agourlay.cornichon.core.CornichonError
import monix.eval.Task
import monix.execution.Scheduler
import org.http4s.HttpRoutes
import org.http4s.server.Router
import org.http4s.server.blaze.BlazeServerBuilder
import org.http4s.implicits._

import scala.jdk.CollectionConverters._
import scala.concurrent.duration._
import scala.util.Random

class MockHttpServer[A](interface: Option[String], port: Option[Range], mockService: HttpRoutes[Task], maxRetries: Int = 5)(useFromAddress: String => Task[A])(implicit scheduler: Scheduler) {

  private val selectedInterface = interface.getOrElse(bestInterface())
  private val randomPortOrder = port.fold(0 :: Nil)(r => Random.shuffle(r.toList))

  private val mockRouter = Router("/" -> mockService).orNotFound

  def useServer(): Task[A] =
    if (randomPortOrder.isEmpty)
      Task.raiseError(MockHttpServerError.toException)
    else
      startServerTryPorts(randomPortOrder)

  private def startServerTryPorts(ports: List[Int], retry: Int = 0): Task[A] =
    startBlazeServer(ports.head).onErrorHandleWith {
      case _: java.net.BindException if ports.length > 1 =>
        startServerTryPorts(ports.tail, retry)
      case _: java.net.BindException if retry < maxRetries =>
        val sleepFor = retry + 1
        println(s"Could not start server on any port. Retrying in $sleepFor seconds...")
        startServerTryPorts(randomPortOrder, retry = retry + 1).delayExecution(sleepFor.seconds)
    }

  private def startBlazeServer(port: Int): Task[A] =
    BlazeServerBuilder[Task](executionContext = scheduler)
      .bindHttp(port, selectedInterface)
      .withoutBanner
      .withHttpApp(mockRouter)
      .withNio2(true)
      .resource
      .use(server => useFromAddress(s"http://${server.address.getHostString}:${server.address.getPort}"))

  private def bestInterface(): String =
    NetworkInterface.getNetworkInterfaces.asScala
      .filter(_.isUp)
      .flatMap(_.getInetAddresses.asScala)
      .find(_.isSiteLocalAddress)
      .map(_.getHostAddress)
      .getOrElse("localhost")
}

case object MockHttpServerError extends CornichonError {
  val baseErrorMessage = "the range of ports provided for the HTTP mock is invalid"
} 
Example 3
Source File: RedocHttp4s.scala    From tapir   with Apache License 2.0 5 votes vote down vote up
package sttp.tapir.redoc.http4s

import cats.effect.{ContextShift, Sync}
import org.http4s.dsl.Http4sDsl
import org.http4s.headers._
import org.http4s.{Charset, HttpRoutes, MediaType}

import scala.io.Source


class RedocHttp4s(title: String, yaml: String, yamlName: String = "docs.yaml") {
  private lazy val html = {
    val fileName = "redoc.html"
    val is = getClass.getClassLoader.getResourceAsStream(fileName)
    assert(Option(is).nonEmpty, s"Could not find file ${fileName} on classpath.")
    val rawHtml = Source.fromInputStream(is).mkString
    // very poor man's templating engine
    rawHtml.replaceAllLiterally("{{docsPath}}", yamlName).replaceAllLiterally("{{title}}", title)
  }

  def routes[F[_]: ContextShift: Sync]: HttpRoutes[F] = {
    val dsl = Http4sDsl[F]
    import dsl._

    HttpRoutes.of[F] {
      case req @ GET -> Root if req.pathInfo.endsWith("/") =>
        Ok(html, `Content-Type`(MediaType.text.html, Charset.`UTF-8`))
      // as the url to the yaml file is relative, it is important that there is a trailing slash
      case req @ GET -> Root =>
        val uri = req.uri
        PermanentRedirect(Location(uri.withPath(uri.path.concat("/"))))
      case GET -> Root / `yamlName` =>
        Ok(yaml, `Content-Type`(MediaType.text.yaml, Charset.`UTF-8`))
    }
  }
} 
Example 4
Source File: BookingRoutes.scala    From ticket-booking-aecor   with Apache License 2.0 5 votes vote down vote up
package ru.pavkin.booking.booking.endpoint

import cats.effect.Effect
import cats.implicits._
import io.circe.syntax._
import org.http4s.HttpRoutes
import org.http4s.circe._
import org.http4s.dsl.Http4sDsl
import ru.pavkin.booking.common.models.{BookingKey, ClientId}

final class BookingRoutes[F[_]: Effect](ops: BookingEndpoint[F]) extends Http4sDsl[F] {

  implicit val placeBookingDecoder = jsonOf[F, PlaceBookingRequest]
  implicit val cancelBookingDecoder = jsonOf[F, CancelBookingRequest]

  private val placeBooking: HttpRoutes[F] = HttpRoutes.of {
    case r @ POST -> Root / userId / "bookings" =>
      r.as[PlaceBookingRequest]
        .flatMap(
          r =>
            ops.placeBooking(ClientId(userId), r.concertId, r.seats).flatMap {
              case Left(err) => BadRequest(err.toString)
              case Right(_)  => Ok()
          }
        )
  }

  private val cancelBooking: HttpRoutes[F] = HttpRoutes.of {
    case r @ DELETE -> Root / userId / "bookings" / bookingId =>
      r.as[CancelBookingRequest]
        .flatMap(
          r =>
            ops.cancelBooking(ClientId(userId), BookingKey(bookingId), r.reason).flatMap {
              case Left(err) => BadRequest(err.toString)
              case Right(_)  => Ok()
          }
        )
  }

  private val clientBookings: HttpRoutes[F] = HttpRoutes.of {
    case GET -> Root / userId / "bookings" =>
      ops.clientBookings(ClientId(userId)).flatMap { bookings =>
        Ok(bookings.asJson)
      }
  }

  val routes: HttpRoutes[F] =
    placeBooking <+> clientBookings <+> cancelBooking

} 
Example 5
Source File: EndpointWirings.scala    From ticket-booking-aecor   with Apache License 2.0 5 votes vote down vote up
package ru.pavkin.booking

import cats.effect.{ConcurrentEffect, Timer}
import org.http4s.HttpRoutes
import org.http4s.implicits._
import org.http4s.server.Router
import org.http4s.server.blaze.BlazeServerBuilder
import ru.pavkin.booking.booking.endpoint.{BookingRoutes, DefaultBookingEndpoint}
import ru.pavkin.booking.config.HttpServer

import scala.concurrent.duration.{Duration => _}

final class EndpointWirings[F[_] : ConcurrentEffect : Timer](
  httpServer: HttpServer,
  postgresWirings: PostgresWirings[F],
  entityWirings: EntityWirings[F]) {

  import entityWirings._
  import postgresWirings._

  val bookingsEndpoint = new DefaultBookingEndpoint(bookings, bookingViewRepo)

  val bookingRoutes = new BookingRoutes(bookingsEndpoint)

  val routes: HttpRoutes[F] = bookingRoutes.routes

  def launchHttpService: F[Unit] =
    BlazeServerBuilder[F]
      .bindHttp(httpServer.port, httpServer.interface)
      .withHttpApp(Router("/" -> routes).orNotFound)
      .serve
      .compile
      .drain

} 
Example 6
Source File: Http4sTextPlainTest.scala    From guardrail   with MIT License 5 votes vote down vote up
package generators.Http4s.Client.contentType

import _root_.tests.contentTypes.textPlain.client.http4s.foo.FooClient
import _root_.tests.contentTypes.textPlain.client.{ http4s => cdefs }
import _root_.tests.contentTypes.textPlain.server.http4s.foo.{ DoBarResponse, DoBazResponse, DoFooResponse, FooHandler, FooResource }
import _root_.tests.contentTypes.textPlain.server.{ http4s => sdefs }
import org.scalatest.{ EitherValues, FunSuite, Matchers }
import org.http4s.dsl.io._
import org.http4s.headers._
import cats.effect.IO
import org.http4s.client.Client
import org.http4s.{ Charset, HttpRoutes, MediaType }

class Http4sTextPlainTest extends FunSuite with Matchers with EitherValues {
  import org.http4s.implicits._
  test("Plain text should be emitted for required parameters (raw)") {
    val route: HttpRoutes[IO] = HttpRoutes.of {
      case req @ POST -> Root / "foo" =>
        if (req.contentType.contains(`Content-Type`(MediaType.text.plain, Charset.`UTF-8`))) {
          for {
            value <- req.as[String]
            resp  <- if (value == "sample") Created() else NotAcceptable()
          } yield resp
        } else NotAcceptable()
    }
    val client: Client[IO] = Client.fromHttpApp(route.orNotFound)
    val fooClient          = FooClient.httpClient(client)
    fooClient.doFoo("sample").attempt.unsafeRunSync().right.value shouldBe cdefs.foo.DoFooResponse.Created
  }

  test("Plain text should be emitted for optional parameters (raw)") {
    val route: HttpRoutes[IO] = HttpRoutes.of {
      case req @ POST -> Root / "bar" =>
        if (req.contentType.contains(`Content-Type`(MediaType.text.plain, Charset.`UTF-8`))) {
          for {
            value <- req.as[String]
            resp  <- if (value == "sample") Created() else NotAcceptable()
          } yield resp
        } else NotAcceptable()
    }
    val client: Client[IO] = Client.fromHttpApp(route.orNotFound)
    val fooClient          = FooClient.httpClient(client)
    fooClient.doBar(Some("sample")).attempt.unsafeRunSync().right.value shouldBe cdefs.foo.DoBarResponse.Created
  }

  test("Plain text should be emitted for required parameters") {
    val route: HttpRoutes[IO] = new FooResource[IO]().routes(new FooHandler[IO] {
      def doFoo(respond: DoFooResponse.type)(body: String): IO[sdefs.foo.DoFooResponse] =
        if (body == "sample") {
          IO.pure(respond.Created)
        } else {
          IO.pure(respond.NotAcceptable)
        }
      def doBar(respond: DoBarResponse.type)(body: Option[String]): IO[sdefs.foo.DoBarResponse] = ???
      def doBaz(respond: DoBazResponse.type)(body: Option[String]): IO[sdefs.foo.DoBazResponse] = ???
    })

    val client: Client[IO] = Client.fromHttpApp(route.orNotFound)
    val fooClient          = FooClient.httpClient(client)
    fooClient.doFoo("sample").attempt.unsafeRunSync().right.value shouldBe cdefs.foo.DoFooResponse.Created
  }

  test("Plain text should be emitted for present optional parameters") {
    val route: HttpRoutes[IO] = new FooResource[IO]().routes(new FooHandler[IO] {
      def doFoo(respond: DoFooResponse.type)(body: String): IO[sdefs.foo.DoFooResponse] = ???
      def doBar(respond: DoBarResponse.type)(body: Option[String]): IO[sdefs.foo.DoBarResponse] =
        if (body.contains("sample")) {
          IO.pure(respond.Created)
        } else {
          IO.pure(respond.NotAcceptable)
        }
      def doBaz(respond: DoBazResponse.type)(body: Option[String]): IO[sdefs.foo.DoBazResponse] = ???
    })

    val client: Client[IO] = Client.fromHttpApp(route.orNotFound)
    val fooClient          = FooClient.httpClient(client)
    fooClient.doBar(Some("sample")).attempt.unsafeRunSync().right.value shouldBe cdefs.foo.DoBarResponse.Created
  }

  test("Plain text should be emitted for missing optional parameters") {
    val route: HttpRoutes[IO] = new FooResource[IO]().routes(new FooHandler[IO] {
      def doFoo(respond: DoFooResponse.type)(body: String): IO[sdefs.foo.DoFooResponse] = ???
      def doBar(respond: DoBarResponse.type)(body: Option[String]): IO[sdefs.foo.DoBarResponse] =
        if (body.isEmpty) {
          IO.pure(respond.Created)
        } else {
          IO.pure(respond.NotAcceptable)
        }
      def doBaz(respond: DoBazResponse.type)(body: Option[String]): IO[sdefs.foo.DoBazResponse] = ???
    })

    val client: Client[IO] = Client.fromHttpApp(route.orNotFound)
    val fooClient          = FooClient.httpClient(client)
    fooClient.doBar(None).attempt.unsafeRunSync().right.value shouldBe cdefs.foo.DoBarResponse.Created
  }
} 
Example 7
Source File: Http4sFullTracerTest.scala    From guardrail   with MIT License 5 votes vote down vote up
package core.Http4s

import _root_.tracer.client.{ http4s => cdefs }
import _root_.tracer.server.http4s.addresses.{ AddressesHandler, AddressesResource, GetAddressResponse, GetAddressesResponse }
import _root_.tracer.server.http4s.users.{ GetUserResponse, UsersHandler, UsersResource }
import _root_.tracer.server.{ http4s => sdefs }
import _root_.tracer.client.http4s.users.UsersClient
import _root_.tracer.client.http4s.addresses.AddressesClient
import _root_.tracer.server.http4s.Http4sImplicits.TraceBuilder
import cats.effect.IO
import org.http4s.{ Header, HttpRoutes, Request }
import org.http4s.client.Client
import org.http4s.implicits._
import org.http4s.syntax.StringSyntax
import org.scalatest.{ EitherValues, FunSuite, Matchers }

class Http4sFullTracerTest extends FunSuite with Matchers with EitherValues with StringSyntax {

  val traceHeaderKey          = "tracer-label"
  def log(line: String): Unit = ()

  def trace: String => Request[IO] => TraceBuilder[IO] = { name => request =>
    // In a real environment, this would be where you could establish a new
    // tracing context and inject that fresh header value.
    log(s"Expecting all requests to have ${traceHeaderKey} header.")
    traceBuilder(request.headers.get(traceHeaderKey.ci).get.value)
  }

  def traceBuilder(parentValue: String): TraceBuilder[IO] = { name => httpClient =>
    Client { req =>
      httpClient.run(req.putHeaders(Header(traceHeaderKey, parentValue)))
    }
  }

  test("full tracer: passing headers through multiple levels") {
    // Establish the "Address" server
    val server2: HttpRoutes[IO] =
      new AddressesResource(trace).routes(
        new AddressesHandler[IO] {
          def getAddress(respond: GetAddressResponse.type)(id: String)(traceBuilder: TraceBuilder[IO]) =
            IO.pure(if (id == "addressId") {
              respond.Ok(sdefs.definitions.Address(Some("line1"), Some("line2"), Some("line3")))
            } else sdefs.addresses.GetAddressResponse.NotFound)
          def getAddresses(respond: GetAddressesResponse.type)()(traceBuilder: TraceBuilder[IO]) =
            IO.pure(sdefs.addresses.GetAddressesResponse.NotFound)
        }
      )

    // Establish the "User" server
    val server1: HttpRoutes[IO] =
      new UsersResource(trace).routes(
        new UsersHandler[IO] {
          // ... using the "Address" server explicitly in the addressesClient
          val addressesClient = AddressesClient.httpClient(Client.fromHttpApp(server2.orNotFound))
          def getUser(respond: GetUserResponse.type)(id: String)(traceBuilder: TraceBuilder[IO]) =
            addressesClient
              .getAddress(traceBuilder, "addressId")
              .map {
                case cdefs.addresses.GetAddressResponse.Ok(address) =>
                  respond.Ok(sdefs.definitions.User("1234", sdefs.definitions.UserAddress(address.line1, address.line2, address.line3)))
                case cdefs.addresses.GetAddressResponse.NotFound => respond.NotFound
              }
        }
      )

    // Build a UsersClient using the User server
    val usersClient = UsersClient.httpClient(Client.fromHttpApp(server1.orNotFound))
    // As this is the entry point, we either have a tracing header from
    // somewhere else, or we generate one for top-level request.
    val testTrace = traceBuilder("top-level-request")

    // Make a request against the mock servers using a hard-coded user ID
    val retrieved: cdefs.users.GetUserResponse = usersClient.getUser(testTrace, "1234").attempt.unsafeRunSync().right.value

    retrieved shouldBe cdefs.users.GetUserResponse
      .Ok(cdefs.definitions.User("1234", cdefs.definitions.UserAddress(Some("line1"), Some("line2"), Some("line3"))))
  }
} 
Example 8
Source File: TransactionRoute.scala    From aecor   with MIT License 5 votes vote down vote up
package aecor.example.transaction

import java.util.UUID

import aecor.example.account
import aecor.example.account.AccountId
import aecor.example.common.Amount
import cats.effect.{Effect, Sync}
import cats.implicits._
import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityDecoder
import org.http4s.dsl.Http4sDsl
import io.circe.generic.auto._


trait TransactionService[F[_]] {
  def authorizePayment(transactionId: TransactionId,
                       from: From[AccountId],
                       to: To[AccountId],
                       amount: Amount): F[TransactionRoute.ApiResult]
}

object TransactionRoute {

  sealed trait ApiResult
  object ApiResult {
    case object Authorized extends ApiResult
    case class Declined(reason: String) extends ApiResult
  }

  final case class CreateTransactionRequest(from: From[AccountId],
                                            to: To[AccountId],
                                            amount: Amount)

  object TransactionIdVar {
    def unapply(arg: String): Option[TransactionId] = TransactionId(arg).some
  }

  private final class Builder[F[_]: Sync](service: TransactionService[F]) extends Http4sDsl[F] with CirceEntityDecoder {
    def routes: HttpRoutes[F] = HttpRoutes.of[F] {
      case req @ PUT -> Root / "transactions" / TransactionIdVar(transactionId) =>
        for {
          body <- req.as[CreateTransactionRequest]
          CreateTransactionRequest(from, to, amount) = body
          resp <- service.authorizePayment(transactionId, from, to, amount).flatMap {
            case ApiResult.Authorized =>
              Ok("Authorized")
            case ApiResult.Declined(reason) =>
              BadRequest(s"Declined: $reason")
          }
        } yield resp
      case POST -> Root / "test" =>
        service
          .authorizePayment(
            TransactionId(UUID.randomUUID.toString),
            From(account.EventsourcedAlgebra.rootAccountId),
            To(AccountId("foo")),
            Amount(1)
          )
          .flatMap {
          case ApiResult.Authorized =>
            Ok("Authorized")
          case ApiResult.Declined(reason) =>
            BadRequest(s"Declined: $reason")
        }
    }
  }

  def apply[F[_]: Effect](api: TransactionService[F]): HttpRoutes[F] =
    new Builder(api).routes

} 
Example 9
Source File: SagaEndpoint.scala    From zio-saga   with MIT License 5 votes vote down vote up
package com.vladkopanev.zio.saga.example.endpoint

import com.vladkopanev.zio.saga.example.{ OrderSagaCoordinator, TaskC }
import com.vladkopanev.zio.saga.example.model.OrderInfo
import org.http4s.circe._
import org.http4s.dsl.Http4sDsl
import org.http4s.implicits._
import org.http4s.{ HttpApp, HttpRoutes }
import zio.interop.catz._

final class SagaEndpoint(orderSagaCoordinator: OrderSagaCoordinator) extends Http4sDsl[TaskC] {

  private implicit val decoder = jsonOf[TaskC, OrderInfo]

  val service: HttpApp[TaskC] = HttpRoutes
    .of[TaskC] {
      case req @ POST -> Root / "saga" / "finishOrder" =>
        for {
          OrderInfo(userId, orderId, money, bonuses) <- req.as[OrderInfo]
          resp <- orderSagaCoordinator
                   .runSaga(userId, orderId, money, bonuses, None)
                   .foldM(fail => InternalServerError(fail.getMessage), _ => Ok("Saga submitted"))
        } yield resp
    }
    .orNotFound
} 
Example 10
Source File: AuthExampleApp.scala    From caliban   with Apache License 2.0 5 votes vote down vote up
package caliban.http4s

import caliban.GraphQL._
import caliban.schema.GenericSchema
import caliban.{ Http4sAdapter, RootResolver }
import org.http4s.HttpRoutes
import org.http4s.dsl.Http4sDsl
import org.http4s.implicits._
import org.http4s.server.blaze.BlazeServerBuilder
import org.http4s.server.{ Router, ServiceErrorHandler }
import org.http4s.util.CaseInsensitiveString
import zio._
import zio.interop.catz._
import zio.interop.catz.implicits._

import scala.concurrent.ExecutionContext

object AuthExampleApp extends CatsApp {

  // Simple service that returns the token coming from the request
  type Auth = Has[Auth.Service]
  object Auth {
    trait Service {
      def token: String
    }
  }
  type AuthTask[A] = RIO[Auth, A]

  case class MissingToken() extends Throwable

  // http4s middleware that extracts a token from the request and eliminate the Auth layer dependency
  object AuthMiddleware {
    def apply(route: HttpRoutes[AuthTask]): HttpRoutes[Task] =
      Http4sAdapter.provideLayerFromRequest(
        route,
        _.headers.get(CaseInsensitiveString("token")) match {
          case Some(value) => ZLayer.succeed(new Auth.Service { override def token: String = value.value })
          case None        => ZLayer.fail(MissingToken())
        }
      )
  }

  // http4s error handler to customize the response for our throwable
  object dsl extends Http4sDsl[Task]
  import dsl._
  val errorHandler: ServiceErrorHandler[Task] = _ => { case MissingToken() => Forbidden() }

  // our GraphQL API
  val schema: GenericSchema[Auth] = new GenericSchema[Auth] {}
  import schema._
  case class Query(token: RIO[Auth, String])
  private val resolver = RootResolver(Query(ZIO.access[Auth](_.get[Auth.Service].token)))
  private val api      = graphQL(resolver)

  override def run(args: List[String]): ZIO[ZEnv, Nothing, ExitCode] =
    (for {
      interpreter <- api.interpreter
      route       = AuthMiddleware(Http4sAdapter.makeHttpService(interpreter))
      _ <- BlazeServerBuilder[Task](ExecutionContext.global)
            .withServiceErrorHandler(errorHandler)
            .bindHttp(8088, "localhost")
            .withHttpApp(Router[Task]("/api/graphql" -> route).orNotFound)
            .resource
            .toManaged
            .useForever
    } yield ()).exitCode
} 
Example 11
Source File: StatusesService.scala    From zio-telemetry   with Apache License 2.0 5 votes vote down vote up
package zio.telemetry.opentracing.example.http

import io.circe.Encoder
import io.opentracing.propagation.Format.Builtin.{ HTTP_HEADERS => HttpHeadersFormat }
import io.opentracing.propagation.TextMapAdapter
import io.opentracing.tag.Tags
import org.http4s.circe.jsonEncoderOf
import org.http4s.dsl.Http4sDsl
import org.http4s.{ EntityEncoder, HttpRoutes }
import sttp.model.Uri
import zio.clock.Clock
import zio.interop.catz._
import zio.telemetry.opentracing.OpenTracing
import zio.UIO
import zio.ZIO
import zio.ZLayer

import scala.collection.mutable
import scala.jdk.CollectionConverters._

object StatusesService {

  def statuses(backendUri: Uri, service: ZLayer[Clock, Throwable, Clock with OpenTracing]): HttpRoutes[AppTask] = {
    val dsl: Http4sDsl[AppTask] = Http4sDsl[AppTask]
    import dsl._

    implicit def encoder[A: Encoder]: EntityEncoder[AppTask, A] = jsonEncoderOf[AppTask, A]

    HttpRoutes.of[AppTask] {
      case GET -> Root / "statuses" =>
        val zio =
          for {
            env     <- ZIO.environment[OpenTracing]
            _       <- env.get.root("/statuses")
            _       <- OpenTracing.tag(Tags.SPAN_KIND.getKey, Tags.SPAN_KIND_CLIENT)
            _       <- OpenTracing.tag(Tags.HTTP_METHOD.getKey, GET.name)
            _       <- OpenTracing.setBaggageItem("proxy-baggage-item-key", "proxy-baggage-item-value")
            buffer  <- UIO.succeed(new TextMapAdapter(mutable.Map.empty[String, String].asJava))
            _       <- OpenTracing.inject(HttpHeadersFormat, buffer)
            headers <- extractHeaders(buffer)
            up      = Status.up("proxy")
            res <- Client
                    .status(backendUri.path("status"), headers)
                    .map(_.body)
                    .flatMap {
                      case Right(s) => Ok(Statuses(List(s, up)))
                      case _        => Ok(Statuses(List(Status.down("backend"), up)))
                    }
          } yield res

        zio.provideLayer(service)
    }
  }

  private def extractHeaders(adapter: TextMapAdapter): UIO[Map[String, String]] = {
    val m = mutable.Map.empty[String, String]
    UIO(adapter.forEach { entry =>
      m.put(entry.getKey, entry.getValue)
      ()
    }).as(m.toMap)
  }

} 
Example 12
Source File: StatusesService.scala    From zio-telemetry   with Apache License 2.0 5 votes vote down vote up
package zio.telemetry.opentelemetry.example.http

import io.circe.Encoder
import io.opentelemetry.OpenTelemetry
import io.opentelemetry.context.propagation.HttpTextFormat.Setter
import io.opentelemetry.trace.{ Span, Status => TraceStatus }
import org.http4s.circe.jsonEncoderOf
import org.http4s.dsl.Http4sDsl
import org.http4s.{ EntityEncoder, HttpRoutes }
import zio.UIO
import zio.interop.catz._
import zio.telemetry.opentelemetry.Tracing.root
import zio.telemetry.opentelemetry.attributevalue.AttributeValueConverterInstances._
import zio.telemetry.opentelemetry.Tracing

import scala.collection.mutable

object StatusesService {

  val dsl: Http4sDsl[AppTask] = Http4sDsl[AppTask]
  import dsl._

  implicit def encoder[A: Encoder]: EntityEncoder[AppTask, A] = jsonEncoderOf[AppTask, A]

  val httpTextFormat                              = OpenTelemetry.getPropagators.getHttpTextFormat
  val setter: Setter[mutable.Map[String, String]] = (carrier, key, value) => carrier.update(key, value)

  val errorMapper: PartialFunction[Throwable, TraceStatus] = { case _ => TraceStatus.UNKNOWN }

  val routes: HttpRoutes[AppTask] = HttpRoutes.of[AppTask] {
    case GET -> Root / "statuses" =>
      root("/statuses", Span.Kind.SERVER, errorMapper) {
        for {
          carrier <- UIO(mutable.Map[String, String]().empty)
          _       <- Tracing.setAttribute("http.method", "get")
          _       <- Tracing.addEvent("proxy-event")
          _       <- Tracing.inject(httpTextFormat, carrier, setter)
          res     <- Client.status(carrier.toMap).flatMap(Ok(_))
        } yield res
      }
  }

} 
Example 13
Source File: BidderHttpAppBuilder.scala    From scala-openrtb   with Apache License 2.0 5 votes vote down vote up
package com.powerspace.openrtb.examples.rtb.http4s.bidder

import com.google.openrtb.BidRequest
import com.powerspace.openrtb.examples.rtb.http4s.common.ExampleSerdeModule
import io.circe.Decoder
import monix.eval.Task
import org.http4s.dsl.Http4sDsl
import org.http4s.{EntityDecoder, HttpApp}

object BidderHttpAppBuilder {

  private val bidder = new Bidder[Task]
  private val dsl = Http4sDsl[Task]
  private val serdeModule = ExampleSerdeModule

  
  private def handleBid(bidRequest: BidRequest) = {
    import dsl._
    import org.http4s.circe._

    bidder
      .bidOn(bidRequest)
      .flatMap {
        case Some(bidResponse) =>
          // encode the bidResponse to a json object as part of the http response body
          Ok(serdeModule.bidResponseEncoder(bidResponse))
        case None =>
          Ok()
      }
  }
} 
Example 14
Source File: HealthCheckRoutes.scala    From http4s-poc-api   with MIT License 5 votes vote down vote up
package server

import cats.effect.Sync
import log.effect.LogWriter
import model.DomainModel._
import org.http4s.dsl.Http4sDsl
import org.http4s.{EntityEncoder, HttpRoutes, Method}
import cats.syntax.flatMap._

sealed abstract class HealthCheckRoutes[F[_]: Sync](
  implicit responseEncoder: EntityEncoder[F, ServiceSignature]
) extends Http4sDsl[F] {
  def make(log: LogWriter[F]): HttpRoutes[F] =
    HttpRoutes.of[F] {
      case Method.GET -> Root => log.debug(s"Serving HealthCheck request") >> Ok(serviceSignature)
    }

  private val serviceSignature =
    ServiceSignature(
      name = BuildInfo.name,
      version = BuildInfo.version,
      scalaVersion = BuildInfo.scalaVersion,
      scalaOrganization = BuildInfo.scalaOrganization,
      buildTime = BuildInfo.buildTime
    )
}

object HealthCheckRoutes {
  def apply[F[_]: Sync: EntityEncoder[*[_], ServiceSignature]]: HealthCheckRoutes[F] =
    new HealthCheckRoutes[F] {}
} 
Example 15
Source File: PriceRoutes.scala    From http4s-poc-api   with MIT License 5 votes vote down vote up
package server

import cats.effect.Sync
import cats.syntax.applicativeError._
import cats.syntax.flatMap._
import cats.syntax.functor._
import cats.syntax.show._
import errors.PriceServiceError
import errors.PriceServiceError._
import external.library.syntax.response._
import model.DomainModel._
import org.http4s.dsl.Http4sDsl
import org.http4s.{EntityDecoder, EntityEncoder, HttpRoutes, Method, Request, Response}
import service.PriceService

sealed abstract class PriceRoutes[F[_]: Sync](
  implicit requestDecoder: EntityDecoder[F, PricesRequestPayload],
  responseEncoder: EntityEncoder[F, List[Price]]
) extends Http4sDsl[F] {
  def make(priceService: PriceService[F]): HttpRoutes[F] =
    HttpRoutes.of[F] {
      case req @ Method.POST -> Root =>
        postResponse(req, priceService) handlingFailures priceServiceErrors handleErrorWith unhandledThrowable
    }

  private[this] def postResponse(request: Request[F], priceService: PriceService[F]): F[Response[F]] =
    for {
      payload <- request.as[PricesRequestPayload]
      prices  <- priceService.prices(payload.userId, payload.productIds)
      resp    <- Ok(prices)
    } yield resp

  private[this] def priceServiceErrors: PriceServiceError => F[Response[F]] = {
    case UserErr(r)                => FailedDependency(r)
    case PreferenceErr(r)          => FailedDependency(r)
    case ProductErr(r)             => FailedDependency(r)
    case ProductPriceErr(r)        => FailedDependency(r)
    case CacheLookupError(r)       => FailedDependency(r)
    case CacheStoreError(r)        => FailedDependency(r)
    case InvalidShippingCountry(r) => BadRequest(r)
  }

  private[this] def unhandledThrowable: Throwable => F[Response[F]] = { th =>
    import external.library.instances.throwable._
    InternalServerError(th.show)
  }
}

object PriceRoutes {
  def apply[
    F[_]: Sync: EntityDecoder[*[_], PricesRequestPayload]: EntityEncoder[*[_], List[Price]]
  ]: PriceRoutes[F] =
    new PriceRoutes[F] {}
} 
Example 16
Source File: HealthCheckHttpApiTests.scala    From http4s-poc-api   with MIT License 5 votes vote down vote up
import cats.instances.string._
import cats.syntax.apply._
import io.circe.generic.auto._
import io.circe.{Decoder, Encoder}
import log.effect.zio.ZioLogWriter.consoleLog
import model.DomainModel.ServiceSignature
import org.http4s.circe.{jsonEncoderOf, jsonOf}
import org.http4s.{HttpRoutes, Request, Status}
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import server.HealthCheckRoutes
import syntax.http4sService._
import syntax.responseVerification._
import zio.Task
import zio.interop.catz._

final class HealthCheckHttpApiTests extends AnyFlatSpec with Matchers with Fixtures {
  implicit def testEncoder[A: Encoder] = jsonEncoderOf[Task, A]
  implicit def testDecoder[A: Decoder] = jsonOf[Task, A]

  it should "respond with Ok status 200 and the correct service signature" in {
    val httpApi: HttpRoutes[Task] =
      HealthCheckRoutes[Task].make(consoleLog)

    val verified = httpApi
      .runFor(Request[Task]())
      .verify[ServiceSignature](
        Status.Ok,
        sign =>
          (
            sign.name isSameAs "http4s-poc-api",
            sign.version isNotSameAs "",
            sign.scalaVersion isSameAs "2.13.2",
            sign.scalaOrganization isSameAs "org.scala-lang"
          ).mapN((_, _, _, _) => sign)
      )

    assertOn(verified)
  }
} 
Example 17
Source File: TestRoutes.scala    From scala-server-lambda   with MIT License 5 votes vote down vote up
package io.github.howardjohn.lambda.http4s

import cats.{Applicative, MonadError}
import cats.effect.Sync
import cats.implicits._
import io.github.howardjohn.lambda.LambdaHandlerBehavior
import io.github.howardjohn.lambda.LambdaHandlerBehavior._
import org.http4s.dsl.Http4sDsl
import org.http4s.{EntityDecoder, Header, HttpRoutes}
import org.http4s.circe._
import io.circe.generic.auto._
import io.circe.syntax._
import org.http4s.dsl.impl.OptionalQueryParamDecoderMatcher

class TestRoutes[F[_]] {

  object TimesQueryMatcher extends OptionalQueryParamDecoderMatcher[Int]("times")

  val dsl = Http4sDsl[F]

  import dsl._

  def routes(implicit sync: Sync[F],
             jsonDecoder: EntityDecoder[F, JsonBody],
             me: MonadError[F, Throwable],
             stringDecoder: EntityDecoder[F, String],
             ap: Applicative[F]): HttpRoutes[F] = HttpRoutes.of[F] {
    case GET -> Root / "hello" :? TimesQueryMatcher(times) =>
      Ok {
        Seq
          .fill(times.getOrElse(1))("Hello World!")
          .mkString(" ")
      }
    case GET -> Root / "long" => Applicative[F].pure(Thread.sleep(1000)).flatMap(_ => Ok("Hello World!"))
    case GET -> Root / "exception" => throw RouteException()
    case GET -> Root / "error" => InternalServerError()
    case req@GET -> Root / "header" =>
      val header = req.headers.find(h => h.name.value == inputHeader).map(_.value).getOrElse("Header Not Found")
      Ok(header, Header(outputHeader, outputHeaderValue))
    case req@POST -> Root / "post" => req.as[String].flatMap(s => Ok(s))
    case req@POST -> Root / "json" => req.as[JsonBody].flatMap(s => Ok(LambdaHandlerBehavior.jsonReturn.asJson))
  }

} 
Example 18
Source File: SwaggerHttp4s.scala    From tapir   with Apache License 2.0 5 votes vote down vote up
package sttp.tapir.swagger.http4s

import java.util.Properties

import cats.effect.{Blocker, ContextShift, Sync}
import org.http4s.{HttpRoutes, StaticFile, Uri}
import org.http4s.dsl.Http4sDsl
import org.http4s.headers.Location

import scala.concurrent.ExecutionContext


class SwaggerHttp4s(
    yaml: String,
    contextPath: String = "docs",
    yamlName: String = "docs.yaml",
    redirectQuery: Map[String, Seq[String]] = Map.empty
) {
  private val swaggerVersion = {
    val p = new Properties()
    val pomProperties = getClass.getResourceAsStream("/META-INF/maven/org.webjars/swagger-ui/pom.properties")
    try p.load(pomProperties)
    finally pomProperties.close()
    p.getProperty("version")
  }

  def routes[F[_]: ContextShift: Sync]: HttpRoutes[F] = {
    val dsl = Http4sDsl[F]
    import dsl._

    HttpRoutes.of[F] {
      case path @ GET -> Root / `contextPath` =>
        val queryParameters = Map("url" -> Seq(s"${path.uri}/$yamlName")) ++ redirectQuery
        Uri
          .fromString(s"${path.uri}/index.html")
          .map(uri => uri.setQueryParams(queryParameters))
          .map(uri => PermanentRedirect(Location(uri)))
          .getOrElse(NotFound())
      case GET -> Root / `contextPath` / `yamlName` =>
        Ok(yaml)
      case GET -> Root / `contextPath` / swaggerResource =>
        StaticFile
          .fromResource(
            s"/META-INF/resources/webjars/swagger-ui/$swaggerVersion/$swaggerResource",
            Blocker.liftExecutionContext(ExecutionContext.global)
          )
          .getOrElseF(NotFound())
    }
  }
} 
Example 19
Source File: MultipleEndpointsDocumentationHttp4sServer.scala    From tapir   with Apache License 2.0 5 votes vote down vote up
package sttp.tapir.examples

import java.util.concurrent.atomic.AtomicReference

import cats.effect._
import cats.implicits._
import com.github.ghik.silencer.silent
import io.circe.generic.auto._
import org.http4s.HttpRoutes
import org.http4s.server.Router
import org.http4s.server.blaze.BlazeServerBuilder
import org.http4s.syntax.kleisli._
import sttp.tapir._
import sttp.tapir.docs.openapi._
import sttp.tapir.json.circe._
import sttp.tapir.openapi.OpenAPI
import sttp.tapir.openapi.circe.yaml._
import sttp.tapir.server.http4s._
import sttp.tapir.swagger.http4s.SwaggerHttp4s

import scala.concurrent.ExecutionContext

object MultipleEndpointsDocumentationHttp4sServer extends App {
  // endpoint descriptions
  case class Author(name: String)
  case class Book(title: String, year: Int, author: Author)

  val booksListing: Endpoint[Unit, Unit, Vector[Book], Nothing] = endpoint.get
    .in("books")
    .in("list" / "all")
    .out(jsonBody[Vector[Book]])

  val addBook: Endpoint[Book, Unit, Unit, Nothing] = endpoint.post
    .in("books")
    .in("add")
    .in(
      jsonBody[Book]
        .description("The book to add")
        .example(Book("Pride and Prejudice", 1813, Author("Jane Austen")))
    )

  // server-side logic
  implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global
  implicit val contextShift: ContextShift[IO] = IO.contextShift(ec)
  implicit val timer: Timer[IO] = IO.timer(ec)

  val books = new AtomicReference(
    Vector(
      Book("The Sorrows of Young Werther", 1774, Author("Johann Wolfgang von Goethe")),
      Book("Iliad", -8000, Author("Homer")),
      Book("Nad Niemnem", 1888, Author("Eliza Orzeszkowa")),
      Book("The Colour of Magic", 1983, Author("Terry Pratchett")),
      Book("The Art of Computer Programming", 1968, Author("Donald Knuth")),
      Book("Pharaoh", 1897, Author("Boleslaw Prus"))
    )
  )

  val booksListingRoutes: HttpRoutes[IO] = booksListing.toRoutes(_ => IO(books.get().asRight[Unit]))
  @silent("discarded")
  val addBookRoutes: HttpRoutes[IO] = addBook.toRoutes(book => IO((books.getAndUpdate(books => books :+ book): Unit).asRight[Unit]))
  val routes: HttpRoutes[IO] = booksListingRoutes <+> addBookRoutes

  // generating the documentation in yml; extension methods come from imported packages
  val openApiDocs: OpenAPI = List(booksListing, addBook).toOpenAPI("The tapir library", "1.0.0")
  val openApiYml: String = openApiDocs.toYaml

  // starting the server
  BlazeServerBuilder[IO](ec)
    .bindHttp(8080, "localhost")
    .withHttpApp(Router("/" -> (routes <+> new SwaggerHttp4s(openApiYml).routes[IO])).orNotFound)
    .resource
    .use { _ =>
      IO {
        println("Go to: http://localhost:8080/docs")
        println("Press any key to exit ...")
        scala.io.StdIn.readLine()
      }
    }
    .unsafeRunSync()
} 
Example 20
Source File: StreamingHttp4sFs2Server.scala    From tapir   with Apache License 2.0 5 votes vote down vote up
package sttp.tapir.examples

import java.nio.charset.StandardCharsets

import cats.effect._
import cats.implicits._
import org.http4s.HttpRoutes
import org.http4s.server.Router
import org.http4s.server.blaze.BlazeServerBuilder
import org.http4s.syntax.kleisli._
import sttp.client._
import sttp.tapir._
import sttp.tapir.server.http4s._
import fs2._
import sttp.model.HeaderNames

import scala.concurrent.ExecutionContext
import scala.concurrent.duration._

// https://github.com/softwaremill/tapir/issues/367
object StreamingHttp4sFs2Server extends App {
  // corresponds to: GET /receive?name=...
  // We need to provide both the schema of the value (for documentation), as well as the format (media type) of the
  // body. Here, the schema is a `string` and the media type is `text/plain`.
  val streamingEndpoint = endpoint.get
    .in("receive")
    .out(header[Long](HeaderNames.ContentLength))
    .out(streamBody[Stream[IO, Byte]](schemaFor[String], CodecFormat.TextPlain(), Some(StandardCharsets.UTF_8)))

  // mandatory implicits
  implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global
  implicit val contextShift: ContextShift[IO] = IO.contextShift(ec)
  implicit val timer: Timer[IO] = IO.timer(ec)

  // converting an endpoint to a route (providing server-side logic); extension method comes from imported packages
  val streamingRoutes: HttpRoutes[IO] = streamingEndpoint.toRoutes { _ =>
    val size = 100L
    Stream
      .emit(List[Char]('a', 'b', 'c', 'd'))
      .repeat
      .flatMap(list => Stream.chunk(Chunk.seq(list)))
      .metered[IO](100.millis)
      .take(size)
      .covary[IO]
      .map(_.toByte)
      .pure[IO]
      .map(s => Right((size, s)))
  }

  // starting the server
  BlazeServerBuilder[IO](ec)
    .bindHttp(8080, "localhost")
    .withHttpApp(Router("/" -> streamingRoutes).orNotFound)
    .resource
    .use { _ =>
      IO {
        implicit val backend: SttpBackend[Identity, Nothing, NothingT] = HttpURLConnectionBackend()
        val result: String = basicRequest.response(asStringAlways).get(uri"http://localhost:8080/receive").send().body
        println("Got result: " + result)

        assert(result == "abcd" * 25)
      }
    }
    .unsafeRunSync()
} 
Example 21
Source File: HelloWorldHttp4sServer.scala    From tapir   with Apache License 2.0 5 votes vote down vote up
package sttp.tapir.examples

import cats.effect._
import sttp.client._
import org.http4s.HttpRoutes
import org.http4s.server.Router
import org.http4s.server.blaze.BlazeServerBuilder
import org.http4s.syntax.kleisli._
import sttp.tapir._
import sttp.tapir.server.http4s._
import cats.implicits._

import scala.concurrent.ExecutionContext

object HelloWorldHttp4sServer extends App {
  // the endpoint: single fixed path input ("hello"), single query parameter
  // corresponds to: GET /hello?name=...
  val helloWorld: Endpoint[String, Unit, String, Nothing] =
    endpoint.get.in("hello").in(query[String]("name")).out(stringBody)

  // mandatory implicits
  implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global
  implicit val contextShift: ContextShift[IO] = IO.contextShift(ec)
  implicit val timer: Timer[IO] = IO.timer(ec)

  // converting an endpoint to a route (providing server-side logic); extension method comes from imported packages
  val helloWorldRoutes: HttpRoutes[IO] = helloWorld.toRoutes(name => IO(s"Hello, $name!".asRight[Unit]))

  // starting the server

  BlazeServerBuilder[IO](ec)
    .bindHttp(8080, "localhost")
    .withHttpApp(Router("/" -> helloWorldRoutes).orNotFound)
    .resource
    .use { _ =>
      IO {
        implicit val backend: SttpBackend[Identity, Nothing, NothingT] = HttpURLConnectionBackend()
        val result: String = basicRequest.response(asStringAlways).get(uri"http://localhost:8080/hello?name=Frodo").send().body
        println("Got result: " + result)

        assert(result == "Hello, Frodo!")
      }
    }
    .unsafeRunSync()
} 
Example 22
Source File: package.scala    From tapir   with Apache License 2.0 5 votes vote down vote up
package sttp.tapir.server.http4s

import org.http4s.HttpRoutes
import sttp.tapir.server.ServerEndpoint
import sttp.tapir.ztapir._
import zio.interop.catz._
import zio.{Task, URIO, ZIO}

package object ztapir {
  implicit class RichZEndpointRoutes[I, E, O](e: ZEndpoint[I, E, O]) {
    def toRoutes(logic: I => ZIO[Any, E, O])(implicit serverOptions: Http4sServerOptions[Task]): HttpRoutes[Task] =
      e.zServerLogic(logic).toRoutes

    def toRoutesR[R](logic: I => ZIO[R, E, O])(implicit serverOptions: Http4sServerOptions[Task]): URIO[R, HttpRoutes[Task]] =
      e.zServerLogic(logic).toRoutesR
  }

  implicit class RichZServerEndpointRoutes[I, E, O](se: ZServerEndpoint[Any, I, E, O]) {
    def toRoutes(implicit serverOptions: Http4sServerOptions[Task]): HttpRoutes[Task] = List(se).toRoutes
  }

  implicit class RichZServerEndpointsRoutes[I, E, O](serverEndpoints: List[ZServerEndpoint[Any, _, _, _]]) {
    def toRoutes(implicit serverOptions: Http4sServerOptions[Task]): HttpRoutes[Task] = {
      new EndpointToHttp4sServer(serverOptions).toRoutes(serverEndpoints)
    }
  }

  implicit class RichZServerEndpointRRoutes[R, I, E, O](se: ZServerEndpoint[R, I, E, O]) {
    def toRoutesR(implicit serverOptions: Http4sServerOptions[Task]): URIO[R, HttpRoutes[Task]] = List(se).toRoutesR
  }

  implicit class RichZServerEndpointsRRoutes[R](serverEndpoints: List[ZServerEndpoint[R, _, _, _]]) {
    def toRoutesR(implicit serverOptions: Http4sServerOptions[Task]): URIO[R, HttpRoutes[Task]] =
      URIO.access[R] { env =>
        val taskServerEndpoints = serverEndpoints.map(toTaskEndpointR(env, _))
        new EndpointToHttp4sServer(serverOptions).toRoutes(taskServerEndpoints)
      }
  }

  private def toTaskEndpointR[R, I, E, O](env: R, se: ZServerEndpoint[R, I, E, O]): ServerEndpoint[I, E, O, Nothing, Task] = {
    ServerEndpoint(
      se.endpoint,
      _ => (i: I) => se.logic(new ZIOMonadError[R])(i).provide(env)
    )
  }
} 
Example 23
Source File: EndpointToHttp4sServer.scala    From tapir   with Apache License 2.0 5 votes vote down vote up
package sttp.tapir.server.http4s

import cats.data._
import cats.effect.{ContextShift, Sync}
import cats.implicits._
import org.http4s.{EntityBody, HttpRoutes, Request, Response}
import org.log4s._
import sttp.tapir.monad.MonadError
import sttp.tapir.server.internal.{DecodeInputsResult, InputValues, InputValuesResult}
import sttp.tapir.server.{DecodeFailureContext, DecodeFailureHandling, ServerDefaults, ServerEndpoint, internal}
import sttp.tapir.{DecodeResult, Endpoint, EndpointIO, EndpointInput}

class EndpointToHttp4sServer[F[_]: Sync: ContextShift](serverOptions: Http4sServerOptions[F]) {
  private val outputToResponse = new OutputToHttp4sResponse[F](serverOptions)

  def toRoutes[I, E, O](se: ServerEndpoint[I, E, O, EntityBody[F], F]): HttpRoutes[F] = {
    val service: HttpRoutes[F] = HttpRoutes[F] { req: Request[F] =>
      def decodeBody(result: DecodeInputsResult): F[DecodeInputsResult] = {
        result match {
          case values: DecodeInputsResult.Values =>
            values.bodyInput match {
              case Some(bodyInput @ EndpointIO.Body(bodyType, codec, _)) =>
                new Http4sRequestToRawBody(serverOptions).apply(req.body, bodyType, req.charset, req).map { v =>
                  codec.decode(v) match {
                    case DecodeResult.Value(bodyV)     => values.setBodyInputValue(bodyV)
                    case failure: DecodeResult.Failure => DecodeInputsResult.Failure(bodyInput, failure): DecodeInputsResult
                  }
                }

              case None => (values: DecodeInputsResult).pure[F]
            }
          case failure: DecodeInputsResult.Failure => (failure: DecodeInputsResult).pure[F]
        }
      }

      def valueToResponse(value: Any): F[Response[F]] = {
        val i = value.asInstanceOf[I]
        se.logic(new CatsMonadError)(i)
          .map {
            case Right(result) => outputToResponse(ServerDefaults.StatusCodes.success, se.endpoint.output, result)
            case Left(err)     => outputToResponse(ServerDefaults.StatusCodes.error, se.endpoint.errorOutput, err)
          }
          .flatTap { response => serverOptions.logRequestHandling.requestHandled(se.endpoint, response.status.code) }
          .onError {
            case e: Exception => serverOptions.logRequestHandling.logicException(se.endpoint, e)
          }
      }

      OptionT(decodeBody(internal.DecodeInputs(se.endpoint.input, new Http4sDecodeInputsContext[F](req))).flatMap {
        case values: DecodeInputsResult.Values =>
          InputValues(se.endpoint.input, values) match {
            case InputValuesResult.Value(params, _)        => valueToResponse(params.asAny).map(_.some)
            case InputValuesResult.Failure(input, failure) => handleDecodeFailure(se.endpoint, input, failure)
          }
        case DecodeInputsResult.Failure(input, failure) => handleDecodeFailure(se.endpoint, input, failure)
      })
    }

    service
  }

  def toRoutes[I, E, O](serverEndpoints: List[ServerEndpoint[_, _, _, EntityBody[F], F]]): HttpRoutes[F] = {
    NonEmptyList.fromList(serverEndpoints.map(se => toRoutes(se))) match {
      case Some(routes) => routes.reduceK
      case None         => HttpRoutes.empty
    }
  }

  private def handleDecodeFailure[I](
      e: Endpoint[_, _, _, _],
      input: EndpointInput[_],
      failure: DecodeResult.Failure
  ): F[Option[Response[F]]] = {
    val decodeFailureCtx = DecodeFailureContext(input, failure)
    val handling = serverOptions.decodeFailureHandler(decodeFailureCtx)
    handling match {
      case DecodeFailureHandling.NoMatch =>
        serverOptions.logRequestHandling.decodeFailureNotHandled(e, decodeFailureCtx).map(_ => None)
      case DecodeFailureHandling.RespondWithResponse(output, value) =>
        serverOptions.logRequestHandling
          .decodeFailureHandled(e, decodeFailureCtx, value)
          .map(_ => Some(outputToResponse(ServerDefaults.StatusCodes.error, output, value)))
    }
  }

  private class CatsMonadError(implicit F: cats.MonadError[F, Throwable]) extends MonadError[F] {
    override def unit[T](t: T): F[T] = F.pure(t)
    override def map[T, T2](fa: F[T])(f: T => T2): F[T2] = F.map(fa)(f)
    override def flatMap[T, T2](fa: F[T])(f: T => F[T2]): F[T2] = F.flatMap(fa)(f)
    override def error[T](t: Throwable): F[T] = F.raiseError(t)
    override protected def handleWrappedError[T](rt: F[T])(h: PartialFunction[Throwable, F[T]]): F[T] = F.recoverWith(rt)(h)
  }
}

object EndpointToHttp4sServer {
  private[http4s] val log: Logger = getLogger
} 
Example 24
Source File: TapirHttp4sServer.scala    From tapir   with Apache License 2.0 5 votes vote down vote up
package sttp.tapir.server.http4s

import cats.Monad
import cats.effect.{ContextShift, Sync}
import cats.implicits._
import org.http4s.{EntityBody, HttpRoutes}
import sttp.tapir.Endpoint
import sttp.tapir.Endpoint
import sttp.tapir.server.ServerEndpoint
import sttp.tapir.typelevel.ReplaceFirstInTuple

import scala.reflect.ClassTag

trait TapirHttp4sServer {
  implicit class RichHttp4sHttpEndpoint[I, E, O, F[_]](e: Endpoint[I, E, O, EntityBody[F]]) {
    def toRoutes(
        logic: I => F[Either[E, O]]
    )(implicit serverOptions: Http4sServerOptions[F], fs: Sync[F], fcs: ContextShift[F]): HttpRoutes[F] = {
      new EndpointToHttp4sServer(serverOptions).toRoutes(e.serverLogic(logic))
    }

    def toRouteRecoverErrors(logic: I => F[O])(implicit
        serverOptions: Http4sServerOptions[F],
        fs: Sync[F],
        fcs: ContextShift[F],
        eIsThrowable: E <:< Throwable,
        eClassTag: ClassTag[E]
    ): HttpRoutes[F] = {
      new EndpointToHttp4sServer(serverOptions).toRoutes(e.serverLogicRecoverErrors(logic))
    }
  }

  implicit class RichHttp4sServerEndpoint[I, E, O, F[_]](se: ServerEndpoint[I, E, O, EntityBody[F], F]) {
    def toRoutes(implicit serverOptions: Http4sServerOptions[F], fs: Sync[F], fcs: ContextShift[F]): HttpRoutes[F] =
      new EndpointToHttp4sServer(serverOptions).toRoutes(se)
  }

  implicit class RichHttp4sServerEndpoints[F[_]](serverEndpoints: List[ServerEndpoint[_, _, _, EntityBody[F], F]]) {
    def toRoutes(implicit serverOptions: Http4sServerOptions[F], fs: Sync[F], fcs: ContextShift[F]): HttpRoutes[F] = {
      new EndpointToHttp4sServer(serverOptions).toRoutes(serverEndpoints)
    }
  }

  implicit class RichToMonadFunction[T, U, F[_]: Monad](a: T => F[U]) {
    @deprecated
    def andThenFirst[U_TUPLE, T_TUPLE, O](
        l: U_TUPLE => F[O]
    )(implicit replaceFirst: ReplaceFirstInTuple[T, U, T_TUPLE, U_TUPLE]): T_TUPLE => F[O] = { tTuple =>
      val t = replaceFirst.first(tTuple)
      a(t).flatMap { u =>
        val uTuple = replaceFirst.replace(tTuple, u)
        l(uTuple)
      }
    }
  }

  implicit class RichToMonadOfEitherFunction[T, U, E, F[_]: Monad](a: T => F[Either[E, U]]) {
    @deprecated
    def andThenFirstE[U_TUPLE, T_TUPLE, O](
        l: U_TUPLE => F[Either[E, O]]
    )(implicit replaceFirst: ReplaceFirstInTuple[T, U, T_TUPLE, U_TUPLE]): T_TUPLE => F[Either[E, O]] = { tTuple =>
      val t = replaceFirst.first(tTuple)
      a(t).flatMap {
        case Left(e) => implicitly[Monad[F]].point(Left(e))
        case Right(u) =>
          val uTuple = replaceFirst.replace(tTuple, u)
          l(uTuple)
      }
    }
  }
} 
Example 25
Source File: Http4sServerTests.scala    From tapir   with Apache License 2.0 5 votes vote down vote up
package sttp.tapir.server.http4s

import cats.data.{Kleisli, NonEmptyList}
import cats.effect._
import cats.implicits._
import org.http4s.server.Router
import org.http4s.server.blaze.BlazeServerBuilder
import org.http4s.syntax.kleisli._
import org.http4s.{EntityBody, HttpRoutes, Request, Response}
import sttp.tapir.server.tests.ServerTests
import sttp.tapir.Endpoint
import sttp.tapir._
import sttp.client._
import sttp.tapir.server.{DecodeFailureHandler, ServerDefaults, ServerEndpoint}
import sttp.tapir.tests.{Port, PortCounter}

import scala.concurrent.ExecutionContext
import scala.reflect.ClassTag

class Http4sServerTests extends ServerTests[IO, EntityBody[IO], HttpRoutes[IO]] {
  implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global
  implicit val contextShift: ContextShift[IO] = IO.contextShift(ec)
  implicit val timer: Timer[IO] = IO.timer(ec)

  override def pureResult[T](t: T): IO[T] = IO.pure(t)
  override def suspendResult[T](t: => T): IO[T] = IO.apply(t)

  override def route[I, E, O](
      e: ServerEndpoint[I, E, O, EntityBody[IO], IO],
      decodeFailureHandler: Option[DecodeFailureHandler] = None
  ): HttpRoutes[IO] = {
    implicit val serverOptions: Http4sServerOptions[IO] = Http4sServerOptions
      .default[IO]
      .copy(
        decodeFailureHandler = decodeFailureHandler.getOrElse(ServerDefaults.decodeFailureHandler)
      )
    e.toRoutes
  }

  override def routeRecoverErrors[I, E <: Throwable, O](e: Endpoint[I, E, O, EntityBody[IO]], fn: I => IO[O])(implicit
      eClassTag: ClassTag[E]
  ): HttpRoutes[IO] = {
    e.toRouteRecoverErrors(fn)
  }

  override def server(routes: NonEmptyList[HttpRoutes[IO]], port: Port): Resource[IO, Unit] = {
    val service: Kleisli[IO, Request[IO], Response[IO]] = routes.reduceK.orNotFound

    BlazeServerBuilder[IO](ExecutionContext.global)
      .bindHttp(port, "localhost")
      .withHttpApp(service)
      .resource
      .void
  }

  override lazy val portCounter: PortCounter = new PortCounter(56000)

  if (testNameFilter.isEmpty) {
    test("should work with a router and routes in a context") {
      val e = endpoint.get.in("test" / "router").out(stringBody).serverLogic(_ => IO.pure("ok".asRight[Unit]))
      val routes = e.toRoutes
      val port = portCounter.next()

      BlazeServerBuilder[IO](ExecutionContext.global)
        .bindHttp(port, "localhost")
        .withHttpApp(Router("/api" -> routes).orNotFound)
        .resource
        .use { _ => basicRequest.get(uri"http://localhost:$port/api/test/router").send().map(_.body shouldBe Right("ok")) }
        .unsafeRunSync()
    }
  }
} 
Example 26
Source File: InvoicesApi.scala    From event-sourcing-kafka-streams   with MIT License 5 votes vote down vote up
package org.amitayh.invoices.web

import java.util.UUID

import cats.effect.{Concurrent, Timer}
import cats.implicits._
import fs2.Stream
import fs2.concurrent.Topic
import io.circe._
import io.circe.generic.auto._
import io.circe.syntax._
import org.amitayh.invoices.common.domain.CommandResult.{Failure, Success}
import org.amitayh.invoices.common.domain.{Command, CommandResult}
import org.amitayh.invoices.dao.InvoiceList
import org.amitayh.invoices.web.CommandDto._
import org.amitayh.invoices.web.PushEvents.CommandResultRecord
import org.http4s.circe._
import org.http4s.dsl.Http4sDsl
import org.http4s.{EntityDecoder, HttpRoutes, Response}

import scala.concurrent.duration._

class InvoicesApi[F[_]: Concurrent: Timer] extends Http4sDsl[F] {

  private val maxQueued = 16

  implicit val commandEntityDecoder: EntityDecoder[F, Command] = jsonOf[F, Command]

  def service(invoiceList: InvoiceList[F],
              producer: Kafka.Producer[F, UUID, Command],
              commandResultsTopic: Topic[F, CommandResultRecord]): HttpRoutes[F] = HttpRoutes.of[F] {
    case GET -> Root / "invoices" =>
      invoiceList.get.flatMap(invoices => Ok(invoices.asJson))

    case request @ POST -> Root / "execute" / "async" / UuidVar(invoiceId) =>
      request
        .as[Command]
        .flatMap(producer.send(invoiceId, _))
        .flatMap(metaData => Accepted(Json.fromLong(metaData.timestamp)))

    case request @ POST -> Root / "execute" / UuidVar(invoiceId) =>
      request.as[Command].flatMap { command =>
        val response = resultStream(commandResultsTopic, command.commandId) merge timeoutStream
        producer.send(invoiceId, command) *> response.head.compile.toList.map(_.head)
      }
  }

  private def resultStream(commandResultsTopic: Topic[F, CommandResultRecord],
                           commandId: UUID): Stream[F, Response[F]] =
    commandResultsTopic.subscribe(maxQueued).collectFirst {
      case Some((_, CommandResult(_, `commandId`, outcome))) => outcome
    }.flatMap {
      case Success(_, _, snapshot) => Stream.eval(Ok(snapshot.asJson))
      case Failure(cause) => Stream.eval(UnprocessableEntity(cause.message))
    }

  private def timeoutStream: Stream[F, Response[F]] =
    Stream.eval(Timer[F].sleep(5.seconds) *> RequestTimeout("timeout"))

}

object InvoicesApi {
  def apply[F[_]: Concurrent: Timer]: InvoicesApi[F] = new InvoicesApi[F]
} 
Example 27
Source File: Statics.scala    From event-sourcing-kafka-streams   with MIT License 5 votes vote down vote up
package org.amitayh.invoices.web

import cats.effect.{ContextShift, Sync}
import org.http4s.dsl.Http4sDsl
import org.http4s.{HttpRoutes, StaticFile}

import scala.concurrent.ExecutionContext.global

class Statics[F[_]: Sync: ContextShift] extends Http4sDsl[F] {

  val service: HttpRoutes[F] = HttpRoutes.of[F] {
    case request @ GET -> fileName =>
      StaticFile
        .fromResource(
          name = s"/statics$fileName",
          blockingExecutionContext = global,
          req = Some(request),
          preferGzipped = true)
        .getOrElseF(NotFound())
  }

}

object Statics {
  def apply[F[_]: Sync: ContextShift]: Statics[F] = new Statics[F]
} 
Example 28
Source File: PushEvents.scala    From event-sourcing-kafka-streams   with MIT License 5 votes vote down vote up
package org.amitayh.invoices.web

import java.util.UUID

import cats.effect._
import fs2.concurrent.Topic
import io.circe.generic.auto._
import io.circe.syntax._
import org.amitayh.invoices.common.domain.{CommandResult, InvoiceSnapshot}
import org.amitayh.invoices.dao.InvoiceRecord
import org.amitayh.invoices.web.PushEvents._
import org.http4s.dsl.Http4sDsl
import org.http4s.{HttpRoutes, ServerSentEvent}

class PushEvents[F[_]: Concurrent] extends Http4sDsl[F] {

  private val maxQueued = 16

  def service(commandResultsTopic: Topic[F, CommandResultRecord],
              invoiceUpdatesTopic: Topic[F, InvoiceSnapshotRecord]): HttpRoutes[F] = HttpRoutes.of[F] {
    case GET -> Root / UuidVar(originId) =>
      val commandResults = commandResultsTopic.subscribe(maxQueued).collect {
        case Some((_, result)) if result.originId == originId =>
          Event(result).asServerSentEvent
      }
      val invoiceUpdates = invoiceUpdatesTopic.subscribe(maxQueued).collect {
        case Some((id, snapshot)) => Event(id, snapshot).asServerSentEvent
      }
      Ok(commandResults merge invoiceUpdates)
  }

}

object PushEvents {
  type CommandResultRecord = Option[(UUID, CommandResult)]
  type InvoiceSnapshotRecord = Option[(UUID, InvoiceSnapshot)]

  def apply[F[_]: Concurrent]: PushEvents[F] = new PushEvents[F]
}

sealed trait Event {
  def asServerSentEvent: ServerSentEvent =
    ServerSentEvent(this.asJson.noSpaces)
}

case class CommandSucceeded(commandId: UUID) extends Event
case class CommandFailed(commandId: UUID, cause: String) extends Event
case class InvoiceUpdated(record: InvoiceRecord) extends Event

object Event {
  def apply(result: CommandResult): Event = result match {
    case CommandResult(_, commandId, _: CommandResult.Success) =>
      CommandSucceeded(commandId)

    case CommandResult(_, commandId, CommandResult.Failure(cause)) =>
      CommandFailed(commandId, cause.message)
  }

  def apply(id: UUID, snapshot: InvoiceSnapshot): Event =
    InvoiceUpdated(InvoiceRecord(id, snapshot))
} 
Example 29
Source File: Http4sRoutingModule.scala    From scala-server-toolkit   with MIT License 5 votes vote down vote up
package com.avast.sst.example.module

import cats.implicits._
import com.avast.sst.example.service.RandomService
import com.avast.sst.http4s.server.Http4sRouting
import com.avast.sst.http4s.server.micrometer.MicrometerHttp4sServerMetricsModule
import org.http4s.client.Client
import org.http4s.dsl.Http4sDsl
import org.http4s.{HttpApp, HttpRoutes}
import zio.Task
import zio.interop.catz._

class Http4sRoutingModule(
    randomService: RandomService,
    client: Client[Task],
    serverMetricsModule: MicrometerHttp4sServerMetricsModule[Task]
) extends Http4sDsl[Task] {

  import serverMetricsModule._

  private val helloWorldRoute = routeMetrics.wrap("hello")(Ok("Hello World!"))

  private val routes = HttpRoutes.of[Task] {
    case GET -> Root / "hello"           => helloWorldRoute
    case GET -> Root / "random"          => randomService.randomNumber.map(_.show).flatMap(Ok(_))
    case GET -> Root / "circuit-breaker" => client.expect[String]("https://httpbin.org/status/500").flatMap(Ok(_))
  }

  val router: HttpApp[Task] = Http4sRouting.make {
    serverMetrics {
      routes
    }
  }

} 
Example 30
Source File: Http4sBlazeServerModuleTest.scala    From scala-server-toolkit   with MIT License 5 votes vote down vote up
package com.avast.sst.http4s.server

import cats.effect.{ContextShift, IO, Timer}
import com.avast.sst.http4s.client.{Http4sBlazeClientConfig, Http4sBlazeClientModule}
import org.http4s.HttpRoutes
import org.http4s.dsl.Http4sDsl
import org.scalatest.funsuite.AsyncFunSuite

import scala.concurrent.ExecutionContext

class Http4sBlazeServerModuleTest extends AsyncFunSuite with Http4sDsl[IO] {

  implicit private val cs: ContextShift[IO] = IO.contextShift(ExecutionContext.global)
  implicit private val timer: Timer[IO] = IO.timer(ExecutionContext.global)

  test("Simple HTTP server") {
    val routes = Http4sRouting.make(HttpRoutes.of[IO] {
      case GET -> Root / "test" => Ok("test")
    })
    val test = for {
      server <- Http4sBlazeServerModule.make[IO](Http4sBlazeServerConfig("127.0.0.1", 0), routes, ExecutionContext.global)
      client <- Http4sBlazeClientModule.make[IO](Http4sBlazeClientConfig(), ExecutionContext.global)
    } yield (server, client)

    test
      .use {
        case (server, client) =>
          client
            .expect[String](s"http://${server.address.getHostString}:${server.address.getPort}/test")
            .map(response => assert(response === "test"))
      }
      .unsafeToFuture()
  }

} 
Example 31
Source File: CorrelationIdMiddleware.scala    From scala-server-toolkit   with MIT License 5 votes vote down vote up
package com.avast.sst.http4s.server.middleware

import java.util.UUID

import cats.data.{Kleisli, OptionT}
import cats.effect.Sync
import cats.syntax.functor._
import com.avast.sst.http4s.server.middleware.CorrelationIdMiddleware.CorrelationId
import io.chrisdavenport.vault.Key
import org.http4s.util.CaseInsensitiveString
import org.http4s.{Header, HttpRoutes, Request, Response}
import org.slf4j.LoggerFactory


class CorrelationIdMiddleware[F[_]: Sync](
    correlationIdHeaderName: CaseInsensitiveString,
    attributeKey: Key[CorrelationId],
    generator: () => String
) {

  private val logger = LoggerFactory.getLogger(this.getClass)

  private val F = Sync[F]

  def wrap(routes: HttpRoutes[F]): HttpRoutes[F] =
    Kleisli[OptionT[F, *], Request[F], Response[F]] { request =>
      request.headers.get(correlationIdHeaderName) match {
        case Some(header) =>
          val requestWithAttribute = request.withAttribute(attributeKey, CorrelationId(header.value))
          routes(requestWithAttribute).map(r => r.withHeaders(r.headers.put(header)))
        case None =>
          for {
            newCorrelationId <- OptionT.liftF(F.delay(generator()))
            _ <- log(newCorrelationId)
            requestWithAttribute = request.withAttribute(attributeKey, CorrelationId(newCorrelationId))
            response <- routes(requestWithAttribute)
          } yield response.withHeaders(response.headers.put(Header(correlationIdHeaderName.value, newCorrelationId)))
      }
    }

  def retrieveCorrelationId(request: Request[F]): Option[CorrelationId] = request.attributes.lookup(attributeKey)

  private def log(newCorrelationId: String) = {
    OptionT.liftF {
      F.delay {
        if (logger.isDebugEnabled()) {
          logger.debug(s"Generated new correlation ID: $newCorrelationId")
        }
      }
    }
  }
}

object CorrelationIdMiddleware {

  final case class CorrelationId(value: String) extends AnyVal

  @SuppressWarnings(Array("scalafix:Disable.toString"))
  def default[F[_]: Sync]: F[CorrelationIdMiddleware[F]] = {
    Key.newKey[F, CorrelationId].map { attributeKey =>
      new CorrelationIdMiddleware(CaseInsensitiveString("Correlation-ID"), attributeKey, () => UUID.randomUUID().toString)
    }
  }

} 
Example 32
Source File: CorrelationIdMiddlewareTest.scala    From scala-server-toolkit   with MIT License 5 votes vote down vote up
package com.avast.sst.http4s.server.middleware

import java.net.InetSocketAddress

import cats.effect.{ContextShift, IO, Resource, Timer}
import com.avast.sst.http4s.server.Http4sRouting
import org.http4s.client.blaze.BlazeClientBuilder
import org.http4s.dsl.Http4sDsl
import org.http4s.server.blaze.BlazeServerBuilder
import org.http4s.util.CaseInsensitiveString
import org.http4s.{Header, HttpRoutes, Request, Uri}
import org.scalatest.funsuite.AsyncFunSuite

import scala.concurrent.ExecutionContext

@SuppressWarnings(Array("scalafix:Disable.get", "scalafix:Disable.toString", "scalafix:Disable.createUnresolved"))
class CorrelationIdMiddlewareTest extends AsyncFunSuite with Http4sDsl[IO] {

  implicit private val cs: ContextShift[IO] = IO.contextShift(ExecutionContext.global)
  implicit private val timer: Timer[IO] = IO.timer(ExecutionContext.global)

  test("CorrelationIdMiddleware fills Request attributes and HTTP response header") {
    val test = for {
      middleware <- Resource.liftF(CorrelationIdMiddleware.default[IO])
      routes = Http4sRouting.make {
        middleware.wrap {
          HttpRoutes.of[IO] {
            case req @ GET -> Root / "test" =>
              val id = middleware.retrieveCorrelationId(req)
              Ok("test").map(_.withHeaders(Header("Attribute-Value", id.toString)))
          }
        }
      }
      server <- BlazeServerBuilder[IO](ExecutionContext.global)
        .bindSocketAddress(InetSocketAddress.createUnresolved("127.0.0.1", 0))
        .withHttpApp(routes)
        .resource
      client <- BlazeClientBuilder[IO](ExecutionContext.global).resource
    } yield (server, client)

    test
      .use {
        case (server, client) =>
          client
            .run(
              Request[IO](uri = Uri.unsafeFromString(s"http://${server.address.getHostString}:${server.address.getPort}/test"))
                .withHeaders(Header("Correlation-Id", "test-value"))
            )
            .use { response =>
              IO.delay {
                assert(response.headers.get(CaseInsensitiveString("Correlation-Id")).get.value === "test-value")
                assert(response.headers.get(CaseInsensitiveString("Attribute-Value")).get.value === "Some(CorrelationId(test-value))")
              }
            }
      }
      .unsafeToFuture()
  }

} 
Example 33
Source File: HttpErrorHandler.scala    From codepropertygraph   with Apache License 2.0 5 votes vote down vote up
package io.shiftleft.cpgserver.route

import cats.data.{Kleisli, OptionT}
import cats.effect.IO
import org.http4s.{HttpRoutes, Request, Response}

trait HttpErrorHandler {
  def handle(routes: HttpRoutes[IO]): HttpRoutes[IO]
}

object HttpErrorHandler {

  def apply(routes: HttpRoutes[IO])(handler: PartialFunction[Throwable, IO[Response[IO]]]): HttpRoutes[IO] = {
    Kleisli { req: Request[IO] =>
      OptionT {
        routes.run(req).value.handleErrorWith { e =>
          if (handler.isDefinedAt(e)) handler(e).map(Option(_))
          else IO.raiseError(e)
        }
      }
    }
  }
} 
Example 34
Source File: MetricsMiddleware.scala    From iotchain   with MIT License 5 votes vote down vote up
package jbok.network.http.server.middleware

import cats.effect.{Clock, Effect, Sync}
import cats.implicits._
import jbok.common.metrics.PrometheusMetrics
import org.http4s.HttpRoutes
import org.http4s.metrics.prometheus.{Prometheus, PrometheusExportService}
import org.http4s.server.middleware

object MetricsMiddleware {
  def exportService[F[_]](implicit F: Sync[F]): F[HttpRoutes[F]] =
    for {
      _ <- PrometheusExportService.addDefaults[F](PrometheusMetrics.registry)
    } yield PrometheusExportService.service[F](PrometheusMetrics.registry)

  def apply[F[_]](routes: HttpRoutes[F], enableMetrics: Boolean)(implicit F: Effect[F], clock: Clock[F]): F[HttpRoutes[F]] =
    if (enableMetrics) {
      Prometheus[F](PrometheusMetrics.registry, "iotchain_http_server").map { metricsOps =>
        middleware.Metrics[F](metricsOps)(routes)
      }
    } else {
      F.pure(routes)
    }
} 
Example 35
Source File: HmacAuthMiddleware.scala    From iotchain   with MIT License 5 votes vote down vote up
package jbok.network.http.server.middleware

import java.time.{Duration, Instant}

import cats.data.{Kleisli, OptionT}
import cats.effect.Sync
import jbok.network.http.server.authentication.HMAC
import org.http4s.headers.Authorization
import org.http4s.util.CaseInsensitiveString
import org.http4s.{AuthScheme, Credentials, HttpRoutes, Request, Response, Status}
import tsec.mac.jca.{HMACSHA256, MacSigningKey}

import scala.concurrent.duration.{FiniteDuration, _}

sealed abstract class HmacAuthError(val message: String) extends Exception(message)
object HmacAuthError {
  case object NoAuthHeader     extends HmacAuthError("Could not find an Authorization header")
  case object NoDatetimeHeader extends HmacAuthError("Could not find an X-Datetime header")
  case object BadMAC           extends HmacAuthError("Bad MAC")
  case object InvalidMacFormat extends HmacAuthError("The MAC is not a valid Base64 string")
  case object InvalidDatetime  extends HmacAuthError("The datetime is not a valid UTC datetime string")
  case object Timeout          extends HmacAuthError("The request time window is closed")
}

object HmacAuthMiddleware {
  val defaultDuration: FiniteDuration = 5.minutes

  private def verifyFromHeader[F[_]](
      req: Request[F],
      key: MacSigningKey[HMACSHA256],
      duration: FiniteDuration
  ): Either[HmacAuthError, Unit] =
    for {
      authHeader <- req.headers
        .get(Authorization)
        .flatMap { t =>
          t.credentials match {
            case Credentials.Token(scheme, token) if scheme == AuthScheme.Bearer =>
              Some(token)
            case _ => None
          }
        }
        .toRight(HmacAuthError.NoAuthHeader)
      datetimeHeader <- req.headers
        .get(CaseInsensitiveString("X-Datetime"))
        .toRight(HmacAuthError.NoDatetimeHeader)
      instant <- HMAC.http.verifyFromHeader(
        req.method.name,
        req.uri.renderString,
        datetimeHeader.value,
        authHeader,
        key
      )
      _ <- Either.cond(
        Instant.now().isBefore(instant.plus(Duration.ofNanos(duration.toNanos))),
        (),
        HmacAuthError.Timeout
      )
    } yield ()

  def apply[F[_]: Sync](key: MacSigningKey[HMACSHA256], duration: FiniteDuration = defaultDuration)(routes: HttpRoutes[F]): HttpRoutes[F] =
    Kleisli { req: Request[F] =>
      verifyFromHeader(req, key, duration) match {
        case Left(error) => OptionT.some[F](Response[F](Status.Forbidden).withEntity(error.message))
        case Right(_)    => routes(req)
      }
    }
} 
Example 36
Source File: HmacAuthMiddlewareSpec.scala    From iotchain   with MIT License 5 votes vote down vote up
package jbok.network.http.server.middleware

import java.time.Instant

import cats.Id
import cats.effect.IO
import cats.implicits._
import jbok.common.CommonSpec
import jbok.network.http.server.authentication.HMAC
import org.http4s.dsl.io._
import org.http4s.headers.Authorization
import org.http4s.implicits._
import org.http4s.{AuthScheme, Credentials, Header, HttpRoutes, Request, Status, Uri}
import scodec.bits.ByteVector
import tsec.mac.jca.{HMACSHA256, MacSigningKey}

import scala.concurrent.duration._

class HmacAuthMiddlewareSpec extends CommonSpec {
  "HmacAuthMiddleware" should {
    val key = HMACSHA256.buildKey[Id](
      ByteVector.fromValidHex("70ea14ac30939a972b5a67cab952d6d7d474727b05fe7f9283abc1e505919e83").toArray
    )

    def sign(url: String): (String, String) = {
      val datetime  = Instant.now().toString
      val signature = HMAC.http.signForHeader("GET", url, datetime, key).unsafeRunSync()
      (signature, datetime)
    }

    val routes = HttpRoutes.of[IO] {
      case GET -> Root / "ping" => Ok("pong")
    }

    val service = routes.orNotFound
    val req     = Request[IO](uri = Uri.uri("/ping"))
    service.run(req).unsafeRunSync().status shouldBe Status.Ok
    val authedService = HmacAuthMiddleware(key)(routes).orNotFound

    "403 if no Authorization header" in {
      val resp = authedService.run(req).unsafeRunSync()
      val text = resp.bodyAsText.compile.foldMonoid.unsafeRunSync()
      resp.status shouldBe Status.Forbidden
      text shouldBe HmacAuthError.NoAuthHeader.message
    }

    "403 if no X-Datetime header" in {
      val signature = HMAC.http.signForHeader("GET", "/ping", Instant.now().toString, key).unsafeRunSync()
      val req =
        Request[IO](uri = Uri.uri("/ping")).putHeaders(Authorization(Credentials.Token(AuthScheme.Bearer, signature)))
      val resp = authedService.run(req).unsafeRunSync()
      val text = resp.bodyAsText.compile.foldMonoid.unsafeRunSync()
      resp.status shouldBe Status.Forbidden
      text shouldBe HmacAuthError.NoDatetimeHeader.message
    }

    "403 if time window is closed" in {
      val authedService = HmacAuthMiddleware(key, 2.seconds)(routes).orNotFound
      val now           = Instant.now()
      val signature     = HMAC.http.signForHeader("GET", "/ping", now.toString, key).unsafeRunSync()
      val req =
        Request[IO](uri = Uri.uri("/ping"))
          .putHeaders(
            Authorization(Credentials.Token(AuthScheme.Bearer, signature)),
            Header("X-Datetime", now.toString)
          )

      val resp = authedService.run(req).unsafeRunSync()
      resp.status shouldBe Status.Ok

      IO.sleep(3.seconds).unsafeRunSync()
      val resp2 = authedService.run(req).unsafeRunSync()
      val text  = resp2.bodyAsText.compile.foldMonoid.unsafeRunSync()
      resp2.status shouldBe Status.Forbidden
      text shouldBe HmacAuthError.Timeout.message
    }

    "helper" in {
      val (sig, date) = sign("/v1/blocks")
      println(("Authorization", s"Bearer $sig"))
      println(("X-Datetime", date))
      println(("Random key", ByteVector(MacSigningKey.toJavaKey[HMACSHA256](HMACSHA256.generateKey[Id]).getEncoded).toHex))
    }
  }
} 
Example 37
Source File: HttpService.scala    From iotchain   with MIT License 5 votes vote down vote up
package jbok.app.service

import cats.effect.{ConcurrentEffect, Resource, Timer}
import io.circe.Json
import cats.implicits._
import fs2._
import javax.net.ssl.SSLContext
import jbok.network.http.server.middleware.{CORSMiddleware, GzipMiddleware, LoggerMiddleware, MetricsMiddleware}
import jbok.core.config.ServiceConfig
import jbok.core.api._
import jbok.crypto.ssl.SSLConfig
import jbok.network.rpc.RpcService
import jbok.network.rpc.http.Http4sRpcServer
import org.http4s.HttpRoutes
import org.http4s.implicits._
import org.http4s.server.{SSLClientAuthMode, Server}
import org.http4s.server.blaze.BlazeServerBuilder

final class HttpService[F[_]](
    config: ServiceConfig,
    sslConfig: SSLConfig,
    account: AccountAPI[F],
    admin: AdminAPI[F],
    block: BlockAPI[F],
    contract: ContractAPI[F],
    miner: MinerAPI[F],
    personal: PersonalAPI[F],
    transaction: TransactionAPI[F],
    sslOpt: Option[SSLContext]
)(implicit F: ConcurrentEffect[F], T: Timer[F]) {
  import jbok.codec.impl.circe._
  import _root_.io.circe.generic.auto._
  import jbok.codec.json.implicits._

  val rpcService: RpcService[F, Json] = {
    var service = RpcService[F, Json]
    if (config.apis.contains("account")) service = service.mount(account) else ()
    if (config.apis.contains("admin")) service = service.mount(admin) else ()
    if (config.apis.contains("block")) service = service.mount(block) else ()
    if (config.apis.contains("contract")) service = service.mount(contract) else ()
    if (config.apis.contains("miner")) service = service.mount(miner) else ()
    if (config.apis.contains("personal")) service = service.mount(personal) else ()
    if (config.apis.contains("transaction")) service = service.mount(transaction) else ()
    service
  }

  val routes: HttpRoutes[F] = Http4sRpcServer.routes(rpcService)

  private val builder: F[BlazeServerBuilder[F]] = {
    val httpApp = for {
      exportRoute <- MetricsMiddleware.exportService[F]
      withMetrics <- MetricsMiddleware[F](routes, config.enableMetrics)
      withLogger = LoggerMiddleware[F](config.logHeaders, config.logBody)((withMetrics <+> exportRoute).orNotFound)
      withCORS   = CORSMiddleware[F](withLogger, config.allowedOrigins)
      app        = GzipMiddleware[F](withCORS)
    } yield app

    val builder = httpApp.map { app =>
      BlazeServerBuilder[F]
        .withHttpApp(app)
        .withNio2(true)
        .enableHttp2(config.enableHttp2)
        .withWebSockets(config.enableWebsockets)
        .bindHttp(config.port, config.local)
    }

    val sslLClientAuthMode = sslConfig.clientAuth match {
      case "NotRequested" => SSLClientAuthMode.NotRequested
      case "Requested"    => SSLClientAuthMode.Requested
      case "Required"     => SSLClientAuthMode.Requested
      case x              => throw new IllegalArgumentException(s"SSLClientAuthMode ${x} is not supported")
    }

    sslOpt match {
      case Some(ssl) => builder.map(_.withSSLContext(ssl, sslLClientAuthMode))
      case None      => builder.map(_.enableHttp2(false))
    }
  }

  val resource: Resource[F, Server[F]] =
    Resource.liftF(builder).flatMap(_.resource)

  val stream: Stream[F, Unit] =
    if (config.enable) {
      Stream.eval(builder).flatMap(_.serve).drain
    } else {
      Stream.empty
    }
} 
Example 38
Source File: Server.scala    From zio-metrics   with Apache License 2.0 5 votes vote down vote up
package zio.metrics.dropwizard

import scala.util.Properties.envOrNone

import cats.data.Kleisli
import org.http4s.server.blaze._
import org.http4s.{ Request, Response }

import zio.{ RIO, ZIO }
import zio.system.System
import zio.clock.Clock
import zio.console.Console
import zio.random.Random
import zio.blocking.Blocking
import zio.interop.catz._
import io.circe.Json
import org.http4s.circe._
import org.http4s.dsl.impl.Root
import org.http4s.dsl.io._
import org.http4s.{ HttpRoutes, Response }
import zio.RIO
import zio.interop.catz._
import zio.metrics.dropwizard.typeclasses._
import zio.metrics.dropwizard.DropwizardExtractor._
import cats.instances.list._
import com.codahale.metrics.MetricRegistry

object Server {
  val port: Int = envOrNone("HTTP_PORT").fold(9090)(_.toInt)

  type HttpEnvironment = Clock with Console with System with Random with Blocking
  type HttpTask[A]     = RIO[HttpEnvironment, A]

  type KleisliApp = Kleisli[HttpTask, Request[HttpTask], Response[HttpTask]]

  //type HttpApp[R <: Registry] = R => KleisliApp

  def builder[Ctx]: KleisliApp => HttpTask[Unit] =
    (app: KleisliApp) =>
      ZIO
        .runtime[HttpEnvironment]
        .flatMap { implicit rts =>
          BlazeServerBuilder[HttpTask]
            .bindHttp(port)
            .withHttpApp(app)
            .serve
            .compile
            .drain
        }

  def serveMetrics: MetricRegistry => HttpRoutes[Server.HttpTask] =
    registry =>
      HttpRoutes.of[Server.HttpTask] {
        case GET -> Root / filter => {
          println(s"filter: $filter")
          val optFilter = if (filter == "ALL") None else Some(filter)
          RegistryPrinter
            .report[List, Json](registry, optFilter)(
              (k: String, v: Json) => Json.obj((k, v))
            )
            .map(m => Response[Server.HttpTask](Ok).withEntity(m))
        }
      }
} 
Example 39
Source File: http4s.scala    From sup   with Apache License 2.0 5 votes vote down vote up
package sup.modules

import cats.effect.Sync
import cats.Monad
import cats.Reducible
import org.http4s.dsl.Http4sDsl
import org.http4s.EntityEncoder
import org.http4s.HttpRoutes
import org.http4s.Response
import sup.HealthCheck
import sup.HealthResult
import cats.implicits._

object http4s {

  
  def healthCheckRoutes[F[_]: Sync, H[_]: Reducible](
    healthCheck: HealthCheck[F, H],
    path: String = "health-check"
  )(
    implicit encoder: EntityEncoder[F, HealthResult[H]]
  ): HttpRoutes[F] = {

    val dsl = new Http4sDsl[F] {}
    import dsl._

    HttpRoutes.of[F] {
      case GET -> Root / `path` =>
        healthCheckResponse(healthCheck)
    }
  }

  def healthCheckResponse[F[_]: Monad, H[_]: Reducible](
    healthCheck: HealthCheck[F, H]
  )(
    implicit encoder: EntityEncoder[F, HealthResult[H]]
  ): F[Response[F]] = {

    val dsl = new Http4sDsl[F] {}
    import dsl._

    healthCheck.check.flatMap { check =>
      if (check.value.reduce.isHealthy) Ok(check)
      else ServiceUnavailable(check)
    }
  }
} 
Example 40
Source File: jwtStatefulExample.scala    From tsec   with MIT License 5 votes vote down vote up
package http4sExamples

import cats.Id
import cats.effect.IO
import cats.syntax.semigroupk._
import org.http4s.HttpRoutes
import org.http4s.dsl.io._
import tsec.authentication._
import tsec.common.SecureRandomId
import tsec.mac.jca.{HMACSHA256, MacSigningKey}

import scala.concurrent.duration._

object jwtStatefulExample {

  import ExampleAuthHelpers._

  type AuthService = TSecAuthService[User, AugmentedJWT[HMACSHA256, Int], IO]

  val jwtStore =
    dummyBackingStore[IO, SecureRandomId, AugmentedJWT[HMACSHA256, Int]](s => SecureRandomId.coerce(s.id))

  //We create a way to store our users. You can attach this to say, your doobie accessor
  val userStore: BackingStore[IO, Int, User] = dummyBackingStore[IO, Int, User](_.id)

  val signingKey
    : MacSigningKey[HMACSHA256] = HMACSHA256.generateKey[Id] //Our signing key. Instantiate in a safe way using GenerateLift

  val jwtStatefulAuth =
    JWTAuthenticator.backed.inBearerToken(
      expiryDuration = 10.minutes, //Absolute expiration time
      maxIdle = None,
      tokenStore = jwtStore,
      identityStore = userStore,
      signingKey = signingKey
    )

  val Auth =
    SecuredRequestHandler(jwtStatefulAuth)

  
      val r: SecuredRequest[IO, User, AugmentedJWT[HMACSHA256, Int]] = request
      Ok()
  }

  val service2: AuthService = TSecAuthService {
    case request @ GET -> Root / "api2" asAuthed user =>
      val r: SecuredRequest[IO, User, AugmentedJWT[HMACSHA256, Int]] = request
      Ok()
  }

  val liftedService1: HttpRoutes[IO] = Auth.liftService(service1)
  val liftedComposed: HttpRoutes[IO] = Auth.liftService(service1 <+> service2)

} 
Example 41
Source File: EncryptedCookieExample.scala    From tsec   with MIT License 5 votes vote down vote up
package http4sExamples

import java.util.UUID

import cats.effect.IO
import cats.syntax.semigroupk._
import org.http4s.HttpRoutes
import org.http4s.dsl.io._
import tsec.authentication._
import tsec.cipher.symmetric.jca._

import scala.concurrent.duration._

object EncryptedCookieExample {

  import ExampleAuthHelpers._
  type AuthService = TSecAuthService[User, AuthEncryptedCookie[AES128GCM, Int], IO]

  implicit val encryptor   = AES128GCM.genEncryptor[IO]
  implicit val gcmstrategy = AES128GCM.defaultIvStrategy[IO]

  val cookieBackingStore: BackingStore[IO, UUID, AuthEncryptedCookie[AES128GCM, Int]] =
    dummyBackingStore[IO, UUID, AuthEncryptedCookie[AES128GCM, Int]](_.id)

  // We create a way to store our users. You can attach this to say, your doobie accessor
  val userStore: BackingStore[IO, Int, User] = dummyBackingStore[IO, Int, User](_.id)

  val settings: TSecCookieSettings = TSecCookieSettings(
    cookieName = "tsec-auth",
    secure = false,
    expiryDuration = 10.minutes, // Absolute expiration time
    maxIdle = None // Rolling window expiration. Set this to a FiniteDuration if you intend to have one
  )

  val key: SecretKey[AES128GCM] = AES128GCM.unsafeGenerateKey //Our encryption key

  val authWithBackingStore = //Instantiate a stateful authenticator
    EncryptedCookieAuthenticator.withBackingStore(
      settings,
      cookieBackingStore,
      userStore,
      key
    )

  val stateless = //Instantiate a stateless authenticator
    EncryptedCookieAuthenticator.stateless(
      settings,
      userStore,
      key
    )

  val Auth =
    SecuredRequestHandler(stateless)

  
      val r: SecuredRequest[IO, User, AuthEncryptedCookie[AES128GCM, Int]] = request
      Ok()
  }

  val rawService2: AuthService = TSecAuthService {
    case request @ GET -> Root / "api2" asAuthed user =>
      val r: SecuredRequest[IO, User, AuthEncryptedCookie[AES128GCM, Int]] = request
      Ok()
  }

  val liftedService: HttpRoutes[IO]  = Auth.liftService(rawService1)
  val liftedComposed: HttpRoutes[IO] = Auth.liftService(rawService1 <+> rawService2)

} 
Example 42
Source File: BearerTokenExample.scala    From tsec   with MIT License 5 votes vote down vote up
package http4sExamples

import cats.effect.IO
import cats.syntax.semigroupk._
import org.http4s.HttpRoutes
import org.http4s.dsl.io._
import tsec.authentication._
import tsec.common.SecureRandomId

import scala.concurrent.duration._

object BearerTokenExample {

  import ExampleAuthHelpers._

  val bearerTokenStore =
    dummyBackingStore[IO, SecureRandomId, TSecBearerToken[Int]](s => SecureRandomId.coerce(s.id))

  type AuthService = TSecAuthService[User, TSecBearerToken[Int], IO]

  //We create a way to store our users. You can attach this to say, your doobie accessor
  val userStore: BackingStore[IO, Int, User] = dummyBackingStore[IO, Int, User](_.id)

  val settings: TSecTokenSettings = TSecTokenSettings(
    expiryDuration = 10.minutes, //Absolute expiration time
    maxIdle = None
  )

  val bearerTokenAuth =
    BearerTokenAuthenticator(
      bearerTokenStore,
      userStore,
      settings
    )

  val Auth =
    SecuredRequestHandler(bearerTokenAuth)

  val authService1: AuthService = TSecAuthService {
    //Where user is the case class User above
    case request @ GET -> Root / "api" asAuthed user =>
      
      val r: SecuredRequest[IO, User, TSecBearerToken[Int]] = request
      Ok()
  }

  val authedService2: AuthService = TSecAuthService {
    case GET -> Root / "api2" asAuthed user =>
      Ok()
  }

  val lifted: HttpRoutes[IO]         = Auth.liftService(authService1)
  val liftedComposed: HttpRoutes[IO] = Auth.liftService(authService1 <+> authedService2)
} 
Example 43
Source File: SignedCookieExample.scala    From tsec   with MIT License 5 votes vote down vote up
package http4sExamples

import java.util.UUID

import cats.Id
import cats.effect.IO
import cats.syntax.semigroupk._
import org.http4s.HttpRoutes
import org.http4s.dsl.io._
import tsec.authentication._
import tsec.mac.jca.{HMACSHA256, MacSigningKey}

import scala.concurrent.duration._

object SignedCookieExample {

  import ExampleAuthHelpers._

  type AuthService = TSecAuthService[User, AuthenticatedCookie[HMACSHA256, Int], IO]

  val cookieBackingStore: BackingStore[IO, UUID, AuthenticatedCookie[HMACSHA256, Int]] =
    dummyBackingStore[IO, UUID, AuthenticatedCookie[HMACSHA256, Int]](_.id)

  // We create a way to store our users. You can attach this to say, your doobie accessor
  val userStore: BackingStore[IO, Int, User] = dummyBackingStore[IO, Int, User](_.id)

  val settings: TSecCookieSettings = TSecCookieSettings(
    cookieName = "tsec-auth",
    secure = false,
    expiryDuration = 10.minutes, // Absolute expiration time
    maxIdle = None // Rolling window expiration. Set this to a FiniteDuration if you intend to have one
  )

  //Our Signing key. Instantiate in a safe way using generateKey[F] where F[_]: Sync
  val key: MacSigningKey[HMACSHA256] = HMACSHA256.generateKey[Id]

  val cookieAuth =
    SignedCookieAuthenticator(
      settings,
      cookieBackingStore,
      userStore,
      key
    )

  val Auth =
    SecuredRequestHandler(cookieAuth)

  val service1: AuthService = TSecAuthService {
    //Where user is the case class User above
    case request @ GET -> Root / "api" asAuthed user =>
      
      val r: SecuredRequest[IO, User, AuthenticatedCookie[HMACSHA256, Int]] = request
      Ok()
  }

  val service2: AuthService = TSecAuthService {
    case request @ GET -> Root / "api2" asAuthed user =>
      val r: SecuredRequest[IO, User, AuthenticatedCookie[HMACSHA256, Int]] = request
      Ok()
  }

  val liftedService1: HttpRoutes[IO] = Auth.liftService(service1)
  val liftedComposed: HttpRoutes[IO] = Auth.liftService(service1 <+> service2)

} 
Example 44
Source File: KamonSupport.scala    From kamon-http4s   with Apache License 2.0 5 votes vote down vote up
package kamon.http4s
package middleware.server

import cats.data.{Kleisli, OptionT}
import cats.effect.{Resource, Sync}
import cats.implicits._
import kamon.Kamon
import kamon.context.Storage
import kamon.instrumentation.http.HttpServerInstrumentation.RequestHandler
import kamon.instrumentation.http.HttpServerInstrumentation
import org.http4s.{HttpRoutes, Request, Response}

object KamonSupport {

  def apply[F[_]: Sync](service: HttpRoutes[F], interface: String, port: Int): HttpRoutes[F] = {
    val httpServerConfig = Kamon.config().getConfig("kamon.instrumentation.http4s.server")
    val instrumentation = HttpServerInstrumentation.from(httpServerConfig, "http4s.server", interface, port)

    Kleisli(kamonService[F](service, instrumentation)(_))
  }


  private def kamonService[F[_]](service: HttpRoutes[F], instrumentation: HttpServerInstrumentation)
                                (request: Request[F])
                                (implicit F: Sync[F]): OptionT[F, Response[F]] = OptionT {
    getHandler(instrumentation)(request).use { handler =>
      for {
        resOrUnhandled  <- service(request).value.attempt
        respWithContext <- kamonServiceHandler(handler, resOrUnhandled, instrumentation.settings)
      } yield respWithContext
    }
  }

  private def processRequest[F[_]](requestHandler: RequestHandler)(implicit F: Sync[F]): Resource[F, RequestHandler] =
    Resource.make(F.delay(requestHandler.requestReceived()))(h => F.delay(h.responseSent()))

  private def withContext[F[_]](requestHandler: RequestHandler)(implicit F: Sync[F]): Resource[F, Storage.Scope] =
    Resource.make(F.delay(Kamon.storeContext(requestHandler.context)))( scope => F.delay(scope.close()))


  private def getHandler[F[_]](instrumentation: HttpServerInstrumentation)(request: Request[F])(implicit F: Sync[F]): Resource[F, RequestHandler] =
    for {
      handler <- Resource.liftF(F.delay(instrumentation.createHandler(buildRequestMessage(request))))
      _       <- processRequest(handler)
      _       <- withContext(handler)
    } yield handler

  private def kamonServiceHandler[F[_]](requestHandler: RequestHandler,
                                        e: Either[Throwable, Option[Response[F]]],
                                       settings: HttpServerInstrumentation.Settings)
                                       (implicit F: Sync[F]): F[Option[Response[F]]] =
    e match {
      case Left(e) =>
        F.delay {
          requestHandler.span.fail(e.getMessage)
          Some(requestHandler.buildResponse(errorResponseBuilder, requestHandler.context))
        } *> F.raiseError(e)
      case Right(None) =>
        F.delay {
          requestHandler.span.name(settings.unhandledOperationName)
          val response: Response[F] = requestHandler.buildResponse[Response[F]](
            notFoundResponseBuilder, requestHandler.context
          )
          Some(response)
        }
      case Right(Some(response)) =>
        F.delay {
          val a = requestHandler.buildResponse(getResponseBuilder(response), requestHandler.context)
          Some(a)
        }
    }

} 
Example 45
Source File: HttpMetricsSpec.scala    From kamon-http4s   with Apache License 2.0 5 votes vote down vote up
package kamon.http4s

import cats.effect._
import kamon.testkit.InstrumentInspection
import org.http4s.HttpRoutes
import org.http4s.dsl.io._
import org.http4s.server.Server
import org.http4s.server.blaze.BlazeServerBuilder
import org.scalatest.concurrent.Eventually
import org.scalatest.time.SpanSugar
import org.scalatest.{Matchers, OptionValues, WordSpec}
import cats.implicits._
import kamon.http4s.middleware.server.KamonSupport
import kamon.instrumentation.http.HttpServerMetrics
import org.http4s.client.blaze.BlazeClientBuilder
import org.http4s.client.Client

import scala.concurrent.ExecutionContext
import org.http4s.implicits._

class HttpMetricsSpec extends WordSpec
  with Matchers
  with Eventually
  with SpanSugar
  with InstrumentInspection.Syntax
  with OptionValues
 {

  implicit val contextShift: ContextShift[IO] = IO.contextShift(ExecutionContext.global)
  implicit val timer: Timer[IO] = IO.timer(ExecutionContext.global)

  val srv =
    BlazeServerBuilder[IO]
      .bindLocal(43567)
      .withHttpApp(KamonSupport(HttpRoutes.of[IO] {
        case GET -> Root / "tracing" / "ok" =>  Ok("ok")
        case GET -> Root / "tracing" / "not-found"  => NotFound("not-found")
        case GET -> Root / "tracing" / "error"  => InternalServerError("This page will generate an error!")
      }, "/127.0.0.1", 43567).orNotFound)
      .resource

  val client =
    BlazeClientBuilder[IO](ExecutionContext.global).withMaxTotalConnections(10).resource

   val metrics =
    Resource.liftF(IO(HttpServerMetrics.of("http4s.server", "/127.0.0.1", 43567)))


  def withServerAndClient[A](f: (Server[IO], Client[IO], HttpServerMetrics.HttpServerInstruments) => IO[A]): A =
   (srv, client, metrics).tupled.use(f.tupled).unsafeRunSync()

  private def get[F[_]: ConcurrentEffect](path: String)(server: Server[F], client: Client[F]): F[String] = {
    client.expect[String](s"http://127.0.0.1:${server.address.getPort}$path")
  }

  "The HttpMetrics" should {

    "track the total of active requests" in withServerAndClient { (server, client, serverMetrics) =>

      val requests = List
        .fill(100) {
          get("/tracing/ok")(server, client)
        }.parSequence_

      val test = IO {
        serverMetrics.activeRequests.distribution().max should be > 1L
        serverMetrics.activeRequests.distribution().min shouldBe 0L
      }
      requests *> test
    }

    "track the response time with status code 2xx" in withServerAndClient { (server, client, serverMetrics) =>
      val requests: IO[Unit] = List.fill(100)(get("/tracing/ok")(server, client)).sequence_

      val test = IO(serverMetrics.requestsSuccessful.value should be >= 0L)

      requests *> test
    }

    "track the response time with status code 4xx" in withServerAndClient { (server, client, serverMetrics) =>
      val requests: IO[Unit] = List.fill(100)(get("/tracing/not-found")(server, client).attempt).sequence_

      val test = IO(serverMetrics.requestsClientError.value should be >= 0L)

      requests *> test
    }

    "track the response time with status code 5xx" in withServerAndClient { (server, client, serverMetrics) =>
      val requests: IO[Unit] = List.fill(100)(get("/tracing/error")(server, client).attempt).sequence_

      val test = IO(serverMetrics.requestsServerError.value should be >= 0L)

      requests *> test
    }
  }
} 
Example 46
Source File: ServerInterpreterTest.scala    From endpoints4s   with MIT License 5 votes vote down vote up
package endpoints4s.http4s.server

import java.net.ServerSocket

import cats.effect.{ContextShift, IO, Timer}
import endpoints4s.{Invalid, Valid}
import endpoints4s.algebra.server.{
  BasicAuthenticationTestSuite,
  DecodedUrl,
  EndpointsTestSuite,
  JsonEntitiesFromSchemasTestSuite,
  SumTypedEntitiesTestSuite,
  TextEntitiesTestSuite
}
import org.http4s.server.Router
import org.http4s.{HttpRoutes, Uri}
import org.http4s.server.blaze.BlazeServerBuilder
import org.http4s.syntax.kleisli._

import scala.concurrent.ExecutionContext

class ServerInterpreterTest
    extends EndpointsTestSuite[EndpointsTestApi]
    with BasicAuthenticationTestSuite[EndpointsTestApi]
    with JsonEntitiesFromSchemasTestSuite[EndpointsTestApi]
    with TextEntitiesTestSuite[EndpointsTestApi]
    with SumTypedEntitiesTestSuite[EndpointsTestApi] {

  val serverApi = new EndpointsTestApi()

  def decodeUrl[A](url: serverApi.Url[A])(rawValue: String): DecodedUrl[A] = {
    val uri =
      Uri.fromString(rawValue).getOrElse(sys.error(s"Illegal URI: $rawValue"))

    url.decodeUrl(uri) match {
      case None                  => DecodedUrl.NotMatched
      case Some(Invalid(errors)) => DecodedUrl.Malformed(errors)
      case Some(Valid(a))        => DecodedUrl.Matched(a)
    }
  }

  private def serveGeneralEndpoint[Req, Resp](
      endpoint: serverApi.Endpoint[Req, Resp],
      request2response: Req => Resp
  )(runTests: Int => Unit): Unit = {
    val port = {
      val socket = new ServerSocket(0)
      try socket.getLocalPort
      finally if (socket != null) socket.close()
    }
    implicit val cs: ContextShift[IO] = IO.contextShift(ExecutionContext.global)
    implicit val timer: Timer[IO] = IO.timer(ExecutionContext.global)
    val service = HttpRoutes.of[IO](endpoint.implementedBy(request2response))
    val httpApp = Router("/" -> service).orNotFound
    val server =
      BlazeServerBuilder[IO](ExecutionContext.global)
        .bindHttp(port, "localhost")
        .withHttpApp(httpApp)
    server.resource.use(_ => IO(runTests(port))).unsafeRunSync()
  }

  def serveEndpoint[Resp](
      endpoint: serverApi.Endpoint[_, Resp],
      response: => Resp
  )(runTests: Int => Unit): Unit =
    serveGeneralEndpoint(endpoint, (_: Any) => response)(runTests)

  def serveIdentityEndpoint[Resp](
      endpoint: serverApi.Endpoint[Resp, Resp]
  )(runTests: Int => Unit): Unit =
    serveGeneralEndpoint(endpoint, identity[Resp])(runTests)
} 
Example 47
Source File: Api.scala    From endpoints4s   with MIT License 5 votes vote down vote up
package sample

import cats.effect.IO
import endpoints4s.http4s.server.{BasicAuthentication, Endpoints, JsonEntitiesFromCodecs}
import org.http4s.HttpRoutes

import scala.util.Random

object Api extends Endpoints[IO] with JsonEntitiesFromCodecs with BasicAuthentication with ApiAlg {

  val router: HttpRoutes[IO] = HttpRoutes.of(
    routesFromEndpoints(
      index.implementedBy { case (name, age, _) => User(name, age) },
      maybe.implementedBy(_ => if (util.Random.nextBoolean()) Some(()) else None) orElse
        action.implementedBy { _ => ActionResult("Action") },
      actionFut.implementedByEffect { _ => IO.pure(ActionResult("Action")) },
      auth.implementedBy { credentials =>
        println(s"Authenticated request: ${credentials.username}")
        if (Random.nextBoolean()) Some(())
        else None // Randomly return a forbidden
      }
    )
  )
} 
Example 48
Source File: VCSExtraAlgTest.scala    From scala-steward   with Apache License 2.0 5 votes vote down vote up
package org.scalasteward.core.vcs

import cats.effect.IO
import org.http4s.HttpRoutes
import org.http4s.client.Client
import org.http4s.dsl.io._
import org.http4s.implicits._
import org.scalasteward.core.TestInstances.ioLogger
import org.scalasteward.core.TestSyntax._
import org.scalasteward.core.data.{ReleaseRelatedUrl, Update}
import org.scalasteward.core.mock.MockContext.config
import org.scalasteward.core.util.{HttpExistenceClient, Nel}
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers

class VCSExtraAlgTest extends AnyFunSuite with Matchers {
  val routes: HttpRoutes[IO] =
    HttpRoutes.of[IO] {
      case HEAD -> Root / "foo" / "bar" / "compare" / "v0.1.0...v0.2.0" => Ok("exist")
      case HEAD -> Root / "foo" / "buz" / "compare" / "v0.1.0...v0.2.0" => PermanentRedirect()
      case _                                                            => NotFound()
    }

  implicit val client = Client.fromHttpApp[IO](routes.orNotFound)
  implicit val httpExistenceClient =
    HttpExistenceClient.create[IO].allocated.map(_._1).unsafeRunSync()

  val vcsExtraAlg = VCSExtraAlg.create[IO]
  val updateFoo = Update.Single("com.example" % "foo" % "0.1.0", Nel.of("0.2.0"))
  val updateBar = Update.Single("com.example" % "bar" % "0.1.0", Nel.of("0.2.0"))
  val updateBuz = Update.Single("com.example" % "buz" % "0.1.0", Nel.of("0.2.0"))

  test("getBranchCompareUrl") {
    vcsExtraAlg
      .getReleaseRelatedUrls(uri"https://github.com/foo/foo", updateFoo)
      .unsafeRunSync() shouldBe List.empty
    vcsExtraAlg
      .getReleaseRelatedUrls(uri"https://github.com/foo/bar", updateBar)
      .unsafeRunSync() shouldBe List(
      ReleaseRelatedUrl.VersionDiff(uri"https://github.com/foo/bar/compare/v0.1.0...v0.2.0")
    )
    vcsExtraAlg
      .getReleaseRelatedUrls(uri"https://github.com/foo/buz", updateBuz)
      .unsafeRunSync() shouldBe List.empty
  }
} 
Example 49
Source File: Http4sRpcServer.scala    From iotchain   with MIT License 5 votes vote down vote up
package jbok.network.rpc.http

import cats.effect.{ConcurrentEffect, Resource, Sync, Timer}
import cats.implicits._
import io.circe.Json
import io.circe.syntax._
import jbok.network.rpc.{RpcRequest, RpcService}
import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityCodec._
import org.http4s.dsl.Http4sDsl
import org.http4s.implicits._
import org.http4s.server.Server
import org.http4s.server.blaze.BlazeServerBuilder

object Http4sRpcServer {
  def routes[F[_]](service: RpcService[F, Json])(implicit F: Sync[F]): HttpRoutes[F] = {
    val dsl = Http4sDsl[F]
    import dsl._

    HttpRoutes.of[F] {
      case req @ POST -> path =>
        for {
          json   <- req.as[Json]
          result <- service.handle(RpcRequest(path.toList, json))
          resp   <- Ok(result.asJson)
        } yield resp
    }
  }

  def server[F[_]](service: RpcService[F, Json])(implicit F: ConcurrentEffect[F], T: Timer[F]): Resource[F, Server[F]] =
    BlazeServerBuilder[F]
      .bindLocal(0)
      .withHttpApp(routes[F](service).orNotFound)
      .withWebSockets(true)
      .resource
} 
Example 50
Source File: InfoRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.joex.routes

import cats.effect.Sync

import docspell.joex.BuildInfo
import docspell.joexapi.model.VersionInfo

import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl

object InfoRoutes {

  def apply[F[_]: Sync](): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] {}
    import dsl._
    HttpRoutes.of[F] {
      case GET -> (Root / "version") =>
        Ok(
          VersionInfo(
            BuildInfo.version,
            BuildInfo.builtAtMillis,
            BuildInfo.builtAtString,
            BuildInfo.gitHeadCommit.getOrElse(""),
            BuildInfo.gitDescribedVersion.getOrElse("")
          )
        )
    }
  }
} 
Example 51
Source File: JoexRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.joex.routes

import cats.effect._
import cats.implicits._

import docspell.common.{Duration, Ident, Timestamp}
import docspell.joex.JoexApp
import docspell.joexapi.model._
import docspell.store.records.{RJob, RJobLog}

import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl

object JoexRoutes {

  def apply[F[_]: ConcurrentEffect: Timer](app: JoexApp[F]): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] {}
    import dsl._
    HttpRoutes.of[F] {
      case POST -> Root / "notify" =>
        for {
          _    <- app.scheduler.notifyChange
          _    <- app.periodicScheduler.notifyChange
          resp <- Ok(BasicResult(true, "Schedulers notified."))
        } yield resp

      case GET -> Root / "running" =>
        for {
          jobs <- app.scheduler.getRunning
          jj = jobs.map(mkJob)
          resp <- Ok(JobList(jj.toList))
        } yield resp

      case POST -> Root / "shutdownAndExit" =>
        for {
          _ <- ConcurrentEffect[F].start(
            Timer[F].sleep(Duration.seconds(1).toScala) *> app.initShutdown
          )
          resp <- Ok(BasicResult(true, "Shutdown initiated."))
        } yield resp

      case GET -> Root / "job" / Ident(id) =>
        for {
          optJob <- app.scheduler.getRunning.map(_.find(_.id == id))
          optLog <- optJob.traverse(j => app.findLogs(j.id))
          jAndL = for { job <- optJob; log <- optLog } yield mkJobLog(job, log)
          resp <- jAndL.map(Ok(_)).getOrElse(NotFound(BasicResult(false, "Not found")))
        } yield resp

      case POST -> Root / "job" / Ident(id) / "cancel" =>
        for {
          flag <- app.scheduler.requestCancel(id)
          resp <- Ok(
            BasicResult(flag, if (flag) "Cancel request submitted" else "Job not found")
          )
        } yield resp
    }
  }

  def mkJob(j: RJob): Job =
    Job(
      j.id,
      j.subject,
      j.submitted,
      j.priority,
      j.retries,
      j.progress,
      j.started.getOrElse(Timestamp.Epoch)
    )

  def mkJobLog(j: RJob, jl: Vector[RJobLog]): JobAndLog =
    JobAndLog(mkJob(j), jl.map(r => JobLogEvent(r.created, r.level, r.message)).toList)
} 
Example 52
Source File: InfoRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.routes

import cats.effect.Sync

import docspell.restapi.model.VersionInfo
import docspell.restserver.BuildInfo

import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl

object InfoRoutes {

  def apply[F[_]: Sync](): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] {}
    import dsl._
    HttpRoutes.of[F] {
      case GET -> (Root / "version") =>
        Ok(
          VersionInfo(
            BuildInfo.version,
            BuildInfo.builtAtMillis,
            BuildInfo.builtAtString,
            BuildInfo.gitHeadCommit.getOrElse(""),
            BuildInfo.gitDescribedVersion.getOrElse("")
          )
        )
    }
  }
} 
Example 53
Source File: TagRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.routes

import cats.effect._
import cats.implicits._

import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken
import docspell.common.Ident
import docspell.restapi.model._
import docspell.restserver.conv.Conversions._
import docspell.restserver.http4s._

import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityDecoder._
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl

object TagRoutes {

  def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] with ResponseGenerator[F] {}
    import dsl._

    HttpRoutes.of {
      case GET -> Root :? QueryParam.QueryOpt(q) =>
        for {
          all  <- backend.tag.findAll(user.account, q.map(_.q))
          resp <- Ok(TagList(all.size, all.map(mkTag).toList))
        } yield resp

      case req @ POST -> Root =>
        for {
          data <- req.as[Tag]
          tag  <- newTag(data, user.account.collective)
          res  <- backend.tag.add(tag)
          resp <- Ok(basicResult(res, "Tag successfully created."))
        } yield resp

      case req @ PUT -> Root =>
        for {
          data <- req.as[Tag]
          tag = changeTag(data, user.account.collective)
          res  <- backend.tag.update(tag)
          resp <- Ok(basicResult(res, "Tag successfully updated."))
        } yield resp

      case DELETE -> Root / Ident(id) =>
        for {
          del  <- backend.tag.delete(id, user.account.collective)
          resp <- Ok(basicResult(del, "Tag successfully deleted."))
        } yield resp
    }
  }

} 
Example 54
Source File: EquipmentRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.routes

import cats.effect._
import cats.implicits._

import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken
import docspell.common.Ident
import docspell.restapi.model._
import docspell.restserver.conv.Conversions._
import docspell.restserver.http4s.QueryParam

import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityDecoder._
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl

object EquipmentRoutes {

  def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] {}
    import dsl._

    HttpRoutes.of {
      case GET -> Root :? QueryParam.QueryOpt(q) =>
        for {
          data <- backend.equipment.findAll(user.account, q.map(_.q))
          resp <- Ok(EquipmentList(data.map(mkEquipment).toList))
        } yield resp

      case req @ POST -> Root =>
        for {
          data  <- req.as[Equipment]
          equip <- newEquipment(data, user.account.collective)
          res   <- backend.equipment.add(equip)
          resp  <- Ok(basicResult(res, "Equipment created"))
        } yield resp

      case req @ PUT -> Root =>
        for {
          data <- req.as[Equipment]
          equip = changeEquipment(data, user.account.collective)
          res  <- backend.equipment.update(equip)
          resp <- Ok(basicResult(res, "Equipment updated."))
        } yield resp

      case DELETE -> Root / Ident(id) =>
        for {
          del  <- backend.equipment.delete(id, user.account.collective)
          resp <- Ok(basicResult(del, "Equipment deleted."))
        } yield resp
    }
  }
} 
Example 55
Source File: UserRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.routes

import cats.effect._
import cats.implicits._

import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken
import docspell.common.Ident
import docspell.restapi.model._
import docspell.restserver.conv.Conversions._
import docspell.restserver.http4s.ResponseGenerator

import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityDecoder._
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl

object UserRoutes {

  def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] with ResponseGenerator[F] {}
    import dsl._

    HttpRoutes.of {
      case req @ POST -> Root / "changePassword" =>
        for {
          data <- req.as[PasswordChange]
          res <- backend.collective.changePassword(
            user.account,
            data.currentPassword,
            data.newPassword
          )
          resp <- Ok(basicResult(res))
        } yield resp

      case GET -> Root =>
        for {
          all <- backend.collective.listUser(user.account.collective)
          res <- Ok(UserList(all.map(mkUser).toList))
        } yield res

      case req @ POST -> Root =>
        for {
          data  <- req.as[User]
          nuser <- newUser(data, user.account.collective)
          added <- backend.collective.add(nuser)
          resp  <- Ok(basicResult(added, "User created."))
        } yield resp

      case req @ PUT -> Root =>
        for {
          data <- req.as[User]
          nuser = changeUser(data, user.account.collective)
          update <- backend.collective.update(nuser)
          resp   <- Ok(basicResult(update, "User updated."))
        } yield resp

      case DELETE -> Root / Ident(id) =>
        for {
          ar   <- backend.collective.deleteUser(id, user.account.collective)
          resp <- Ok(basicResult(ar, "User deleted."))
        } yield resp
    }
  }

} 
Example 56
Source File: OrganizationRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.routes

import cats.effect._
import cats.implicits._

import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken
import docspell.common.Ident
import docspell.restapi.model._
import docspell.restserver.conv.Conversions._
import docspell.restserver.http4s.QueryParam

import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityDecoder._
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl

object OrganizationRoutes {

  def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] {}
    import dsl._

    HttpRoutes.of {
      case GET -> Root :? QueryParam.FullOpt(full) +& QueryParam.QueryOpt(q) =>
        if (full.getOrElse(false))
          for {
            data <- backend.organization.findAllOrg(user.account, q.map(_.q))
            resp <- Ok(OrganizationList(data.map(mkOrg).toList))
          } yield resp
        else
          for {
            data <- backend.organization.findAllOrgRefs(user.account, q.map(_.q))
            resp <- Ok(ReferenceList(data.map(mkIdName).toList))
          } yield resp

      case req @ POST -> Root =>
        for {
          data   <- req.as[Organization]
          newOrg <- newOrg(data, user.account.collective)
          added  <- backend.organization.addOrg(newOrg)
          resp   <- Ok(basicResult(added, "New organization saved."))
        } yield resp

      case req @ PUT -> Root =>
        for {
          data   <- req.as[Organization]
          upOrg  <- changeOrg(data, user.account.collective)
          update <- backend.organization.updateOrg(upOrg)
          resp   <- Ok(basicResult(update, "Organization updated."))
        } yield resp

      case DELETE -> Root / Ident(id) =>
        for {
          delOrg <- backend.organization.deleteOrg(id, user.account.collective)
          resp   <- Ok(basicResult(delOrg, "Organization deleted."))
        } yield resp
    }
  }

} 
Example 57
Source File: SourceRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.routes

import cats.effect._
import cats.implicits._

import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken
import docspell.common.Ident
import docspell.restapi.model._
import docspell.restserver.conv.Conversions._
import docspell.restserver.http4s.ResponseGenerator

import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityDecoder._
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl

object SourceRoutes {

  def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] with ResponseGenerator[F] {}
    import dsl._

    HttpRoutes.of {
      case GET -> Root =>
        for {
          all <- backend.source.findAll(user.account)
          res <- Ok(SourceList(all.map(mkSource).toList))
        } yield res

      case req @ POST -> Root =>
        for {
          data  <- req.as[Source]
          src   <- newSource(data, user.account.collective)
          added <- backend.source.add(src)
          resp  <- Ok(basicResult(added, "Source added."))
        } yield resp

      case req @ PUT -> Root =>
        for {
          data <- req.as[Source]
          src = changeSource(data, user.account.collective)
          updated <- backend.source.update(src)
          resp    <- Ok(basicResult(updated, "Source updated."))
        } yield resp

      case DELETE -> Root / Ident(id) =>
        for {
          del  <- backend.source.delete(id, user.account.collective)
          resp <- Ok(basicResult(del, "Source deleted."))
        } yield resp
    }
  }

} 
Example 58
Source File: PersonRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.routes

import cats.effect._
import cats.implicits._

import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken
import docspell.common.Ident
import docspell.common.syntax.all._
import docspell.restapi.model._
import docspell.restserver.conv.Conversions._
import docspell.restserver.http4s.QueryParam

import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityDecoder._
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl
import org.log4s._

object PersonRoutes {
  private[this] val logger = getLogger

  def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] {}
    import dsl._

    HttpRoutes.of {
      case GET -> Root :? QueryParam.FullOpt(full) +& QueryParam.QueryOpt(q) =>
        if (full.getOrElse(false))
          for {
            data <- backend.organization.findAllPerson(user.account, q.map(_.q))
            resp <- Ok(PersonList(data.map(mkPerson).toList))
          } yield resp
        else
          for {
            data <- backend.organization.findAllPersonRefs(user.account, q.map(_.q))
            resp <- Ok(ReferenceList(data.map(mkIdName).toList))
          } yield resp

      case req @ POST -> Root =>
        for {
          data   <- req.as[Person]
          newPer <- newPerson(data, user.account.collective)
          added  <- backend.organization.addPerson(newPer)
          resp   <- Ok(basicResult(added, "New person saved."))
        } yield resp

      case req @ PUT -> Root =>
        for {
          data   <- req.as[Person]
          upPer  <- changePerson(data, user.account.collective)
          update <- backend.organization.updatePerson(upPer)
          resp   <- Ok(basicResult(update, "Person updated."))
        } yield resp

      case DELETE -> Root / Ident(id) =>
        for {
          _      <- logger.fdebug(s"Deleting person ${id.id}")
          delOrg <- backend.organization.deletePerson(id, user.account.collective)
          resp   <- Ok(basicResult(delOrg, "Person deleted."))
        } yield resp
    }
  }

} 
Example 59
Source File: CheckFileRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.routes

import cats.effect._
import cats.implicits._

import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken
import docspell.common.Ident
import docspell.restapi.model.{BasicItem, CheckFileResult}
import docspell.restserver.http4s.ResponseGenerator
import docspell.store.records.RItem

import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl

object CheckFileRoutes {

  def secured[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] with ResponseGenerator[F] {}
    import dsl._

    HttpRoutes.of {
      case GET -> Root / checksum =>
        for {
          items <-
            backend.itemSearch.findByFileCollective(checksum, user.account.collective)
          resp <- Ok(convert(items))
        } yield resp

    }
  }

  def open[F[_]: Effect](backend: BackendApp[F]): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] with ResponseGenerator[F] {}
    import dsl._

    HttpRoutes.of {
      case GET -> Root / Ident(id) / checksum =>
        for {
          items <- backend.itemSearch.findByFileSource(checksum, id)
          resp  <- Ok(convert(items))
        } yield resp
    }
  }

  def convert(v: Vector[RItem]): CheckFileResult =
    CheckFileResult(
      v.nonEmpty,
      v.map(r => BasicItem(r.id, r.name, r.direction, r.state, r.created, r.itemDate))
        .toList
    )

} 
Example 60
Source File: JobQueueRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.routes

import cats.effect._
import cats.implicits._

import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken
import docspell.common.Ident
import docspell.restserver.conv.Conversions

import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl

object JobQueueRoutes {

  def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] {}
    import dsl._

    HttpRoutes.of {
      case GET -> Root / "state" =>
        for {
          js <- backend.job.queueState(user.account.collective, 200)
          res = Conversions.mkJobQueueState(js)
          resp <- Ok(res)
        } yield resp

      case POST -> Root / Ident(id) / "cancel" =>
        for {
          result <- backend.job.cancelJob(id, user.account.collective)
          resp   <- Ok(Conversions.basicResult(result))
        } yield resp
    }
  }

} 
Example 61
Source File: RegisterRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.routes

import cats.effect._
import cats.implicits._

import docspell.backend.BackendApp
import docspell.backend.ops.OCollective.RegisterData
import docspell.backend.signup.{NewInviteResult, SignupResult}
import docspell.restapi.model._
import docspell.restserver.Config
import docspell.restserver.http4s.ResponseGenerator

import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityDecoder._
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl
import org.log4s._

object RegisterRoutes {
  private[this] val logger = getLogger

  def apply[F[_]: Effect](backend: BackendApp[F], cfg: Config): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] with ResponseGenerator[F] {}
    import dsl._

    HttpRoutes.of {
      case req @ POST -> Root / "register" =>
        for {
          data <- req.as[Registration]
          res  <- backend.signup.register(cfg.backend.signup)(convert(data))
          resp <- Ok(convert(res))
        } yield resp

      case req @ POST -> Root / "newinvite" =>
        for {
          data <- req.as[GenInvite]
          res  <- backend.signup.newInvite(cfg.backend.signup)(data.password)
          resp <- Ok(convert(res))
        } yield resp
    }
  }

  def convert(r: NewInviteResult): InviteResult =
    r match {
      case NewInviteResult.Success(id) =>
        InviteResult(true, "New invitation created.", Some(id))
      case NewInviteResult.InvitationDisabled =>
        InviteResult(false, "Signing up is not enabled for invitations.", None)
      case NewInviteResult.PasswordMismatch =>
        InviteResult(false, "Password is invalid.", None)
    }

  def convert(r: SignupResult): BasicResult =
    r match {
      case SignupResult.CollectiveExists =>
        BasicResult(false, "A collective with this name already exists.")
      case SignupResult.InvalidInvitationKey =>
        BasicResult(false, "Invalid invitation key.")
      case SignupResult.SignupClosed =>
        BasicResult(false, "Sorry, registration is closed.")
      case SignupResult.Failure(ex) =>
        logger.error(ex)("Error signing up")
        BasicResult(false, s"Internal error: ${ex.getMessage}")
      case SignupResult.Success =>
        BasicResult(true, "Signup successful")
    }

  def convert(r: Registration): RegisterData =
    RegisterData(r.collectiveName, r.login, r.password, r.invite)
} 
Example 62
Source File: CollectiveRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.routes

import cats.effect._
import cats.implicits._

import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken
import docspell.backend.ops.OCollective
import docspell.restapi.model._
import docspell.restserver.conv.Conversions
import docspell.restserver.http4s._

import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityDecoder._
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl

object CollectiveRoutes {

  def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] with ResponseGenerator[F] {}
    import dsl._

    HttpRoutes.of {
      case GET -> Root / "insights" =>
        for {
          ins  <- backend.collective.insights(user.account.collective)
          resp <- Ok(Conversions.mkItemInsights(ins))
        } yield resp

      case req @ POST -> Root / "settings" =>
        for {
          settings <- req.as[CollectiveSettings]
          sett = OCollective.Settings(settings.language, settings.integrationEnabled)
          res <-
            backend.collective
              .updateSettings(user.account.collective, sett)
          resp <- Ok(Conversions.basicResult(res, "Settings updated."))
        } yield resp

      case GET -> Root / "settings" =>
        for {
          collDb <- backend.collective.find(user.account.collective)
          sett = collDb.map(c => CollectiveSettings(c.language, c.integrationEnabled))
          resp <- sett.toResponse()
        } yield resp

      case GET -> Root / "contacts" :? QueryParam.QueryOpt(q) +& QueryParam
            .ContactKindOpt(kind) =>
        for {
          res <-
            backend.collective
              .getContacts(user.account.collective, q.map(_.q), kind)
              .take(50)
              .compile
              .toList
          resp <- Ok(ContactList(res.map(Conversions.mkContact)))
        } yield resp

      case GET -> Root =>
        for {
          collDb <- backend.collective.find(user.account.collective)
          coll = collDb.map(c => Collective(c.id, c.state, c.created))
          resp <- coll.toResponse()
        } yield resp
    }
  }

} 
Example 63
Source File: WebjarRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.webapp

import cats.effect._

import org.http4s.HttpRoutes
import org.http4s.server.staticcontent.NoopCacheStrategy
import org.http4s.server.staticcontent.WebjarService.{Config => WebjarConfig, WebjarAsset}
import org.http4s.server.staticcontent.webjarService

object WebjarRoutes {

  def appRoutes[F[_]: Effect](
      blocker: Blocker
  )(implicit C: ContextShift[F]): HttpRoutes[F] =
    webjarService(
      WebjarConfig(
        filter = assetFilter,
        blocker = blocker,
        cacheStrategy = NoopCacheStrategy[F]
      )
    )

  def assetFilter(asset: WebjarAsset): Boolean =
    List(
      ".js",
      ".css",
      ".html",
      ".json",
      ".jpg",
      ".png",
      ".eot",
      ".woff",
      ".woff2",
      ".svg",
      ".otf",
      ".ttf",
      ".yml",
      ".xml"
    ).exists(e => asset.asset.endsWith(e))

} 
Example 64
Source File: TracedHttpRoute.scala    From http4s-tracer   with Apache License 2.0 5 votes vote down vote up
package dev.profunktor.tracer

import cats.Monad
import cats.data.{Kleisli, OptionT}
import cats.syntax.flatMap._
import cats.syntax.functor._
import dev.profunktor.tracer.Tracer.TraceId
import org.http4s.{HttpRoutes, Request, Response}

object TracedHttpRoute {
  case class TracedRequest[F[_]](traceId: TraceId, request: Request[F])

  def apply[F[_]: Monad: Tracer](
      pf: PartialFunction[TracedRequest[F], F[Response[F]]]
  ): HttpRoutes[F] =
    Kleisli[OptionT[F, ?], Request[F], Response[F]] { req =>
      OptionT {
        Tracer[F]
          .getTraceId(req)
          .map(x => TracedRequest[F](x.getOrElse(TraceId("-")), req))
          .flatMap { tr =>
            val rs: OptionT[F, Response[F]] = pf.andThen(OptionT.liftF(_)).applyOrElse(tr, Function.const(OptionT.none))
            rs.value
          }
      }

    }

}