diff --git a/example/src/main/scala/example/Endpoints.scala b/example/src/main/scala/example/Endpoints.scala deleted file mode 100644 index 298ffbdcb3..0000000000 --- a/example/src/main/scala/example/Endpoints.scala +++ /dev/null @@ -1,25 +0,0 @@ -package example - -import zhttp.endpoint._ -import zhttp.http.Method.GET -import zhttp.http.Response -import zhttp.service.Server -import zio.{App, ExitCode, UIO, URIO} - -object Endpoints extends App { - def h1 = GET / "a" / *[Int] / "b" / *[Boolean] to { a => - Response.text(a.params.toString) - } - - def h2 = GET / "b" / *[Int] / "b" / *[Boolean] to { a => - Response.text(a.params.toString) - } - - def h3 = GET / "b" / *[Int] / "c" / *[Boolean] to { a => - UIO(Response.text(a.params.toString)) - } - - // Run it like any simple app - override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] = - Server.start(8091, (h3 ++ h2 ++ h1)).exitCode -} diff --git a/zio-http/src/main/scala/zhttp/endpoint/CanCombine.scala b/zio-http/src/main/scala/zhttp/endpoint/CanCombine.scala deleted file mode 100644 index 6686dfc312..0000000000 --- a/zio-http/src/main/scala/zhttp/endpoint/CanCombine.scala +++ /dev/null @@ -1,35 +0,0 @@ -package zhttp.endpoint - -sealed trait CanCombine[A, B] { - type Out -} - -object CanCombine { - type Aux[A, B, C] = CanCombine[A, B] { - type Out = C - } - - // scalafmt: { maxColumn = 1200 } - implicit def combine0[A, B](implicit ev: A =:= Unit): CanCombine.Aux[A, B, B] = null - implicit def combine1[A, B](implicit evA: CanExtract[A], evB: CanExtract[B]): CanCombine.Aux[A, B, (A, B)] = null - implicit def combine2[A, B, T1, T2](implicit evA: A =:= (T1, T2), evB: CanExtract[B]): CanCombine.Aux[A, B, (T1, T2, B)] = null - implicit def combine3[A, B, T1, T2, T3](implicit evA: A =:= (T1, T2, T3), evB: CanExtract[B]): CanCombine.Aux[A, B, (T1, T2, T3, B)] = null - implicit def combine4[A, B, T1, T2, T3, T4](implicit evA: A =:= (T1, T2, T3, T4), evB: CanExtract[B]): CanCombine.Aux[A, B, (T1, T2, T3, T4, B)] = null - implicit def combine5[A, B, T1, T2, T3, T4, T5](implicit evA: A =:= (T1, T2, T3, T4, T5), evB: CanExtract[B]): CanCombine.Aux[A, B, (T1, T2, T3, T4, T5, B)] = null - implicit def combine6[A, B, T1, T2, T3, T4, T5, T6](implicit evA: A =:= (T1, T2, T3, T4, T5, T6), evB: CanExtract[B]): CanCombine.Aux[A, B, (T1, T2, T3, T4, T5, T6, B)] = null - implicit def combine7[A, B, T1, T2, T3, T4, T5, T6, T7](implicit evA: A =:= (T1, T2, T3, T4, T5, T6, T7), evB: CanExtract[B]): CanCombine.Aux[A, B, (T1, T2, T3, T4, T5, T6, T7, B)] = null - implicit def combine8[A, B, T1, T2, T3, T4, T5, T6, T7, T8](implicit evA: A =:= (T1, T2, T3, T4, T5, T6, T7, T8), evB: CanExtract[B]): CanCombine.Aux[A, B, (T1, T2, T3, T4, T5, T6, T7, T8, B)] = null - implicit def combine9[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9](implicit evA: A =:= (T1, T2, T3, T4, T5, T6, T7, T8, T9), evB: CanExtract[B]): CanCombine.Aux[A, B, (T1, T2, T3, T4, T5, T6, T7, T8, T9, B)] = null - implicit def combine10[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10](implicit evA: A =:= (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10), evB: CanExtract[B]): CanCombine.Aux[A, B, (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, B)] = null - implicit def combine11[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11](implicit evA: A =:= (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11), evB: CanExtract[B]): CanCombine.Aux[A, B, (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, B)] = null - implicit def combine12[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12](implicit evA: A =:= (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12), evB: CanExtract[B]): CanCombine.Aux[A, B, (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, B)] = null - implicit def combine13[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13](implicit evA: A =:= (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13), evB: CanExtract[B]): CanCombine.Aux[A, B, (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, B)] = null - implicit def combine14[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14](implicit evA: A =:= (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14), evB: CanExtract[B]): CanCombine.Aux[A, B, (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, B)] = null - implicit def combine15[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15](implicit evA: A =:= (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15), evB: CanExtract[B]): CanCombine.Aux[A, B, (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, B)] = null - implicit def combine16[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16](implicit evA: A =:= (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16), evB: CanExtract[B]): CanCombine.Aux[A, B, (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, B)] = null - implicit def combine17[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17](implicit evA: A =:= (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17), evB: CanExtract[B]): CanCombine.Aux[A, B, (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, B)] = null - implicit def combine18[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18](implicit evA: A =:= (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18), evB: CanExtract[B]): CanCombine.Aux[A, B, (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, B)] = null - implicit def combine19[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19](implicit evA: A =:= (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19), evB: CanExtract[B]): CanCombine.Aux[A, B, (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, B)] = null - implicit def combine20[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20](implicit evA: A =:= (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20), evB: CanExtract[B]): CanCombine.Aux[A, B, (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, B)] = null - implicit def combine21[A, B, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21](implicit evA: A =:= (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21), evB: CanExtract[B]): CanCombine.Aux[A, B, (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, B)] = null -} diff --git a/zio-http/src/main/scala/zhttp/endpoint/CanConstruct.scala b/zio-http/src/main/scala/zhttp/endpoint/CanConstruct.scala deleted file mode 100644 index c3e276b826..0000000000 --- a/zio-http/src/main/scala/zhttp/endpoint/CanConstruct.scala +++ /dev/null @@ -1,53 +0,0 @@ -package zhttp.endpoint - -import zhttp.http.{Http, HttpApp, Request, Response} -import zio.ZIO - -/** - * Constructors to make an HttpApp using an Endpoint - */ -sealed trait CanConstruct[A, B] { - type ROut - type EOut - def make(route: Endpoint[A], f: Request.ParameterizedRequest[A] => B): HttpApp[ROut, EOut] -} - -object CanConstruct { - type Aux[R, E, A, B] = CanConstruct[A, B] { - type ROut = R - type EOut = E - } - - implicit def response[A]: Aux[Any, Nothing, A, Response] = new CanConstruct[A, Response] { - override type ROut = Any - override type EOut = Nothing - - override def make(route: Endpoint[A], f: Request.ParameterizedRequest[A] => Response): HttpApp[Any, Nothing] = - Http - .collectHttp[Request] { case req => - route.extract(req) match { - case Some(value) => Http.succeed(f(Request.ParameterizedRequest(req, value))) - case None => Http.empty - } - } - } - - implicit def responseZIO[R, E, A]: Aux[R, E, A, ZIO[R, E, Response]] = - new CanConstruct[A, ZIO[R, E, Response]] { - override type ROut = R - override type EOut = E - - override def make( - route: Endpoint[A], - f: Request.ParameterizedRequest[A] => ZIO[R, E, Response], - ): HttpApp[R, E] = { - Http - .collectHttp[Request] { case req => - route.extract(req) match { - case Some(value) => Http.fromZIO(f(Request.ParameterizedRequest(req, value))) - case None => Http.empty - } - } - } - } -} diff --git a/zio-http/src/main/scala/zhttp/endpoint/CanExtract.scala b/zio-http/src/main/scala/zhttp/endpoint/CanExtract.scala deleted file mode 100644 index fdde97182e..0000000000 --- a/zio-http/src/main/scala/zhttp/endpoint/CanExtract.scala +++ /dev/null @@ -1,18 +0,0 @@ -package zhttp.endpoint - -import scala.util.Try - -trait CanExtract[+A] { - def parse(data: String): Option[A] -} -object CanExtract { - implicit object IntImpl extends CanExtract[Int] { - override def parse(data: String): Option[Int] = Try(data.toInt).toOption - } - implicit object StringImpl extends CanExtract[String] { - override def parse(data: String): Option[String] = Option(data) - } - implicit object BooleanImpl extends CanExtract[Boolean] { - override def parse(data: String): Option[Boolean] = Try(data.toBoolean).toOption - } -} diff --git a/zio-http/src/main/scala/zhttp/endpoint/Endpoint.scala b/zio-http/src/main/scala/zhttp/endpoint/Endpoint.scala deleted file mode 100644 index 5263a78ef2..0000000000 --- a/zio-http/src/main/scala/zhttp/endpoint/Endpoint.scala +++ /dev/null @@ -1,52 +0,0 @@ -package zhttp.endpoint - -import zhttp.http._ - -/** - * Description of an Http endpoint containing a Method and a ParameterList - */ -final case class Endpoint[A](method: Method, params: ParameterList[A]) { self => - - /** - * Appends a string literal to the endpoint - */ - def /(name: String): Endpoint[A] = Endpoint(self.method, name :: self.params) - - /** - * Appends the parameter to the endpoint - */ - def /[B, C](other: Parameter[B])(implicit ev: CanCombine.Aux[A, B, C]): Endpoint[C] = - Endpoint(self.method, other :: self.params) - - /** - * Creates an HttpApp from a Request to Response function - */ - def to[B](f: Request.ParameterizedRequest[A] => B)(implicit ctor: CanConstruct[A, B]): HttpApp[ctor.ROut, ctor.EOut] = - ctor.make(self, f) - - private[zhttp] def extract(path: Path): Option[A] = Endpoint.extract(path, self) - private[zhttp] def extract(request: Request): Option[A] = Endpoint.extract(request, self) -} - -private[zhttp] object Endpoint { - - def unit[A]: Option[A] = Option(().asInstanceOf[A]) - - /** - * Create Route[Unit] from a Method - */ - def fromMethod(method: Method): Endpoint[Unit] = Endpoint(method, ParameterList.Empty) - - /** - * Extracts the parameter list from the given path - */ - private[zhttp] def extract[A](path: Path, route: Endpoint[A]): Option[A] = route.params.extract(path) - - /** - * Extracts the parameter list from the given request - */ - private[zhttp] def extract[A](request: Request, self: Endpoint[A]): Option[A] = - if (self.method == request.method) { self.extract(request.path) } - else None - -} diff --git a/zio-http/src/main/scala/zhttp/endpoint/Parameter.scala b/zio-http/src/main/scala/zhttp/endpoint/Parameter.scala deleted file mode 100644 index 5c1ca4ee11..0000000000 --- a/zio-http/src/main/scala/zhttp/endpoint/Parameter.scala +++ /dev/null @@ -1,19 +0,0 @@ -package zhttp.endpoint - -sealed trait Parameter[+A] { self => - def parse(string: String): Option[A] = Parameter.extract(self, string) -} -object Parameter { - private[zhttp] final case class Literal(s: String) extends Parameter[Unit] - private[zhttp] final case class Param[A](r: CanExtract[A]) extends Parameter[A] - - private[zhttp] def extract[A](rt: Parameter[A], string: String): Option[A] = rt match { - case Parameter.Literal(s) => if (s == string) Endpoint.unit else None - case Parameter.Param(r) => r.parse(string) - } - - /** - * Creates a new Parameter placeholder for an Endpoint - */ - def apply[A](implicit ev: CanExtract[A]): Parameter[A] = Parameter.Param(ev) -} diff --git a/zio-http/src/main/scala/zhttp/endpoint/ParameterList.scala b/zio-http/src/main/scala/zhttp/endpoint/ParameterList.scala deleted file mode 100644 index 11383a453d..0000000000 --- a/zio-http/src/main/scala/zhttp/endpoint/ParameterList.scala +++ /dev/null @@ -1,43 +0,0 @@ -package zhttp.endpoint - -import zhttp.endpoint.Parameter.Literal -import zhttp.http.Path - -import scala.annotation.tailrec - -/** - * A special type-safe data structure that holds a list of Endpoint Parameters. - */ -sealed trait ParameterList[+A] { self => - private[zhttp] def extract(path: Path): Option[A] = ParameterList.extract(self, path.toList.reverse) - - def ::[A1 >: A, B, C](head: Parameter[B])(implicit ev: CanCombine.Aux[A1, B, C]): ParameterList[C] = - ParameterList.Cons(head, self) - - def ::(literal: String): ParameterList[A] = ParameterList.Cons(Literal(literal), self) -} -object ParameterList { - private[zhttp] case object Empty extends ParameterList[Unit] - private[zhttp] final case class Cons[A, B, C](head: Parameter[B], tail: ParameterList[A]) extends ParameterList[C] - - def empty: ParameterList[Unit] = Empty - - private def extract[A](r: ParameterList[A], p: List[String]): Option[A] = { - @tailrec - def loop(r: ParameterList[Any], p: List[String], output: List[Any]): Option[Any] = { - r match { - case Empty => TupleBuilder(output) - case Cons(head, tail) => - if (p.isEmpty) None - else { - head.parse(p.head) match { - case Some(value) => - if (value.isInstanceOf[Unit]) loop(tail, p.tail, output) else loop(tail, p.tail, value :: output) - case None => None - } - } - } - } - loop(r.asInstanceOf[ParameterList[Any]], p, List.empty[Any]).asInstanceOf[Option[A]] - } -} diff --git a/zio-http/src/main/scala/zhttp/endpoint/TupleBuilder.scala b/zio-http/src/main/scala/zhttp/endpoint/TupleBuilder.scala deleted file mode 100644 index 9874ea6793..0000000000 --- a/zio-http/src/main/scala/zhttp/endpoint/TupleBuilder.scala +++ /dev/null @@ -1,37 +0,0 @@ -package zhttp.endpoint - -object TupleBuilder { - - /** - * Utility to create Tuple from a list - */ - def apply(input: List[Any]): Option[Any] = - input match { - // scalafmt: { maxColumn = 1200 } - case List() => Some(()) - case List(a0) => Some(a0) - case List(a0, a1) => Some((a0, a1)) - case List(a0, a1, a2) => Some((a0, a1, a2)) - case List(a0, a1, a2, a3) => Some((a0, a1, a2, a3)) - case List(a0, a1, a2, a3, a4) => Some((a0, a1, a2, a3, a4)) - case List(a0, a1, a2, a3, a4, a5) => Some((a0, a1, a2, a3, a4, a5)) - case List(a0, a1, a2, a3, a4, a5, a6) => Some((a0, a1, a2, a3, a4, a5, a6)) - case List(a0, a1, a2, a3, a4, a5, a6, a7) => Some((a0, a1, a2, a3, a4, a5, a6, a7)) - case List(a0, a1, a2, a3, a4, a5, a6, a7, a8) => Some((a0, a1, a2, a3, a4, a5, a6, a7, a8)) - case List(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) => Some((a0, a1, a2, a3, a4, a5, a6, a7, a8, a9)) - case List(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) => Some((a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10)) - case List(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) => Some((a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11)) - case List(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) => Some((a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12)) - case List(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13) => Some((a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13)) - case List(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14) => Some((a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14)) - case List(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) => Some((a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15)) - case List(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16) => Some((a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16)) - case List(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17) => Some((a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17)) - case List(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18) => Some((a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18)) - case List(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19) => Some((a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19)) - case List(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) => Some((a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)) - case List(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21) => Some((a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21)) - case _ => None - } - // scalafmt: { maxColumn = 120 } -} diff --git a/zio-http/src/main/scala/zhttp/endpoint/package.scala b/zio-http/src/main/scala/zhttp/endpoint/package.scala deleted file mode 100644 index 0f976923d2..0000000000 --- a/zio-http/src/main/scala/zhttp/endpoint/package.scala +++ /dev/null @@ -1,20 +0,0 @@ -package zhttp - -import zhttp.http.Method - -package object endpoint { - - /** - * Extends Http Method to support syntax to create endpoints. - */ - implicit class EndpointSyntax(method: Method) { - def /(name: String): Endpoint[Unit] = Endpoint.fromMethod(method) / name - def /[A](token: Parameter[A])(implicit ev: CanCombine.Aux[Unit, A, A]): Endpoint[A] = - Endpoint.fromMethod(method) / token - } - - /** - * Alias to `Parameter[A]` - */ - final def *[A](implicit ev: CanExtract[A]): Parameter[A] = Parameter[A] -} diff --git a/zio-http/src/main/scala/zhttp/http/Http.scala b/zio-http/src/main/scala/zhttp/http/Http.scala index 05b5c05e00..23cee59b95 100644 --- a/zio-http/src/main/scala/zhttp/http/Http.scala +++ b/zio-http/src/main/scala/zhttp/http/Http.scala @@ -1,7 +1,7 @@ package zhttp.http import io.netty.buffer.{ByteBuf, ByteBufUtil} -import io.netty.channel.ChannelHandler +import io.netty.channel.{ChannelHandler, ChannelHandlerContext} import io.netty.handler.codec.http.HttpHeaderNames import zhttp.html._ import zhttp.http.headers.HeaderModifier @@ -12,8 +12,9 @@ import zio.clock.Clock import zio.duration.Duration import zio.stream.ZStream -import java.io.File +import java.io.{File, IOException} import java.net +import java.net.{InetAddress, InetSocketAddress} import java.nio.charset.Charset import java.nio.file.Paths import scala.annotation.unused @@ -740,6 +741,11 @@ object Http { def combine[R, E, A, B](i: Iterable[Http[R, E, A, B]]): Http[R, E, A, B] = i.reduce(_.defaultWith(_)) + /** + * Provides access to the request's ChannelHandlerContext + */ + def context: Http[Any, Nothing, Request, ChannelHandlerContext] = Http.fromFunction[Request](_.unsafeContext) + /** * Returns an http app that dies with the specified `Throwable`. This method * can be used for terminating an app because a defect has been detected in @@ -947,6 +953,17 @@ object Http { */ def ok: HttpApp[Any, Nothing] = status(Status.Ok) + /** + * Provides access to the request's remote address + */ + def remoteAddress: Http[Any, IOException, Request, InetAddress] = + context flatMap { ctx => + ctx.channel().remoteAddress() match { + case m: InetSocketAddress => Http.succeed(m.getAddress) + case _ => Http.fail(new IOException("Unable to get remote address")) + } + } + /** * Creates an Http app which always responds with the same value. */ @@ -997,6 +1014,12 @@ object Http { */ def tooLarge: HttpApp[Any, Nothing] = Http.status(Status.RequestEntityTooLarge) + /** + * Provides low level access to an HttpApp to perform unsafe operations using + * the request's ChannelHandlerContext. + */ + def usingContext[R, E](f: ChannelHandlerContext => HttpApp[R, E]): HttpApp[R, E] = context.flatMap(f(_)) + // Ctor Help final case class PartialCollectZIO[A](unit: Unit) extends AnyVal { def apply[R, E, B](pf: PartialFunction[A, ZIO[R, E, B]]): Http[R, E, A, B] = diff --git a/zio-http/src/main/scala/zhttp/http/Request.scala b/zio-http/src/main/scala/zhttp/http/Request.scala index 7ec1a4cac6..5c793c7ed8 100644 --- a/zio-http/src/main/scala/zhttp/http/Request.scala +++ b/zio-http/src/main/scala/zhttp/http/Request.scala @@ -1,16 +1,22 @@ package zhttp.http +import io.netty.channel.ChannelHandlerContext import io.netty.handler.codec.http.{DefaultFullHttpRequest, HttpRequest} import zhttp.http.headers.HeaderExtension -import java.net.InetAddress +import java.io.IOException trait Request extends HeaderExtension[Request] with HttpDataExtension[Request] { self => /** - * Updates the headers using the provided function + * Accesses the channel's context for more low level control */ - final override def updateHeaders(update: Headers => Headers): Request = self.copy(headers = update(self.headers)) + private[zhttp] def unsafeContext: ChannelHandlerContext + + /** + * Gets the HttpRequest + */ + private[zhttp] def unsafeEncode: HttpRequest def copy( version: Version = self.version, @@ -23,13 +29,13 @@ trait Request extends HeaderExtension[Request] with HttpDataExtension[Request] { val h = headers val v = version new Request { - override def method: Method = m - override def url: URL = u - override def headers: Headers = h - override def version: Version = v - override def unsafeEncode: HttpRequest = self.unsafeEncode - override def remoteAddress: Option[InetAddress] = self.remoteAddress - override def data: HttpData = self.data + override def method: Method = m + override def url: URL = u + override def headers: Headers = h + override def version: Version = v + override def unsafeEncode: HttpRequest = self.unsafeEncode + override def data: HttpData = self.data + override def unsafeContext: ChannelHandlerContext = self.unsafeContext } } @@ -59,47 +65,41 @@ trait Request extends HeaderExtension[Request] with HttpDataExtension[Request] { def path: Path = url.path /** - * Gets the remote address if available + * Gets the complete url */ - def remoteAddress: Option[InetAddress] + def url: URL + + /** + * Gets the request's http protocol version + */ + def version: Version /** * Overwrites the method in the request */ - def setMethod(method: Method): Request = self.copy(method = method) + final def setMethod(method: Method): Request = self.copy(method = method) /** * Overwrites the path in the request */ - def setPath(path: Path): Request = self.copy(url = self.url.copy(path = path)) + final def setPath(path: Path): Request = self.copy(url = self.url.copy(path = path)) /** * Overwrites the url in the request */ - def setUrl(url: URL): Request = self.copy(url = url) + final def setUrl(url: URL): Request = self.copy(url = url) /** * Returns a string representation of the request, useful for debugging, * logging or other purposes. It contains the essential properties of HTTP * request: protocol version, method, URL, headers and remote address. */ - override def toString = - s"Request($version, $method, $url, $headers, $remoteAddress)" - - /** - * Gets the HttpRequest - */ - private[zhttp] def unsafeEncode: HttpRequest - - /** - * Gets the complete url - */ - def url: URL + final override def toString = s"Request($version, $method, $url, $headers)" /** - * Gets the request's http protocol version + * Updates the headers using the provided function */ - def version: Version + final override def updateHeaders(update: Headers => Headers): Request = self.copy(headers = update(self.headers)) } @@ -113,48 +113,27 @@ object Request { method: Method = Method.GET, url: URL = URL.root, headers: Headers = Headers.empty, - remoteAddress: Option[InetAddress] = None, data: HttpData = HttpData.Empty, ): Request = { - val m = method - val u = url - val h = headers - val ra = remoteAddress - val d = data - val v = version + val m = method + val u = url + val h = headers + val d = data + val v = version new Request { - override def method: Method = m - override def url: URL = u - override def headers: Headers = h - override def version: Version = v - override def unsafeEncode: HttpRequest = { + override def method: Method = m + override def url: URL = u + override def headers: Headers = h + override def version: Version = v + override def unsafeEncode: HttpRequest = { val jVersion = v.toJava val path = url.relative.encode new DefaultFullHttpRequest(jVersion, method.toJava, path) } - override def remoteAddress: Option[InetAddress] = ra - override def data: HttpData = d + override def data: HttpData = d + override def unsafeContext: ChannelHandlerContext = throw new IOException("Request does not have a context") } } - - /** - * Lift request to TypedRequest with option to extract params - */ - final class ParameterizedRequest[A](req: Request, val params: A) extends Request { - override def headers: Headers = req.headers - override def method: Method = req.method - override def remoteAddress: Option[InetAddress] = req.remoteAddress - override def url: URL = req.url - override def version: Version = req.version - override def unsafeEncode: HttpRequest = req.unsafeEncode - override def data: HttpData = req.data - override def toString: String = - s"ParameterizedRequest($req, $params)" - } - - object ParameterizedRequest { - def apply[A](req: Request, params: A): ParameterizedRequest[A] = new ParameterizedRequest(req, params) - } } diff --git a/zio-http/src/main/scala/zhttp/service/Handler.scala b/zio-http/src/main/scala/zhttp/service/Handler.scala index f75ff127e4..ba611f4d0f 100644 --- a/zio-http/src/main/scala/zhttp/service/Handler.scala +++ b/zio-http/src/main/scala/zhttp/service/Handler.scala @@ -7,8 +7,6 @@ import zhttp.http._ import zhttp.service.server.WebSocketUpgrade import zio.{UIO, ZIO} -import java.net.{InetAddress, InetSocketAddress} - @Sharable private[zhttp] final case class Handler[R]( app: HttpApp[R, Throwable], @@ -35,17 +33,14 @@ private[zhttp] final case class Handler[R]( override def headers: Headers = Headers.make(jReq.headers()) - override def remoteAddress: Option[InetAddress] = getRemoteAddress - override def data: HttpData = HttpData.fromByteBuf(jReq.content()) override def version: Version = Version.unsafeFromJava(jReq.protocolVersion()) - /** - * Gets the HttpRequest - */ override def unsafeEncode: HttpRequest = jReq + override def unsafeContext: Ctx = ctx + }, ) catch { @@ -75,15 +70,13 @@ private[zhttp] final case class Handler[R]( override def method: Method = Method.fromHttpMethod(jReq.method()) - override def remoteAddress: Option[InetAddress] = getRemoteAddress(ctx) + override def url: URL = URL.fromString(jReq.uri()).getOrElse(null) - override def url: URL = URL.fromString(jReq.uri()).getOrElse(null) override def version: Version = Version.unsafeFromJava(jReq.protocolVersion()) - /** - * Gets the HttpRequest - */ override def unsafeEncode: HttpRequest = jReq + + override def unsafeContext: Ctx = ctx }, ) catch { @@ -100,13 +93,6 @@ private[zhttp] final case class Handler[R]( } - private def getRemoteAddress(implicit ctx: Ctx) = { - ctx.channel().remoteAddress() match { - case m: InetSocketAddress => Some(m.getAddress) - case _ => None - } - } - private def canHaveBody(req: HttpRequest): Boolean = { req.method() == HttpMethod.TRACE || req.headers().get(HttpHeaderNames.CONTENT_LENGTH) != null || diff --git a/zio-http/src/test/scala/zhttp/endpoint/EndpointSpec.scala b/zio-http/src/test/scala/zhttp/endpoint/EndpointSpec.scala deleted file mode 100644 index 17749c1354..0000000000 --- a/zio-http/src/test/scala/zhttp/endpoint/EndpointSpec.scala +++ /dev/null @@ -1,85 +0,0 @@ -package zhttp.endpoint - -import zhttp.http._ -import zio.UIO -import zio.test.Assertion._ -import zio.test.{DefaultRunnableSpec, assert, assertM} - -object EndpointSpec extends DefaultRunnableSpec { - def spec = suite("Route") { - test("match method") { - val route = Endpoint.fromMethod(Method.GET) - val request = Request(method = Method.GET) - assert(route.extract(request))(isSome(equalTo(()))) - } - test("not match method") { - val route = Endpoint.fromMethod(Method.POST) - val request = Request(method = Method.GET) - assert(route.extract(request))(isNone) - } + - test("match method and string") { - val route = Method.GET / "a" - val request = Request(method = Method.GET, url = URL(Path("a"))) - assert(route.extract(request))(isSome(equalTo(()))) - } + - test("match method and not string") { - val route = Method.GET / "a" - val request = Request(method = Method.GET, url = URL(Path("b"))) - assert(route.extract(request))(isNone) - } - } + suite("Path") { - test("Route[Int]") { - val route = Method.GET / *[Int] - assert(route.extract(!! / "1"))(isSome(equalTo(1))) && assert(route.extract(!! / "a"))(isNone) - } + - test("Route[String]") { - val route = Method.GET / *[String] - assert(route.extract(!! / "a"))(isSome(equalTo("a"))) - } + - test("Route[Boolean]") { - val route = Method.GET / *[Boolean] - assert(route.extract(!! / "True"))(isSome(isTrue)) && - assert(route.extract(!! / "False"))(isSome(isFalse)) && - assert(route.extract(!! / "a"))(isNone) && - assert(route.extract(!! / "1"))(isNone) - } + - test("Route[Int] / Route[Int]") { - val route = Method.GET / *[Int] / *[Int] - assert(route.extract(!! / "1" / "2"))(isSome(equalTo((1, 2)))) && - assert(route.extract(!! / "1" / "b"))(isNone) && - assert(route.extract(!! / "b" / "1"))(isNone) && - assert(route.extract(!! / "1"))(isNone) && - assert(route.extract(!!))(isNone) - } + - test("Route[Int] / c") { - val route = Method.GET / *[Int] / "c" - assert(route.extract(!! / "1" / "c"))(isSome(equalTo(1))) && - assert(route.extract(!! / "1"))(isNone) && - assert(route.extract(!! / "c"))(isNone) - } + - test("Route[Int] / c") { - val route = Method.GET / *[Int] / "c" - assert(route.extract(!! / "1" / "c"))(isSome(equalTo(1))) && - assert(route.extract(!! / "1"))(isNone) && - assert(route.extract(!! / "c"))(isNone) - } - } + - suite("to") { - testM("endpoint doesn't match") { - val app = Method.GET / "a" to { _ => Response.ok } - assertM(app(Request(url = URL(!! / "b"))).flip)(isNone) - } + - testM("endpoint with effect doesn't match") { - val app = Method.GET / "a" to { _ => UIO(Response.ok) } - assertM(app(Request(url = URL(!! / "b"))).flip)(isNone) - } + - testM("endpoint matches") { - val app = Method.GET / "a" to { _ => Response.ok } - assertM(app(Request(url = URL(!! / "a"))).map(_.status))(equalTo(Status.Ok)) - } + - testM("endpoint with effect matches") { - val app = Method.GET / "a" to { _ => UIO(Response.ok) } - assertM(app(Request(url = URL(!! / "a"))).map(_.status))(equalTo(Status.Ok)) - } - } -} diff --git a/zio-http/src/test/scala/zhttp/http/RequestSpec.scala b/zio-http/src/test/scala/zhttp/http/RequestSpec.scala index 98c9e13a00..5b9597b420 100644 --- a/zio-http/src/test/scala/zhttp/http/RequestSpec.scala +++ b/zio-http/src/test/scala/zhttp/http/RequestSpec.scala @@ -10,19 +10,10 @@ object RequestSpec extends DefaultRunnableSpec { testM("should produce string representation of a request") { check(HttpGen.request) { req => assert(req.toString)( - equalTo(s"Request(${req.version}, ${req.method}, ${req.url}, ${req.headers}, ${req.remoteAddress})"), + equalTo(s"Request(${req.version}, ${req.method}, ${req.url}, ${req.headers})"), ) } - } + - testM("should produce string representation of a parameterized request") { - check(HttpGen.parameterizedRequest(Gen.alphaNumericString)) { req => - assert(req.toString)( - equalTo( - s"ParameterizedRequest(Request(${req.version}, ${req.method}, ${req.url}, ${req.headers}, ${req.remoteAddress}), ${req.params})", - ), - ) - } - } + } }, ) } diff --git a/zio-http/src/test/scala/zhttp/internal/HttpGen.scala b/zio-http/src/test/scala/zhttp/internal/HttpGen.scala index e778a9d723..cdb904b5db 100644 --- a/zio-http/src/test/scala/zhttp/internal/HttpGen.scala +++ b/zio-http/src/test/scala/zhttp/internal/HttpGen.scala @@ -1,7 +1,6 @@ package zhttp.internal import io.netty.buffer.Unpooled -import zhttp.http.Request.ParameterizedRequest import zhttp.http.Scheme.{HTTP, HTTPS, WS, WSS} import zhttp.http.URL.Location import zhttp.http._ @@ -126,20 +125,13 @@ object HttpGen { } yield p } - def parameterizedRequest[R, A](paramsGen: Gen[R, A]): Gen[R with Random with Sized, ParameterizedRequest[A]] = { - for { - req <- request - params <- paramsGen - } yield ParameterizedRequest(req, params) - } - def request: Gen[Random with Sized, Request] = for { version <- httpVersion method <- HttpGen.method url <- HttpGen.url headers <- Gen.listOf(HttpGen.header).map(Headers(_)) data <- HttpGen.httpData(Gen.listOf(Gen.alphaNumericString)) - } yield Request(version, method, url, headers, None, data) + } yield Request(version, method, url, headers, data) def response[R](gContent: Gen[R, List[String]]): Gen[Random with Sized with R, Response] = { for {