Skip to content
Permalink
Browse files

DSL for defining custom 404s and Exception handling

  * removes ErrorService
  * moves 404/exception handling to Appservice
  * removed IntegrationSpec in favor of
  * ExampleSpec
  • Loading branch information
Christopher Burnett committed Nov 20, 2012
1 parent add4c36 commit 37c81957271dde77d4c3f6361bbae705a5142c89
@@ -17,35 +17,36 @@ package com.twitter.finatra

import com.twitter.finagle.Service
import com.twitter.util.Future
import org.jboss.netty.buffer.ChannelBuffers.copiedBuffer
import org.jboss.netty.handler.codec.http._
import org.jboss.netty.handler.codec.http.HttpResponseStatus._
import org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1
import org.jboss.netty.util.CharsetUtil.UTF_8
import com.twitter.finagle.http.{Request => FinagleRequest, Response => FinagleResponse}


class AppService(controllers: ControllerCollection)
extends Service[FinagleRequest, FinagleResponse] with Logging {

def notFoundResponse = {
val resp = new DefaultHttpResponse(HTTP_1_1, NOT_FOUND)
resp.setContent(copiedBuffer("not found", UTF_8))
Future.value(FinagleResponse(resp))
}
def render = new Response

def apply(rawRequest: FinagleRequest) = {
val adaptedRequest = RequestAdapter(rawRequest)
val responseConverter = new FinatraResponseConverter

try {
attemptRequest(rawRequest)
} catch {
case e: Exception =>
logger.error(e, "Internal Server Error")
adaptedRequest.error = Some(e)
responseConverter(controllers.errorHandler(adaptedRequest))
}

def errorResponse = {
val resp = new DefaultHttpResponse(HTTP_1_1, INTERNAL_SERVER_ERROR)
resp.setContent(copiedBuffer("ERROR", UTF_8))
Future.value(FinagleResponse(resp))
}

def apply(rawRequest: FinagleRequest) = {
def attemptRequest(rawRequest: FinagleRequest) = {
val adaptedRequest = RequestAdapter(rawRequest)
val responseConverter = new FinatraResponseConverter
controllers.dispatch(rawRequest) match {
case Some(response) =>
response.asInstanceOf[Future[FinagleResponse]]
case None =>
notFoundResponse
responseConverter(controllers.notFoundHandler(adaptedRequest))
}
}

@@ -1,3 +1,19 @@
/**
* Copyright (C) 2012 Twitter Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.twitter.finatra

object Config {
@@ -27,13 +27,23 @@ class Controller(statsReceiver: StatsReceiver = NullStatsReceiver) extends Loggi

val routes = new RouteVector[(HttpMethod, PathPattern, Request => Future[Response])]

var notFoundHandler: Option[(Request) => Future[Response]] = None
var errorHandler: Option[(Request) => Future[Response]] = None

def get(path: String) (callback: Request => Future[Response]) { addRoute(HttpMethod.GET, path)(callback) }
def delete(path: String)(callback: Request => Future[Response]) { addRoute(HttpMethod.DELETE, path)(callback) }
def post(path: String) (callback: Request => Future[Response]) { addRoute(HttpMethod.POST, path)(callback) }
def put(path: String) (callback: Request => Future[Response]) { addRoute(HttpMethod.PUT, path)(callback) }
def head(path: String) (callback: Request => Future[Response]) { addRoute(HttpMethod.HEAD, path)(callback) }
def patch(path: String) (callback: Request => Future[Response]) { addRoute(HttpMethod.PATCH, path)(callback) }

def notFound(callback: Request => Future[Response]) {
notFoundHandler = Option(callback)
}

def error(callback: Request => Future[Response]) {
errorHandler = Option(callback)
}

def dispatch(request: FinagleRequest): Option[FinagleResponse] = {
logger.info("%s %s", request.method, request.uri)
@@ -3,11 +3,22 @@ package com.twitter.finatra
import com.twitter.finagle.http.{Request => FinagleRequest, Response => FinagleResponse}

class ControllerCollection {
var ctrls: Seq[Controller] = Seq.empty
var controllers: Seq[Controller] = Seq.empty

var notFoundHandler = { request:Request =>
render.status(404).plain("Not Found").toFuture
}

var errorHandler = { request:Request =>
render.status(500).plain("Something went wrong!").toFuture
}

def render = new Response

def dispatch(request: FinagleRequest):Option[FinagleResponse] = {
var response:Option[FinagleResponse] = None
ctrls.find { ctrl =>

controllers.find { ctrl =>
ctrl.dispatch(request) match {
case Some(callbackResponse) =>
response = Some(callbackResponse)
@@ -16,11 +27,14 @@ class ControllerCollection {
false
}
}

response
}

def add(controller: Controller) {
ctrls = ctrls ++ Seq(controller)
notFoundHandler = controller.notFoundHandler.getOrElse(notFoundHandler)
errorHandler = controller.errorHandler.getOrElse(errorHandler)
controllers = controllers ++ Seq(controller)
}

}

This file was deleted.

@@ -79,9 +79,7 @@ class FinatraServer extends Logging {

val appService = new AppService(controllers)
val fileService = new FileService
val errorService = new ErrorFilter

addFilter(errorService)
addFilter(fileService)

val port = Config.getInt("port")
@@ -21,8 +21,9 @@ import com.twitter.finagle.http.{Request => FinagleRequest, RequestProxy}
class Request(rawRequest: FinagleRequest) extends RequestProxy {

var multiParams: Map[String, MultipartItem] = Map.empty
var routeParams:Map[String, String] = Map.empty
var routeParams: Map[String, String] = Map.empty

var request = rawRequest
var error: Option[Exception] = None

}
@@ -19,7 +19,7 @@ import com.twitter.util.Future
import scala.collection.Map
import org.jboss.netty.util.CharsetUtil.UTF_8
import com.twitter.finagle.http.{Request => FinagleRequest, Response => FinagleResponse}
import com.twitter.finatra.Controller
import com.twitter.finatra.{AppService, ControllerCollection, Controller}
import org.jboss.netty.handler.codec.http.HttpMethod
import org.scalatest.FlatSpec
import org.scalatest.matchers.ShouldMatchers
@@ -47,10 +47,12 @@ abstract class SpecHelper extends FlatSpec with ShouldMatchers {
request.httpRequest.setHeader(header._1, header._2)
}

lastResponse = app.dispatch(request).asInstanceOf[Option[Future[FinagleResponse]]] match {
case Some(r) => r
case None => throw new IllegalStateException("Unable to route path '" + path +"'")
}
val collection = new ControllerCollection
collection.add(app)

val appService = new AppService(collection)

lastResponse = appService(request)
}

def app:Controller
@@ -1,3 +1,18 @@
/**
* Copyright (C) 2012 Twitter Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.twitter.finatra

import test.SpecHelper
@@ -88,6 +103,31 @@ class ExampleSpec extends SpecHelper {
val anView = new AnView
render.view(anView).toFuture
}


/**
* Custom Error Handling
*
* curl http://localhost:7070/error
*/
get("/error") { request =>
1234/0
render.plain("we never make it here").toFuture
}

error { request =>
render.status(500).plain("whoops!").toFuture
}


/**
* Custom 404s
*
* curl http://localhost:7070/notfound
*/
notFound { request =>
render.status(404).plain("not found yo").toFuture
}
}

val app = new ExampleApp
@@ -97,6 +137,18 @@ class ExampleSpec extends SpecHelper {

/* ###BEGIN_SPEC### */

"GET /notfound" should "respond 404" in {
get("/notfound")
response.body should equal ("not found yo")
response.code should equal (404)
}

"GET /error" should "respond 500" in {
get("/error")
response.body should equal ("whoops!")
response.code should equal (500)
}

"GET /hello" should "respond with hello world" in {
get("/")
response.body should equal ("hello world")

This file was deleted.

0 comments on commit 37c8195

Please sign in to comment.