Java for Web学习笔记(四八):WebSocket(5)encoder,decoder和configurator

模拟聊天的例子

在Professional Java for Web Applications中给通书的例子中给出如何利用WebSocket实现聊天室的例子,作为案例,学习一下。用户可以进入聊天室聊天,HTTPSession终结,WebSocket也相应关闭。因此小例子中Server Endpoint是承载在一个HttpSessionListener之上。

在sessionDestroyed()中,关闭WebScoket,向聊天的对方提示状态。

Endpoint的annotation

我们先看看@ServerEndpoint和@ClientEndpoint的例子

@ServerEndpoint(
   value = "/sample",
   decoders = ChatDecoder.class,
   encoders = DisconnectResponseEncoder.class,
   subprotocols = {"subprtotocol1", "subprotocol2"},
   configurator = Configurator.class )

@ClientEndpoint(
   decoders = SampleDecoder.class,
   encoders = SampleEncoder.class,
   subprotocols = {"subprtotocol1", "subprotocol2"},
   configurator = ClientConfigurator.class)

subprotocols就是握手过程中HTTP header Sec-WebSocket-Protocol。网上有很个很好的教程 https://tyrus.java.net/documentation/1.4/index/websocket-api.html

configurator

//我们重点学习这种写法:在@ServerEndpoint或@ClientEndpoint中
// (1)指定消息的编码器和解码器,可以在onMessage接口中直接获得对象,通过RemoteEndpoint.Basic|Async.sendObject()发送对象
// (2)设定configurator,通过对ServerEndpointConfig.Configurator的继承,可以将一些request的属性或参数,放入WebSocket.Session中
@ServerEndpoint(value = "/chat/{sessionId}",
    encoders = ChatMessageCodec.class,
    decoders = ChatMessageCodec.class,
    configurator = ChatEndpoint.EndpointConfigurator.class)
@WebListener
public class ChatEndpoint implements HttpSessionListener {
    private static final String HTTP_SESSION_PROPERTY = "cn.wei.flowingflying.chat.HTTP_SESSION";
    ... ...
    @OnOpen
    public void onOpen(Session session, @PathParam("sessionId") long sessionId){
        //【2】获取通过configurator存放在Session中的属性
        HttpSession httpSession = (HttpSession)session.getUserProperties().get(ChatEndpoint.HTTP_SESSION_PROPERTY);
        ... ...
    }

    @OnMessage
    public void onMessage(Session session, ChatMessage message) {
        ... ...
    }

    @OnClose
    public void onClose(Session session, CloseReason reason){
        ... ...
    }

    @OnError
    public void onError(Session session, Throwable e){
        ... ...
    }
    
   /**
    * @see HttpSessionListener#sessionCreated(HttpSessionEvent)
    */
   public void sessionCreated(HttpSessionEvent se)  { /* do nothing */   }

   /**
    * @see HttpSessionListener#sessionDestroyed(HttpSessionEvent)
    */
    public void sessionDestroyed(HttpSessionEvent event)  { 
        ... ...
    }

    //【1】设定configurator:本例子将HttpSession放入Session的属性中。modifyHandshake也就是握手阶段HTTP交换。
    public static class EndpointConfigurator extends ServerEndpointConfig.Configurator{
        @Override
        public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) {
            super.modifyHandshake(config, request, response);
            config.getUserProperties().put(ChatEndpoint.HTTP_SESSION_PROPERTY, request.getHttpSession());
        }
    } 
}

encoder和decoder

例子中消息ChatMessage将采用JSON的格式进行传递。下面演示了一个含有enum类型的处理:

public class ChatMessage {
    private OffsetDateTime timestamp;
    private Type type;
    private String user;
    private String content;

    ... 作为一个POJO的getters 和 setters,略...    

    public static enum Type {
        STARTED, JOINED, ERROR, LEFT, TEXT
    }
}

下面是一个消息的例子

{"user":"wei","content":"Hello, world!","timestamp":1490235820.560000000,"type":"JOINED"}

Encoder和decoder虽然有文本和二进制两种方式,本例子采用二进制方式,虽然JSON常使用String格式。对于String的方式,可以参考Websockets: using Encoders and Decoders

//实现Encode.BinaryStream和Decoder.BinaryStream,其他格式还有 Encode.Binary/Decoder.Binary,Encoder.Text/Decoder.Text 或者 Encoder.TextStream/Decoder.TextStream
public class ChatMessageCodec implements Encoder.BinaryStream<ChatMessage>,Decoder.BinaryStream<ChatMessage>{
    //【1】定义JSON的Mapper及其相关属性
    private static final ObjectMapper MAPPER = new ObjectMapper();
    static {
        MAPPER.findAndRegisterModules();
        MAPPER.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false);
    }

    @Override
    public void init(EndpointConfig config) {     }

    @Override
    public void destroy() {    }

    //【2】实现decoder
    @Override
    public ChatMessage decode(InputStream inputStream) throws DecodeException, IOException {
        try{
            return ChatMessageCodec.MAPPER.readValue(inputStream, ChatMessage.class);
        }catch(JsonGenerationException | JsonMappingException e){
            throw new DecodeException((ByteBuffer)null, e.getMessage(), e);
        }
    }

    //【3】实现encoder 
    @Override
    public void encode(ChatMessage chatMessage, OutputStream outputStream) throws EncodeException, IOException {
        try{
            ChatMessageCodec.MAPPER.writeValue(outputStream, chatMessage);
        }catch(JsonGenerationException | JsonMappingException e){
            throw new EncodeException(chatMessage, e.getMessage(), e);
        }
    }
}

Client:发送JSON的二进制消息

下面是相关的JavaScript:

 send = function() {
     if(server == null) {
         ... ...
     } else {
         var message = {
             timestamp: new Date(), type: 'TEXT', user: username,
             content: messageArea.get(0).value
         };
         try {
             var json = JSON.stringify(message);
             var length = json.length;
             var buffer = new ArrayBuffer(length);
             var array = new Uint8Array(buffer);
             for(var i = 0; i < length; i++) {
                 array[i] = json.charCodeAt(i);
             }
             server.send(buffer);
             messageArea.get(0).value = '';
        } catch(error) {
             modalErrorBody.text(error);
             modalError.modal('show');
        }
    }
};

相关链接: 我的Professional Java for Web Applications相关文章

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值