org.http4s.Header Scala Examples

The following examples show how to use org.http4s.Header. 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: 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 2
Source File: MavenCentralClient.scala    From zorechka-bot   with MIT License 5 votes vote down vote up
package com.wix.zorechka.clients

import com.wix.zorechka.Dep
import org.http4s.{EntityDecoder, Header, Headers, Method, Request, Uri}
import zio.{Task, ZIO}
import zio.interop.catz._
import io.circe.generic.auto._
import org.http4s.circe.jsonOf
import org.http4s.client.Client

trait MavenCentralClient {
  val client: MavenCentralClient.Service
}

object MavenCentralClient {
  trait Service {
    def allVersions(dep: Dep): Task[List[Dep]]
  }

  trait Live extends MavenCentralClient {
    protected val httpClient: Client[Task]

    val client = new MavenCentralClient.Service {
      case class Response(response: InnerResponse)
      case class InnerResponse(docs: Seq[Document])
      case class Document(v: String)

      implicit val decoder: EntityDecoder[Task, Response] = jsonOf[Task, Response]

      override def allVersions(dep: Dep): Task[List[Dep]] = {
        ZIO.accessM {
          client =>
            val uri = Uri
              .unsafeFromString("http://search.maven.org/solrsearch/select")
              .withQueryParam("rows", "10")
              .withQueryParam("core", "gav")
              .withQueryParam("q", s""" g:"${dep.groupId}" AND a:"${dep.artifactId}" """)
            println(s"Maven search: ${uri.renderString}")

            val request = Request[Task](Method.GET, uri, headers = Headers.of(Header("Accept", "application/json")))

            httpClient.fetch(request)(response => response.as[Response]).map {
              _.response.docs.map(_.v).map(v => Dep(dep.groupId, dep.artifactId, v)).toList
            }
        }
      }
    }
  }
} 
Example 3
Source File: TracingClient.scala    From opencensus-scala   with Apache License 2.0 5 votes vote down vote up
package io.opencensus.scala.http4s

import cats.effect.{Effect, Resource}
import cats.implicits._
import io.opencensus.scala.Tracing
import io.opencensus.scala.http.propagation.Propagation
import io.opencensus.scala.http.{HttpAttributes => BaseHttpAttributes}
import io.opencensus.scala.http4s.HttpAttributes._
import io.opencensus.scala.http4s.TracingUtils.recordResponse
import io.opencensus.scala.http4s.propagation.Http4sFormatPropagation
import io.opencensus.trace.{Span, Status}
import org.http4s.client.Client
import org.http4s.{Header, Request, Response}

abstract class TracingClient[F[_]: Effect] {

  protected val tracing: Tracing
  protected val propagation: Propagation[Header, Request[F]]

  
  def trace(client: Client[F], parentSpan: Option[Span] = None): Client[F] = {
    val tracedOpen: Request[F] => Resource[F, Response[F]] =
      req =>
        for {
          span <- Resource.liftF(startSpan(parentSpan, req))
          enrichedReq = addTraceHeaders(req, span)
          res <- client
            .run(enrichedReq)
            .onError(traceError(span).andThen(x => Resource.liftF(x)))
        } yield recordResponse(span, tracing)(res)

    Client(tracedOpen)
  }

  private def traceError(span: Span): PartialFunction[Throwable, F[Unit]] = {
    case _ => recordException(span)
  }

  private def startSpan(parentSpan: Option[Span], req: Request[F]) =
    Effect[F].delay(startAndEnrichSpan(req, parentSpan))

  private def startAndEnrichSpan(
      req: Request[F],
      parentSpan: Option[Span]
  ): Span = {
    val name = req.uri.path.toString
    val span = parentSpan.fold(tracing.startSpan(name))(span =>
      tracing.startSpanWithParent(name, span)
    )
    BaseHttpAttributes.setAttributesForRequest(span, req)
    span
  }

  private def addTraceHeaders(request: Request[F], span: Span): Request[F] =
    request.withHeaders(
      request.headers.put(propagation.headersWithTracingContext(span): _*)
    )

  private def recordException(span: Span) =
    Effect[F].delay(tracing.endSpan(span, Status.INTERNAL))
}

object TracingClient {
  def apply[F[_]: Effect]: TracingClient[F] =
    new TracingClient[F] {
      override protected val tracing: Tracing = Tracing
      override protected val propagation: Propagation[Header, Request[F]] =
        new Http4sFormatPropagation[F] {}
    }
} 
Example 4
Source File: Http4sAttributesSpec.scala    From opencensus-scala   with Apache License 2.0 5 votes vote down vote up
package io.opencensus.scala.http4s

import cats.Id
import io.opencensus.scala.http.HttpAttributesSpec
import org.http4s.{Header, Headers, Request, Response, Status, Uri}

class Http4sAttributesSpec extends HttpAttributesSpec {
  import HttpAttributes._

  "Http4s attributes extraction" should behave like httpAttributes(
    request,
    response
  )

  def request: BuildRequest => Request[Id] =
    (request: BuildRequest) =>
      Request[Id](
        uri = Uri.unsafeFromString(request.host ++ request.path),
        headers = Headers(
          List(Header("User-Agent", request.userAgent)) ++ request.hostHeader
            .map(Header("Host", _))
        )
      )

  def response: Int => Response[Id] =
    (code: Int) => Response[Id](Status(code))
} 
Example 5
Source File: Github.scala    From sonar-scala   with GNU Lesser General Public License v3.0 5 votes vote down vote up
package com.mwz.sonar.scala
package pr
package github

import cats.effect.Sync
import cats.syntax.flatMap._
import com.mwz.sonar.scala.pr.github.Codec._
import io.circe.generic.auto._
import mouse.boolean._
import org.http4s.client.Client
import org.http4s.{Header, Headers, Method, Request, Uri}

trait Github[F[_]] {
  def authenticatedUser: F[User]
  def pullRequest: F[PullRequest]
  def comments: F[List[Comment]]
  def createComment(comment: NewComment): F[Unit]
  def files: F[List[File]]
  def createStatus(sha: String, status: NewStatus): F[Unit]
}

object Github {
  def apply[F[_]: Sync](client: Client[F], pr: GlobalConfig.PullRequest): Github[F] =
    new Github[F] {
      val auth: Header = Header("Authorization", s"token ${pr.github.oauth}")
      val userUri: Uri = pr.github.apiuri / "user"
      val prUri: Uri =
        (pr.github.apiuri / "repos").addPath(pr.github.repository) / "pulls" / pr.prNumber
      val commentsUri: Uri = prUri / "comments"
      val filesUri: Uri = prUri / "files"
      def newStatusUri(sha: String): Uri =
        (pr.github.apiuri / "repos").addPath(pr.github.repository) / "statuses" / sha
      def request(uri: Uri): Request[F] = {
        Request[F](
          uri = uri,
          headers = Headers.of(auth)
        )
      }
      def authenticatedUser: F[User] = client.expect[User](request(userUri))
      def pullRequest: F[PullRequest] = client.expect[PullRequest](request(prUri))
      def comments: F[List[Comment]] = client.expect[List[Comment]](request(commentsUri))
      def createComment(comment: NewComment): F[Unit] = {
        val request: F[Request[F]] = Sync[F].pure(
          Request(Method.POST, commentsUri, headers = Headers.of(auth))
            .withEntity(comment)
        )
        pr.dryRun.fold(Sync[F].unit, client.expect[Comment](request) >> Sync[F].unit)
      }
      def files: F[List[File]] = client.expect[List[File]](request(filesUri))
      def createStatus(sha: String, status: NewStatus): F[Unit] = {
        val request: F[Request[F]] = Sync[F].pure(
          Request(Method.POST, newStatusUri(sha), headers = Headers.of(auth))
            .withEntity(status)
        )
        pr.dryRun.fold(Sync[F].unit, client.expect[Status](request) >> Sync[F].unit)
      }
    }
} 
Example 6
Source File: JdkHttpClient.scala    From http4s-jdk-http-client   with Apache License 2.0 5 votes vote down vote up
package org.http4s.client.jdkhttpclient

import java.net.URI
import java.net.http.HttpRequest.BodyPublishers
import java.net.http.HttpResponse.BodyHandlers
import java.net.http.{HttpClient, HttpRequest, HttpResponse}
import java.nio.ByteBuffer
import java.util
import java.util.concurrent.Flow

import cats.ApplicativeError
import cats.effect._
import cats.implicits._
import fs2.concurrent.SignallingRef
import fs2.interop.reactivestreams._
import fs2.{Chunk, Stream}
import org.http4s.client.Client
import org.http4s.client.jdkhttpclient.compat.CollectionConverters._
import org.http4s.internal.fromCompletionStage
import org.http4s.util.CaseInsensitiveString
import org.http4s.{Header, Headers, HttpVersion, Request, Response, Status}
import org.reactivestreams.FlowAdapters

object JdkHttpClient {

  
  def simple[F[_]](implicit F: ConcurrentEffect[F], CS: ContextShift[F]): F[Client[F]] =
    F.delay(HttpClient.newHttpClient()).map(apply(_))

  def convertHttpVersionFromHttp4s[F[_]](
      version: HttpVersion
  )(implicit F: ApplicativeError[F, Throwable]): F[HttpClient.Version] =
    version match {
      case HttpVersion.`HTTP/1.1` => HttpClient.Version.HTTP_1_1.pure[F]
      case HttpVersion.`HTTP/2.0` => HttpClient.Version.HTTP_2.pure[F]
      case _ => F.raiseError(new IllegalArgumentException("invalid HTTP version"))
    }

  // see jdk.internal.net.http.common.Utils#DISALLOWED_HEADERS_SET
  private val restrictedHeaders =
    Set(
      "connection",
      "content-length",
      "date",
      "expect",
      "from",
      "host",
      "upgrade",
      "via",
      "warning"
    ).map(CaseInsensitiveString(_))
} 
Example 7
Source File: package.scala    From temperature-machine   with Apache License 2.0 5 votes vote down vote up
package bad.robot.temperature

import cats.effect.IO
import org.http4s.{Header, Response, Status}
import org.specs2.matcher.{Expectable, MatchResult, Matcher}

package object test {

  def haveStatus(status: Status) = new Matcher[Response[IO]] {
    def apply[S <: Response[IO]](e: Expectable[S]): MatchResult[S] = result(
      e.value.status == status,
      s"""Status of [${e.value.status}]
          |
          |is
          |
          |$status""".stripMargin,
      s"""Status of
          |[${e.value.status}]
          |
          |is not
          |
          |[$status]
          |
          |(${e.value.as[String]})""".stripMargin,
      e)
  }

  def containsHeader(name: String, value: String) = new Matcher[Response[IO]] {
    def apply[S <: Response[IO]](e: Expectable[S]): MatchResult[S] = result(
      e.value.headers.toList.map(_.toRaw) contains Header(name, value),
      s"""${e.value.headers}
          |
          |contains
          |
          |$name""".stripMargin,
      s"""The response headers '${e.value.headers.toList.mkString("\n")}'
          |
          |do not contain
          |
          |[$name: $value]
          |""".stripMargin,
      e)
  }
} 
Example 8
Source File: ResponseGenerator.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.http4s

import cats.Applicative
import cats.implicits._

import org.http4s.dsl.Http4sDsl
import org.http4s.{EntityEncoder, Header, Response}

trait ResponseGenerator[F[_]] {
  self: Http4sDsl[F] =>

  implicit final class EitherResponses[A, B](e: Either[A, B]) {
    def toResponse(headers: Header*)(implicit
        F: Applicative[F],
        w0: EntityEncoder[F, A],
        w1: EntityEncoder[F, B]
    ): F[Response[F]] =
      e.fold(
        a => UnprocessableEntity(a),
        b => Ok(b)
      ).map(_.withHeaders(headers: _*))
  }

  implicit final class OptionResponse[A](o: Option[A]) {
    def toResponse(
        headers: Header*
    )(implicit F: Applicative[F], w0: EntityEncoder[F, A]): F[Response[F]] =
      o.map(a => Ok(a)).getOrElse(NotFound()).map(_.withHeaders(headers: _*))
  }

}

object ResponseGenerator {} 
Example 9
Source File: Tracer.scala    From http4s-tracer   with Apache License 2.0 5 votes vote down vote up
package dev.profunktor.tracer

import cats.Applicative
import cats.data.Kleisli
import cats.effect.Sync
import cats.syntax.all._
import org.http4s.syntax.StringSyntax
import org.http4s.{Header, HttpApp, Request}


object Tracer extends StringSyntax {

  private[tracer] val DefaultTraceIdHeader = "Trace-Id"

  final case class TraceId(value: String) extends AnyVal {
    override def toString = s"[Trace-Id] - [$value]"
  }

  def apply[F[_]](implicit ev: Tracer[F]): Tracer[F] = ev

  def create[F[_]](headerName: String = DefaultTraceIdHeader): Tracer[F] = new Tracer[F](headerName)

}

class Tracer[F[_]] private (headerName: String) {

  import Trace._, Tracer._

  def middleware(
      http: HttpApp[F],
      logRequest: Boolean = false,
      logResponse: Boolean = false
  )(implicit F: Sync[F], L: TracerLog[Trace[F, ?]]): HttpApp[F] =
    Kleisli { req =>
      val createId: F[(Request[F], TraceId)] =
        for {
          id <- GenUUID.make[F]
          tr <- F.delay(req.putHeaders(Header(headerName, id.value)))
        } yield (tr, id)

      for {
        mi       <- getTraceId(req)
        (tr, id) <- mi.fold(createId)(id => (req, id).pure[F])
        _        <- if (logRequest) L.info[Tracer[F]](s"$req").run(id) else F.unit
        rs       <- http(tr).map(_.putHeaders(Header(headerName, id.value)))
        _        <- if (logResponse) L.info[Tracer[F]](s"$rs").run(id) else F.unit
      } yield rs
    }

  def loggingMiddleware(
      http: HttpApp[F]
  )(implicit F: Sync[F], L: TracerLog[Trace[F, ?]]): HttpApp[F] =
    middleware(http, logRequest = true, logResponse = true)

  def getTraceId(request: Request[F])(implicit F: Applicative[F]): F[Option[TraceId]] =
    F.pure(request.headers.get(headerName.ci).map(h => TraceId(h.value)))

} 
Example 10
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 11
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 12
Source File: Paginate.scala    From github   with MIT License 5 votes vote down vote up
package io.chrisdavenport.github.endpoints.utils

import org.http4s.{Header, Uri}
import cats.implicits._


  def paginate(uri: Uri, numPages: Int): Map[Int, Header] = {
    (1 to numPages).map { currentPage =>
      currentPage -> Header(
        "Link",
        List(
          prevPage(uri, currentPage),
          nextPage(uri, numPages, currentPage),
          lastPage(uri, numPages, currentPage),
          firstPage(uri, currentPage)
        )
          .flatten
          .map { case (uri, rel) =>
            s""" <${uri.toString}>; rel="$rel""""
          }. mkString(",")
      )
    }.toMap
  }

  private def firstPage(uri: Uri, page: Int): Option[(Uri, String)] =
    Option(page)
      .filterNot(_ == 1)
      .as((uri.withQueryParam[String, String]("page", "1"), "first"))

  private def prevPage(uri: Uri, page: Int): Option[(Uri, String)] =
    Option(page)
      .filterNot(_ == 1)
      .map { p =>
        (uri.withQueryParam[String, String]("page", (p - 1).toString), "prev")
      }

  private def nextPage(uri: Uri, numPages: Int, page: Int): Option[(Uri, String)] =
    Option(page)
      .filterNot(_ == numPages)
      .map { p =>
        (uri.withQueryParam[String, String]("page", (p + 1).toString), "next")
      }

  private def lastPage(uri: Uri, numPages: Int, page: Int): Option[(Uri, String)] =
    Option(page)
      .filterNot(_ == numPages)
      .as((uri.withQueryParam[String, String]("page", numPages.toString), "last"))

} 
Example 13
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 14
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 15
Source File: KmsCommand.scala    From aws4s   with MIT License 5 votes vote down vote up
package org.aws4s.kms

import cats.effect.Effect
import cats.implicits._
import io.circe.{Decoder, Json}
import org.aws4s.core.ExtraEntityDecoderInstances._
import org.aws4s._
import org.aws4s.core.{Command, CommandPayload, RenderedParam, ServiceName}
import org.http4s.circe._
import org.http4s.headers.{Host, `Content-Type`}
import org.http4s.{Header, Headers, MediaType, Method, Request, Uri}

private[kms] abstract class KmsCommand[F[_]: Effect, R: Decoder] extends Command[F, Json, R] {
  override def serviceName:    ServiceName    = ServiceName.Kms
  override def payloadSigning: PayloadSigning = PayloadSigning.Signed

  def action: String

  override final val requestGenerator: List[RenderedParam[Json]] => F[Request[F]] = { params =>
    val host = s"kms.${region.name}.amazonaws.com"
    val payload: Json = CommandPayload.jsonObject(params)
    Request[F](
      Method.POST,
      Uri.unsafeFromString(s"https://$host/"),
      headers = Headers(Header("X-Amz-Target", s"TrentService.$action"), Host(host))
    ).withBody(payload).map(_.withContentType(`Content-Type`.apply(MediaType.fromKey(("application", "x-amz-json-1.1")))))
  }
} 
Example 16
Source File: DynamoDbCommand.scala    From aws4s   with MIT License 5 votes vote down vote up
package org.aws4s.dynamodb

import cats.effect.Effect
import io.circe.{Decoder, Json}
import org.aws4s.PayloadSigning
import org.http4s.headers.{Host, `Content-Type`}
import org.http4s.{Header, Headers, MediaType, Method, Request, Uri}
import org.http4s.circe._
import cats.implicits._
import org.aws4s.core.ExtraEntityDecoderInstances._
import org.aws4s.core.{Command, CommandPayload, RenderedParam, ServiceName}

private[dynamodb] abstract class DynamoDbCommand[F[_]: Effect, R: Decoder] extends Command[F, Json, R] {
  override def serviceName:    ServiceName    = ServiceName.DynamoDb
  override def payloadSigning: PayloadSigning = PayloadSigning.Signed

  def action: String

  override final val requestGenerator: List[RenderedParam[Json]] => F[Request[F]] = params => {
    val host = s"dynamodb.${region.name}.amazonaws.com"
    val payload: Json = CommandPayload.jsonObject(params)
    Request[F](
      Method.POST,
      Uri.unsafeFromString(s"https://$host/"),
      headers = Headers(Header("X-Amz-Target", s"DynamoDB_20120810.$action"), Host(host))
    ).withBody(payload).map(_.withContentType(`Content-Type`.apply(MediaType.fromKey(("application", "x-amz-json-1.0")))))
  }
}