Spider

Oh, what a tangled web we weave when first we practice http

Spider is built on top of the built-in Java HttpServer.

JVM HttpServer

In the context of the HttpServer, you build HttpHandlers that handles an HttpExchange (receiving a request and returning a response). These handlers are registered with a root URI to the server via an HttpContext

When a HTTP request is received, the appropriate HttpContext (and handler) is located by finding the context whose path is the longest matching prefix of the request URI's path. Paths are matched literally, which means that the strings are compared case sensitively, though there is a ci string interpolator to help with case-insensitive matching.

Spider

Spider encapsulates this via RequestHandlers and ContextHandlers.

There is a RequestHandler[I,O] to extend for handling a route. You will need to use a Convserion from Array[Byte] to an Input model I, and a similar outgoing conversion for you output model O. With these conversions, the RequestHandler takes care of parsing the input streams from the HttpExchange and writing the response to the output stream. Some conversions for simple types are provided by import RequestHandler.given.

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

Implementing this trait, you will then need to write the function that will handle a Request[I] and return a Response[O].

Here is an example that returns the string Aloha.

import RequestHandler.given

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

Request is a case class that wraps/holds some values based on the request of the HttpContext, and Response similarly models the output in a more Scala friendly way.

With all of your request handlers made, we can then use them in a Contexthandler.

trait ContextHandler(val path: String) 

The main thing to implement here is the contextRouter. The contextRouter is a PartialFunction that matches the http method/verb and request path and maps to a specific Requesthandler.

Here is an example:


case class EchoGetter(msg: String) extends RequestHandler[Unit, String] {
  override def handle(request: Request[Unit]): Response[String] = {
    Response(msg)
  }
}

val myhandler = new ContextHandler("/") {

  override val contextRouter
  : PartialFunction[(HttpVerb, Path), RequestHandler[?, ?]] = {
    case HttpVerb.GET -> >> / "some" / "path" => alohaGreeter
    case HttpVerb.GET -> >> / "some" / "path" / s"$arg" => EchoGetter(arg)
  }

}

We can then register out ContextHandler to an instance of the http server

ContextHandler.registerHandler(myhandler)(using httpServer: HttpServer)

ContextHandlers support Filters (what might typically be described as middleware, that ge process in the request/response chain), as well as the Authenticator class. These are specific to the root path the ContextHandler is bound to, and this could help determine when to group things into multiple ContextHandlers.

There is an HttpApp trait that sets up the server for you in an entry point. A quick example:

object HttpAppExample extends HttpApp {

  import RequestHandler.given

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

  val alohaGreeter = GreeterGetter()

  case class EchoGetter(msg: String) extends RequestHandler[Unit, String] {
    override def handle(request: Request[Unit]): Response[String] = {
      Response(msg)
    }
  }

  val myhandler = new ContextHandler("/") {

    override val filters: Seq[Filter] = Seq(
      ContextHandler.timingFilter
    )

    override val contextRouter
    : PartialFunction[(HttpVerb, Path), RequestHandler[?, ?]] = {
      case HttpVerb.GET -> >> / "some" / "path" => alohaGreeter
      case HttpVerb.GET -> >> / "some" / "path" / s"$arg" => EchoGetter(arg)
    }

  }

  ContextHandler.registerHandler(myhandler)
}

Other Libraries

If you like Spider, you should check out Tapir