Upload
wilfred-springer
View
58
Download
1
Embed Size (px)
Citation preview
Whoami
Code/train Scala/Node.js for a living
* Currently @ Gust* Formerly @ ProQuest Flow* Formerly @ ProQuest Udini* Formerly @ Bol.com* Formerly @ Xebia* Formerly @ TomTom* Formerly @ Sun Microsystems
© Wilfred Springer, 2015 | http://www.flotsam.nl/
What are we trying to solve here?
GET / HTTP/1.0 HTTP/1.1 200 OKHost: nxt.flotsam.nl Date: Mon, 27 April, 2015Accept: text/html … Last-Modified: … ETag: "4ce43…" Content-Type: text/html Content-Length: 9636 Server: AmazonS3 Content: …
© Wilfred Springer, 2015 | http://www.flotsam.nl/
And therefore, we shall have an ∞/insane number of web
frameworks
© Wilfred Springer, 2015 | http://www.flotsam.nl/
Web Application
Request1 => Response1Request2 => Response2Request3 => Response3Request4 => Response4
© Wilfred Springer, 2015 | http://www.flotsam.nl/
Wait, that rings a bell
… match { case Request1 => Response1 case Request2 => Response2 case Request3 => Response3 case Request4 => Response4}
© Wilfred Springer, 2015 | http://www.flotsam.nl/
Pattern matching
def doubleOf(obj: Any) = {
obj match { case i: Int => i * 2 case s: String => s + s }
}
© Wilfred Springer, 2015 | http://www.flotsam.nl/
Partial Function
{ case i: Int => i * 2 case s: String => s + s}
© Wilfred Springer, 2015 | http://www.flotsam.nl/
Partial Function
val doubleOf = PartialFunction[Any, Any] = { case i: Int => i * 2 case s: String => s + s}
doubleOf(3) // 6doubleOf("ha") // haha
© Wilfred Springer, 2015 | http://www.flotsam.nl/
Unfiltered AnatomyThe Intent (Simplified)
type Intent = PartialFunction[HttpRequest, ResponseFunction]
© Wilfred Springer, 2015 | http://www.flotsam.nl/
Unfiltered AnatomyThe Intent
type Intent[A,B] = PartialFunction[ HttpRequest[A], // A: some request representation ResponseFunction[B] // B: some response representation]
© Wilfred Springer, 2015 | http://www.flotsam.nl/
Unfiltered AnatomyThe Intent
{ case _ => ResponseString("yay")}
---------------------------------
GET / => HTTP/1.1 200 OK Content-Length: 3 Content: yay
© Wilfred Springer, 2015 | http://www.flotsam.nl/
Unfiltered AnatomyBinding
import unfiltered.filterimport unfiltered.netty
// Turn it into a Jetty compatible Filterval plan = filter.Planify(intent)
// Turn it into something Netty compatible val plan = netty.cycle.Planify(intent)
© Wilfred Springer, 2015 | http://www.flotsam.nl/
Unfiltered AnatomyLaunching
import unfiltered.jettyimport unfiltered.filterimport unfiltered.netty
// Turn it into a Jetty compatible Filterval plan = filter.Planify(intent) jetty.Server.http(8080).plan(plan).run()
// Turn it into something Netty compatible val plan = netty.cycle.Planify(intent)netty.Server.http(8080).plan(plan).run()
© Wilfred Springer, 2015 | http://www.flotsam.nl/
ResponseFunctionSimplified, and not really true
trait ResponseFunction[-A] extends HttpResponse[A] => HttpResponse[A]
© Wilfred Springer, 2015 | http://www.flotsam.nl/
ResponseFunction
case … => …case … => … ~> … ~> …
------------------------------------
Ok Html(…)NotFound Html5(…)ResponseString(…) JsonContentJson(…) TextXmlContentResponseWriter(…) Redirect(…)
© Wilfred Springer, 2015 | http://www.flotsam.nl/
ResponseFunction
Ok ~> PlainTextContent ~> ResponseString("foo")
-----------------------------------------------------------
Ok resp.setStatus(SC_OK)
PlainTextContent resp.setContentType("text/plain")
ResponseString("foo") resp.setCharacterEncoding("UTF-8") val writer = resp.getWriter() writer.print("foo") writer.close
© Wilfred Springer, 2015 | http://www.flotsam.nl/
ResponseFunctionComposition using ~>
Ok ~> PlainTextContent ~> ResponseString("foo")
NotFound ~> HtmlContent ~> Html(<html> <body>Not found</body></html>)
Redirect("/index.html")
© Wilfred Springer, 2015 | http://www.flotsam.nl/
Or rather…Scalate
// index.scaml!!! 5%html %head %body Not found
// .scalaimport unfiltered.scalate.Scalate
Ok ~> HtmlContent ~> Scalate(req, "/index.scaml")
© Wilfred Springer, 2015 | http://www.flotsam.nl/
Matching the HttpRequest
case … => …case … & … & … =>
GET(…) Accept(…)POST(…) UserAgent(…)DELETE(…) Host(…)PUT(…) IfModifiedSince(…)Path(…) Referer(…)
© Wilfred Springer, 2015 | http://www.flotsam.nl/
Matching the HttpRequestSamples
case _ => case GET(Path('/index')) case GET(Path(p)) if p endsWith ".json"case Path(Seg("give", "it", "to", "me" :: Nil))case Path("/") & Params(params) case Accept("application/json")
© Wilfred Springer, 2015 | http://www.flotsam.nl/
It's just extractors, dude
object DotJson { unapply[A](req: HttpRequest[A]) = req.uri endsWith ".json"}
case DotJson() => ResponseString("Json!")
© Wilfred Springer, 2015 | http://www.flotsam.nl/
Summary so far
— An Intent is just a partial function
— accepting an HttpRequest,
— producing a ResponseFunction
— … which will in turn produce an HttpResponse
— Matching based on HttpRequest extractors
— Use & to compose HttpRequest extractors
— Use ~> to compose ResponseFunctions© Wilfred Springer, 2015 | http://www.flotsam.nl/
Reusable Composite ResponseFunctionsDon't Repeat Yourself
… => Ok ~> HtmlContent ~> Html(<html><body>text1</body></html>)… => Ok ~> HtmlContent ~> Html(<html><body>text2</body></html>)… => Ok ~> HtmlContent ~> Html(<html><body>text2</body></html>)
// vs
case class Html5(text: String) extends ComposeResponse( Ok ~> HtmlContent ~> Html(<html><body>{text}</body></html>) )
… => Html5("text1")… => Html5("text2")… => Html5("text3")
© Wilfred Springer, 2015 | http://www.flotsam.nl/
Cross Cutting ConcernsKit
In general, a Kit is something that:
— Takes an Intent (but it doesn't have to)
— Produces an Intent (which it always has to)
Anything that produces an Intent should be considered a Kit
© Wilfred Springer, 2015 | http://www.flotsam.nl/
Cross Cutting ConcernsAuthentication
import unfiltered.kit.Auth
def verify(username: String, password: String): Boolean = …
Auth.basic(verify) { | { case … => … | case … => … case … => … | case … => … case … => … | case … => …} | }
© Wilfred Springer, 2015 | http://www.flotsam.nl/
Cross Cutting Concerns
// Can you tell the issue with this code?import unfiltered.request._import unfiltered.response._
val Simple = unfiltered.filter.Planify { case Path("/") & Accepts.Json(_) => JsonContent ~> ResponseString("""{ "response": "Ok" }""")}unfiltered.jetty.Server(8080).plan(Simple).run()
© Wilfred Springer, 2015 | http://www.flotsam.nl/
Cross Cutting ConcernsDirectives for error handling
import unfiltered.directives._, Directives._
val Smart = unfiltered.filter.Planify { Directive.Intent { case Path("/") => for { _ <- Accepts.Json } yield JsonContent ~> ResponseString("""{ "response": "Ok" }""")} }unfiltered.jetty.Server(8080).plan(Smart).run()
© Wilfred Springer, 2015 | http://www.flotsam.nl/
Cross Cutting ConcernsDirectives for routing
val Sweet = unfiltered.filter.Planify { Directive.Intent.Path { case "/" => for { _ <- Accepts.Json } yield JsonContent ~> ResponseString("""{ "response": "Ok" }""")} }unfiltered.jetty.Server(8080).plan(Sweet).run()
© Wilfred Springer, 2015 | http://www.flotsam.nl/
Parameter Based RoutingPrimitive (never do this)
case Params(ps) if ps contains "name" => val name = ps.get("name").head
© Wilfred Springer, 2015 | http://www.flotsam.nl/
Parameter Based RoutingNo longer preferred
// Building extractor for name parameterobject Name extends Params.Extract("name", Params.first)
case GET("/") & Name(name) =>
© Wilfred Springer, 2015 | http://www.flotsam.nl/
Parameter Based RoutingGetting it right with Directives and Interpreters
Directive.Intent { case Path("/") => for { name <- data.as.String ~> required named "name" } yield ResponseString(name)}
© Wilfred Springer, 2015 | http://www.flotsam.nl/