From 57138509521e04f9e8ca8ca77472fee4578aa49a Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Sun, 12 Aug 2018 20:50:38 +0800 Subject: add readme for main configuration --- cask/src/cask/main/Main.scala | 105 ++++++++++++++++++++++++------------------ readme.md | 50 ++++++++++++++++++++ 2 files changed, 109 insertions(+), 46 deletions(-) diff --git a/cask/src/cask/main/Main.scala b/cask/src/cask/main/Main.scala index 4a8182b..87c66c4 100644 --- a/cask/src/cask/main/Main.scala +++ b/cask/src/cask/main/Main.scala @@ -17,8 +17,9 @@ class Main(servers0: Routes*) extends BaseMain{ abstract class BaseMain{ def mainDecorators = Seq.empty[cask.main.Decorator] def allRoutes: Seq[Routes] - val port: Int = 8080 - val host: String = "localhost" + def port: Int = 8080 + def host: String = "localhost" + def debugMode: Boolean = true lazy val routeList = for{ routes <- allRoutes @@ -44,64 +45,76 @@ abstract class BaseMain{ response.data.write(exchange.getOutputStream) } - def handleError(statusCode: Int): Response = { + def handleNotFound(): Response = { Response( - s"Error $statusCode: ${Status.codesToStatus(statusCode).reason}", - statusCode = statusCode + s"Error 404: ${Status.codesToStatus(404).reason}", + statusCode = 404 ) } - def defaultHandler = new HttpHandler() { - def handleRequest(exchange: HttpServerExchange): Unit = { - routeTries(exchange.getRequestMethod.toString.toLowerCase()).lookup(Util.splitPath(exchange.getRequestPath).toList, Map()) match{ - case None => writeResponse(exchange, handleError(404)) - case Some(((routes, metadata), extBindings, remaining)) => - val ctx = ParamContext(exchange, remaining) - def rec(remaining: List[Decorator], - bindings: List[Map[String, Any]]): Router.Result[Response] = try { - remaining match { - case head :: rest => - head.wrapFunction(ctx, args => rec(rest, args :: bindings)) - - case Nil => - metadata.endpoint.wrapFunction(ctx, epBindings => - metadata.entryPoint - .asInstanceOf[EntryPoint[cask.main.Routes, cask.model.ParamContext]] - .invoke(routes, ctx, (epBindings ++ extBindings.mapValues(metadata.endpoint.wrapPathSegment)) :: bindings.reverse) - .asInstanceOf[Router.Result[Nothing]] - ) - + def defaultHandler = new BlockingHandler( + new HttpHandler() { + def handleRequest(exchange: HttpServerExchange): Unit = { + routeTries(exchange.getRequestMethod.toString.toLowerCase()).lookup(Util.splitPath(exchange.getRequestPath).toList, Map()) match{ + case None => writeResponse(exchange, handleNotFound()) + case Some(((routes, metadata), extBindings, remaining)) => + val ctx = ParamContext(exchange, remaining) + def rec(remaining: List[Decorator], + bindings: List[Map[String, Any]]): Router.Result[Response] = try { + remaining match { + case head :: rest => + head.wrapFunction(ctx, args => rec(rest, args :: bindings)) + + case Nil => + metadata.endpoint.wrapFunction(ctx, epBindings => + metadata.entryPoint + .asInstanceOf[EntryPoint[cask.main.Routes, cask.model.ParamContext]] + .invoke(routes, ctx, (epBindings ++ extBindings.mapValues(metadata.endpoint.wrapPathSegment)) :: bindings.reverse) + .asInstanceOf[Router.Result[Nothing]] + ) + + } + // Make sure we wrap any exceptions that bubble up from decorator + // bodies, so outer decorators do not need to worry about their + // delegate throwing on them + }catch{case e: Throwable => Router.Result.Error.Exception(e) } + + rec((metadata.decorators ++ routes.decorators ++ mainDecorators).toList, Nil)match{ + case Router.Result.Success(response: Response) => writeResponse(exchange, response) + case e: Router.Result.Error => writeResponse(exchange, handleEndpointError(exchange, routes, metadata, e)) } - // Make sure we wrap any exceptions that bubble up from decorator - // bodies, so outer decorators do not need to worry about their - // delegate throwing on them - }catch{case e: Throwable => Router.Result.Error.Exception(e) } - - rec((metadata.decorators ++ routes.decorators ++ mainDecorators).toList, Nil)match{ - case Router.Result.Success(response: Response) => writeResponse(exchange, response) - case e: Router.Result.Error => - - writeResponse(exchange, - Response( - ErrorMsgs.formatInvokeError( - routes, - metadata.entryPoint.asInstanceOf[EntryPoint[cask.main.Routes, _]], - e - ), - statusCode = 500 - ) - ) - } + } } } + ) + + def handleEndpointError(exchange: HttpServerExchange, + routes: Routes, + metadata: Routes.EndpointMetadata[_], + e: Router.Result.Error) = { + val statusCode = e match { + case _: Router.Result.Error.Exception => 500 + case _: Router.Result.Error.InvalidArguments => 400 + case _: Router.Result.Error.MismatchedArguments => 400 + } + Response( + if (!debugMode) s"Error $statusCode: ${Status.codesToStatus(statusCode).reason}" + else ErrorMsgs.formatInvokeError( + routes, + metadata.entryPoint.asInstanceOf[EntryPoint[cask.main.Routes, _]], + e + ), + statusCode = statusCode + ) + } def main(args: Array[String]): Unit = { val server = Undertow.builder .addHttpListener(port, host) - .setHandler(new BlockingHandler(defaultHandler)) + .setHandler(defaultHandler) .build server.start() } diff --git a/readme.md b/readme.md index b791c8d..4add926 100644 --- a/readme.md +++ b/readme.md @@ -844,3 +844,53 @@ object Server extends cask.MainRoutes{ initialize() } ``` + +Main Customization +------------------ + +Apart from the code used to configure and define your routes and endpoints, Cask +also allows global configuration for things that apply to the entire web server. +This can be done by overriding the following methods on `cask.Main` or +`cask.MainRoutes`: + +### def debugMode: Boolean = true + +Makes the Cask report verbose error messages and stack traces if an endpoint +fails; useful for debugging, should be disabled for production. + +### def main + +The cask program entrypoint. By default just spins up a webserver, but you can +override it to do whatever you like before or after the webserver runs. + +### def defaultHandler + +Cask is built on top of the [Undertow](http://undertow.io/) web server. If you +need some low-level functionality not exposed by the Cask API, you can override +`defaultHandler` to make use of Undertow's own +[handler API](http://undertow.io/undertow-docs/undertow-docs-2.0.0/index.html#built-in-handlers) +for customizing your webserver. This allows for things that Cask itself doesn't +internally support: asynchronous requests & response, +[Websockets](http://undertow.io/undertow-docs/undertow-docs-2.0.0/index.html#websockets), +etc. + +### def port: Int = 8080, def host: String = "localhost" + +The host & port to attach your webserver to. + +### def handleNotFound + +The response to serve when the incoming request does not match any of the routes +or endpoints; defaults to a typical 404 + +### def handleEndpointError + +The response to serve when the incoming request matches a route and endpoint, +but then fails for other reasons. Defaults to 400 for mismatched or invalid +endpoint arguments and 500 for exceptions in the endpoint body, and provides +useful stack traces or metadata for debugging if `debugMode = true`. + +### def mainDecorators + +Any `cask.Decorator`s that you want to apply to all routes and all endpoints in +the entire web application \ No newline at end of file -- cgit v1.2.3