unfiltered unveiled

39
Unfiltered Unveiled The Un-framework © Wilfred Springer, 2015 | http://www.flotsam.nl/

Upload: wilfred-springer

Post on 18-Jul-2015

55 views

Category:

Software


1 download

TRANSCRIPT

Unfiltered UnveiledThe Un-framework

© Wilfred Springer, 2015 | http://www.flotsam.nl/

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/

HTTP

Stuff => Stuff

© Wilfred Springer, 2015 | http://www.flotsam.nl/

HTTP

Request => Response

© 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/

Upping our gameWhat else do we need?

© 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 Concernsor, how to use Spring AOP

© Wilfred Springer, 2015 | http://www.flotsam.nl/

Cross Cutting Concernsor, how to use Spring AOP

© Wilfred Springer, 2015 | http://www.flotsam.nl/

Cross Cutting ConcernsHow NOT to use Spring AOP

© 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/

© Wilfred Springer, 2015 | http://www.flotsam.nl/