-
Notifications
You must be signed in to change notification settings - Fork 1k
Add Cask tutorials #3056
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Add Cask tutorials #3056
Changes from 1 commit
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
{% altDetails require-info-box 'Getting Cask' %} | ||
|
||
{% tabs cask-install class=tabs-build-tool %} | ||
|
||
{% tab 'Scala CLI' %} | ||
You can declare dependency on Cask with `using` directive: | ||
```scala | ||
//> using dep "com.lihaoyi::cask::0.9.2" | ||
``` | ||
{% endtab %} | ||
|
||
{% tab 'sbt' %} | ||
In your `build.sbt`, you can add a dependency on Cask: | ||
```scala | ||
lazy val example = project.in(file("example")) | ||
.settings( | ||
scalaVersion := "3.4.2", | ||
libraryDependencies += "com.lihaoyi" %% "cask" % "0.9.2", | ||
fork := true | ||
) | ||
``` | ||
{% endtab %} | ||
|
||
{% tab 'Mill' %} | ||
In your `build.sc`, you can add a dependency on Cask: | ||
```scala | ||
object example extends RootModule with ScalaModule { | ||
def scalaVersion = "3.3.3" | ||
def ivyDeps = Agg( | ||
ivy"com.lihaoyi::cask::0.9.2" | ||
) | ||
} | ||
``` | ||
{% endtab %} | ||
|
||
{% endtabs %} | ||
{% endaltDetails %} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
188 changes: 188 additions & 0 deletions
188
_overviews/toolkit/web-server-cookies-and-decorators.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,188 @@ | ||||||
--- | ||||||
title: How to use cookies and decorators? | ||||||
type: section | ||||||
description: Using cookies and decorators with Cask | ||||||
num: 36 | ||||||
previous-page: web-server-query-websockets | ||||||
next-page: | ||||||
--- | ||||||
|
||||||
{% include markdown.html path="_markdown/install-cask.md" %} | ||||||
|
||||||
## Using cookies | ||||||
|
||||||
Cookies are saved by adding them to the `cookies` parameter of `cask.Response` constructor. | ||||||
|
||||||
In this example we are building a rudimentary authentication service. The `getLogin` method provides a form where | ||||||
username and password can be inputted. The `postLogin` reads the credentials and if they match the expected ones, a session | ||||||
identifier is generated, saved in the application state and sends back a cookie with the identifier. | ||||||
|
||||||
Cookies can be read either with a method parameter with `cask.Cookie` type or by accessing `cask.Request` directly. | ||||||
If using the former method, names of parameters have to match the names of cookies. If a cookie with matching name is not | ||||||
found, an error response will be returned. In the `checkLogin` function the former method is used, as the cookie is not | ||||||
present before user logs in. | ||||||
|
||||||
To delete a cookie set its `expires` parameter to an instant in the past, for example `Instant.EPOCH`. | ||||||
|
||||||
{% tabs web-server-cookies-1 class=tabs-scala-version %} | ||||||
{% tab 'Scala 2' %} | ||||||
|
||||||
```scala | ||||||
import java.util.UUID | ||||||
import java.util.concurrent.ConcurrentHashMap | ||||||
|
||||||
object MyApp extends cask.MainRoutes { | ||||||
|
||||||
val sessionIds = ConcurrentHashMap.newKeySet[String]() | ||||||
|
||||||
@cask.get("/login") | ||||||
def getLogin() = { | ||||||
val html = | ||||||
"""<!doctype html> | ||||||
|<html> | ||||||
|<body> | ||||||
|<form action="/login" method="post"> | ||||||
| <label for="name">Username:</label><br> | ||||||
| <input type="text" name="name" value=""><br> | ||||||
| <label for="password">Password:</label><br> | ||||||
| <input type="text" name="password" value=""><br><br> | ||||||
| <input type="submit" value="Submit"> | ||||||
|</form> | ||||||
|</body> | ||||||
|</html>""".stripMargin | ||||||
|
||||||
cask.Response(data = html, headers = Seq("Content-Type" -> "text/html")) | ||||||
} | ||||||
|
||||||
@cask.postForm("/login") | ||||||
def postLogin(name: String, password: String) = { | ||||||
if (name == "user" && password == "password") { | ||||||
val sessionId = UUID.randomUUID().toString | ||||||
sessionIds.add(sessionId) | ||||||
cask.Response(data = "Success!", cookies = Seq(cask.Cookie("sessionId", sessionId))) | ||||||
} else { | ||||||
cask.Response(data = "Authentication failed", statusCode = 401) | ||||||
} | ||||||
} | ||||||
|
||||||
@cask.get("/check") | ||||||
def checkLogin(request: cask.Request) = { | ||||||
val sessionId = request.cookies.get("sessionId") | ||||||
if (sessionId.exists(cookie => sessionIds.contains(cookie.value))) { | ||||||
"You are logged in" | ||||||
} else { | ||||||
"You are not logged in" | ||||||
} | ||||||
} | ||||||
|
||||||
@cask.get("/logout") | ||||||
def logout(sessionId: cask.Cookie) = { | ||||||
sessionIds.remove(sessionId.value) | ||||||
cask.Response(data = "Successfully logged out!", cookies = Seq(cask.Cookie("sessionId", "", expires = Instant.EPOCH))) | ||||||
} | ||||||
|
||||||
initialize() | ||||||
} | ||||||
``` | ||||||
{% endtab %} | ||||||
{% tab 'Scala 3' %} | ||||||
```scala | ||||||
import java.util.UUID | ||||||
import java.util.concurrent.ConcurrentHashMap | ||||||
|
||||||
object MyApp extends cask.MainRoutes: | ||||||
|
||||||
val sessionIds = ConcurrentHashMap.newKeySet[String]() | ||||||
|
||||||
@cask.get("/login") | ||||||
def getLogin() = | ||||||
val html = | ||||||
"""<!doctype html> | ||||||
|<html> | ||||||
|<body> | ||||||
|<form action="/login" method="post"> | ||||||
| <label for="name">Username:</label><br> | ||||||
| <input type="text" name="name" value=""><br> | ||||||
| <label for="password">Password:</label><br> | ||||||
| <input type="text" name="password" value=""><br><br> | ||||||
| <input type="submit" value="Submit"> | ||||||
|</form> | ||||||
|</body> | ||||||
|</html>""".stripMargin | ||||||
|
||||||
cask.Response(data = html, headers = Seq("Content-Type" -> "text/html")) | ||||||
|
||||||
@cask.postForm("/login") | ||||||
def postLogin(name: String, password: String) = | ||||||
if name == "user" && password == "password": | ||||||
val sessionId = UUID.randomUUID().toString | ||||||
sessionIds.add(sessionId) | ||||||
cask.Response(data = "Success!", cookies = Seq(cask.Cookie("sessionId", sessionId))) | ||||||
else | ||||||
cask.Response(data = "Authentication failed", statusCode = 401) | ||||||
|
||||||
@cask.get("/check") | ||||||
def checkLogin(request: cask.Request) = | ||||||
val sessionId = request.cookies.get("sessionId") | ||||||
if sessionId.exists(cookie => sessionIds.contains(cookie.value)): | ||||||
"You are logged in" | ||||||
else | ||||||
"You are not logged in" | ||||||
|
||||||
@cask.get("/logout") | ||||||
def logout(sessionId: cask.Cookie) = | ||||||
sessionIds.remove(sessionId.value) | ||||||
cask.Response(data = "Successfully logged out!", cookies = Seq(cask.Cookie("sessionId", "", expires = Instant.EPOCH))) | ||||||
|
||||||
initialize() | ||||||
``` | ||||||
{% endtab %} | ||||||
{% endtabs %} | ||||||
|
||||||
## Using decorators | ||||||
|
||||||
Decorators can be used for extending endpoints functionality with validation or new parameters. They are defined by extending | ||||||
`cask.RawDecorator` class and then used as annotations. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
In this example, the `loggedIn` decorator is used for checking if user is logged in before accessing the `/decorated` | ||||||
endpoint. | ||||||
|
||||||
The decorator class can pass additional arguments to the decorated endpoint using a map. The passed arguments are available | ||||||
through the last argument group. Here we are passing the session identifier to an argument named `sessionId`. | ||||||
|
||||||
{% tabs web-server-cookies-2 class=tabs-scala-version %} | ||||||
{% tab 'Scala 2' %} | ||||||
```scala | ||||||
class loggedIn extends cask.RawDecorator { | ||||||
override def wrapFunction(ctx: cask.Request, delegate: Delegate) = { | ||||||
ctx.cookies.get("sessionId") match { | ||||||
case Some(cookie) if sessionIds.contains(cookie.value) => delegate(Map("sessionId" -> cookie.value)) | ||||||
case _ => cask.router.Result.Success(cask.model.Response("You aren't logged in", 403)) | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
@loggedIn() | ||||||
@cask.get("/decorated") | ||||||
def decorated()(sessionId: String) = { | ||||||
s"You are logged in with id: $sessionId" | ||||||
} | ||||||
``` | ||||||
{% endtab %} | ||||||
{% tab 'Scala 3' %} | ||||||
```scala | ||||||
class loggedIn extends cask.RawDecorator: | ||||||
override def wrapFunction(ctx: cask.Request, delegate: Delegate) = | ||||||
ctx.cookies.get("sessionId") match | ||||||
case Some(cookie) if sessionIds.contains(cookie.value) => | ||||||
delegate(Map("sessionId" -> cookie.value)) | ||||||
case _ => | ||||||
cask.router.Result.Success(cask.model.Response("You aren't logged in", 403)) | ||||||
|
||||||
|
||||||
@loggedIn() | ||||||
@cask.get("/decorated") | ||||||
def decorated()(sessionId: String) = s"You are logged in with id: $sessionId" | ||||||
``` | ||||||
{% endtab %} | ||||||
{% endtabs %} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.