summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLi Haoyi <haoyi.sg@gmail.com>2018-08-12 20:50:38 +0800
committerLi Haoyi <haoyi.sg@gmail.com>2018-08-12 20:50:38 +0800
commit57138509521e04f9e8ca8ca77472fee4578aa49a (patch)
tree5f80e900e5406f7334237723c0e376aca9958631
parent0c1ebbd786ff97e525025a1aaed59dd4d5b512ca (diff)
downloadcask-57138509521e04f9e8ca8ca77472fee4578aa49a.tar.gz
cask-57138509521e04f9e8ca8ca77472fee4578aa49a.tar.bz2
cask-57138509521e04f9e8ca8ca77472fee4578aa49a.zip
add readme for main configuration
-rw-r--r--cask/src/cask/main/Main.scala105
-rw-r--r--readme.md50
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