• Xitrum学习笔记09


    Xitrum不会自动发送默认响应,必须调用respondXXX方法发送响应。如果没有调用respondXXX,Xitrum会保持HTTP连接,可以过后调用respondXXX。

    调用 channel.isOpen 来检查 HTTP连接 是否还处于打开状态。还可以使用addConnectionClosedListener方法,定义连接关闭之后的动作。

    addConnectionClosedListener {
      // The connection has been closed
      // Unsubscribe from events, release resources etc.
    }

    因为异步性质,响应不会立刻被发送。respondXXX返回io.netty.channel.ChannelFuture,可以用它来执行响应被发送后的动作。例如:

    import io.netty.channel.{ChannelFuture, ChannelFutureListener}
    
    val future = respondText("Hello")
    
    future.addListener(new ChannelFutureListener {
      def operationComplete(future: ChannelFuture) {
        future.getChannel.close()
      } 
    })

    或者更简单的

    respondText("Hello").addListener(ChannelFutureListener.CLOSE)

    WebSocket

    import scala.runtime.ScalaRunTime
    import xitrum.annotation.WEBSOCKET
    import xitrum.{WebSocketAction, WebSocketBinary, WebSocketText, WebSocketPing, WebSocketPong}
    @WEBSOCKET("echo")
    class EchoWebSocketActor extends WebSocketAction {
      def execute() {
        // Here you can extract session data, request headers etc.
        // but do not use respondText, respondView etc.
        // To respond, use respondWebSocketXXX like below.
        log.debug("onOpen")
        context.become {
        case WebSocketText(text) =>
          log.info("onTextMessage: " + text)
          respondWebSocketText(text.toUpperCase)
        case WebSocketBinary(bytes) =>
          log.info("onBinaryMessage: " + ScalaRunTime.stringOf(bytes))
          respondWebSocketBinary(bytes)
        case WebSocketPing =>
          log.debug("onPing")
        case WebSocketPong =>
          log.debug("onPong")
        }
      }
      override def postStop() {
        log.debug("onClose")
        super.postStop()
      }
    }

    一个Actor在由请求的时候会被创建,它在以下情况下回被停止:连接关闭;WebSocket close frame被接收或发送。

    用来发送WebSocket frames的方法:
    • respondWebSocketText
    • respondWebSocketBinary
    • respondWebSocketPing

    • respondWebSocketClose

    当Xitrum接收ping frame时,会自动发送pong frame,所以没有respondWebSocketPong的方法

    获取WebSocket action的URL:

    // Probably you want to use this in Scalate view etc.
    val url = absWebSocketUrl[EchoWebSocketActor]

    SockJS

    SockJS是一个浏览器端的JavaScript库,为不支持WebSocket的浏览器提供类似于WebSocket的对象。

    SockJS会先尝试使用WebSocket,如果失败则会使用类似于WebSocket的对象。

    如果要在所有的浏览器都是用WebSocket API,则应该使用SockJS而避免直接使用WebSocket

    <script>
      var sock = new SockJS('http://mydomain.com/path_prefix');
      sock.onopen = function() {
        console.log('open');
      };
      sock.onmessage = function(e) {
        console.log('message', e.data);
      };
      sock.onclose = function() {
        console.log('close');
      };
    </script>

    Xitrum通过在View模板中调用 jsDefaults 包含了 SockJS的JavaScript

    一个Actor在出现新的SockJS session时被创建,在SockJS session关闭时被停止。

    使用 respondSockJsText和respondSockJsClose发送 SockJS frame。

    代码示例:

    import xitrum.{Action, SockJsAction, SockJsText}
    import xitrum.annotation.SOCKJS
    @SOCKJS("echo")
    class EchoSockJsActor extends SockJsAction {
      def execute() {
        // To respond, use respondSockJsXXX like below
        log.info("onOpen")
        context.become {
        case SockJsText(text) =>
          log.info("onMessage: " + text)
          respondSockJsText(text)
        }
      }
      override def postStop() {
        log.info("onClose")
        super.postStop()
      }
    }

    分块响应

    要发送分块响应

    1. 调用 setChunked

    2. 多次调用 respondXXX

    3. 最后,调用 respondLastChunk

    分块响应示例,创建一个很大的CSV文件

    // "Cache-Control" header will be automatically set to:
    // "no-store, no-cache, must-revalidate, max-age=0"
    //
    // Note that "Pragma: no-cache" is linked to requests, not responses:
    // http://palizine.plynt.com/issues/2008Jul/cache-control-attributes/
    setChunked()
    val generator = new MyCsvGenerator
    generator.onFirstLine { line =>
      val future = respondText(header, "text/csv")
      future.addListener(new ChannelFutureListener {
        def operationComplete(future: ChannelFuture) {
          if (future.isSuccess) generator.next()
        }
      }
    }
    generator.onNextLine { line =>
      val future = respondText(line)
      future.addListener(new ChannelFutureListener {
        def operationComplete(future: ChannelFuture) {
          if (future.isSuccess) generator.next()
        }
      })
    }
    generator.onLastLine { line =>
      val future = respondText(line)
      future.addListener(new ChannelFutureListener {
        def operationComplete(future: ChannelFuture) {
          if (future.isSuccess) respondLastChunk()
        }
      })
    }
    generator.generate()

    注意:

    • 头信息在第一个 respondXXX 被调用时被发送.
    • 可选择的头部尾信息可以在调用respondLastChunk中发送
    • 页面和action缓存不能被以分块响应的形式使用

    永存的IFrame

    Forever Iframe(永存的Iframe)技术涉及了一个置于页面中的隐藏Iframe标签,该标签的src属性指向返回服务器端事件的servlet路径。

    每次在事件到达时,servlet写入并刷新一个新的script标签,该标签内部带有JavaScript代码,iframe的内容被附加上这一script标签,标签中的内容就会得到执行。

    在页面中嵌入iframe:

    ...
    <script>
    var functionForForeverIframeSnippetsToCall = function() {...}
    </script>
    ...
    <iframe width="1" height="1" src="path/to/forever/iframe"></iframe>
    ...

    script标签的代码片段

    // Prepare forever iframe
    setChunked()
    // Need something like "123" for Firefox to work
    respondText("<html><body>123", "text/html")
    // Most clients (even curl!) do not execute <script> snippets right away,
    // we need to send about 2KB dummy data to bypass this problem
    for (i <- 1 to 100) respondText("<script></script>
    ")

    过后,要传递数据到浏览器时,发送这个代码片段

    if (channel.isOpen)
      respondText("<script>parent.functionForForeverIframeSnippetsToCall()</script>
    ")
    else
      // The connection has been closed, unsubscribe from events etc.
      // You can also use ``addConnectionClosedListener``.

    使用Event Source实现页面消息推送

    Server-sent events(SSE)是一种能让浏览器通过HTTP连接自动收到服务器端更新的技术,SSE EventSource 接口被W3C制定为HTML5的一部分。

    Event Source响应是一种特殊的分块响应,数据必须是UTF-8的

    可以多次调用respondEventSource以响应event source

    respondEventSource("data1", "event1") // Event name is "event1"
    respondEventSource("data2") // Event name is set to "message" by default
  • 相关阅读:
    java并发AtomicIntegerArray
    java并发:原子类之AtomicLong
    java并发:初探消费者和生产者模式
    java并发:初探用户线程和守护线程
    java并发:interrupt进程终止
    java并发:join源码分析
    java并发:初探sleep方法
    java并发(二):初探syncronized
    java并发(一):初探线程的创建
    Git 操作
  • 原文地址:https://www.cnblogs.com/sunspeedzy/p/6857420.html
Copyright © 2020-2023  润新知