Hello. I've been trying out Cask and got an HTTP server with a Scala.js front-end working without problems, and all seems great. I then wanted to try out Websockets but have got stuck. I tried copying verbatim the first example from the codebase as a starting point:
https://github.com/com-lihaoyi/cask/blob/master/example/websockets/app/src/Websockets.scala
but I get compilation errors.
case cask.Ws.Text("") => channel.send(cask.Ws.Close())
no implicit argument of type sourcecode.FileName was found for parameter fileName of method send in class BaseActor
From looking at the code, I understand that WsActor inherits from Castor's BaseActor (via SimpleActor) and the latter's send method requires an implicit sourcecode fileName and line. I don't understand what these could be in the context of Websockets (but I'm quite new to Websockets).
I tried running the websockets example test from the Cask codebase, in case it was something odd in my own setup, but hit another problem doing that. I think it's a separate Mill-related question so I'll omit details.
Any suggestion how I might be getting the compilation error above?
getRawParams
is no longer valid? https://com-lihaoyi.github.io/cask/#extending-endpoints-with-decorators
How should I specify the implicit params FileName and Line for castor actors?
no implicit argument of type sourcecode.FileName was found for parameter fileName of method send in class BaseActor
@k0ala That was the problem I raised above, I think. I fixed it (at least for compilation) by adding a dependency on a newer version of castor than the one used by cask, com.lihaoyi::castor:0.2.0
@jsonApi(TodoRoutes.getATodo)
def aTodo(id: Int): Option[Todos] = {
myDB.aTodo(id).headOption
}
which is a cask route... the decorator wraps the result into a response.
mill resolve _
, it shows:[$]> mill resolve _
[1/1] resolve
all
clean
inspect
par
path
plan
resolve
show
shutdown
version
visualize
visualizePlan
I am trying to adapt the chatbot example from Hands-on-Scala to use some buttons and to modify a counter. At the moment I have
@cask.get("/")
def hello() =
doctype("html")(
html(
head(href := bootstrap, script(src := "/static/app2.js")),
body(
div(id := "messageList")(i(color.red)(cnt)),
form(onsubmit := "submitForm(); return false")(
button(`type` := "submit", id := "nameInput", value :="1")("1")
//button(`type` := "submit", id := "nameInput", value :="2")("2")
)
)
))
@cask.postJson("/")
def postChatMsg(name: String) = {
cnt = cnt + 1
ujson.Obj("success" -> true, "txt" -> frag(i(color.red)(cnt)).render, "err" -> "oops")
}
However, when I uncomment the second button, I receive a 400 (Bad Request). The helper app2.js is as follows:
function submitForm() {
fetch(
"/",
{method: "POST", body: JSON.stringify({name: nameInput.value})}
).then(response => response.json())
.then(json => {
if (json["success"]) {
messageList.innerHTML = json["txt"]
}
})
}
What is the right approach to get more than one button to work? Thanks a lot!
@cask.staticResources("/static")
def static() = "todo"
/public/
I couldn't get anything to work. Also I don't understand the return value from def static()
.
object Router extends LazyLogging:
private val utf8 = Codec.UTF8.name
private val contentType = "Content-Type"
private val basePath = "/public/"
private val indexHtml = loadResource("index.html")
private val indexHtmlHeader = contentType -> "text/html; charset=UTF-8"
def loadResource(resource: String): String =
val path = s"$basePath$resource"
logger.debug(s"*** load resource: $path")
Using( Source.fromInputStream(getClass.getResourceAsStream(path), utf8) ) {
source => source.mkString
}.getOrElse("")
def toHeader(resource: String): (String, String) =
logger.debug(s"*** to header: ${resource.split('.').last}")
resource.split('.').last match
case "css" => contentType -> "text/css"
case "ico" => contentType -> "image/x-icon"
case "png" => contentType -> "image/png"
case "js" => contentType -> "text/javascript"
case "map" => contentType -> "application/json"
case "html" => indexHtmlHeader
case _ => contentType -> "text/plain"
class Router(dispatcher: Dispatcher) extends Routes with LazyLogging:
import Router._
@cask.get("/")
def index() = Response(indexHtml, 200, Seq(indexHtmlHeader))
@cask.get(Router.basePath, subpath = true)
def resources(request: Request) =
val resource = request.remainingPathSegments.head
val content = loadResource(resource)
val headers = Seq(toHeader(resource))
logger.debug(s"*** headers: $headers")
Response(content, 200, headers)
@cask.post("/command")
def command(request: Request) =
val command = read[Command](request.text())
logger.debug(s"*** Command: $command")
val event = dispatcher.dispatch(command)
logger.debug(s"*** Event: $event")
write[Event](event)
initialize()
import cask.main.Routes
import cask.model.{Request, Response}
import com.typesafe.scalalogging.LazyLogging
import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream
import javax.imageio.ImageIO
import scala.io.{Codec, Source}
import scala.util.{Try, Using}
trait Resources(val basePath: String) extends LazyLogging:
val utf8 = Codec.UTF8.name
val contentType = "Content-Type"
val indexHtml = loadResource("index.html")
val indexHtmlHeader = contentType -> "text/html; charset=UTF-8"
def toContentType(resource: String): String = resource.split('.').last
def toPath(resource: String): String = s"$basePath$resource"
def toHeader(resource: String): (String, String) =
logger.debug(s"*** to header: ${toContentType(resource)}")
toContentType(resource) match
case "css" => contentType -> "text/css"
case "ico" => contentType -> "image/x-icon"
case "png" => contentType -> "image/png"
case "js" => contentType -> "text/javascript"
case "map" => contentType -> "application/json"
case "html" => indexHtmlHeader
case _ => contentType -> "text/plain"
def isImage(resource: String): Boolean =
toContentType(resource) match
case "ico" | "png" => true
case _ => false
def loadResource(resource: String): Array[Byte] =
val path = toPath(resource)
logger.debug(s"*** load resource: $path")
Using( Source.fromInputStream(getClass.getResourceAsStream(path), utf8) ) {
source => source.mkString.getBytes
}.getOrElse(Array.empty[Byte])
def loadImage(resource: String): Array[Byte] =
val path = toPath(resource)
logger.debug(s"*** load image: $path")
val url = getClass.getResource(path)
val image = ImageIO.read(url)
val baos = new ByteArrayOutputStream()
val contentType = toContentType(resource)
ImageIO.write(image, contentType, baos)
baos.toByteArray
class Router(dispatcher: Dispatcher) extends Routes with LazyLogging with Resources("/public/"):
@cask.get("/")
def index() = Response(indexHtml, 200, Seq(indexHtmlHeader))
@cask.get(basePath, subpath = true)
def resources(request: Request) =
val resource = request.remainingPathSegments.head
val headers = Seq(toHeader(resource))
if isImage(resource) then Response(loadImage(resource), 200, headers)
else Response(loadResource(resource), 200, headers)
initialize()