Skip to content

Commit

Permalink
add readme for main configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
lihaoyi committed Aug 12, 2018
1 parent 0c1ebbd commit 5713850
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 46 deletions.
105 changes: 59 additions & 46 deletions cask/src/cask/main/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
}
Expand Down
50 changes: 50 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 5713850

Please sign in to comment.