起因

很久以前,用jetty玩过websocket,为了nutz.cn的自动提醒功能,又实践了一次websocket.

websocket需要服务器端,nginx,页面端,同时配合,才能工作.

页面端

        var ws;
        var WS_URL = window.location.host+"${base}/yvr/topic/socket";
        if (location.protocol == 'http:') { // 需要特别注意的地方,根据http/https协议选不同的websocket前缀
            ws = new WebSocket("ws://"+WS_URL); // 普通http用ws,全称WebSocket
        } else {
            ws = new WebSocket("wss://"+WS_URL); // https环境下需要wss协议,全称WebSocket Secure
        }
        
        ws.onmessage = function(event) { // onmessage 特指服务器端发送消息过来
            var re = JSON.parse(event.data);
            _replies_count = re.count;
            var n = new Notification(re.data, re.options); // 这里使用了Chrome Notification API
            n.onclick = function() {
                location.reload();
            };
        };
        window.setInterval(function(){ // 定时查询
            ws.send(JSON.stringify({id:'${obj.topic.id}',replies:_replies_count}));
        }, 5000);

服务器端

// 省略import

@ServerEndpoint("/yvr/topic/socket")
public class YvrTopicWebSocket {
    
    private static final Log log = Logs.get();
    
    @OnMessage
    public void onMessage(String message, Session session) {
        if (yvrService == null) // 从全局ioc容器取出需要的ioc bean
            yvrService = Mvcs.ctx().getDefaultIoc().get(YvrService.class);
        try {
            NutMap map = Json.fromJson(NutMap.class, message); // 看页面端,发送过来的是json字符串
            String topicId = map.getString("id");
            int replies = map.getInt("replies");
            Object re = yvrService.check(topicId, replies);
            if (re instanceof Map) {
                // 消息反馈回去, 会调用js中的onmessage方法
                session.getBasicRemote().sendText(Json.toJson(re));
            }
        }
        catch (Throwable e) {
            log.debug("message="+message, e);
        }
    }
    
    YvrService yvrService; // 注意,并非注入
}

Maven配置

编译依赖:

    <dependencies>
        <dependency>
            <groupId>javax.websocket</groupId>
            <artifactId>javax.websocket-api</artifactId>
            <version>1.1</version>
        </dependency>
    </dependencies>

jetty插件需要的依赖

必须加websocket-server,否则不扫描websocket相关的注解

            <plugin>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-maven-plugin</artifactId>
                <version>9.3.8.v20160314</version>
                <configuration>
                    <jvmArgs>-Dfile.encoding=UTF-8</jvmArgs>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>org.eclipse.jetty.websocket</groupId>
                        <artifactId>websocket-server</artifactId>
                        <version>9.3.8.v20160314</version>
                    </dependency>
                </dependencies>
            </plugin>

Nginx配置

这个我也是后来测试才发现,需要加点东西, websocket才能传到后端去

                location / {
                        proxy_http_version 1.1;  #版本也必须是http 1.1
                        client_max_body_size 10m;
                        proxy_pass http://nutz;
                        proxy_set_header Host $http_host;
                        proxy_set_header X-Forwarded-For $remote_addr;
                        proxy_redirect http:// https://;
                        #add_header Access-Control-Allow-Origin "*";
                        # 下面两行是为了websocket特意加的.
                        proxy_set_header Upgrade $http_upgrade;
                        proxy_set_header Connection "upgrade";
                }

分析一下YvrTopicWebSocket可否注入

在其构造方法打印堆栈信息,显示如下, 而且是第一次访问时才打印(即第一次访问时才创建对象)

java.lang.Throwable
    at net.wendal.nutzbook.module.yvr.YvrTopicWebSocket.<init>(YvrTopicWebSocket.java:21)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
    at java.lang.Class.newInstance(Class.java:442)
    at org.apache.tomcat.websocket.server.DefaultServerEndpointConfigurator.getEndpointInstance(DefaultServerEndpointConfigurator.java:36)
    at org.apache.tomcat.websocket.pojo.PojoEndpointServer.onOpen(PojoEndpointServer.java:50)
    at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.init(WsHttpUpgradeHandler.java:138)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:701)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1500)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1456)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:745)

可以看到是DefaultServerEndpointConfigurator这个类调用clazz.newInstance生成实例

该类的源码地址 http://grepcode.com/file/repo1.maven.org/maven2/org.apache.tomcat/tomcat-websocket/8.0.24/org/apache/tomcat/websocket/server/DefaultServerEndpointConfigurator.java/

依赖ServiceLoader加载的,但还没成功,明天再说了.



blog comments powered by Disqus

Published

2016-04-20

Categories


Tags

Fork me on GitHub