Sunday, October 25, 2015

A tiny web framework demonstrating functional programming patterns

From the «Functional Programming Patterns» book.

package com.mblinn.mbfpp.oo.tinyweb

import scala.util.Random


/**
 * Created by IDEA on 25/10/15.
 */
object Stepfour {

  import com.mblinn.oo.tinyweb.{ControllerException, RenderingException}

  type Model = Map[String, List[String]]

  trait View {
    def render(model: Model): String
  }

  class FunctionView(viewRenderer: (Model) => String) extends View {
    def render(model: Map[String, List[String]]) =
      try
        viewRenderer(model)
      catch {
        case e: Exception => throw new RenderingException(e)
      }
  }


  case class HttpRequest(headers: Model = Map(), body: String, path: String)

  case class HttpResponse(body: String, responseCode: Integer)


  trait Controller {
    def handleRequest(httpRequest: HttpRequest): HttpResponse
  }

  class FunctionController(view: View, doRequest: (HttpRequest) =>
    Model) extends Controller {

    def handleRequest(request: HttpRequest): HttpResponse =
      try {
        val model = doRequest(request)
        val responseBody = view.render(model)
        HttpResponse(responseBody, 200)
      } catch {
        case e: ControllerException =>
          HttpResponse("", e.getStatusCode)
        case e: RenderingException =>
          HttpResponse("Exception while rendering.", 500)
        case e: Exception =>
          HttpResponse("", 500)
      }

  }


  class TinyWeb(controllers: Map[String, Controller],
                filters: List[(HttpRequest) => HttpRequest]) {

    def handleRequest(httpRequest: HttpRequest): Option[HttpResponse] = {
      val composedFilter = filters.reverse.reduceLeft(
        (composed, next) => composed compose next)
      val filteredRequest = composedFilter(httpRequest)
      val controllerOption = controllers.get(filteredRequest.path)
      controllerOption map { controller => controller.handleRequest(filteredRequest) }
    }
  }

}

object Test {

  import Stepfour._

  // generate a view
  def renderGreeting(greeting: String) = {
    "<h2>%s</h2>".format(greeting)
  }

  def greetingViewRenderer(model: Model) =
    "<h1>Friendly Greetings: %s</h1>".format(
      model.getOrElse("greetings", List.empty[String])
        .map(renderGreeting)
        .mkString(" ")
    )

  def greetingView = new FunctionView(greetingViewRenderer) // generate a view end


  // generate a model
  import scala.util.Random
  def random = new Random()

  val greetings = List("Hi", "Hoi", "Hola", "Bonjour")

  def makeGreeting(name: String): String = {
    "%s, %s".format(greetings(random.nextInt(greetings.size)), name)
  }

  def handleGreetingRequest(request: HttpRequest): Model = {
    Map("greetings" -> request.body.split(",").toList.map(makeGreeting))
  } // generate a model end


  // controller
  def greetingController = new FunctionController(greetingView, handleGreetingRequest) // controller end

  def loggingFilter(request: HttpRequest) = {
    println("In Logging Filter - request for path: %s".format(request.path))
    request
  }

  def tinyweb = new TinyWeb(
    Map("/greeting" -> greetingController),
    List(loggingFilter))
  def testHttpRequest = HttpRequest(
    body="Mike,Joe,John,Steve",
    path="/greeting")

  tinyweb.handleRequest(testHttpRequest).get.body
}

0 comments: