Java用webSocket實現tomcat的日誌實時輸出到web頁面

原文:http://blog.csdn.net/smile326/article/details/52218264css

1.場景需求

後臺攻城獅和前端攻城獅一塊兒開發時,常常受到前端攻城獅的騷擾,動不動就來一句,那誰,幫我看一下接口訪問出什麼錯了。。。我剛剛上傳的參數過來了嗎。。。你返回的是什麼。。。我請求過去了嗎。。。 
好吧,就是這樣的一種狀況,而後我但願讓他們本身去看後臺日誌,而又不想給他們登錄服務器的權限TAT。那就想辦法把訪問日誌實時輸出到web頁面,這樣他們打開頁面就能夠了。html

2.特別鳴謝

1)特別感謝http://blog.csdn.net/xiao__gui/article/details/50041673的啓發,該文章中利用的是linux中的tail日誌,本文便是受此啓發,基本思路如出一轍,感謝感謝。 
2)感謝國產博客中各類websocket教程,獲益頗多,本文其實也是一個websocket教程,只是稍做拓展。前端

3.進入正題

配置websocket,大部分web項目都會用到spring框架吧,全部這裏貼spring websocket的配置,不用spring的請自行去掉spring部分。spring使用4.0以上,tomcat使用7.0.68版本。

1)pom.xml中包的引入:java

  <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.3.1</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.3.3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-messaging</artifactId>
            <version>4.0.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-websocket</artifactId>
            <version>4.0.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.0.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.3.1</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>

 

2)websocket邏輯代碼,須要一個config和一個handle 
configuration:jquery

package com.he.websocket;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
/**
 * WebScoket配置處理器
 * @author he
 * @Date 2016年03月15日 下午1:15:09
 */
@Configuration
@EnableWebSocket
public class WebsocketConfig implements WebSocketConfigurer {
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myHandler(), "/ws").addInterceptors(new HttpSessionHandshakeInterceptor());
        registry.addHandler(myHandler(), "/ws/sockjs").addInterceptors(new HttpSessionHandshakeInterceptor()).withSockJS();
    }

    @Bean
    public WebsocketHandler myHandler() {
        return new WebsocketHandler();
    }
}

handle:linux

package com.he.websocket;

import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;  
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;  
import org.springframework.web.socket.handler.TextWebSocketHandler;  
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.he.entity.Message;

@Component
public class WebsocketHandler extends TextWebSocketHandler {  
    protected static final Logger LOG = Logger.getLogger(WebsocketHandler.class);

    public static final Map<Object, WebSocketSession> userSocketSessionMap;
    static {
        userSocketSessionMap = new HashMap<Object, WebSocketSession>();
    }
    /**
     * 創建鏈接後
     */
    public void afterConnectionEstablished(WebSocketSession session)
            throws Exception {
        String uid = (String) session.getAttributes().get("uid");
        if (userSocketSessionMap.get(uid) == null) {
            userSocketSessionMap.put(uid, session);
        }
        LOG.warn("======創建鏈接完成======");
    }
    /**
     * 消息處理,在客戶端經過Websocket API發送的消息會通過這裏,而後進行相應的處理
     */
    @Override 
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
            if(message.getPayloadLength()==0)return;
            Message msg=new Gson().fromJson(message.getPayload().toString(),Message.class);
            String msgString = message.getPayload().toString();
            LOG.warn("收到的消息是:" + msgString);
            LOG.warn("發送的對象是:" + msg.getTo());
            msg.setDate(new Date());
            sendMessageToUser(msg.getTo(), new TextMessage(new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create().toJson(msg)));

            LOG.warn("======消息處理結束======");
    }
    /**
     * 消息傳輸錯誤處理
     */
    public void handleTransportError(WebSocketSession session,
            Throwable exception) throws Exception {
        if (session.isOpen()) {
            session.close();
        }
        Iterator<Entry<Object, WebSocketSession>> it = userSocketSessionMap
                .entrySet().iterator();
        LOG.warn("======消息傳輸錯誤======");
        // 移除Socket會話
        while (it.hasNext()) {
            Entry<Object, WebSocketSession> entry = it.next();
            if (entry.getValue().getId().equals(session.getId())) {
                userSocketSessionMap.remove(entry.getKey());
                System.out.println("Socket會話已經移除:用戶ID" + entry.getKey());
                break;
            }
        }
    }
    /**
     * 關閉鏈接後
     */
    public void afterConnectionClosed(WebSocketSession session,
            CloseStatus closeStatus) throws Exception {
        LOG.warn("Websocket:" + session.getId() + "已經關閉");
        Iterator<Entry<Object, WebSocketSession>> it = userSocketSessionMap
                .entrySet().iterator();
        // 移除Socket會話
        LOG.warn("======關閉鏈接======");
        while (it.hasNext()) {
            Entry<Object, WebSocketSession> entry = it.next();
            if (entry.getValue().getId().equals(session.getId())) {
                userSocketSessionMap.remove(entry.getKey());
                LOG.warn("Socket會話已經移除:用戶ID" + entry.getKey());
                break;
            }
        }
    }
    public boolean supportsPartialMessages() {
        return false;
    }
    /**
     * 給全部在線用戶發送消息
     * 
     * @param message
     * @throws IOException
     */
    public void broadcast(final TextMessage message) throws IOException {
        Iterator<Entry<Object, WebSocketSession>> it = userSocketSessionMap
                .entrySet().iterator();
        LOG.warn("======羣發======");
        // 多線程羣發
        while (it.hasNext()) {
            final Entry<Object, WebSocketSession> entry = it.next();
            if (entry.getValue().isOpen()) {
                // entry.getValue().sendMessage(message);
                new Thread(new Runnable() {
                    public void run() {
                        try {
                            if (entry.getValue().isOpen()) {
                                entry.getValue().sendMessage(message);
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        }
    }
    /**
     * 給某個用戶發送消息
     * 
     * @param userName
     * @param message
     * @throws IOException
     */
    public void sendMessageToUser(String uid, TextMessage message)
            throws IOException {
        WebSocketSession session = userSocketSessionMap.get(uid);
        LOG.warn("======給某個用戶發送消息======");
        if (session != null && session.isOpen()) {
            session.sendMessage(message);
        }
    }
}  

3)web.xml中配置spring和springMVC,由於藉助與springMVC來攔截處理websocket請求,若是用其餘MVC框架,請自行替換。不想換也能夠用springMVC,侵入性很低,而且原本就能夠多種MVC共存。git

<context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/classes/applicationContext.xml
        </param-value>
    </context-param>
    <servlet>
        <servlet-name>SpringMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/classes/applicationContext.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>SpringMVC</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>

 

注意web.xml的頭部:github

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
    http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

4)spring的xml文件中對websocket的配置:web

<!-- websocket配置 -->
    <bean id="websocket" class="com.he.websocket.WebsocketHandler" />
    <websocket:handlers>
        <websocket:mapping path="/ws.do" handler="websocket" />
        <websocket:handshake-interceptors>
            <bean class="org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor" />
        </websocket:handshake-interceptors>
    </websocket:handlers>

    <!-- 須要掃描的包-->
    <context:component-scan base-package="com.he.websocket.WebsocketConfig" />
    <context:component-scan base-package="com.he.websocket.WebsocketHandler" />

注意spring的xml的頭部:spring

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
                        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
                        http://www.springframework.org/schema/context   
                        http://www.springframework.org/schema/context/spring-context-4.0.xsd  
                        http://www.springframework.org/schema/websocket 
                        http://www.springframework.org/schema/websocket/spring-websocket.xsd
                        http://www.springframework.org/schema/mvc   
                        http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
                         ">

5)日誌接收頁面:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>tail log</title>
<script src="//cdn.bootcss.com/jquery/2.1.4/jquery.js"></script>
<style>
    html,body
    {
        height:100%;
        width:100%;
    }
</style>
</head>
<body>
    <div id="log-container" style="height: 100%; overflow-y: scroll; background: #333; color: #aaa; padding: 10px;">
        <div>
        </div>
    </div>
</body>
<script>
    $(document).ready(function() {
        // 指定websocket路徑
        var websocket = new WebSocket('ws://localhost:8080/websocket/ws.do');
        websocket.onmessage = function(event) {
            // 接收服務端的實時日誌並添加到HTML頁面中
            $("#log-container div").append(event.data + "<p> </p>");
            // 滾動條滾動到最低部
            $("#log-container").scrollTop($("#log-container div").height() - $("#log-container").height());
        };
    });
</script>
</body>
</html>

此頁面打開時發送了一個websocket請求,ws.do,會被springMVC攔截處理。鏈接創建以後每當收到新的消息會追加到當前文本的末尾並換行(p標籤用來換行)。

6)控制日誌的實時打印示例:

PrintWriter out = response.getWriter();
MyWebSocketHandler.broadcast(new TextMessage(new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create().toJson("訪問test接口")));
out.println("訪問test接口");

每次返回給前端某些值的時候經過一個廣播將該值傳送到web頁面。若是不想廣播,只須要在程序中對log的session進行特別的標籤,而後定向傳送便可。

效果示意圖:瀏覽器輸入localhost:8080/projectName/log.html,而後訪問localhost:8080/projectName/test,每次訪問test接口,網頁上實時顯示tomcat輸出的日誌。 
這裏寫圖片描述

4.備註:

須要完整代碼留郵箱,看到就發送,發送的工程是一個基本的web工程,只集成spring websocket,本文的全部效果和引用都有。或者發郵件到smile326@qq.com提醒我。 
本例中爲方便消息的處理用到以下兩個實體類(我也是沿用了別人教程代碼): 
Message.Java

public class Message {

    //發送者
    public Long from;
    //發送者名稱
    public String fromName;
    //接收者
    public Long to;
    //發送的文本
    public String text;
    //發送日期
    public Date date;

    public Long getFrom() {
        return from;
    }

    public void setFrom(Long from) {
        this.from = from;
    }

    public Long getTo() {
        return to;
    }

    public void setTo(Long to) {
        this.to = to;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public String getFromName() {
        return fromName;
    }

    public void setFromName(String fromName) {
        this.fromName = fromName;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

}

user.java:

public class User {

    private Long id;

    private String name;

    private String password;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

}

應你們需求,完成工程的代碼放在了github上面啦!傳上去的代碼進一步完善了一下,把創建鏈接的時候上傳參數的問題處理了一下;把存放session的容器換成了線程安全型的!歡迎你們在線討論! 
https://github.com/smile326/springWebsockeForTomcatLog

相關文章
相關標籤/搜索