基于SpringBoot实现一个JAVA代理HTTP/ WS的方法
作者:猪悟道
本文主要介绍了基于SpringBoot实现一个JAVA代理HTTP/WS的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
1.环境信息
组件 | 版本 |
---|---|
JDK | 21 |
Springboot | 3.5.3 |
netty-all | 4.1.108.Final |
smiley-http-proxy-servlet | 2.0 |
2.项目结构和代码
2.1 项目结构
2.2 依赖包
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.demo</groupId> <artifactId>ProxyAPP</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>21</maven.compiler.source> <maven.compiler.target>21</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring-boot.version>3.5.3</spring-boot.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.108.Final</version> </dependency> <dependency> <groupId>org.mitre.dsmiley.httpproxy</groupId> <artifactId>smiley-http-proxy-servlet</artifactId> <version>2.0</version> </dependency> </dependencies> <build> <finalName>my-proxy</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>${spring-boot.version}</version> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
2.3 代理配置类
启动类
package com.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.socket.config.annotation.EnableWebSocket; /** * @author zhx && moon */ @EnableWebSocket @SpringBootApplication public class ProxyApp { public static void main(String[] args) { SpringApplication.run(ProxyApp.class, args); } }
2.3.1 HTTP 代理
用于 HTTP 代理转发
package com.demo.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @Author zhx && moon * @Since 1.8 * @Date 2024-12-12 PM 2:27 */ @Configuration public class ProxyConfig { @Value("${proxy.target.url}") private String targetUri; @Bean public ServletRegistrationBean proxyServlet() { ServletRegistrationBean servlet = new ServletRegistrationBean(new URITemplateProxyServletSUB()); servlet.addUrlMappings("/*"); servlet.addInitParameter("targetUri", targetUri); return servlet; } }
转发参数处理类,如设置头、添加权限控制、转发记录等操作
package com.demo.config; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.config.SocketConfig; import org.mitre.dsmiley.httpproxy.URITemplateProxyServlet; import java.io.IOException; /** * @Author zhx && moon * @Since 1.8 * @Date 2025-02-26 PM 5:44 */ public class URITemplateProxyServletSUB extends URITemplateProxyServlet { @Override protected SocketConfig buildSocketConfig() { return SocketConfig.custom() .setSoTimeout(3600 * 1000) .setSoKeepAlive(true) .build(); } @Override protected HttpResponse doExecute(HttpServletRequest servletRequest, HttpServletResponse servletResponse, HttpRequest proxyRequest) throws IOException { //重置请求头 proxyRequest.setHeader("Connection", "keep-alive"); //调用父方法 return super.doExecute(servletRequest, servletResponse, proxyRequest); } }
2.3.2 WS 代理
基于 NETTY 实现 WS 协议,完成转发
NETTY 服务端定义
package com.demo.ws; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import jakarta.annotation.PostConstruct; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.util.concurrent.CopyOnWriteArraySet; /** * @Author zhx && moon * @Since 1.8 * @Date 2025-02-28 PM 3:28 */ @Component public class WebSocketServer { Logger logger = LoggerFactory.getLogger(WebSocketServer.class); private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>(); @Value("${server.websocket.is-ws:true}") private boolean isWS; @Value("${server.websocket.port}") private int websocketPort; @Value("${server.websocket.path}") private String websocketPath; @Value("${server.websocket.remote-uri}") private String remoteUri; /** * 初始化本地 WS 服务 * @throws Exception */ @PostConstruct private void init() throws Exception { if (isWS){ // 创建线程 Thread thread = new Thread(() -> { try { run(); } catch (Exception e) { logger.error("WebSocketServer init error: {}", e.getMessage()); } }); // 启动线程 thread.setDaemon(true); thread.setName("WebSocketServer-Proxy"); thread.start(); } } /** * 构建本地 WS 服务 * @throws Exception */ public void run() throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { ChannelPipeline pipeline = ch.pipeline(); // 添加 HTTP 编解码器 pipeline.addLast(new HttpServerCodec()); // 添加 HTTP 内容聚合器 pipeline.addLast(new HttpObjectAggregator(65536)); // 添加 WebSocket 处理器 pipeline.addLast(new WebSocketServerProtocolHandler(websocketPath)); // 添加自定义处理器 pipeline.addLast(new WebSocketProxyHandler(remoteUri)); } }); // 绑定端口并启动服务器 Channel channel = bootstrap.bind(websocketPort).sync().channel(); // 输出启动信息 logger.info("webSocket server started on port {}", websocketPort); // 等待服务器 socket 关闭 channel.closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
WS 处理器
package com.demo.ws; import com.demo.ws.remote.WebSocketClientConnector; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketFrame; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; /** * @Author zhx && moon * @Since 1.8 * @Date 2025-02-28 PM 3:29 */ public class WebSocketProxyHandler extends SimpleChannelInboundHandler<WebSocketFrame> { Logger logger = LoggerFactory.getLogger(WebSocketProxyHandler.class); /** * 远端 WS 服务连接器 */ private final WebSocketClientConnector connector; /** * 远端 WS 服务会话 */ private WebSocketSession session; private AtomicReference<ChannelHandlerContext> ctx = new AtomicReference<>(); /** * 构造 WS 消息处理器 * @param uri */ public WebSocketProxyHandler(String uri) { //添加目标服务 connector = new WebSocketClientConnector(uri, ctx); //建立连接 try { connector.connect(); } catch (Exception e) { logger.info("connect to remote server failed", e); } } /** * 处理接收到的消息,并转发到远端服务 * @param ctx * @param frame * @throws Exception */ @Override protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception { if (frame instanceof TextWebSocketFrame) { // 处理文本帧 String text = ((TextWebSocketFrame) frame).text(); logger.info("proxy received text message: {}", text); // 获取 SESSION if (Objects.isNull(this.ctx.get())){ this.ctx.set(ctx); this.session = connector.getRemoteSession(); } // 转发到远端服务 if (Objects.nonNull(this.session)){ session.sendMessage(new TextMessage(text)); } else { this.ctx.get().writeAndFlush(new TextWebSocketFrame("remote server connection failed!")); } } else if (frame instanceof BinaryWebSocketFrame) { // 处理二进制帧 logger.info("received binary message"); } else if (frame instanceof CloseWebSocketFrame) { // 处理关闭帧 ctx.close(); } } /** * 链接关闭处理 * @param ctx * @throws Exception */ @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { // 关闭远端连接 connector.close(); // 记录日志 logger.info("client disconnected: {}", ctx.channel().remoteAddress()); } /** * 异常处理 * @param ctx * @param cause * @throws Exception */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { logger.error("proxy exception caught", cause); ctx.close(); } }
NETTY 客户端,用于连接第三方 WS
package com.demo.ws.remote; import io.netty.channel.ChannelHandlerContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.client.WebSocketClient; import org.springframework.web.socket.client.WebSocketConnectionManager; import org.springframework.web.socket.client.standard.StandardWebSocketClient; import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; /** * @Author zhx && moon * @Since 1.8 * @Date 2025-02-28 PM 6:27 */ public class WebSocketClientConnector { Logger logger = LoggerFactory.getLogger(WebSocketClientConnector.class); private final String uri; private final AtomicReference<ChannelHandlerContext> ctx; private final WebSocketClient webSocketClient = new StandardWebSocketClient(); private final WebSocketClientHandler webSocketClientHandler; private WebSocketConnectionManager connectionManager; /** * 构建访问远端 WS 服务的本地客户端 * @param uri * @param ctx */ public WebSocketClientConnector(String uri, AtomicReference<ChannelHandlerContext> ctx) { this.uri = uri; this.ctx = ctx; this.webSocketClientHandler = new WebSocketClientHandler(ctx); } /** * 连接远端服务 */ public void connect() { // 创建 WebSocket 连接管理器 this.connectionManager = new WebSocketConnectionManager( webSocketClient, webSocketClientHandler, uri ); // 启动连接 this.connectionManager.start(); // 记录日志 logger.info("web socket client started and connecting to: {}", uri); } /** * 关闭连接 */ public void close(){ this.connectionManager.stop(); } /** * 获取与远端的会话 SESSION * @return */ public WebSocketSession getRemoteSession(){ if (Objects.nonNull(webSocketClientHandler.getSession())) { // 发送一条消息到服务器 return webSocketClientHandler.getSession(); } return null; } }
WS 客户端处理器
package com.demo.ws.remote; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; /** * @Author zhx && moon * @Since 1.8 * @Date 2025-02-28 PM 6:06 */ public class WebSocketClientHandler extends TextWebSocketHandler { Logger logger = LoggerFactory.getLogger(WebSocketClientHandler.class); /** * WebSocket 会话 */ private WebSocketSession session; /** * 本地 WS 服务的 NIO 通道上下文 */ private final AtomicReference<ChannelHandlerContext> ctx; /** * 构造 WS 客户端消息处理器, 获取本第 WS 服务的 NIO 通道上下文 * @param ctx */ public WebSocketClientHandler(AtomicReference<ChannelHandlerContext> ctx) { this.ctx = ctx; } /** * 建立连接后操作 * @param session * @throws Exception */ @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { // 连接建立成功 logger.info("connected to web socket server: {}", session.getUri()); // Save the session this.session = session; } /** * 消息处理,接收远端服务 * @param session * @param message * @throws Exception */ @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { // 处理从服务器收到的消息 logger.info("received message: {}", message.getPayload()); // 转发到本地 WS 服务,并回写给其他连接 if (Objects.nonNull(this.ctx.get())){ this.ctx.get().writeAndFlush(new TextWebSocketFrame(message.getPayload())); } } /** * 连接关闭后处理 * @param session * @param status * @throws Exception */ @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { logger.info("disconnected from web socket server: {}", status.getReason()); } /** * 报错处理 * @param session * @param exception * @throws Exception */ @Override public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { logger.error("web socket transport error: ", exception); } /** * 获取到远端服务的 WebSocket 会话 * @return */ public WebSocketSession getSession(){ return this.session; } }
2.4 YML 配置文件
server: port: 8081 websocket: is-ws: true port: 8082 path: /ws/conn remote-uri: ws://127.0.0.1:8080/ws/conn proxy.target.url: http://127.0.0.1:8080
3.测试用第三方服务示例
3.1 项目结构
依赖包
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>WebTest</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>21</maven.compiler.source> <maven.compiler.target>21</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring-boot.version>3.5.3</spring-boot.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> </dependencies> <build> <finalName>my-proxy</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>${spring-boot.version}</version> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
3.2 代码
启动类
package com.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.socket.config.annotation.EnableWebSocket; /** * @author zhx && moon */ @EnableWebSocket @SpringBootApplication public class ProxyApp { public static void main(String[] args) { SpringApplication.run(ProxyApp.class, args); } }
HTTP 接口类
package org.example.controller; import org.springframework.web.bind.annotation.*; /** * @author zhuwd && moon * @Description * @create 2025-06-29 12:41 */ @RestController @RequestMapping("/test") public class TestController { @GetMapping("/get") public String get(@RequestParam("params") String params){ return "Hello " + params; } @PostMapping("/post") public String post(@RequestBody String params){ return "Hello " + params; } }
WS 配置
package org.example.config; 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; /** * @author zhuwd && moon * @Description * @create 2025-06-29 12:58 */ @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(webSocketHandler(), "/ws/conn") .setAllowedOrigins("*"); // 允许所有来源 } public ServerWebSocketHandler webSocketHandler() { return new ServerWebSocketHandler(); } }
WS 处理器,将消息转大写返回
package org.example.config; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; import java.io.IOException; import java.util.concurrent.ConcurrentHashMap; /** * @author zhuwd && moon * @Description * @create 2025-06-29 14:31 */ public class ServerWebSocketHandler extends TextWebSocketHandler { // 存储所有活动会话 private static final ConcurrentHashMap<String, WebSocketSession> sessions = new ConcurrentHashMap<>(); // 消息计数 private static int messageCount = 0; @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { sessions.put(session.getId(), session); System.out.println("连接建立: " + session.getId()); // 向新连接的客户端发送欢迎消息 session.sendMessage(new TextMessage( "{\"type\": \"system\", \"message\": \"连接服务器成功!发送 'broadcast' 可以广播消息\"}" )); } @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { String payload = message.getPayload(); System.out.println("收到消息 [" + session.getId() + "]: " + payload); messageCount++; // 处理特殊指令 if ("broadcast".equalsIgnoreCase(payload)) { broadcastMessage("来自服务器的广播消息 (" + messageCount + ")"); } else { // 默认回显消息 String response = "{\"type\": \"echo\", \"original\": \"" + escapeJson(payload) + "\", \"modified\": \"" + payload.toUpperCase() + "\"}"; session.sendMessage(new TextMessage(response)); } } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { sessions.remove(session.getId()); System.out.println("连接关闭: " + session.getId() + ", 状态: " + status); } // 广播消息给所有客户端 private void broadcastMessage(String message) throws IOException { TextMessage msg = new TextMessage("{\"type\": \"broadcast\", \"message\": \"" + escapeJson(message) + "\"}"); for (WebSocketSession session : sessions.values()) { if (session.isOpen()) { session.sendMessage(msg); } } } // 处理JSON转义 private String escapeJson(String str) { return str.replace("\\", "\\\\") .replace("\"", "\\\"") .replace("\b", "\\b") .replace("\f", "\\f") .replace("\n", "\\n") .replace("\r", "\\r") .replace("\t", "\\t"); } }
4.测试
HTML 实现一个 WS 客户端
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>WebSocket测试工具</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { background: linear-gradient(135deg, #1a2a6c, #2b5876, #4e4376); color: #fff; min-height: 100vh; padding: 20px; } .container { max-width: 1200px; margin: 0 auto; } header { text-align: center; padding: 20px 0; margin-bottom: 30px; } h1 { font-size: 2.5rem; background: linear-gradient(to right, #00d2ff, #3a7bd5); -webkit-background-clip: text; -webkit-text-fill-color: transparent; text-shadow: 0 2px 4px rgba(0,0,0,0.2); } .subtitle { color: #bbd4ff; margin-top: 10px; font-weight: 300; } .panel { background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(10px); border-radius: 15px; padding: 25px; margin-bottom: 30px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); border: 1px solid rgba(255, 255, 255, 0.1); } .connection-panel { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; } .connection-status { display: flex; align-items: center; } .status-indicator { width: 20px; height: 20px; border-radius: 50%; margin-right: 10px; background-color: #6c757d; } .status-connected { background-color: #28a745; box-shadow: 0 0 10px #28a745; } .status-disconnected { background-color: #dc3545; } .connection-controls { display: flex; gap: 15px; } input { padding: 12px 15px; border: none; border-radius: 8px; background: rgba(255, 255, 255, 0.1); color: #fff; font-size: 1rem; width: 100%; outline: none; } input:focus { background: rgba(255, 255, 255, 0.15); box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25); } button { padding: 12px 25px; border: none; border-radius: 8px; background: linear-gradient(to right, #2193b0, #6dd5ed); color: white; font-size: 1rem; font-weight: 600; cursor: pointer; transition: all 0.3s ease; outline: none; } button:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); } button:active { transform: translateY(0); } button:disabled { background: linear-gradient(to right, #5a6268, #6c757d); cursor: not-allowed; transform: none; box-shadow: none; } .disconnect-btn { background: linear-gradient(to right, #f85032, #e73827); } .form-group { margin-bottom: 20px; } label { display: block; margin-bottom: 8px; color: #b3d7ff; } .log-container { height: 400px; overflow-y: auto; background: rgba(0, 0, 0, 0.2); border-radius: 10px; padding: 15px; font-family: monospace; font-size: 0.9rem; margin-bottom: 15px; } .log-entry { margin-bottom: 8px; padding: 8px; border-radius: 5px; background: rgba(0, 0, 0, 0.1); } .incoming { border-left: 4px solid #4db8ff; } .outgoing { border-left: 4px solid #28a745; } .system { border-left: 4px solid #ffc107; } .error { border-left: 4px solid #dc3545; color: #ffabab; } .timestamp { color: #999; font-size: 0.8rem; margin-right: 10px; } .flex-container { display: flex; gap: 20px; } .flex-container > div { flex: 1; } .info-container { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 15px; margin-top: 20px; } .info-box { background: rgba(0, 0, 0, 0.15); padding: 15px; border-radius: 10px; } .info-box h3 { margin-bottom: 10px; color: #80bdff; font-weight: 500; } .code-example { background: rgba(0, 0, 0, 0.2); padding: 15px; border-radius: 10px; margin-top: 30px; font-family: monospace; font-size: 0.9rem; color: #e9ecef; overflow-x: auto; } @media (max-width: 768px) { .flex-container { flex-direction: column; } .connection-controls { width: 100%; margin-top: 15px; } .connection-panel { flex-direction: column; align-items: flex-start; } } </style> </head> <body> <div class="container"> <header> <h1>WebSocket 测试工具</h1> <p class="subtitle">测试、调试和监控您的WebSocket连接</p> </header> <div class="panel connection-panel"> <div class="connection-status"> <div class="status-indicator" id="statusIndicator"></div> <span id="statusText">未连接</span> </div> <div class="connection-controls"> <input type="text" id="serverUrl" placeholder="ws://127.0.0.1:8082/ws/conn" value="ws://127.0.0.1:8082/ws/conn"> <button id="connectBtn" class="connect-btn">连接</button> <button id="disconnectBtn" class="disconnect-btn" disabled>断开</button> </div> </div> <div class="flex-container"> <div> <div class="panel"> <h2>发送消息</h2> <div class="form-group"> <label for="messageInput">输入要发送的消息:</label> <input type="text" id="messageInput" placeholder="输入消息内容..." disabled> </div> <button id="sendBtn" disabled>发送消息</button> </div> <div class="panel"> <h2>连接信息</h2> <div class="info-container"> <div class="info-box"> <h3>当前状态</h3> <p id="currentState">未连接</p> </div> <div class="info-box"> <h3>传输协议</h3> <p id="protocol">-</p> </div> <div class="info-box"> <h3>消息计数</h3> <p id="messageCount">已发送: 0 | 已接收: 0</p> </div> </div> </div> </div> <div> <div class="panel"> <h2>通信日志</h2> <div class="log-container" id="logContainer"></div> <button id="clearLogBtn">清除日志</button> </div> </div> </div> <div class="panel code-example"> <h3>示例服务器URL:</h3> <p>本地测试服务器: ws://localhost:8082/ws/conn</p> <p>SSL测试服务器: wss://websocket-echo.com</p> </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { // 页面元素引用 const statusIndicator = document.getElementById('statusIndicator'); const statusText = document.getElementById('statusText'); const serverUrl = document.getElementById('serverUrl'); const connectBtn = document.getElementById('connectBtn'); const disconnectBtn = document.getElementById('disconnectBtn'); const messageInput = document.getElementById('messageInput'); const sendBtn = document.getElementById('sendBtn'); const logContainer = document.getElementById('logContainer'); const clearLogBtn = document.getElementById('clearLogBtn'); const currentState = document.getElementById('currentState'); const protocol = document.getElementById('protocol'); const messageCount = document.getElementById('messageCount'); // 状态变量 let socket = null; let messageCounter = { sent: 0, received: 0 }; // 连接状态更新函数 function updateConnectionStatus(connected) { if (connected) { statusIndicator.className = 'status-indicator status-connected'; statusText.textContent = `已连接到: ${socket.url}`; currentState.textContent = "已连接"; // 启用发送控件 messageInput.disabled = false; sendBtn.disabled = false; connectBtn.disabled = true; disconnectBtn.disabled = false; } else { statusIndicator.className = 'status-indicator status-disconnected'; statusText.textContent = '未连接'; currentState.textContent = "未连接"; // 禁用发送控件 messageInput.disabled = true; sendBtn.disabled = true; connectBtn.disabled = false; disconnectBtn.disabled = true; } } // 日志函数 function addLog(type, content) { const logEntry = document.createElement('div'); logEntry.className = `log-entry ${type}`; const now = new Date(); const timestamp = `[${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}]`; logEntry.innerHTML = `<span class="timestamp">${timestamp}</span> ${content}`; logContainer.prepend(logEntry); } // 连接WebSocket function connect() { const url = serverUrl.value.trim(); if (!url) { alert('请输入有效的WebSocket服务器URL'); return; } try { socket = new WebSocket(url); // 连接打开 socket.addEventListener('open', (event) => { updateConnectionStatus(true); protocol.textContent = "WebSocket"; addLog('system', `连接已建立于: ${url}`); }); // 接收消息 socket.addEventListener('message', (event) => { messageCounter.received++; updateMessageCount(); addLog('incoming', `接收: ${event.data}`); }); // 错误处理 socket.addEventListener('error', (error) => { addLog('error', `错误: ${error.message || '未知错误'}`); }); // 连接关闭 socket.addEventListener('close', (event) => { updateConnectionStatus(false); addLog('system', `连接关闭 (代码: ${event.code}, 原因: ${event.reason || '无'})`); socket = null; }); } catch (error) { updateConnectionStatus(false); addLog('error', `连接失败: ${error.message}`); } } // 断开WebSocket连接 function disconnect() { if (socket) { socket.close(); socket = null; } } // 发送消息 function sendMessage() { if (!socket || socket.readyState !== WebSocket.OPEN) { addLog('error', '错误: 连接未就绪,无法发送消息'); return; } const message = messageInput.value.trim(); if (!message) { alert('请输入要发送的消息'); return; } try { socket.send(message); messageCounter.sent++; updateMessageCount(); addLog('outgoing', `发送: ${message}`); messageInput.value = ''; } catch (error) { addLog('error', `发送失败: ${error.message}`); } } // 更新消息计数显示 function updateMessageCount() { messageCount.textContent = `已发送: ${messageCounter.sent} | 已接收: ${messageCounter.received}`; } // 清除日志 function clearLog() { logContainer.innerHTML = ''; } // 设置事件监听器 connectBtn.addEventListener('click', connect); disconnectBtn.addEventListener('click', disconnect); sendBtn.addEventListener('click', sendMessage); clearLogBtn.addEventListener('click', clearLog); // 支持按Enter键发送消息 messageInput.addEventListener('keypress', (event) => { if (event.key === 'Enter' && !sendBtn.disabled) { sendMessage(); } }); // 初始化日志 addLog('system', 'WebSocket测试工具已准备就绪'); addLog('system', '请连接到一个WebSocket服务器开始测试'); }); </script> </body> </html>
4.1 HTTP 和 WS 测试
分别启动测试服务和代理服务
测试服务
代理服务
HTTP 测试,8081 转发到 8080
Get
Post
WS 测试 8982 转发到 8080
到此这篇关于基于SpringBoot实现一个JAVA代理HTTP/ WS的方法的文章就介绍到这了,更多相关SpringBoot 代理HTTP/ WS内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!