当前位置: 首页 > news >正文

基于 SpringBoot 实现一个 JAVA 代理 HTTP / WS

基于 SpringBoot 实现一个 JAVA 代理 HTTP / WS

  • 1.环境信息
  • 2.项目结构和代码
    • 2.1 项目结构
    • 2.2 依赖包
    • 2.3 代理配置类
      • 2.3.1 HTTP 代理
      • 2. WS 代理
    • 2.4 YML 配置文件
  • 3.测试用第三方服务示例
    • 3.1 项目结构
    • 3.2 代码
  • 4.测试
    • 4.1 HTTP 和 WS 测试

1.环境信息

组件版本
JDK21
Springboot3.5.3
netty-all4.1.108.Final
smiley-http-proxy-servlet2.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;@Beanpublic 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 {@Overrideprotected SocketConfig buildSocketConfig() {return SocketConfig.custom().setSoTimeout(3600 * 1000).setSoKeepAlive(true).build();}@Overrideprotected HttpResponse doExecute(HttpServletRequest servletRequest, HttpServletResponse servletResponse, HttpRequest proxyRequest) throws IOException {//重置请求头proxyRequest.setHeader("Connection", "keep-alive");//调用父方法return super.doExecute(servletRequest, servletResponse, proxyRequest);}}

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*/@PostConstructprivate 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>() {@Overrideprotected 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*/@Overrideprotected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception {if (frame instanceof TextWebSocketFrame) {// 处理文本帧String text = ((TextWebSocketFrame) frame).text();logger.info("proxy received text message: {}", text);// 获取 SESSIONif (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*/@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {// 关闭远端连接connector.close();// 记录日志logger.info("client disconnected: {}", ctx.channel().remoteAddress());}/*** 异常处理* @param ctx* @param cause* @throws Exception*/@Overridepublic 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*/@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {// 连接建立成功logger.info("connected to web socket server: {}", session.getUri());// Save the sessionthis.session = session;}/*** 消息处理,接收远端服务* @param session* @param message* @throws Exception*/@Overrideprotected 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*/@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {logger.info("disconnected from web socket server: {}", status.getReason());}/*** 报错处理* @param session* @param exception* @throws Exception*/@Overridepublic 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: 8081websocket:is-ws: trueport: 8082path: /ws/connremote-uri: ws://127.0.0.1:8080/ws/connproxy.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 {@Overridepublic 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;@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {sessions.put(session.getId(), session);System.out.println("连接建立: " + session.getId());// 向新连接的客户端发送欢迎消息session.sendMessage(new TextMessage("{\"type\": \"system\", \"message\": \"连接服务器成功!发送 'broadcast' 可以广播消息\"}"));}@Overrideprotected 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));}}@Overridepublic 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);}// 连接WebSocketfunction 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

在这里插入图片描述

http://www.lqws.cn/news/568711.html

相关文章:

  • node js入门,包含express,npm管理
  • SRS流媒体服务器之本地测试rtc推流bug
  • 2.安装Docker
  • 嵌入式硬件中电容的基本原理与详解
  • python动漫周边电商网站系统
  • ORB EPNP
  • web3区块链-ETH以太坊
  • es6特性-第二部分
  • 【JavaScript】setTimeout和setInterval中的陷阱
  • 数据挖掘、机器学习与人工智能:概念辨析与应用边界
  • Linux基本命令篇 —— cal命令
  • 模型预测控制专题:基于增量模型的无差拍预测电流控制
  • Rust 和C++工业机器人实践
  • React与Vue的主要区别
  • 数据分析标普500
  • 打造地基: App拉起基础小程序容器
  • 【AOSP专题】07. FART脱壳-02
  • Python训练营-Day45-tensorboard
  • 设计模式精讲 Day 18:备忘录模式(Memento Pattern)
  • 如何搭建基于RK3588的边缘服务器集群?支持12个RK3588云手机
  • FAST-LIO2源码分析-状态预测
  • 二叉树进阶刷题02(非递归的前中后序遍历)
  • libevent(2)之使用教程(1)介绍
  • 【分析学】 从闭区间套定理出发(二) -- 闭区间连续函数性质
  • 【WRF-Urban数据集】 WRF 模型城市冠层参数 GloUCP 的使用
  • 1 Studying《Computer Vision: Algorithms and Applications 2nd Edition》11-15
  • 【修电脑的小记录】连不上网
  • 强制IDEA始终使用Java 8
  • (24)如何在 Qt 里创建 c++ 类,以前已经学习过如何在 Qt 里引入资源图片文件。以及如何为继承于 Qt已有类的自定义类重新实现虚函数
  • Java面试宝典:基础二