Hí mn, Hôm nay mình sẽ hướng dẫn mn setup 1 project Java Spring boot + Socket.io Client đơn giản nhất.
MÌnh đã research trên internet các tutorial về đề tài này, hầu hết chúng đều hướng dẫn sử dụng lib này https://github.com/mrniko/netty-socketio.
Có một nhược điểm của nó là nó được implement trên một server Netty có sẵn, thế nên nếu sử dụng Spring boot để tích hợp thì chúng ta sẽ phải sử dụng 2 port.
- Khiến cho proj setup bị phức tạp khi ta phải config 2 domain trong quá trình dev và prod.
- Khó khăn trong việc tích hợp security giữa Socket.io server và Spring security.
Không dài dòng nữa, chúng ta bắt tay vào làm thôi.
Website quen thuộc của Springer dev. https://start.spring.io/
Dependencies:
- JDK 17
- Spring boot 2.x
- Spring web
- Websocket
Đây là github của Socket.io java server
- https://github.com/trinopoty/socket.io-server-java
- https://trinopoty.github.io/socket.io-server-java/
Mn thêm dependency này vào nha.
<dependency> <groupId>io.socket</groupId> <artifactId>socket.io-server</artifactId> <version>4.0.1</version>
</dependency>
Tạo proj dir theo cấu trúc ntn.
Classes.
/**
* @Author HuyVu
* @CreatedDate 2/24/2023 1:41 PM
*/
package io.huyvu.springbootsocketio.config.socketio; import io.socket.engineio.server.EngineIoServer;
import io.socket.engineio.server.EngineIoServerOptions;
import io.socket.socketio.server.SocketIoNamespace;
import io.socket.socketio.server.SocketIoServer;
import io.socket.socketio.server.SocketIoSocket;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import java.util.List; @Configuration
public class BeanConfig { @Bean EngineIoServer engineIoServer() { EngineIoServerOptions opt = EngineIoServerOptions.newFromDefault(); opt.setCorsHandlingDisabled(true); EngineIoServer eioServer = new EngineIoServer(opt); return eioServer; } @Bean SocketIoServer socketIoServer(EngineIoServer eioServer) { SocketIoServer sioServer = new SocketIoServer(eioServer); SocketIoNamespace namespace = sioServer.namespace("/mynamespace"); namespace.on("connection", args -> { SocketIoSocket socket = (SocketIoSocket) args[0]; System.out.println("Client " + socket.getId() + " (" + socket.getInitialHeaders().get("remote_addr") + ") has connected."); socket.on("message", args1 -> { System.out.println("[Client " + socket.getId() + "] " + List.of(args1)); socket.send("hello", "Heo khô đi những kỉ niệm xưa kia"); }); }); return sioServer; } }
package io.huyvu.springbootsocketio.config.socketio; 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; @Configuration
@EnableWebSocket
public class EngineIoConfigurator implements WebSocketConfigurer { private final EngineIoHandler mEngineIoHandler; public EngineIoConfigurator(EngineIoHandler engineIoHandler) { mEngineIoHandler = engineIoHandler; } @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(mEngineIoHandler, "/socket.io/") .addInterceptors(mEngineIoHandler) .setAllowedOrigins("*"); }
}
package io.huyvu.springbootsocketio.config.socketio; import io.socket.engineio.server.EngineIoServer;
import io.socket.engineio.server.EngineIoWebSocket;
import io.socket.engineio.server.utils.ParseQS;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.socket.*;
import org.springframework.web.socket.server.HandshakeInterceptor; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map; @Controller
public class EngineIoHandler implements HandshakeInterceptor, WebSocketHandler { private static final String ATTRIBUTE_ENGINE_IO_BRIDGE = "engine.io.bridge"; private static final String ATTRIBUTE_ENGINE_IO_QUERY = "engine.io.query"; private static final String ATTRIBUTE_ENGINE_IO_HEADERS = "engine.io.headers"; private final EngineIoServer mEngineIoServer; public EngineIoHandler(EngineIoServer engineIoServer) { mEngineIoServer = engineIoServer; } @RequestMapping( value = "/socket.io/", method = {RequestMethod.GET, RequestMethod.POST, RequestMethod.OPTIONS}, headers = "Connection!=Upgrade") public void httpHandler(HttpServletRequest request, HttpServletResponse response) throws IOException { mEngineIoServer.handleRequest(request, response); } /* HandshakeInterceptor */ @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) { attributes.put(ATTRIBUTE_ENGINE_IO_QUERY, request.getURI().getQuery()); attributes.put(ATTRIBUTE_ENGINE_IO_HEADERS, request.getHeaders()); return true; } @Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { } /* WebSocketHandler */ @Override public boolean supportsPartialMessages() { return false; } @Override public void afterConnectionEstablished(WebSocketSession webSocketSession) { final EngineIoSpringWebSocket webSocket = new EngineIoSpringWebSocket(webSocketSession); webSocketSession.getAttributes().put(ATTRIBUTE_ENGINE_IO_BRIDGE, webSocket); mEngineIoServer.handleWebSocket(webSocket); } @Override public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) { ((EngineIoSpringWebSocket)webSocketSession.getAttributes().get(ATTRIBUTE_ENGINE_IO_BRIDGE)) .afterConnectionClosed(closeStatus); } @Override public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) { ((EngineIoSpringWebSocket)webSocketSession.getAttributes().get(ATTRIBUTE_ENGINE_IO_BRIDGE)) .handleMessage(webSocketMessage); } @Override public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) { ((EngineIoSpringWebSocket)webSocketSession.getAttributes().get(ATTRIBUTE_ENGINE_IO_BRIDGE)) .handleTransportError(throwable); } private static final class EngineIoSpringWebSocket extends EngineIoWebSocket { private final WebSocketSession mSession; private final Map<String, String> mQuery; private final Map<String, List<String>> mHeaders; EngineIoSpringWebSocket(WebSocketSession session) { mSession = session; final String queryString = (String)mSession.getAttributes().get(ATTRIBUTE_ENGINE_IO_QUERY); if (queryString != null) { mQuery = ParseQS.decode(queryString); } else { mQuery = new HashMap<>(); } this.mHeaders = (Map<String, List<String>>) mSession.getAttributes().get(ATTRIBUTE_ENGINE_IO_HEADERS); } /* EngineIoWebSocket */ @Override public Map<String, String> getQuery() { return mQuery; } @Override public Map<String, List<String>> getConnectionHeaders() { return mHeaders; } @Override public void write(String message) throws IOException { mSession.sendMessage(new TextMessage(message)); } @Override public void write(byte[] message) throws IOException { mSession.sendMessage(new BinaryMessage(message)); } @Override public void close() { try { mSession.close(); } catch (IOException ignore) { } } /* WebSocketHandler */ void afterConnectionClosed(CloseStatus closeStatus) { emit("close"); } void handleMessage(WebSocketMessage<?> message) { if (message.getPayload() instanceof String || message.getPayload() instanceof byte[]) { emit("message", (Object) message.getPayload()); } else { throw new RuntimeException(String.format( "Invalid message type received: %s. Expected String or byte[].", message.getPayload().getClass().getName())); } } void handleTransportError(Throwable exception) { emit("error", "write error", exception.getMessage()); } }
}
Client
index.html
<!DOCTYPE html>
<html lang="en">
<head> <meta charset="UTF-8"> <title>Socket io client</title> <script src="https://cdn.socket.io/4.4.1/socket.io.min.js"></script>
</head>
<body>
<script> const socket = io('/mynamespace') socket.on('hello', (arg) => { console.log('connected', arg) }) socket.on('disconnect', () => { console.log('disconnect', socket.id) // undefined }) function submit() { const txt = document.getElementById('input').value socket.emit('message', txt) } </script> <input id="input" type="text"/>
<button onclick="submit()">Submit</button> </body>
</html>
Và đây là thành quả. github src: https://github.com/huyvu8051/springboot-socketio
Nhanh gọn lẹ đúng không nào, nếu có bất cứ câu hỏi nào plz leave comments. Thank.