PlayFramework 2.2 scala close WebSocket connection - scala

How can I close a WebSocket connection? The example on the documentation works if you want to close it immediately.
But how about the following case: I want to close the connection when some condition occurs in the future. For instance, when I receive a certain message from the client.
def indexWS = WebSocket.using[String] {
request => {
var channel: Option[Concurrent.Channel[String]] = None
var outEnumerator: Enumerator[String] = Concurrent.unicast(c => channel = Some(c))
val myIteratee: Iteratee[String, Unit] = Iteratee.foreach[String] {gotString => {
// received a string from the client
if (gotString == "close_me") {
// outEnumerator = Enumerator.eof // doesn't work
// outEnumerator >>> Enumerator.eof // doesn't work
}
}}
(myIteratee, outEnumerator)
}
}
Thank you for your help!

I got it: I had to go through the channel that I opened at
var outEnumerator: Enumerator[String] = Concurrent.unicast(c => channel = Some(c))
and the commented out block would become
if (gotString == "close_me") {
channel.foreach(_.eofAndEnd())
}
which will push an EOF through the enumerator and close the connection.

Related

Play Framework - edit Sec-WebSocket-Protocol in Web socket Response Header

Edit the Web socket header response sent from server to client.
I am creating a websocket server application using playframework. Right now the websocket response from the server is
taken care by Play. Following is the response header,
Request Header:
(UpgradeToWebSocket,),
(Host,localhost:8083),
(Connection,Upgrade),
(Upgrade,websocket),
(Sec-WebSocket-Version,13),
(Accept-Encoding,gzip, deflate, br),
(Accept-Language,en-US,en;q=0.9),
(Sec-WebSocket-Key,ZvfzpVo3EX4DFA4BRcgRIA==)
def chatSystem(): WebSocket = WebSocket.acceptOrResult[String, String] { request =>
Future.successful{
AuthenticationService.doBasicAuthentication(request.headers) match {
case Results.Ok => Right(ActorFlow.actorRef { out => ChatServiceActor.props(out) })
case _ => Left(Unauthorized)
}
}
}
I want to validate Sec-WebSocket-Protocol if it is present in the request header or add the same with value in the server response if it is not present.
I used the following code:
// Defined at http://tools.ietf.org/html/rfc6455#section-4.2.2
val MagicGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
def websocketAcceptForKey(key: String): String = {
val sha1 = MessageDigest.getInstance("sha1")
val salted = key + MagicGuid
val hash = sha1.digest(salted.asciiBytes)
val acceptKey: String = Base64.rfc2045().encodeToString(hash, false)
acceptKey
}
Use it like the following:
val wsKey: Optional[HttpHeader] = request.getHeader("Sec-WebSocket-Key")
val wsAccept = if (wsKey.isPresent) Some(RawHeader("Sec-WebSocket-Accept", websocketAcceptForKey(wsKey.get.value()))) else None

How to use http4s server and client library as a proxy?

I want use http4s as proxy(like nginx), how to forward all data from my http4s server to another http server?
What I really want do is append a verify function on every request before do forward function. Hopefully like this:
HttpService[IO] {
case request =>
val httpClient: Client[IO] = Http1Client[IO]().unsafeRunSync
if(verifySuccess(request)) { // forward all http data to host2 and
// get a http response.
val result = httpClient.forward(request, "http://host2")
result
} else {
Forbidden //403
}
}
How to do this with http4s and it's client?
Thanks
Updated
with the help of #TheInnerLight, I give it a try with the snippet code:
val httpClient = Http1Client[IO]()
val service: HttpService[IO] = HttpService[IO] {
case req =>
if(true) {
for {
client <- httpClient
newAuthority = req.uri.authority.map(_.copy(host = RegName("scala-lang.org"), port = Some(80)))
proxiedReq = req.withUri(req.uri.copy(authority = newAuthority))
response <- client.fetch(proxiedReq)(IO.pure(_))
} yield response
} else {
Forbidden("Some forbidden message...")
}
}
With a request: http://localhost:28080(http4s server listen at 28080):
but occurred a error:
[ERROR] org.http4s.client.PoolManager:102 - Error establishing client connection for key RequestKey(Scheme(http),localhost)
java.net.ConnectException: Connection refused
at sun.nio.ch.UnixAsynchronousSocketChannelImpl.checkConnect(Native Method)
at sun.nio.ch.UnixAsynchronousSocketChannelImpl.finishConnect(UnixAsynchronousSocketChannelImpl.java:252)
at sun.nio.ch.UnixAsynchronousSocketChannelImpl.finish(UnixAsynchronousSocketChannelImpl.java:198)
at sun.nio.ch.UnixAsynchronousSocketChannelImpl.onEvent(UnixAsynchronousSocketChannelImpl.java:213)
at sun.nio.ch.KQueuePort$EventHandlerTask.run(KQueuePort.java:301)
at java.lang.Thread.run(Thread.java:748)
[ERROR] org.http4s.server.service-errors:88 - Error servicing request: GET / from 0:0:0:0:0:0:0:1
java.net.ConnectException: Connection refused
at sun.nio.ch.UnixAsynchronousSocketChannelImpl.checkConnect(Native Method)
at sun.nio.ch.UnixAsynchronousSocketChannelImpl.finishConnect(UnixAsynchronousSocketChannelImpl.java:252)
at sun.nio.ch.UnixAsynchronousSocketChannelImpl.finish(UnixAsynchronousSocketChannelImpl.java:198)
at sun.nio.ch.UnixAsynchronousSocketChannelImpl.onEvent(UnixAsynchronousSocketChannelImpl.java:213)
at sun.nio.ch.KQueuePort$EventHandlerTask.run(KQueuePort.java:301)
at java.lang.Thread.run(Thread.java:748)
Latest Version
val httpClient: IO[Client[IO]] = Http1Client[IO]()
override val service: HttpService[IO] = HttpService[IO] {
case req =>
val hostName = "scala-lang.org"
val myPort = 80
if(true) {
val newHeaders = {
val filterHeader = req.headers.filterNot{h =>
h.name == CaseInsensitiveString("Connection") ||
h.name == CaseInsensitiveString("Keep-Alive") ||
h.name == CaseInsensitiveString("Proxy-Authenticate") ||
h.name == CaseInsensitiveString("Proxy-Authorization") ||
h.name == CaseInsensitiveString("TE") ||
h.name == CaseInsensitiveString("Trailer") ||
h.name == CaseInsensitiveString("Transfer-Encoding") ||
h.name == CaseInsensitiveString("Upgrade")
}
filterHeader.put(Header("host", hostName))
}
for {
client <- httpClient
newAuthority = req.uri.authority
.map(_.copy(host = RegName(hostName), port = Some(myPort)))
.getOrElse( Authority(host = RegName(hostName), port = Some(myPort)))
proxiedReq = req.withUri(req.uri.copy(authority = Some(newAuthority)))
.withHeaders(newHeaders)
response <- client.fetch(proxiedReq)(x => IO.pure(x))
} yield {
val rst = response
rst
}
} else {
Forbidden("Some forbidden message...")
}
}
It works fine enough for my REST API web server.
There are some error when proxy scala-lang.org for test:
[ERROR] org.http4s.blaze.pipeline.Stage:226 - Error writing body
org.http4s.InvalidBodyException: Received premature EOF.
How about something like this:
HttpService[IO] {
case req =>
if(verifyRequest(req)) {
for {
client <- Http1Client[IO]()
newHost = "host2"
newAuthority = Authority(host = RegName("host2"), port = Some(80))
proxiedReq =
req.withUri(req.uri.copy(authority = Some(newAuthority)))
.withHeaders(req.headers.put(Header("host", newHost)))
response <- client.fetch(proxiedReq)(IO.pure(_))
} yield response
} else {
Forbidden("Some forbidden message...")
}
}
Note that you should definitely avoid littering your code with calls tounsafeRunSync. You should generally be using it at most once in your program (in Main). In other circumstances, you should focus on lifting the effects into the monad you're working in.

my Tornado chat is losing messages

i am losing messages in my tornado chat and i do not known how to detect when the message wasn't sent and to send the message again
there is any way to detect when the conexion get lost? and when the conexión restart send the message
this is my code
def get(self):
try:
json.dumps(MessageMixin.cache)
except KeyError:
raise tornado.web.HTTPError(404)
class MessageMixin(object):
waiters = {}
cache = {}
cache_size = 200
def wait_for_messages(self,cursor=None):
t = self.section_slug
waiters = self.waiters.setdefault(t, [])
result_future = Future()
waiters.append(result_future)
return result_future
def cancel_wait(self, future):
t = self.section_slug
waiters = self.waiters.setdefault(t, [])
waiters.remove(future)
# Set an empty result to unblock any coroutines waiting.
future.set_result([])
def new_messages(self, message):
t = self.section_slug
#cache = self.cache.setdefault(t, [])
#print t
#print self.waiters.setdefault(t, [])
waiters = self.waiters.setdefault(t, [])
for future in waiters:
try:
if message is not None:
future.set_result(message)
except Exception:
logging.error("Error in waiter callback", exc_info=True)
waiters = []
#self.cache.extend(message)
#if len(self.cache) > self.cache_size:
#self.cache = self.cache[-self.cache_size:]
class MessageNewHandler(MainHandler, MessageMixin):
def post(self, section_slug):
self.section_slug = section_slug
post = self.get_argument("html")
idThread = self.get_argument("idThread")
isOpPost = self.get_argument("isOpPost")
arg_not = self.get_argument("arg")
type_not = self.get_argument("type")
redirect_to = self.get_argument("next", None)
message= {"posts": [post],"idThread": idThread,"isOpPost": isOpPost,
"type": type_not,"arg_not": arg_not}
if redirect_to:
self.redirect(redirect_to)
else:
self.write(post)
self.new_messages(message)
class MessageUpdatesHandler(MainHandler, MessageMixin):
#gen.coroutine
def post(self, section_slug):
self.section_slug = section_slug
try:
self.future = self.wait_for_messages(cursor=self.get_argument("cursor", None))
data = yield self.future
if self.request.connection.stream.closed():
return
self.write(data)
except Exception:
raise tornado.web.HTTPError(404)
def on_connection_close(self):
self.cancel_wait(self.future)
class Application(tornado.web.Application):
def __init__(self):
handlers = [
(r"/api/1\.0/stream/(\w+)", MessageUpdatesHandler),
(r"/api/1\.0/streamp/(\w+)", MessageNewHandler)
]
tornado.web.Application.__init__(self, handlers)
def main():
tornado.options.parse_command_line()
app = Application()
port = int(os.environ.get("PORT", 5000))
app.listen(port)
tornado.ioloop.IOLoop.instance().start()
if __name__ == "__main__":
main()
In the original chatdemo, this is what the cursor parameter to wait_for_messages is for: the browser tells you the last message it got, so you can send it every message since then. You need to buffer messages and potentially re-send them in wait_for_messages. The code you've quoted here will only send messages to those clients that are connected at the time the message came in (and remember that in long-polling, sending a message puts the client out of the "waiting" state for the duration of the network round-trip, so even when things are working normally clients will constantly enter and leave the waiting state)

How can we send data to a websocket client side function?

In play-scala websocket using Iteratee how can we send data to a websocket client side function? How can we specify the function in scala before doing the channel push?
My current websocket server code is as follows :
lazy val (out, channel) = Concurrent.broadcast[String] def connect = WebSocket.using[String] { request => val in = Iteratee.foreach[String] {
msg => println(msg)
channel push("I received your message: " + msg)
}
(in,out)
}
My current client code is as follow:
var ws = new WebSocket("ws://localhost:9000/connect.ws");
ws.onopen = function(){
console.log("Socket has been opened! ");
};
ws.onmessage = function(message) {
console.log(JSON.parse(message.data))
};
Every time I send a message using “channel push”, “ws.onmessage” is getting triggered. How can I emit/trigger the custom event in client side from server side?
Thank you very much in advance.

Scala with Play Framework 2.3.6: sending a message to all socket clients

I'm new to Scala and the Play Framework, so I'm experimenting a bit.
I've successfully created websockets, but I'd like to be able to send a message to multiple socket clients from a simple POST request.
For instance, I have 10 different random browsers connected to my socket (ws:// ... /websocket), and I can myself send a "POST: HELLO" to /newMessage. How do I make so this "HELLO" gets sent to each of the 10 clients ?
Here's the controller receiving the HELLO. Works fine and prints "Got: AnyContentAsText(HELLO)" :
def newMessage = Action { implicit request =>
println("Got: " + request.body)
/* add something here to send "request.body" to every socket client */
Ok("Got: " + request.body)
}
And here's my simple "Socket" controller that sends "Welcome" to connected clients :
object Socket extends Controller {
def txSocket = WebSocket.using[String] { request =>
// Log events to the console
val in = Iteratee.foreach[String](println).map { _ =>
println("Disconnected")
}
// Send a single 'Welcome!' message
val out = Enumerator("Welcome!")
(in, out)
}
}
How can I, from my "Message" controller, send request.body to the websocket ?
Thank you for your time !
Each websocket connection makes a new actor. You need to select the actors and send the message to them.
Like so.
object Application extends Controller {
def connect = WebSocket.acceptWithActor[JsValue, JsValue] { request => out =>
ClientActor.props(out)
}
def broadcast = Action { _ =>
system.actorSelection("akka://application/system/websockets/*/handler") ! "msg"
Ok
}
def system = play.api.libs.concurrent.Akka.system(play.api.Play.current)
}