Skip to content

Commit

Permalink
* Add some helpers for setting content type response headers
Browse files Browse the repository at this point in the history
* Refactor Request/Response to their own files
* Fixes issue where response headers weren't being sent
  • Loading branch information
alterationx10 committed Nov 3, 2024
1 parent caef3df commit 2d64258
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 25 deletions.
71 changes: 71 additions & 0 deletions .scala-build/stacktraces/1730645056-10443284620945790960.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
java.io.IOException: Cannot run program "/Users/alt/Library/Caches/Coursier/arc/https/github.com/adoptium/temurin19-binaries/releases/download/jdk-19%252B36/OpenJDK19U-jdk_x64_mac_hotspot_19_36.tar.gz/jdk-19+36/Contents/Home/bin/java" (in directory "/Users/alt/Projects/wishingtreedev/branch"): error=2, No such file or directory
java.base@17.0.6/java.lang.ProcessBuilder.start(ProcessBuilder.java:1143)
java.base@17.0.6/java.lang.ProcessBuilder.start(ProcessBuilder.java:1073)
os.proc.proc$lzyINIT1$1(ProcessOps.scala:138)
os.proc.proc$1(ProcessOps.scala:142)
os.proc.spawn(ProcessOps.scala:144)
os.proc.call(ProcessOps.scala:87)
scala.build.internal.OsLibc$.javaVersion(OsLibc.scala:80)
scala.build.options.BuildOptions$JavaHomeInfo$.apply(BuildOptions.scala:617)
scala.build.options.BuildOptions.javaCommand0$lzyINIT1$$anonfun$1(BuildOptions.scala:243)
scala.build.Positioned.map(Positioned.scala:15)
scala.build.options.BuildOptions.javaCommand0$lzyINIT1(BuildOptions.scala:243)
scala.build.options.BuildOptions.javaCommand0(BuildOptions.scala:243)
scala.build.options.BuildOptions.javaHome(BuildOptions.scala:251)
scala.cli.commands.repl.Repl$.maybeRunRepl$1(Repl.scala:410)
scala.cli.commands.repl.Repl$.runRepl$$anonfun$1(Repl.scala:476)
scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
scala.build.EitherCps$Helper.apply(EitherCps.scala:19)
scala.cli.commands.repl.Repl$.runRepl(Repl.scala:478)
scala.cli.commands.repl.Repl$.doRunRepl$1(Repl.scala:144)
scala.cli.commands.repl.Repl$.runThing$1(Repl.scala:195)
scala.cli.commands.repl.Repl$.runCommand(Repl.scala:198)
scala.cli.commands.default.Default.runCommand(Default.scala:53)
scala.cli.commands.default.Default.runCommand(Default.scala:40)
scala.cli.commands.ScalaCommand.run(ScalaCommand.scala:388)
scala.cli.commands.ScalaCommand.run(ScalaCommand.scala:369)
caseapp.core.app.CaseApp.main(CaseApp.scala:157)
scala.cli.commands.ScalaCommand.main(ScalaCommand.scala:354)
caseapp.core.app.CommandsEntryPoint.main(CommandsEntryPoint.scala:169)
scala.cli.ScalaCliCommands.main(ScalaCliCommands.scala:125)
scala.cli.ScalaCli$.main0(ScalaCli.scala:295)
scala.cli.ScalaCli$.main(ScalaCli.scala:119)
scala.cli.ScalaCli.main(ScalaCli.scala)
java.io.IOException: error=2, No such file or directory
java.base@17.0.6/java.lang.ProcessImpl.forkAndExec(ProcessImpl.java)
java.base@17.0.6/java.lang.ProcessImpl.<init>(ProcessImpl.java:314)
java.base@17.0.6/java.lang.ProcessImpl.start(ProcessImpl.java:244)
java.base@17.0.6/java.lang.ProcessBuilder.start(ProcessBuilder.java:1110)
java.base@17.0.6/java.lang.ProcessBuilder.start(ProcessBuilder.java:1073)
os.proc.proc$lzyINIT1$1(ProcessOps.scala:138)
os.proc.proc$1(ProcessOps.scala:142)
os.proc.spawn(ProcessOps.scala:144)
os.proc.call(ProcessOps.scala:87)
scala.build.internal.OsLibc$.javaVersion(OsLibc.scala:80)
scala.build.options.BuildOptions$JavaHomeInfo$.apply(BuildOptions.scala:617)
scala.build.options.BuildOptions.javaCommand0$lzyINIT1$$anonfun$1(BuildOptions.scala:243)
scala.build.Positioned.map(Positioned.scala:15)
scala.build.options.BuildOptions.javaCommand0$lzyINIT1(BuildOptions.scala:243)
scala.build.options.BuildOptions.javaCommand0(BuildOptions.scala:243)
scala.build.options.BuildOptions.javaHome(BuildOptions.scala:251)
scala.cli.commands.repl.Repl$.maybeRunRepl$1(Repl.scala:410)
scala.cli.commands.repl.Repl$.runRepl$$anonfun$1(Repl.scala:476)
scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
scala.build.EitherCps$Helper.apply(EitherCps.scala:19)
scala.cli.commands.repl.Repl$.runRepl(Repl.scala:478)
scala.cli.commands.repl.Repl$.doRunRepl$1(Repl.scala:144)
scala.cli.commands.repl.Repl$.runThing$1(Repl.scala:195)
scala.cli.commands.repl.Repl$.runCommand(Repl.scala:198)
scala.cli.commands.default.Default.runCommand(Default.scala:53)
scala.cli.commands.default.Default.runCommand(Default.scala:40)
scala.cli.commands.ScalaCommand.run(ScalaCommand.scala:388)
scala.cli.commands.ScalaCommand.run(ScalaCommand.scala:369)
caseapp.core.app.CaseApp.main(CaseApp.scala:157)
scala.cli.commands.ScalaCommand.main(ScalaCommand.scala:354)
caseapp.core.app.CommandsEntryPoint.main(CommandsEntryPoint.scala:169)
scala.cli.ScalaCliCommands.main(ScalaCliCommands.scala:125)
scala.cli.ScalaCli$.main0(ScalaCli.scala:295)
scala.cli.ScalaCli$.main(ScalaCli.scala:119)
scala.cli.ScalaCli.main(ScalaCli.scala)
71 changes: 71 additions & 0 deletions .scala-build/stacktraces/1730645327-221960474321627545.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
java.io.IOException: Cannot run program "/Users/alt/Library/Caches/Coursier/arc/https/github.com/adoptium/temurin19-binaries/releases/download/jdk-19%252B36/OpenJDK19U-jdk_x64_mac_hotspot_19_36.tar.gz/jdk-19+36/Contents/Home/bin/java" (in directory "/Users/alt/Projects/wishingtreedev/branch"): error=2, No such file or directory
java.base@17.0.6/java.lang.ProcessBuilder.start(ProcessBuilder.java:1143)
java.base@17.0.6/java.lang.ProcessBuilder.start(ProcessBuilder.java:1073)
os.proc.proc$lzyINIT1$1(ProcessOps.scala:138)
os.proc.proc$1(ProcessOps.scala:142)
os.proc.spawn(ProcessOps.scala:144)
os.proc.call(ProcessOps.scala:87)
scala.build.internal.OsLibc$.javaVersion(OsLibc.scala:80)
scala.build.options.BuildOptions$JavaHomeInfo$.apply(BuildOptions.scala:617)
scala.build.options.BuildOptions.javaCommand0$lzyINIT1$$anonfun$1(BuildOptions.scala:243)
scala.build.Positioned.map(Positioned.scala:15)
scala.build.options.BuildOptions.javaCommand0$lzyINIT1(BuildOptions.scala:243)
scala.build.options.BuildOptions.javaCommand0(BuildOptions.scala:243)
scala.build.options.BuildOptions.javaHome(BuildOptions.scala:251)
scala.cli.commands.repl.Repl$.maybeRunRepl$1(Repl.scala:410)
scala.cli.commands.repl.Repl$.runRepl$$anonfun$1(Repl.scala:476)
scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
scala.build.EitherCps$Helper.apply(EitherCps.scala:19)
scala.cli.commands.repl.Repl$.runRepl(Repl.scala:478)
scala.cli.commands.repl.Repl$.doRunRepl$1(Repl.scala:144)
scala.cli.commands.repl.Repl$.runThing$1(Repl.scala:195)
scala.cli.commands.repl.Repl$.runCommand(Repl.scala:198)
scala.cli.commands.default.Default.runCommand(Default.scala:53)
scala.cli.commands.default.Default.runCommand(Default.scala:40)
scala.cli.commands.ScalaCommand.run(ScalaCommand.scala:388)
scala.cli.commands.ScalaCommand.run(ScalaCommand.scala:369)
caseapp.core.app.CaseApp.main(CaseApp.scala:157)
scala.cli.commands.ScalaCommand.main(ScalaCommand.scala:354)
caseapp.core.app.CommandsEntryPoint.main(CommandsEntryPoint.scala:169)
scala.cli.ScalaCliCommands.main(ScalaCliCommands.scala:125)
scala.cli.ScalaCli$.main0(ScalaCli.scala:295)
scala.cli.ScalaCli$.main(ScalaCli.scala:119)
scala.cli.ScalaCli.main(ScalaCli.scala)
java.io.IOException: error=2, No such file or directory
java.base@17.0.6/java.lang.ProcessImpl.forkAndExec(ProcessImpl.java)
java.base@17.0.6/java.lang.ProcessImpl.<init>(ProcessImpl.java:314)
java.base@17.0.6/java.lang.ProcessImpl.start(ProcessImpl.java:244)
java.base@17.0.6/java.lang.ProcessBuilder.start(ProcessBuilder.java:1110)
java.base@17.0.6/java.lang.ProcessBuilder.start(ProcessBuilder.java:1073)
os.proc.proc$lzyINIT1$1(ProcessOps.scala:138)
os.proc.proc$1(ProcessOps.scala:142)
os.proc.spawn(ProcessOps.scala:144)
os.proc.call(ProcessOps.scala:87)
scala.build.internal.OsLibc$.javaVersion(OsLibc.scala:80)
scala.build.options.BuildOptions$JavaHomeInfo$.apply(BuildOptions.scala:617)
scala.build.options.BuildOptions.javaCommand0$lzyINIT1$$anonfun$1(BuildOptions.scala:243)
scala.build.Positioned.map(Positioned.scala:15)
scala.build.options.BuildOptions.javaCommand0$lzyINIT1(BuildOptions.scala:243)
scala.build.options.BuildOptions.javaCommand0(BuildOptions.scala:243)
scala.build.options.BuildOptions.javaHome(BuildOptions.scala:251)
scala.cli.commands.repl.Repl$.maybeRunRepl$1(Repl.scala:410)
scala.cli.commands.repl.Repl$.runRepl$$anonfun$1(Repl.scala:476)
scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
scala.build.EitherCps$Helper.apply(EitherCps.scala:19)
scala.cli.commands.repl.Repl$.runRepl(Repl.scala:478)
scala.cli.commands.repl.Repl$.doRunRepl$1(Repl.scala:144)
scala.cli.commands.repl.Repl$.runThing$1(Repl.scala:195)
scala.cli.commands.repl.Repl$.runCommand(Repl.scala:198)
scala.cli.commands.default.Default.runCommand(Default.scala:53)
scala.cli.commands.default.Default.runCommand(Default.scala:40)
scala.cli.commands.ScalaCommand.run(ScalaCommand.scala:388)
scala.cli.commands.ScalaCommand.run(ScalaCommand.scala:369)
caseapp.core.app.CaseApp.main(CaseApp.scala:157)
scala.cli.commands.ScalaCommand.main(ScalaCommand.scala:354)
caseapp.core.app.CommandsEntryPoint.main(CommandsEntryPoint.scala:169)
scala.cli.ScalaCliCommands.main(ScalaCliCommands.scala:125)
scala.cli.ScalaCli$.main0(ScalaCli.scala:295)
scala.cli.ScalaCli$.main(ScalaCli.scala:119)
scala.cli.ScalaCli.main(ScalaCli.scala)
13 changes: 5 additions & 8 deletions example/src/main/scala/app/wishingtree/HttpAppExample.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,24 @@ package app.wishingtree

import com.sun.net.httpserver.Filter
import dev.wishingtree.branch.spider.Paths.*
import dev.wishingtree.branch.spider.{
ContextHandler,
HttpApp,
HttpVerb,
RequestHandler
}
import dev.wishingtree.branch.spider.*

object HttpAppExample extends HttpApp {

import RequestHandler.given
case class GreeterGetter() extends RequestHandler[Unit, String] {
override def handle(request: Request[Unit]): Response[String] = {
Response(Map.empty, "Aloha")
Response("Aloha")
}
}

val alohaGreeter = GreeterGetter()

case class EchoGetter(msg: String) extends RequestHandler[Unit, String] {
override def handle(request: Request[Unit]): Response[String] = {
Response(Map.empty, msg)
Response(msg)
.textContent
.withHeader("this" -> "that")
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package dev.wishingtree.branch.spider

case class ContentType(content: String)

object ContentType {

extension (ct: ContentType) {
def toHeader: (String, List[String]) = "Content-Type" -> List(ct.content)
}

// https://developer.mozilla.org/en-US/docs/Web/HTTP/MIME_types/Common_types

val html = ContentType("text/html")
val plainText = ContentType("text/plain")
val javaScript = ContentType("text/javascript")

val jpeg = ContentType("image/jpeg")
val png = ContentType("image/png")

val octetStream = ContentType("application/octet-stream")
val json = ContentType("application/json")

}
22 changes: 22 additions & 0 deletions spider/src/main/scala/dev/wishingtree/branch/spider/Request.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package dev.wishingtree.branch.spider

import java.net.URI

case class Request[A](uri: URI, headers: Map[String, List[String]], body: A)

object Request {

def parseQueryParams(qpStr: String): Map[String, String] = {
qpStr
.split("&")
.map { case s"$key=$value" =>
key -> value
}
.toMap
}

extension [A](r: Request[A]) {
def queryParams: Map[String, String] = parseQueryParams(r.uri.getQuery)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,13 @@ package dev.wishingtree.branch.spider
import com.sun.net.httpserver.HttpExchange
import dev.wishingtree.branch.lzy.Lazy

import java.net.URI
import scala.jdk.CollectionConverters.*

trait RequestHandler[I, O](using
requestDecoder: Conversion[Array[Byte], I],
responseEncoder: Conversion[O, Array[Byte]]
) {

private def parseQueryParams(qpStr: String): Map[String, String] = {
qpStr
.split("&")
.map { case s"$key=$value" =>
key -> value
}
.toMap
}

case class Request[A](uri: URI, headers: Map[String, List[String]], body: A) {
final lazy val queryParams = parseQueryParams(uri.getQuery)
}
case class Response[A](headers: Map[String, List[String]], body: A)

def handle(request: Request[I]): Response[O]

private def decodeRequest(
Expand All @@ -46,12 +31,12 @@ trait RequestHandler[I, O](using
): Lazy[Unit] = {
for {
rawResponse <- Lazy.fn(responseEncoder(response.body))
_ <- Lazy.fn(exchange.sendResponseHeaders(200, rawResponse.length))
_ <- Lazy.fn {
response.headers.foreach { (k, v) =>
exchange.getResponseHeaders.set(k, v.mkString(","))
}
}
_ <- Lazy.fn(exchange.sendResponseHeaders(200, rawResponse.length))
_ <- Lazy.fn(exchange.getResponseBody.write(rawResponse))
} yield ()
}
Expand Down
28 changes: 28 additions & 0 deletions spider/src/main/scala/dev/wishingtree/branch/spider/Response.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package dev.wishingtree.branch.spider

case class Response[A](
body: A,
headers: Map[String, List[String]] = Map(
ContentType.octetStream.toHeader
)
)

object Response {

extension [A](r: Response[A]) {

def withHeader(header: (String, String)) =
r.copy(headers = r.headers + (header._1 -> List(header._2)))

def withContentType(contentType: ContentType): Response[A] =
r.copy(headers = r.headers + contentType.toHeader)

def withContentType(contentType: String): Response[A] =
withContentType(ContentType(contentType))

def textContent: Response[A] =
r.withContentType(ContentType.plainText)

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class RequestHandlerSpec extends HttpFunSuite {

case class AlohaGreeter() extends RequestHandler[Unit, String] {
override def handle(request: Request[Unit]): Response[String] = {
Response(Map.empty, "Aloha")
Response("Aloha")
}
}

Expand Down

0 comments on commit 2d64258

Please sign in to comment.