From f8bb6f693b8450f8e049396fd0e7d032ac7acb23 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Sat, 18 Aug 2018 00:08:01 +0800 Subject: tweak docs --- .../1 - Cask: a Scala HTTP micro-framework .md | 455 --------------------- .../1 - Cask: a Scala HTTP micro-framework.md | 407 ++++++++++++++++++ docs/pages/2 - Main Customization.md | 44 ++ docs/pages/3 - About Cask.md | 94 +++++ 4 files changed, 545 insertions(+), 455 deletions(-) delete mode 100644 docs/pages/1 - Cask: a Scala HTTP micro-framework .md create mode 100644 docs/pages/1 - Cask: a Scala HTTP micro-framework.md create mode 100644 docs/pages/2 - Main Customization.md create mode 100644 docs/pages/3 - About Cask.md (limited to 'docs') diff --git a/docs/pages/1 - Cask: a Scala HTTP micro-framework .md b/docs/pages/1 - Cask: a Scala HTTP micro-framework .md deleted file mode 100644 index e0d0906..0000000 --- a/docs/pages/1 - Cask: a Scala HTTP micro-framework .md +++ /dev/null @@ -1,455 +0,0 @@ -[![Build Status][travis-badge]][travis-link] [![Gitter Chat][gitter-badge]][gitter-link] [![Patreon][patreon-badge]][patreon-link] - - - -[travis-badge]: https://travis-ci.org/lihaoyi/cask.svg -[travis-link]: https://travis-ci.org/lihaoyi/cask -[gitter-badge]: https://badges.gitter.im/Join%20Chat.svg -[gitter-link]: https://gitter.im/lihaoyi/cask?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge -[patreon-badge]: https://img.shields.io/badge/patreon-sponsor-ff69b4.svg -[patreon-link]: https://www.patreon.com/lihaoyi - - -$$$minimalApplication - -[Cask](https://github.com/lihaoyi/cask) is a simple Scala web framework inspired -by Python's [Flask](http://flask.pocoo.org/docs/1.0/) project. It aims to bring -simplicity, flexibility and ease-of-use to Scala webservers, avoiding cryptic -DSLs or complicated asynchrony. - -Getting Started ---------------- - -The easiest way to begin using Cask is by downloading the example project above. - -Unzip one of the example projects available on this page (e.g. above) into a -folder. This should give you the following files: - -```text -build.sc -app/src/MinimalExample.scala -app/test/src/ExampleTests.scala -``` - -- `cd` into the folder, and run - -```bash -./cask -w app.runBackground -``` - -This will server up the Cask application on `http://localhost:8080`. You can -immediately start interacting with it either via the browser, or -programmatically via `curl` or a HTTP client like -[Requests-Scala](https://github.com/lihaoyi/requests-scala): - -```scala -val host = "http://localhost:8080" - -val success = requests.get(host) - -success.text() ==> "Hello World!" -success.statusCode ==> 200 - -requests.get(host + "/doesnt-exist").statusCode ==> 404 - -requests.post(host + "/do-thing", data = "hello").text() ==> "olleh" - -requests.get(host + "/do-thing").statusCode ==> 404 -``` - -These HTTP calls are part of the test suite for the example project, which you -can run using: - -```bash -./cask -w app.test -``` - -To configure your Cask application to work with IntelliJ, you can use: - -```bash -./cask mill.scalalib.GenIdea/idea -``` - -This will need to be re-run when you re-configure your `build.sc` file, e.g. -when adding additional modules or third-party dependencies. - -Cask is just a Scala library, and you can use Cask in any existing Scala project -via the following coordinates: - -```scala -// Mill -ivy"com.lihaoyi::cask:0.1.0" - -// SBT -"com.lihaoyi" %% "cask" % "0.1.0" -``` - -The `./cask` command is just a wrapper around the -[Mill build tool](http://www.lihaoyi.com/mill/); the `build.sc` files you see in -all examples are Mill build files, and you can use your own installation of Mill -instead of `./cask` if you wish. All normal Mill commands and functionality -works for `./cask`. - -Using Cask ----------- - -The following examples will walk you through how to use Cask to accomplish tasks -common to anyone writing a web application. Each example comes with a -downloadable example project with code and unit tests, which you can use via the -same `./cask -w app.runBackground` or `./cask -w app.test` workflows we saw above. - -### Minimal Example - -$$$minimalApplication - -The rough outline of how the minimal example works should be easy to understand: - -- You define an object that inherits from `cask.MainRoutes` - -- Define endpoints using annotated functions, using `@cask.get` or `@cask.post` - with the route they should match - -- Each function can return the data you want in the response, or a - `cask.Response` if you want further customization: response code, headers, - etc. - -- Your function can tale an optional `cask.Request`, which exposes the entire - incoming HTTP request if necessary. In the above example, we use it to read - the request body into a string and return it reversed. - -In most cases, Cask provides convenient helpers to extract exactly the data from -the incoming HTTP request that you need, while also de-serializing it into the -data type you need and returning meaningful errors if they are missing. Thus, -although you can always get all the data necessary through `cask.Request`, it is -often more convenient to use another way, which will go into below. - -As your application grows, you will likely want to split up the routes into -separate files, themselves separate from any configuration of the Main -entrypoint (e.g. overriding the port, host, default error handlers, etc.). You -can do this by splitting it up into `cask.Routes` and `cask.Main` objects: - -$$$minimalApplication2 - -You can split up your routes into separate `cask.Routes` objects as makes sense -and pass them all into `cask.Main`. - -### Variable Routes - -$$$variableRoutes - -You can bind variables to endpoints by declaring them as parameters: these are -either taken from a path-segment matcher of the same name (e.g. `postId` above), -or from query-parameters of the same name (e.g. `param` above). You can make -`param` take a `: String` to match `?param=hello`, an `: Int` for `?param=123` a -`Seq[T]` (as above) for repeated params such as `?param=hello¶m=world`, or -`: Option[T]` for cases where the `?param=hello` is optional. - -If you need to capture the entire sub-path of the request, you can set the flag -`subpath=true` and ask for a `: cask.Subpath` (the name of the param doesn't -matter). This will make the route match any sub-path of the prefix given to the -`@cask` decorator, and give you the remainder to use in your endpoint logic. - -### Multi-method Routes - -$$$httpMethods - -Sometimes, you may want to handle multiple kinds of HTTP requests in the same -endpoint function, e.g. with code that can accept both GETs and POSTs and decide -what to do in each case. You can use the `@cask.route` annotation to do so - -### Receiving Form-encoded or JSON data - -$$$formJsonPost - -If you need to handle a JSON-encoded POST request, you can use the -`@cast.postJson` decorator. This assumes the posted request body is a JSON dict, -and uses its keys to populate the endpoint's parameters, either as raw -`ujson.Js.Value`s or deserialized into `Seq[Int]`s or other things. -Deserialization is handled using the -[uPickle](https://github.com/lihaoyi/upickle) JSON library, though you could -write your own version of `postJson` to work with any other JSON library of your -choice. - -Similarly, you can mark endpoints as `@cask.postForm`, in which case the -endpoints params will be taken from the form-encoded POST body either raw (as -`cask.FormValue`s) or deserialized into simple data structures. Use -`cask.FormFile` if you want the given form value to be a file upload. - -Both normal forms and multipart forms are handled the same way. - -If the necessary keys are not present in the JSON/form-encoded POST body, or the -deserialization into Scala data-types fails, a 400 response is returned -automatically with a helpful error message. - - -### Processing Cookies - -$$$cookies - -Cookies are most easily read by declaring a `: cask.Cookie` parameter; the -parameter name is used to fetch the cookie you are interested in. Cookies can be -stored by setting the `cookie` attribute in the response, and deleted simply by -setting `expires = java.time.Instant.EPOCH` (i.e. to have expired a long time -ago) - -### Serving Static Files - -$$$staticFiles - -You can ask Cask to serve static files by defining a `@cask.staticFiles` endpoint. -This will match any subpath of the value returned by the endpoint (e.g. above -`/static/file.txt`, `/static/folder/file.txt`, etc.) and return the file -contents from the corresponding file on disk (and 404 otherwise). - -Similarly, `@cask.staticResources` attempts to serve a request based on the JVM -resource path, returning the data if a resource is present and a 404 otherwise. - -### Redirects or Aborts - -$$$redirectAbort - -Cask provides some convenient helpers `cask.Redirect` and `cask.Abort` which you -can return; these are simple wrappers around `cask.Request`, and simply set up -the relevant headers or status code for you. - -### HTML Rendering - -Cask doesn't come bundled with HTML templating functionality, but it makes it -really easy to use community-standard libraries like -[Scalatags](https://github.com/lihaoyi/scalatags) to render your HTML. Simply -adding the relevant `ivy"com.lihaoyi::scalatags:0.6.7"` dependency to your -`build.sc` file is enough to render Scalatags templates: - -$$$scalatags - - -### Extending Endpoints with Decorators - - -$$$decorated - -You can write extra decorator annotations that stack on top of the existing -`@cask.get`/`@cask.post` to provide additional arguments or validation. This is -done by implementing the `cask.Decorator` interface and it's `getRawParams` -function. `getRawParams`: - -- Receives a `ParamContext`, which basically gives you full access to the - underlying undertow HTTP connection so you can pick out whatever data you - would like - -- Returns an `Either[Response, cask.Decor[Any]]`. Returning a `Left` lets you - bail out early with a fixed `cask.Response`, avoiding further processing. - Returning a `Right` provides a map of parameter names and values that will - then get passed to the endpoint function in consecutive parameter lists (shown - above), as well as an optional cleanup function that is run after the endpoint - terminates. - -Each additional decorator is responsible for one additional parameter list to -the right of the existing parameter lists, each of which can contain any number -of parameters. - -Decorators are useful for things like: - -- Making an endpoint return a HTTP 403 if the user isn't logged in, but if they are - logged in providing the `: User` object to the body of the endpoint function - -- Rate-limiting users by returning early with a HTTP 429 if a user tries to - access an endpoint too many times too quickly - -- Providing request-scoped values to the endpoint function: perhaps a database - transaction that commits when the function succeeds (and rolls-back if it - fails), or access to some system resource that needs to be released. - -For decorators that you wish to apply to multiple routes at once, you can define -them by overriding the `cask.Routes#decorators` field (to apply to every -endpoint in that routes object) or `cask.Main#mainDecorators` (to apply to every -endpoint, period): - -$$$decorated2 - -This is convenient for cases where you want a set of decorators to apply broadly -across your web application, and do not want to repeat them over and over at -every single endpoint. - -### Custom Endpoints - -$$$endpoints - -When you need more flexibility than decorators allow, you can define your own -custom `cask.Endpoint`s to replace the default set that Cask provides. This -allows you to - -- Change the expected return type of the annotated function, and how allows you - to that type gets processed: the above example trivially expects an allows you - to `Int` which becomes the status code, but you could make it e.g. - automatically serialize returned objects to JSON responses via your favorite - library, or serialize them to bytes via protobufs - -- Change where the first parameter list's params are taken from: `@cask.get` - takes them from query params, `@cask.postForm` takes them from the - form-encoded POST body, and you can write your own endpoint to take the params - from where-ever you like: perhaps from the request headers, or a protobuf- - encoded request body - -- Change how parameters are deserialized: e.g. `@cask.postJson` de-serializes - parameters using the [uPickle](https://github.com/lihaoyi/upickle) JSON - library, and your own custom endpoint could change that to use another library - like [Circe](https://github.com/circe/circe) or - [Jackson](https://github.com/FasterXML/jackson-module-scala) - -- DRY up common sets of decorators: if all your endpoint functions use the same - decorators, you can extract that functionality into a single `cask.Endpoint` - to do the job. - -Generally you should not be writing custom `cask.Endpoint`s every day, but if -you find yourself trying to standardize on a way of doing things across your web -application, it might make sense to write a custom endpoint decorator: to DRY -things up , separate business logic (inside the annotated function) from -plumbing (in the endpoint function and decorators), and enforcing a standard of -how endpoint functions are written. - -### Gzip & Deflated Responses - - -$$$compress - -Cask provides a useful `@cask.decorators.compress` decorator that gzips or -deflates a response body if possible. This is useful if you don't have a proxy -like Nginx or similar in front of your server to perform the compression for -you. - -Like all decorators, `@cask.decorators.compress` can be defined on a level of a -set of `cask.Routes`: - -$$$compress2 - -Or globally, in your `cask.Main`: - -$$$compress3 - -### Websockets - -$$$websockets - -Cask's Websocket endpoints are very similar to Cask's HTTP endpoints. Annotated -with `@cask.websocket` instead of `@cask.get` or `@cast.post`, the primary -difference is that instead of only returning a `cask.Response`, you now have an -option of returning a `io.undertow.websockets.WebSocketConnectionCallback`. - -The `WebSocketConnectionCallback` allows you to pro-actively start sending -websocket messages once a connection has been made, and it lets you register a -`AbstractReceiveListener` that allows you to react to any messages the client on -the other side of the websocket connection sends you. You can use these two APIs -to perform full bi-directional, asynchronous communications, as websockets are -intended to be used for. - -Returning a `cask.Response` immediately closes the websocket connection, and is -useful if you want to e.g. return a 404 or 403 due to the initial request being -invalid. - -Cask intentionally provides a relatively low-level websocket interface. It -leaves it up to you to manage open channels, react to incoming messages, or -pro-actively send them out, mostly using the underlying Undertow webserver -interface. While Cask does not model streams, backpressure, iteratees, or -provide any higher level API, it should not be difficult to take the Cask API -and build whatever higher-level abstractions you prefer to use. - -### TodoMVC Api Server - - -$$$todoApi - -This is a simple self-contained example of using Cask to write an in-memory API -server for the common [TodoMVC example app](http://todomvc.com/). - -This minimal example intentionally does not contain javascript, HTML, styles, -etc.. Those can be managed via the normal mechanism for -[Serving Static Files](#serving-static-files). - - -### TodoMVC Database Integration - -$$$todoDb - -This example demonstrates how to use Cask to write a TodoMVC API server that -persists it's state in a database rather than in memory. We use the -[Quill](http://getquill.io/) database access library to write a `@transactional` -decorator that automatically opens one transaction per call to an endpoint, -ensuring that database queries are properly committed on success or rolled-back -on error. Note that because the default database connector propagates its -transaction context in a thread-local, `@transactional` does not need to pass -the `ctx` object into each endpoint as an additional parameter list, and so we -simply leave it out. - -While this example is specific to Quill, you can easily modify the -`@transactional` decorator to make it work with whatever database access library -you happen to be using. For libraries which need an implicit transaction, it can -be passed into each endpoint function as an additional parameter list as -described in -[Extending Endpoints with Decorators](#extending-endpoints-with-decorators). - -### TodoMVC Full Stack Web - - -The following code snippet is the complete code for a full-stack TodoMVC -implementation: including HTML generation for the web UI via -[Scalatags](https://github.com/lihaoyi/scalatags), Javascript for the -interactivity, static file serving, and database integration via -[Quill](https://github.com/getquill/quill). While slightly long, this example -should give you a tour of all the things you need to know to use Cask. - -Note that this is a "boring" server-side-rendered webapp with Ajax interactions, -without any complex front-end frameworks or libraries: it's purpose is to -demonstrate a simple working web application of using Cask end-to-end, which you -can build upon to create your own Cask web application architected however you -would like. - -$$$todo - -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. - -### 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 diff --git a/docs/pages/1 - Cask: a Scala HTTP micro-framework.md b/docs/pages/1 - Cask: a Scala HTTP micro-framework.md new file mode 100644 index 0000000..4ed5841 --- /dev/null +++ b/docs/pages/1 - Cask: a Scala HTTP micro-framework.md @@ -0,0 +1,407 @@ + + +[![Build Status][travis-badge]][travis-link] +[![Gitter Chat][gitter-badge]][gitter-link] +[![Patreon][patreon-badge]][patreon-link] + + +[travis-badge]: https://travis-ci.org/lihaoyi/cask.svg +[travis-link]: https://travis-ci.org/lihaoyi/cask +[gitter-badge]: https://badges.gitter.im/Join%20Chat.svg +[gitter-link]: https://gitter.im/lihaoyi/cask?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge +[patreon-badge]: https://img.shields.io/badge/patreon-sponsor-ff69b4.svg +[patreon-link]: https://www.patreon.com/lihaoyi + + +$$$minimalApplication + +[Cask](https://github.com/lihaoyi/cask) is a simple Scala web framework inspired +by Python's [Flask](http://flask.pocoo.org/docs/1.0/) project. It aims to bring +simplicity, flexibility and ease-of-use to Scala webservers, avoiding cryptic +DSLs or complicated asynchrony. + +Getting Started +--------------- + +The easiest way to begin using Cask is by downloading the example project above. + +Unzip one of the example projects available on this page (e.g. above) into a +folder. This should give you the following files: + +```text +build.sc +app/src/MinimalExample.scala +app/test/src/ExampleTests.scala +``` + +- `cd` into the folder, and run + +```bash +./cask -w app.runBackground +``` + +This will server up the Cask application on `http://localhost:8080`. You can +immediately start interacting with it either via the browser, or +programmatically via `curl` or a HTTP client like +[Requests-Scala](https://github.com/lihaoyi/requests-scala): + +```scala +val host = "http://localhost:8080" + +val success = requests.get(host) + +success.text() ==> "Hello World!" +success.statusCode ==> 200 + +requests.get(host + "/doesnt-exist").statusCode ==> 404 + +requests.post(host + "/do-thing", data = "hello").text() ==> "olleh" + +requests.get(host + "/do-thing").statusCode ==> 404 +``` + +These HTTP calls are part of the test suite for the example project, which you +can run using: + +```bash +./cask -w app.test +``` + +To configure your Cask application to work with IntelliJ, you can use: + +```bash +./cask mill.scalalib.GenIdea/idea +``` + +This will need to be re-run when you re-configure your `build.sc` file, e.g. +when adding additional modules or third-party dependencies. + +Cask is just a Scala library, and you can use Cask in any existing Scala project +via the following coordinates: + +```scala +// Mill +ivy"com.lihaoyi::cask:0.1.0" + +// SBT +"com.lihaoyi" %% "cask" % "0.1.0" +``` + +The `./cask` command is just a wrapper around the +[Mill build tool](http://www.lihaoyi.com/mill/); the `build.sc` files you see in +all examples are Mill build files, and you can use your own installation of Mill +instead of `./cask` if you wish. All normal Mill commands and functionality +works for `./cask`. + +The following examples will walk you through how to use Cask to accomplish tasks +common to anyone writing a web application. Each example comes with a +downloadable example project with code and unit tests, which you can use via the +same `./cask -w app.runBackground` or `./cask -w app.test` workflows we saw above. + +## Minimal Example + +$$$minimalApplication + +The rough outline of how the minimal example works should be easy to understand: + +- You define an object that inherits from `cask.MainRoutes` + +- Define endpoints using annotated functions, using `@cask.get` or `@cask.post` + with the route they should match + +- Each function can return the data you want in the response, or a + `cask.Response` if you want further customization: response code, headers, + etc. + +- Your function can tale an optional `cask.Request`, which exposes the entire + incoming HTTP request if necessary. In the above example, we use it to read + the request body into a string and return it reversed. + +In most cases, Cask provides convenient helpers to extract exactly the data from +the incoming HTTP request that you need, while also de-serializing it into the +data type you need and returning meaningful errors if they are missing. Thus, +although you can always get all the data necessary through `cask.Request`, it is +often more convenient to use another way, which will go into below. + +As your application grows, you will likely want to split up the routes into +separate files, themselves separate from any configuration of the Main +entrypoint (e.g. overriding the port, host, default error handlers, etc.). You +can do this by splitting it up into `cask.Routes` and `cask.Main` objects: + +$$$minimalApplication2 + +You can split up your routes into separate `cask.Routes` objects as makes sense +and pass them all into `cask.Main`. + +## Variable Routes + +$$$variableRoutes + +You can bind variables to endpoints by declaring them as parameters: these are +either taken from a path-segment matcher of the same name (e.g. `postId` above), +or from query-parameters of the same name (e.g. `param` above). You can make +`param` take a `: String` to match `?param=hello`, an `: Int` for `?param=123` a +`Seq[T]` (as above) for repeated params such as `?param=hello¶m=world`, or +`: Option[T]` for cases where the `?param=hello` is optional. + +If you need to capture the entire sub-path of the request, you can set the flag +`subpath=true` and ask for a `: cask.Subpath` (the name of the param doesn't +matter). This will make the route match any sub-path of the prefix given to the +`@cask` decorator, and give you the remainder to use in your endpoint logic. + +## Multi-method Routes + +$$$httpMethods + +Sometimes, you may want to handle multiple kinds of HTTP requests in the same +endpoint function, e.g. with code that can accept both GETs and POSTs and decide +what to do in each case. You can use the `@cask.route` annotation to do so + +## Receiving Form-encoded or JSON data + +$$$formJsonPost + +If you need to handle a JSON-encoded POST request, you can use the +`@cast.postJson` decorator. This assumes the posted request body is a JSON dict, +and uses its keys to populate the endpoint's parameters, either as raw +`ujson.Js.Value`s or deserialized into `Seq[Int]`s or other things. +Deserialization is handled using the +[uPickle](https://github.com/lihaoyi/upickle) JSON library, though you could +write your own version of `postJson` to work with any other JSON library of your +choice. + +Similarly, you can mark endpoints as `@cask.postForm`, in which case the +endpoints params will be taken from the form-encoded POST body either raw (as +`cask.FormValue`s) or deserialized into simple data structures. Use +`cask.FormFile` if you want the given form value to be a file upload. + +Both normal forms and multipart forms are handled the same way. + +If the necessary keys are not present in the JSON/form-encoded POST body, or the +deserialization into Scala data-types fails, a 400 response is returned +automatically with a helpful error message. + + +## Processing Cookies + +$$$cookies + +Cookies are most easily read by declaring a `: cask.Cookie` parameter; the +parameter name is used to fetch the cookie you are interested in. Cookies can be +stored by setting the `cookie` attribute in the response, and deleted simply by +setting `expires = java.time.Instant.EPOCH` (i.e. to have expired a long time +ago) + +## Serving Static Files + +$$$staticFiles + +You can ask Cask to serve static files by defining a `@cask.staticFiles` endpoint. +This will match any subpath of the value returned by the endpoint (e.g. above +`/static/file.txt`, `/static/folder/file.txt`, etc.) and return the file +contents from the corresponding file on disk (and 404 otherwise). + +Similarly, `@cask.staticResources` attempts to serve a request based on the JVM +resource path, returning the data if a resource is present and a 404 otherwise. + +## Redirects or Aborts + +$$$redirectAbort + +Cask provides some convenient helpers `cask.Redirect` and `cask.Abort` which you +can return; these are simple wrappers around `cask.Request`, and simply set up +the relevant headers or status code for you. + +## HTML Rendering + +Cask doesn't come bundled with HTML templating functionality, but it makes it +really easy to use community-standard libraries like +[Scalatags](https://github.com/lihaoyi/scalatags) to render your HTML. Simply +adding the relevant `ivy"com.lihaoyi::scalatags:0.6.7"` dependency to your +`build.sc` file is enough to render Scalatags templates: + +$$$scalatags + + +## Extending Endpoints with Decorators + + +$$$decorated + +You can write extra decorator annotations that stack on top of the existing +`@cask.get`/`@cask.post` to provide additional arguments or validation. This is +done by implementing the `cask.Decorator` interface and it's `getRawParams` +function. `getRawParams`: + +- Receives a `ParamContext`, which basically gives you full access to the + underlying undertow HTTP connection so you can pick out whatever data you + would like + +- Returns an `Either[Response, cask.Decor[Any]]`. Returning a `Left` lets you + bail out early with a fixed `cask.Response`, avoiding further processing. + Returning a `Right` provides a map of parameter names and values that will + then get passed to the endpoint function in consecutive parameter lists (shown + above), as well as an optional cleanup function that is run after the endpoint + terminates. + +Each additional decorator is responsible for one additional parameter list to +the right of the existing parameter lists, each of which can contain any number +of parameters. + +Decorators are useful for things like: + +- Making an endpoint return a HTTP 403 if the user isn't logged in, but if they are + logged in providing the `: User` object to the body of the endpoint function + +- Rate-limiting users by returning early with a HTTP 429 if a user tries to + access an endpoint too many times too quickly + +- Providing request-scoped values to the endpoint function: perhaps a database + transaction that commits when the function succeeds (and rolls-back if it + fails), or access to some system resource that needs to be released. + +For decorators that you wish to apply to multiple routes at once, you can define +them by overriding the `cask.Routes#decorators` field (to apply to every +endpoint in that routes object) or `cask.Main#mainDecorators` (to apply to every +endpoint, period): + +$$$decorated2 + +This is convenient for cases where you want a set of decorators to apply broadly +across your web application, and do not want to repeat them over and over at +every single endpoint. + +## Custom Endpoints + +$$$endpoints + +When you need more flexibility than decorators allow, you can define your own +custom `cask.Endpoint`s to replace the default set that Cask provides. This +allows you to + +- Change the expected return type of the annotated function, and how allows you + to that type gets processed: the above example trivially expects an allows you + to `Int` which becomes the status code, but you could make it e.g. + automatically serialize returned objects to JSON responses via your favorite + library, or serialize them to bytes via protobufs + +- Change where the first parameter list's params are taken from: `@cask.get` + takes them from query params, `@cask.postForm` takes them from the + form-encoded POST body, and you can write your own endpoint to take the params + from where-ever you like: perhaps from the request headers, or a protobuf- + encoded request body + +- Change how parameters are deserialized: e.g. `@cask.postJson` de-serializes + parameters using the [uPickle](https://github.com/lihaoyi/upickle) JSON + library, and your own custom endpoint could change that to use another library + like [Circe](https://github.com/circe/circe) or + [Jackson](https://github.com/FasterXML/jackson-module-scala) + +- DRY up common sets of decorators: if all your endpoint functions use the same + decorators, you can extract that functionality into a single `cask.Endpoint` + to do the job. + +Generally you should not be writing custom `cask.Endpoint`s every day, but if +you find yourself trying to standardize on a way of doing things across your web +application, it might make sense to write a custom endpoint decorator: to DRY +things up , separate business logic (inside the annotated function) from +plumbing (in the endpoint function and decorators), and enforcing a standard of +how endpoint functions are written. + +## Gzip & Deflated Responses + + +$$$compress + +Cask provides a useful `@cask.decorators.compress` decorator that gzips or +deflates a response body if possible. This is useful if you don't have a proxy +like Nginx or similar in front of your server to perform the compression for +you. + +Like all decorators, `@cask.decorators.compress` can be defined on a level of a +set of `cask.Routes`: + +$$$compress2 + +Or globally, in your `cask.Main`: + +$$$compress3 + +## Websockets + +$$$websockets + +Cask's Websocket endpoints are very similar to Cask's HTTP endpoints. Annotated +with `@cask.websocket` instead of `@cask.get` or `@cast.post`, the primary +difference is that instead of only returning a `cask.Response`, you now have an +option of returning a `io.undertow.websockets.WebSocketConnectionCallback`. + +The `WebSocketConnectionCallback` allows you to pro-actively start sending +websocket messages once a connection has been made, and it lets you register a +`AbstractReceiveListener` that allows you to react to any messages the client on +the other side of the websocket connection sends you. You can use these two APIs +to perform full bi-directional, asynchronous communications, as websockets are +intended to be used for. + +Returning a `cask.Response` immediately closes the websocket connection, and is +useful if you want to e.g. return a 404 or 403 due to the initial request being +invalid. + +Cask intentionally provides a relatively low-level websocket interface. It +leaves it up to you to manage open channels, react to incoming messages, or +pro-actively send them out, mostly using the underlying Undertow webserver +interface. While Cask does not model streams, backpressure, iteratees, or +provide any higher level API, it should not be difficult to take the Cask API +and build whatever higher-level abstractions you prefer to use. + +## TodoMVC Api Server + + +$$$todoApi + +This is a simple self-contained example of using Cask to write an in-memory API +server for the common [TodoMVC example app](http://todomvc.com/). + +This minimal example intentionally does not contain javascript, HTML, styles, +etc.. Those can be managed via the normal mechanism for +[Serving Static Files](#serving-static-files). + + +## TodoMVC Database Integration + +$$$todoDb + +This example demonstrates how to use Cask to write a TodoMVC API server that +persists it's state in a database rather than in memory. We use the +[Quill](http://getquill.io/) database access library to write a `@transactional` +decorator that automatically opens one transaction per call to an endpoint, +ensuring that database queries are properly committed on success or rolled-back +on error. Note that because the default database connector propagates its +transaction context in a thread-local, `@transactional` does not need to pass +the `ctx` object into each endpoint as an additional parameter list, and so we +simply leave it out. + +While this example is specific to Quill, you can easily modify the +`@transactional` decorator to make it work with whatever database access library +you happen to be using. For libraries which need an implicit transaction, it can +be passed into each endpoint function as an additional parameter list as +described in +[Extending Endpoints with Decorators](#extending-endpoints-with-decorators). + +## TodoMVC Full Stack Web + + +The following code snippet is the complete code for a full-stack TodoMVC +implementation: including HTML generation for the web UI via +[Scalatags](https://github.com/lihaoyi/scalatags), Javascript for the +interactivity, static file serving, and database integration via +[Quill](https://github.com/getquill/quill). While slightly long, this example +should give you a tour of all the things you need to know to use Cask. + +Note that this is a "boring" server-side-rendered webapp with Ajax interactions, +without any complex front-end frameworks or libraries: it's purpose is to +demonstrate a simple working web application of using Cask end-to-end, which you +can build upon to create your own Cask web application architected however you +would like. + +$$$todo diff --git a/docs/pages/2 - Main Customization.md b/docs/pages/2 - Main Customization.md new file mode 100644 index 0000000..25434bd --- /dev/null +++ b/docs/pages/2 - Main Customization.md @@ -0,0 +1,44 @@ +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. + +## 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 diff --git a/docs/pages/3 - About Cask.md b/docs/pages/3 - About Cask.md new file mode 100644 index 0000000..8e5e5f3 --- /dev/null +++ b/docs/pages/3 - About Cask.md @@ -0,0 +1,94 @@ + +## Functions First + +Inspired by [Flask](http://flask.pocoo.org/), Cask allows you to define your web +applications endpoints using simple function `def`s that you already know and +love, annotated with the minimal additional metadata necessary to work as HTTP +endpoints. + +It turns out that function `def`s already provide almost everything you need in +a HTTP endpoint: + +- The parameters the endpoint takes +- If any parameters are optional, and their default values +- The ability to return a `Response` + +Cask extends these basics with annotations, providing: + +- What request path the endpoint is available at +- Automated deserialization of endpoint parameters from the respective format + (Form-encoded? Query-string? JSON?) +- Wrapping the endpoint's function `def` with custom logic: logging, + authentication, ... + +While these annotations add a bit of complexity, they allow Cask to avoid +needing custom DSLs for defining your HTTP routes, custom action-types, and many +other things which you may be used to working with HTTP in Scala. + +## Extensible Annotations + +Unlike most other annotation-based frameworks in Scala or Java, Cask's +annotations are not magic markers, but self-contained classes containing all the +logic they need to function. This has several benefits: + +- You can jump to the definition of an annotation and see what it does + +- It trivial to implement your own annotations as + [decorators](/using-cask#extending-endpoints-with-decorators) or + [endpoints](/using-cask#custom-endpoints). + +- Stacking multiple annotations on a single function has a well-defined contract + and semantics + +Overall, Cask annotations behave a lot more like Python decorators than +"traditional" Java/Scala annotations: first-class, customizable, inspectable, +and self-contained. This allows Cask to have the syntactic convenience of an +annotation-based API, without the typical downsides of inflexibility and +undiscoverability. + +## Simple First + +Cask intentionally eskews many things that other, more enterprise-grade +frameworks provide: + +- Async +- Akka +- Streaming Computations +- Backpressure + +While these features all are valuable in specific cases, Cask aims for the 99% +of code for which simple, boring code is perfectly fine. Cask's endpoints are +synchronous by default, do not tie you to any underlying concurrency model, and +should "just work" without any advanced knowledge apart from basic Scala and +HTTP. Cask's [websockets](/using-cask#websockets) API is intentionally low-level, making it +both simple to use and also simple to build on top of if you want to wrap it in +your own concurrency-library-of-choice. + +## Thin Wrapper + +Cask is implemented as a thin wrapper around the excellent Undertow HTTP server. +If you need more advanced functionality, Cask lets you ask for the `exchange: +HttpServerExchange` in your endpoint, override +[defaultHandler](/using-cask#def-defaulthandler) and add your own Undertow handlers next to +Cask's and avoid Cask's routing/endpoint system altogether, or override +[main](/using-cask#def-main) if you want to change how the server is initialized. + +Rather than trying to provide APIs for all conceivable functionality, Cask +simply provides what it does best - simple routing for simple endpoints - and +leaves the door wide open in case you need to drop down to the lower level +Undertow APIs. + +## Community Libraries + +Cask aims to re-use much of the excellent code that is already written and being +used out in the Scala community, rather than trying to re-invent the wheel. Cask +uses the [Mill](https://github.com/lihaoyi/mill) build tool, comes bundled with +the [uPickle](https://github.com/lihaoyi/upickle) JSON library, and makes it +trivial to pull in libraries like +[Scalatags](https://github.com/lihaoyi/scalatags) to render HTML or +[Quill](https://github.com/getquill/quill) for database access. + +Each of these are stable, well-known, well-documented libraries you may already +be familiar with, and Cask simply provides the HTTP/routing layer with the hooks +necessary to tie everything together (e.g. into a +[TodoMVC](/using-cask#todomvc-full-stack-web) webapp) \ No newline at end of file -- cgit v1.2.3