Skip to content

Commit 94fe70b

Browse files
authored
Redesigned model (#1916)
* Initial changes * Removed HExit.empty * Separation of Total/Partial Https, work in progress * wrap * Web middlewares and request logging * More work on middlewares * Core project compiles * Fixed many tests * Fixes * Core tests passing * Fix 2.12. * Fix Scala 3 * Reintroduced declarative middleware application * Eliminated redundant middleware-for-total * Fixes for Scala 2.12 and 3 * Readded @nowarn * Fix * Updated CI * Attempts to improve performance * Attempts to improve performance * Attempts to improve performance * Fix error handling * Linux * OS dependent sed * Attempts to improve performance * Remove commented out code * Http extends PartialFunction * Fix middleware bounds * Fix * Middleware input transformation tracking * Fix Scala 2.12 and 3.2.1 * Redesigned route/handler/middleware models * Adjusting tests and examples to the new model, WIP * Adjusting tests and examples to the new model, WIP * Everything compiles * Fixes * All tests pass * Restored the fallback feature of fromFile/fromStream * Fix scaladoc reference * Format * Unsafe API * Using Unhandled instead of null in Route * sealed traits Route and Handler * Renamed toXXX(in) to runXXX(in) * Renamed Route to Http and made it a tree * HandlerMiddleware is Middleware * toRoute renamed to toHttp * Fix tests * Use Exit instead of HExit * Readded most of the old ScalaDoc comments * Cleaned up traces * Clean up type aliases
1 parent 597eabc commit 94fe70b

File tree

101 files changed

+3575
-3426
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

101 files changed

+3575
-3426
lines changed

.github/workflows/ci.yml

+4-4
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
strategy:
2525
matrix:
2626
os: [ubuntu-latest]
27-
scala: [2.12.16, 2.13.8, 3.2.0]
27+
scala: [2.12.16, 2.13.8, 3.2.1]
2828
java: [graal_21.1.0@11, temurin@8]
2929
runs-on: ${{ matrix.os }}
3030
steps:
@@ -144,12 +144,12 @@ jobs:
144144
tar xf targets.tar
145145
rm targets.tar
146146
147-
- name: Download target directories (3.2.0)
147+
- name: Download target directories (3.2.1)
148148
uses: actions/download-artifact@v2
149149
with:
150-
name: target-${{ matrix.os }}-3.2.0-${{ matrix.java }}
150+
name: target-${{ matrix.os }}-3.2.1-${{ matrix.java }}
151151

152-
- name: Inflate target directories (3.2.0)
152+
- name: Inflate target directories (3.2.1)
153153
run: |
154154
tar xf targets.tar
155155
rm targets.tar

benchmark-zio-http.sh

+5-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ rm ../FrameworkBenchMarks/frameworks/Scala/zio-http/src/main/scala/Main.scala
2323
cp ../FrameworkBenchMarks/frameworks/Scala/zio-http/base.build.sbt ../FrameworkBenchMarks/frameworks/Scala/zio-http/build.sbt
2424
cp ./zio-http-example/src/main/scala/example/$SERVER.scala ../FrameworkBenchMarks/frameworks/Scala/zio-http/src/main/scala/Main.scala
2525
cd ../FrameworkBenchMarks
26-
sed -i '' "s|---COMMIT_SHA---|${ZIO_HTTP}|g" frameworks/Scala/zio-http/build.sbt
26+
if [ "$OS" = 'Darwin' ]; then
27+
sed -i '' "s|---COMMIT_SHA---|${ZIO_HTTP}|g" frameworks/Scala/zio-http/build.sbt
28+
else
29+
sed -i "s|---COMMIT_SHA---|${ZIO_HTTP}|g" frameworks/Scala/zio-http/build.sbt
30+
fi
2731
./tfb --test zio-http | tee result
2832
RESULT_REQUEST=$(echo $(grep -B 1 -A 17 "Concurrency: 256 for plaintext" result) | grep -oiE "requests/sec: [0-9]+.[0-9]+")
2933
RESULT_CONCURRENCY=$(echo $(grep -B 1 -A 17 "Concurrency: 256 for plaintext" result) | grep -oiE "concurrency: [0-9]+")

project/BuildHelper.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import xerial.sbt.Sonatype.autoImport._
66
object BuildHelper extends ScalaSettings {
77
val Scala212 = "2.12.16"
88
val Scala213 = "2.13.8"
9-
val ScalaDotty = "3.2.0"
9+
val ScalaDotty = "3.2.1"
1010
val ScoverageVersion = "1.9.3"
1111
val JmhVersion = "0.4.3"
1212

zio-http-benchmarks/src/main/scala-2.13/zio/http/benchmarks/ApiBenchmark.scala

+6-6
Original file line numberDiff line numberDiff line change
@@ -169,13 +169,13 @@ class ApiBenchmark {
169169
@Benchmark
170170
def benchmarkSmallDataZioApi(): Unit =
171171
unsafeRun {
172-
apiHttpApp(smallDataRequest).repeatN(REPEAT_N)
172+
apiHttpApp.runZIO(smallDataRequest).repeatN(REPEAT_N)
173173
}
174174

175175
@Benchmark
176176
def benchmarkSmallDataZioCollect(): Unit =
177177
unsafeRun {
178-
collectHttpApp(smallDataRequest).repeatN(REPEAT_N)
178+
collectHttpApp.runZIO(smallDataRequest).repeatN(REPEAT_N)
179179
}
180180

181181
// @Benchmark
@@ -277,13 +277,13 @@ class ApiBenchmark {
277277
@Benchmark
278278
def benchmarkDeepPathZioApi(): Unit =
279279
unsafeRun {
280-
deepPathHttpApp(deepPathRequest).repeatN(REPEAT_N)
280+
deepPathHttpApp.runZIO(deepPathRequest).repeatN(REPEAT_N)
281281
}
282282

283283
@Benchmark
284284
def benchmarkDeepPathZioCollect(): Unit =
285285
unsafeRun {
286-
deepPathCollectHttpApp(deepPathRequest).repeatN(REPEAT_N)
286+
deepPathCollectHttpApp.runZIO(deepPathRequest).repeatN(REPEAT_N)
287287
}
288288

289289
// @Benchmark
@@ -638,13 +638,13 @@ class ApiBenchmark {
638638
@Benchmark
639639
def benchmarkBroadZioApi(): Unit =
640640
unsafeRunResult {
641-
ZIO.foreachDiscard(broadZioRequests)(broadApiApp(_))
641+
ZIO.foreachDiscard(broadZioRequests)(broadApiApp.runZIO(_))
642642
}
643643

644644
@Benchmark
645645
def benchmarkBroadZioCollect(): Unit =
646646
unsafeRun {
647-
ZIO.foreachDiscard(broadZioRequests)(broadCollectApp(_))
647+
ZIO.foreachDiscard(broadZioRequests)(broadCollectApp.runZIO(_))
648648
}
649649

650650
// @Benchmark

zio-http-benchmarks/src/main/scala/zhttp.benchmarks/HttpCollectEval.scala

+27-6
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,43 @@ package zio.benchmarks
22

33
import org.openjdk.jmh.annotations._
44
import zio.http._
5+
import zio.{Trace, Unsafe}
56

67
import java.util.concurrent.TimeUnit
78

89
@State(Scope.Thread)
910
@BenchmarkMode(Array(Mode.Throughput))
1011
@OutputTimeUnit(TimeUnit.SECONDS)
1112
class HttpCollectEval {
12-
private val MAX = 10000
13-
private val app = Http.collect[Int] { case 0 => 1 }
14-
private val http = Http.collect[Request] { case _ -> !! / "text" => 1 }
13+
private val MAX = 10000
14+
private val app = Http.collect[Int] { case 0 => 1 }
15+
private val http = Http.collect[Request] { case _ -> !! / "text" => 1 }
16+
private val httpTotal = Handler
17+
.fromFunction[Request] {
18+
case _ -> !! / "text" => 1
19+
case _ => 0 // representing "not found"
20+
}
1521

16-
private val base: Int => Int = _ => 1
22+
private val base: PartialFunction[Int, Int] = { case 0 => 1 }
23+
private val baseTotal: Int => Int = _ => 1
1724

1825
@Benchmark
1926
def benchmarkApp(): Unit = {
20-
(0 to MAX).foreach(_ => app.execute(0))
27+
(0 to MAX).foreach(_ => app.runZIOOrNull(0)(Unsafe.unsafe, Trace.empty))
2128
()
2229
}
2330

2431
@Benchmark
2532
def benchmarkHttp(): Unit = {
26-
(0 to MAX).foreach(_ => http.execute(Request.get(url = URL(!! / "text"))))
33+
(0 to MAX).foreach(_ => http.runZIOOrNull(Request.get(url = URL(!! / "text")))(Unsafe.unsafe, Trace.empty))
34+
()
35+
}
36+
37+
@Benchmark
38+
def benchmarkHttpTotal(): Unit = {
39+
(0 to MAX).foreach(_ =>
40+
httpTotal.toHttp.runZIOOrNull(Request.get(url = URL(!! / "text")))(Unsafe.unsafe, Trace.empty),
41+
)
2742
()
2843
}
2944

@@ -32,4 +47,10 @@ class HttpCollectEval {
3247
(0 to MAX).foreach(_ => base(0))
3348
()
3449
}
50+
51+
@Benchmark
52+
def benchmarkBaseTotal(): Unit = {
53+
(0 to MAX).foreach(_ => baseTotal(0))
54+
()
55+
}
3556
}

zio-http-benchmarks/src/main/scala/zhttp.benchmarks/HttpCombineEval.scala

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package zio.benchmarks
22

33
import org.openjdk.jmh.annotations._
44
import zio.http._
5+
import zio.{Trace, Unsafe}
56

67
import java.util.concurrent.TimeUnit
78

@@ -15,13 +16,13 @@ class HttpCombineEval {
1516

1617
@Benchmark
1718
def empty(): Unit = {
18-
spec.execute(-1)
19+
spec.runZIOOrNull(-1)(Unsafe.unsafe, Trace.empty)
1920
()
2021
}
2122

2223
@Benchmark
2324
def ok(): Unit = {
24-
spec.execute(0)
25+
spec.runZIOOrNull(0)(Unsafe.unsafe, Trace.empty)
2526
()
2627
}
2728
}

zio-http-benchmarks/src/main/scala/zhttp.benchmarks/HttpNestedFlatMapEval.scala

+4-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package zio.benchmarks
22

33
import org.openjdk.jmh.annotations._
44
import zio.http._
5+
import zio.{Trace, Unsafe}
56

67
import java.util.concurrent.TimeUnit
78

@@ -12,12 +13,12 @@ class HttpNestedFlatMapEval {
1213

1314
private val MAX = 1000
1415

15-
val programFlatMap: Http[Any, Nothing, Int, Int] =
16-
(0 to MAX).foldLeft(Http.identity[Int])((a, _) => a.flatMap(i => Http.succeed(i + 1)))
16+
val programFlatMap: Handler[Any, Nothing, Int, Int] =
17+
(0 to MAX).foldLeft(Handler.identity[Int])((a, _) => a.flatMap(i => Handler.succeed(i + 1)))
1718

1819
@Benchmark
1920
def benchmarkHttpFlatMap(): Unit = {
20-
programFlatMap.execute(0)
21+
programFlatMap.toHttp.runZIOOrNull(0)(Unsafe.unsafe, Trace.empty)
2122
()
2223
}
2324
}

zio-http-benchmarks/src/main/scala/zhttp.benchmarks/HttpRouteTextPerf.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ class HttpRouteTextPerf {
1414
private val runtime = Runtime.default
1515

1616
private val res = Response.text("HELLO WORLD")
17-
private val app = Http.succeed(res)
17+
private val app = Handler.succeed(res)
1818
private val req: Request = Request.get(URL(!!))
19-
private val httpProgram = ZIO.foreachDiscard(0 to 1000) { _ => app.execute(req).toZIO }
19+
private val httpProgram = ZIO.foreachDiscard(0 to 1000) { _ => app(req) }
2020
private val UIOProgram = ZIO.foreachDiscard(0 to 1000) { _ => ZIO.succeed(res) }
2121

2222
@Benchmark

zio-http-example/src/main/scala/example/AuthenticationServer.scala

+4-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import zio.http._
77
import zio.http.model.{Method, Status}
88

99
import java.time.Clock
10+
import javax.management.MBeanNotificationInfo
1011

1112
object AuthenticationServer extends ZIOAppDefault {
1213

@@ -39,19 +40,19 @@ object AuthenticationServer extends ZIOAppDefault {
3940
}
4041

4142
// Http app that is accessible only via a jwt token
42-
def user: UHttpApp = Http.collect[Request] { case Method.GET -> !! / "user" / name / "greet" =>
43+
def user: HttpApp[Any, Nothing] = Http.collect[Request] { case Method.GET -> !! / "user" / name / "greet" =>
4344
Response.text(s"Welcome to the ZIO party! ${name}")
4445
} @@ bearerAuth(jwtDecode(_).isDefined)
4546

4647
// App that let's the user login
4748
// Login is successful only if the password is the reverse of the username
48-
def login: UHttpApp = Http.collect[Request] { case Method.GET -> !! / "login" / username / password =>
49+
def login: HttpApp[Any, Nothing] = Http.collect[Request] { case Method.GET -> !! / "login" / username / password =>
4950
if (password.reverse.hashCode == username.hashCode) Response.text(jwtEncode(username))
5051
else Response.text("Invalid username or password.").setStatus(Status.Unauthorized)
5152
}
5253

5354
// Composing all the HttpApps together
54-
val app: UHttpApp = login ++ user
55+
val app: HttpApp[Any, Nothing] = login ++ user
5556

5657
// Run it like any simple app
5758
override val run = Server.serve(app).provide(Server.default)

zio-http-example/src/main/scala/example/BasicAuth.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ import zio.http.model.Method
88
object BasicAuth extends ZIOAppDefault {
99

1010
// Http app that requires a JWT claim
11-
val user: UHttpApp = Http.collect[Request] { case Method.GET -> !! / "user" / name / "greet" =>
11+
val user: HttpApp[Any, Nothing] = Http.collect[Request] { case Method.GET -> !! / "user" / name / "greet" =>
1212
Response.text(s"Welcome to the ZIO party! ${name}")
1313
}
1414

1515
// Composing all the HttpApps together
16-
val app: UHttpApp = user @@ basicAuth("admin", "admin")
16+
val app: HttpApp[Any, Nothing] = user @@ basicAuth("admin", "admin")
1717

1818
// Run it like any simple app
1919
val run = Server.serve(app).provide(Server.default)

zio-http-example/src/main/scala/example/CSRF.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ object CSRF extends ZIOAppDefault {
1717
}
1818
.withMiddleware(api.Middleware.csrfGenerate()) // set x-csrf token cookie
1919

20-
val app = publicApp ++ privateApp
20+
val app = (publicApp ++ privateApp).withDefaultErrorResponse
2121

2222
def run = Server.serve(app).provide(Server.default)
2323
}

zio-http-example/src/main/scala/example/ClientServer.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@ object ClientServer extends ZIOAppDefault {
1616
}
1717

1818
val run = {
19-
Server.serve(app).provide(Server.default, Client.default).exitCode
19+
Server.serve(app.withDefaultErrorResponse).provide(Server.default, Client.default).exitCode
2020
}
2121
}

zio-http-example/src/main/scala/example/ConcreteEntity.scala

+4-4
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,17 @@ object ConcreteEntity extends ZIOAppDefault {
1313
// Response
1414
case class UserCreated(id: Long)
1515

16-
val user: Http[Any, Nothing, CreateUser, UserCreated] =
17-
Http.collect[CreateUser] { case CreateUser(_) =>
16+
val user: Handler[Any, Nothing, CreateUser, UserCreated] =
17+
Handler.fromFunction[CreateUser] { case CreateUser(_) =>
1818
UserCreated(2)
1919
}
2020

21-
val app: HttpApp[Any, Nothing] =
21+
val app: RequestHandler[Any, Nothing] =
2222
user
2323
.contramap[Request](req => CreateUser(req.path.encode)) // Http[Any, Nothing, Request, UserCreated]
2424
.map(userCreated => Response.text(userCreated.id.toString)) // Http[Any, Nothing, Request, Response]
2525

2626
// Run it like any simple app
2727
val run =
28-
Server.serve(app).provide(Server.default)
28+
Server.serve(app.toHttp.withDefaultErrorResponse).provide(Server.default)
2929
}

zio-http-example/src/main/scala/example/FileStreaming.scala

+4-4
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ import java.nio.file.Paths
1111
object FileStreaming extends ZIOAppDefault {
1212

1313
// Create HTTP route
14-
val app = Http.collectHttp[Request] {
15-
case Method.GET -> !! / "health" => Http.ok
14+
val app = Http.collectRoute[Request] {
15+
case Method.GET -> !! / "health" => Handler.ok.toHttp
1616

1717
// Read the file as ZStream
1818
// Uses the blocking version of ZStream.fromFile
19-
case Method.GET -> !! / "blocking" => Http.fromStream(ZStream.fromPath(Paths.get("README.md")))
19+
case Method.GET -> !! / "blocking" => Handler.fromStream(ZStream.fromPath(Paths.get("README.md"))).toHttp
2020

2121
// Uses netty's capability to write file content to the Channel
2222
// Content-type response headers are automatically identified and added
@@ -27,5 +27,5 @@ object FileStreaming extends ZIOAppDefault {
2727

2828
// Run it like any simple app
2929
val run =
30-
Server.serve(app).provide(Server.default)
30+
Server.serve(app.withDefaultErrorResponse).provide(Server.default)
3131
}

zio-http-example/src/main/scala/example/HelloWorldWithMiddlewares.scala

+8-11
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,27 @@ package example
22

33
import zio._
44
import zio.http._
5-
import zio.http.middleware.HttpMiddleware
65
import zio.http.model.Method
76

87
import java.io.IOException
98
import java.util.concurrent.TimeUnit
109

1110
object HelloWorldWithMiddlewares extends ZIOAppDefault {
1211

13-
val app: HttpApp[Any, Nothing] = Http.collectZIO[Request] { request =>
14-
request match {
15-
// this will return result instantly
16-
case Method.GET -> !! / "text" => ZIO.succeed(Response.text("Hello World!"))
17-
// this will return result after 5 seconds, so with 3 seconds timeout it will fail
18-
case Method.GET -> !! / "long-running" => ZIO.succeed(Response.text("Hello World!")).delay(5 seconds)
19-
}
12+
val app: HttpApp[Any, Nothing] = Http.collectZIO[Request] {
13+
// this will return result instantly
14+
case Method.GET -> !! / "text" => ZIO.succeed(Response.text("Hello World!"))
15+
// this will return result after 5 seconds, so with 3 seconds timeout it will fail
16+
case Method.GET -> !! / "long-running" => ZIO.succeed(Response.text("Hello World!")).delay(5 seconds)
2017
}
2118

22-
val serverTime: HttpMiddleware[Any, Nothing] = Middleware.patchZIO(_ =>
19+
val serverTime: RequestHandlerMiddleware[Any, Nothing] = Middleware.patchZIO(_ =>
2320
for {
2421
currentMilliseconds <- Clock.currentTime(TimeUnit.MILLISECONDS)
2522
withHeader = Patch.addHeader("X-Time", currentMilliseconds.toString)
2623
} yield withHeader,
2724
)
28-
val middlewares: HttpMiddleware[Any, IOException] =
25+
val middlewares: RequestHandlerMiddleware[Any, IOException] =
2926
// print debug info about request and response
3027
Middleware.debug ++
3128
// close connection if request takes more than 3 seconds
@@ -36,5 +33,5 @@ object HelloWorldWithMiddlewares extends ZIOAppDefault {
3633
serverTime
3734

3835
// Run it like any simple app
39-
val run = Server.serve(app @@ middlewares).provide(Server.default)
36+
val run = Server.serve((app @@ middlewares).withDefaultErrorResponse).provide(Server.default)
4037
}

zio-http-example/src/main/scala/example/HtmlTemplating.scala

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ object HtmlTemplating extends ZIOAppDefault {
77
// Importing everything from `zio.html`
88
import zio.http.html._
99

10-
def app: HttpApp[Any, Nothing] = {
10+
def app: Handler[Any, Nothing, Any, Response] = {
1111
// Html response takes in a `Html` instance.
12-
Http.html {
12+
Handler.html {
1313

1414
// Support for default Html tags
1515
html(
@@ -46,5 +46,5 @@ object HtmlTemplating extends ZIOAppDefault {
4646
}
4747
}
4848

49-
def run = Server.serve(app).provide(Server.default)
49+
def run = Server.serve(app.toHttp.withDefaultErrorResponse).provide(Server.default)
5050
}

0 commit comments

Comments
 (0)