summaryrefslogtreecommitdiff
path: root/docs/pages/1 - Cask: a Scala HTTP micro-framework.md
diff options
context:
space:
mode:
authorLi Haoyi <haoyi.sg@gmail.com>2018-08-18 00:08:01 +0800
committerLi Haoyi <haoyi.sg@gmail.com>2018-08-18 00:08:01 +0800
commitf8bb6f693b8450f8e049396fd0e7d032ac7acb23 (patch)
tree60f2588cfee71e933965aa5e1e792608595b2a17 /docs/pages/1 - Cask: a Scala HTTP micro-framework.md
parent8f1d1de6545bab97ffa64af1bbdb01709c99b722 (diff)
downloadcask-f8bb6f693b8450f8e049396fd0e7d032ac7acb23.tar.gz
cask-f8bb6f693b8450f8e049396fd0e7d032ac7acb23.tar.bz2
cask-f8bb6f693b8450f8e049396fd0e7d032ac7acb23.zip
tweak docs
Diffstat (limited to 'docs/pages/1 - Cask: a Scala HTTP micro-framework.md')
-rw-r--r--docs/pages/1 - Cask: a Scala HTTP micro-framework.md407
1 files changed, 407 insertions, 0 deletions
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&param=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