一、设计选题
选题背景
在互联网时代,HTTP代理服务器是网络通信的重要组件,广泛应用于网页服务器、API服务等各种网络应用中。通过代理服务器可以提升网络访问速度、节省带宽、提高安全性和匿名性。本项目的主要目的是通过开发一个简易的HTTP代理服务器,除了来处理和转发客户端的HTTP请求外,也可以当作独立的服务器部署资源,最后通过图形用户界面展示这些请求的详细信息。
二、课题简介
2.1 基本功能
2.1.1 请求处理部分
- 请求解析:使用Java的BufferedReader和InputStreamReader类读取HTTP请求的内容,并解析出请求行和请求头信息。
- 数据提取:从解析后的请求中提取关键信息,包括请求方法(GET或POST)、请求URL、请求头和请求体(如果有)。
- 数据处理:将提取的请求信息存储在Request对象中,方便后续处理和显示。
- 数据记录:通过调用RequestLoggerGUI类的logRequest方法,将请求信息记录并显示在图形用户界面中。
- 请求响应:根据请求类型(GET或POST)生成相应的HTTP响应,并将响应结果返回给客户端。
- 黑名单管理:维护一个IP地址黑名单列表,阻止来自黑名单IP的请求。
- 流量监控:记录并显示此网络服务器内接受请求以及转发请求的流量数据,支持按时间段查看平均流量。
- 安全管理:通过支持HTTPS请求以及对请求数量进行限流配置,可以使得服务器更加安全和稳定
2.1.2 可视化部分
- 窗口展示:使用Swing库创建一个主窗口,并设置其属性,如大小和布局。
- 请求记录显示:在窗口中创建一个表格组件(JTable),用于展示所有记录的HTTP请求,包括请求路径、请求协议和时间戳等信息。
- 请求详细信息展示:在窗口下方创建一个多行文本区域(JTextArea),当用户选择某个请求时,详细显示该请求的所有信息,包括请求头和请求体。
- 搜索功能:在窗口顶部创建一个文本框和一个搜索按钮,用户可以在文本框中输入关键字,并通过点击搜索按钮来查找相应的请求记录。
- 交互功能:在表格组件上添加鼠标点击事件监听器,当用户点击某个请求记录时,触发事件并显示该请求的详细信息。
- 黑名单管理:提供一个黑名单管理窗口,允许用户添加或移除黑名单中的IP地址。
- 流量监控:提供一个流量监控窗口,显示指定时间范围内的平均流量数据。
2.2 技术采用
- GUI开发:使用Swing库进行图形用户界面(GUI)的开发。Swing是Java的标准库,用于创建窗口、按钮、标签等GUI组件,以及处理用户交互。
- 多线程处理:使用Java的ExecutorService和ThreadPoolExecutor来管理线程池,实现服务器对多个客户端请求的并发处理。
- 请求解析:使用Java的BufferedReader和InputStreamReader类读取并解析HTTP请求内容,提取请求行、请求头和请求体。
- 数据存储和管理:使用Java集合类(如ArrayList和HashMap)来存储和管理请求数据。每个请求的数据都存储在一个Request对象中,方便后续处理和显示。
- 数据可视化:通过Swing组件(如JTable和JTextArea)实现数据的可视化展示。使用JTable展示所有请求记录,并使用JTextArea显示选定请求的详细信息。
- 交互功能:在Swing界面中实现搜索功能,允许用户通过关键字搜索请求记录。使用事件监听器处理用户交互事件,如点击表格行显示请求详情。
- HTTP请求处理:使用Java的Socket和ServerSocket类创建和管理服务器Socket,监听并接受客户端的HTTP请求,处理接收到的请求并返回响应。
- HTTPS支持:通过使用Java的SSLContext和SSLServerSocket类,实现HTTPS请求的处理和转发。
- 发送请求:在RequestLoggerGUI类中,使用Java的HttpURLConnection类来发送HTTP请求,获取并显示响应结果。同时记录请求和响应的数据大小,用于流量监控。
2.3 社会价值
1.提高服务器性能和用户体验
通过开发和使用这个HTTP服务器,可以帮助开发者更好地了解和分析HTTP请求的处理过程,从而提高服务器的性能和用户体验。通过详细记录和分析每个请求的信息,包括请求头、请求体、时间戳和URL等,开发者可以发现并优化性能瓶颈,提升服务器的响应速度和稳定性。
2.推动网络应用发展
通过对HTTP请求数据的分析和可视化展示,可以帮助网络应用开发者了解用户行为和使用模式,从而改进现有应用或开发新功能。例如,分析请求的高峰时间段、常见的请求路径和参数模式,可以为优化服务器配置、改进用户界面和提升用户体验提供有价值的参考。
3.提升安全性和稳定性
通过记录和分析HTTP请求信息,可以帮助系统管理员和安全专家发现潜在的安全威胁和异常请求行为。例如,监控和分析请求头中的用户代理信息,可以识别和防范恶意爬虫和攻击行为,提升服务器的安全性和稳定性。
三、运行环境
3.1 开发环境
- Java:这是主要使用的编程语言,用于实现HTTP服务器的功能,包括请求处理、记录和可视化。
- IntelliJ IDEA:IntelliJ IDEA是一款强大的Java集成开发环境(IDE),提供代码编辑、调试、版本控制等功能,有助于高效地编写、调试和管理Java代码。
- Java SE Development Kit (JDK) 8:IntelliJ IDEA与JDK进行交互,编译和执行编写的Java代码。我们使用JDK 8作为开发环境。
- 项目配置:在IntelliJ IDEA中,设置项目的根目录、依赖库、Java环境等。配置包括设置正确的JDK版本和项目构建工具(如Maven或Gradle)。
- 代码编辑器:IntelliJ IDEA提供了一个强大的代码编辑器,支持语法高亮、代码补全、代码导航等功能,帮助开发者高效地编写和管理代码。
- 调试器:IntelliJ IDEA集成了调试器,有助于在开发过程中调试代码,查找和修复错误。调试器可以逐行运行代码、观察变量值和执行流程,以便更好地理解和调试程序。
- 版本控制:IntelliJ IDEA集成了Git版本控制系统,方便团队协作和代码管理。开发者可以使用Git进行代码提交、分支管理和合并操作,确保代码的稳定性和版本的可追溯性
3.2 技术实现
在该项目中所用类:
Swing | 该项目使用Swing库来创建图形用户界面(GUI)。Swing是Java的标准GUI库,提供了创建窗口、按钮、标签等GUI组件的功能。 |
ExecutorService | 项目中使用ExecutorService类来管理线程池,实现服务器对多个客户端请求的并发处理。ExecutorService提供了高效的线程池管理和任务调度功能。 |
BufferedReader | 项目中使用BufferedReader类来读取HTTP请求内容。BufferedReader是Java的I/O类,提供了读取字符输入流的功能,可以有效地读取请求行和请求头。 |
InputStreamReader | 项目中使用InputStreamReader类将字节流转换为字符流。InputStreamReader是Java的I/O类,可以将字节输入流转换为字符输入流,方便字符数据的读取。 |
Socket | 项目中使用Socket类来创建和管理服务器端的网络连接。Socket是Java的网络编程类,用于实现TCP连接,接受客户端请求。 |
ServerSocket | 项目中使用ServerSocket类来监听客户端连接请求。ServerSocket是Java的网络编程类,用于创建服务器端的套接字,等待客户端连接。 |
ArrayList | 项目中使用ArrayList类来存储请求记录。ArrayList是Java的集合类,提供了动态数组的实现,可以方便地存储和管理请求数据。 |
HashMap | 项目中使用HashMap类来存储请求头信息。HashMap是Java的集合类,提供了基于哈希表的数据结构,可以高效地存储和查找键值对。 |
SSLContext | 项目中使用SSLContext类来实现HTTPS连接,提供了安全的套接字层(SSL)和传输层安全(TLS)协议支持。 |
HttpURLConnection | 项目中使用HttpURLConnection类来发送HTTP请求,并获取响应结果。该类提供了与HTTP服务器进行通信的功能,支持GET、POST等多种请求方法。 |
四、具体实现
4.1 功能架构图
图1整个系统的功能架构图
图2包图
图3类图
4.2 代码结构说明
代码结构如图1所示。
图1 系统总体框架与结构示意图
下面分别介绍各个部分的作用及功能:
1.SimpleHttpServer.java
功能:服务器启动类,负责创建服务器Socket,监听并接受客户端连接请求。
实现方式:使用多线程技术,通过ExecutorService管理线程池,实现对多个客户端请求的并发处理。
详细描述:该类中包含启动HTTP和HTTPS服务器的方法,通过ServerSocket监听指定端口的连接请求。每当有客户端连接时,创建一个新的Socket,并将其交给线程池中的一个线程进行处理,确保服务器能够同时处理多个并发请求。此外,该类还实现了加载黑名单文件的功能,将黑名单中的IP地址读取到内存中,以便在处理请求时进行过滤。具体功能如下:
startHttpServer 和 startHttpsServer 方法分别启动HTTP和HTTPS服务器,并监听相应的端口。
loadBlacklist 方法从文件读取黑名单IP地址,并存储在内存中的集合中。
线程池管理:通过ExecutorService创建一个固定大小的线程池,用于处理客户端请求。
2.ProxyClientHandler.java:
功能:请求处理类,负责解析和处理HTTP请求。
实现方式:实现了GET和POST请求的处理逻辑,并将请求的详细信息记录下来,包括请求头和请求体。
详细描述:该类中包含处理客户端请求的核心逻辑。每个客户端连接由一个ProxyClientHandler实例处理,通过run方法执行请求处理流程。该流程包括:
请求解析:读取并解析HTTP请求,提取请求行和请求头信息。
数据记录:记录请求的详细信息,包括请求方法、URL、时间戳、请求头和请求体,并将这些信息传递给RequestLoggerGUI进行展示。
请求响应:根据请求类型(GET或POST),生成相应的HTTP响应并返回给客户端。
黑名单管理:检查客户端IP是否在黑名单中,如果在则拒绝请求。
流量监控:记录每个请求的处理时间和流量数据,用于后续分析和展示。
静态资源处理:对于GET请求,检查请求路径是否对应服务器上的静态文件,如果是则读取文件内容并返回给客户端。
文件处理:通过BufferedInputStream读取文件内容,并根据文件类型设置相应的Content-Type。
动态请求转发:对于非静态资源请求,将请求转发给目标服务器,并返回响应给客户端。
3.RequestLoggerGUI.java:
功能:前端类,主要实现窗口展示、请求记录显示、请求详情展示、搜索功能、黑名单管理和流量监控等。
实现方式:使用Swing库创建图形用户界面,展示所有记录的HTTP请求,并通过交互功能(如点击和搜索)提供详细的信息查看。
详细描述:该类中包含创建和管理GUI组件的逻辑。主要功能包括:
主窗口:创建主窗口,设置窗口大小和布局。
请求记录表格:创建请求记录表格(JTable),用于展示所有记录的HTTP请求,包括请求路径和时间戳等信息。
详细信息展示区域:创建详细信息展示区域(JTextArea),当用户选择某个请求时,显示该请求的所有详细信息。
搜索功能:实现搜索功能,允许用户通过输入关键字来搜索请求记录。
黑名单管理:实现黑名单管理功能,提供添加和移除黑名单IP地址的界面。
流量监控:实现流量监控功能,通过图形化界面展示指定时间范围内的平均流量数据。
发送请求:提供一个输入框和按钮,允许用户输入URL并发送HTTP请求,显示响应结果。
4.Request.java:
功能:数据类,负责存储HTTP请求的详细信息,包括请求方法、URL、时间戳、请求头和请求体。
实现方式:该类提供相应的构造方法和访问方法,用于获取和设置请求的各项信息。
详细描述:该类用于封装每个HTTP请求的详细信息,提供了一组属性(如protocol、url、timestamp、headers、requestBody)和对应的getter方法。通过创建Request对象,可以将HTTP请求的所有相关信息集中存储,方便在需要时进行访问和操作。Request对象在ProxyClientHandler中创建,并传递给RequestLoggerGUI进行展示。
4.3 核心功能实现
1.SimpleHttpServer.java
package launcher;
import handler.ProxyClientHandler;
import view.RequestLoggerGUI;
import javax.net.ssl.*;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.KeyStore;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.nio.file.*;
public class SimpleHttpServer {
private static final int HTTP_PORT = 8080;
private static final int HTTPS_PORT = 8443;
private static RequestLoggerGUI logger;
public static void main(String[] args) throws Exception {
loadBlacklist();
logger = new RequestLoggerGUI();
ExecutorService threadPool = Executors.newFixedThreadPool(20);
// HTTP server
new Thread(new Runnable() {
@Override
public void run() {
startHttpServer(threadPool);
}
}).start();
// HTTPS server
new Thread(new Runnable() {
@Override
public void run() {
try {
startHttpsServer(threadPool);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
private static void startHttpServer(ExecutorService threadPool) {
try (ServerSocket serverSocket = new ServerSocket(HTTP_PORT)) {
System.out.println("HTTP 服务器启动 端口为 " + HTTP_PORT);
while (true) {
Socket clientSocket = serverSocket.accept();
threadPool.submit(new ProxyClientHandler(clientSocket, logger, "HTTP"));
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void startHttpsServer(ExecutorService threadPool) throws Exception {
KeyStore keyStore = KeyStore.getInstance("JKS");
try (FileInputStream keyStoreStream = new FileInputStream("keystore.jks")) {
keyStore.load(keyStoreStream, "123456".toCharArray());
}
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, "123456".toCharArray());
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), null, null);
// 创建 SSL 服务器
SSLServerSocketFactory ssf = sslContext.getServerSocketFactory();
try (SSLServerSocket serverSocket = (SSLServerSocket) ssf.createServerSocket(HTTPS_PORT)) {
serverSocket.setNeedClientAuth(false);
System.out.println("HTTPS 服务器启动, 端口为 " + HTTPS_PORT);
while (true) {
SSLSocket clientSocket = (SSLSocket) serverSocket.accept();
threadPool.submit(new ProxyClientHandler(clientSocket, logger, "HTTPS"));
}
}
}
private static void loadBlacklist() {
try {
List<String> lines = Files.readAllLines(Paths.get("blacklist.txt"));
for (String line : lines) {
ProxyClientHandler.addIPToBlacklist(line.trim());
}
System.out.println("黑名单已加载.");
} catch (IOException e) {
System.err.println("无法加载黑名单: " + e.getMessage());
}
}
}
2.ProxyClientHandler.java:
package handler;
import view.RequestLoggerGUI;
import javax.swing.*;
import java.io.*;
import java.net.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;
public class ProxyClientHandler implements Runnable {
private static final int MAX_CONCURRENT_REQUESTS = 10; // 最大并发请求数
private static final Semaphore semaphore = new Semaphore(MAX_CONCURRENT_REQUESTS);
private static final Set<String> blacklistedIPs = ConcurrentHashMap.newKeySet();
// 流量数据
public static final List<Long> trafficData = Collections.synchronizedList(new ArrayList<>());
// 请求时间
public static final List<Long> trafficTimestamps = Collections.synchronizedList(new ArrayList<>());
private Socket clientSocket;
private RequestLoggerGUI logger;
private String protocol;
public ProxyClientHandler(Socket socket, RequestLoggerGUI logger, String protocol) {
this.clientSocket = socket;
this.logger = logger;
this.protocol = protocol;
}
@Override
public void run() {
try {
String clientIP = clientSocket.getInetAddress().getHostAddress();
if (blacklistedIPs.contains(clientIP)) {
System.out.println("IP " + clientIP + " 在黑名单列表");
clientSocket.close();
return;
}
semaphore.acquire(); // 获取信号量,限制并发请求数
handleRequest();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 释放信号量
}
}
private void handleRequest() {
long startTime = System.currentTimeMillis();
try (BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
OutputStream out = clientSocket.getOutputStream()) {
String requestLine = in.readLine();
if (requestLine == null) {
return;
}
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
Map<String, String> headers = new HashMap<>();
String headerLine;
StringBuilder requestBody = new StringBuilder();
while (!(headerLine = in.readLine()).isEmpty()) {
String[] headerParts = headerLine.split(": ");
if (headerParts.length == 2) {
headers.put(headerParts[0], headerParts[1]);
}
}
if (headers.containsKey("Content-Length")) {
int contentLength = Integer.parseInt(headers.get("Content-Length"));
char[] body = new char[contentLength];
in.read(body, 0, contentLength);
requestBody.append(body);
}
//logger.logRequest(protocol, requestLine.split(" ")[1], timestamp, headers, requestBody.toString(), "");
String requestPath = requestLine.split(" ")[1];
if (requestLine.startsWith("CONNECT")) {
handleHttpsRequest(requestLine, in, out);
} else if (requestPath.startsWith("/redirect=")) {
String targetUrl = requestPath.substring(10);
for (String blacklistedIP : blacklistedIPs) {
if (targetUrl.contains(blacklistedIP)) {
System.out.println("IP " + blacklistedIP + " 在黑名单列表");
clientSocket.close();
return;
}
}
forwardRequest(targetUrl, requestLine.split(" ")[0], headers, requestBody.toString(), out, timestamp);
} else if (requestLine.startsWith("GET") || requestLine.startsWith("HEAD")) {
handleStaticFileRequest(requestLine, out, headers, timestamp);
} else {
forwardRequest(requestLine.split(" ")[1], requestLine.split(" ")[0], headers, requestBody.toString(), out, timestamp);
}
long endTime = System.currentTimeMillis();
synchronized (trafficData) {
trafficData.add(endTime - startTime);
trafficTimestamps.add(endTime);
}
} catch (SocketException e) {
System.err.println("连接被客户端中断: " + e.getMessage());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void handleHttpsRequest(String requestLine, BufferedReader in, OutputStream out) throws IOException {
String[] parts = requestLine.split(" ");
String hostPort = parts[1];
String[] hostPortSplit = hostPort.split(":");
String host = hostPortSplit[0];
int port = Integer.parseInt(hostPortSplit[1]);
try (Socket forwardSocket = new Socket(host, port)) {
out.write(("HTTP/1.1 200 Connection Established\r\n").getBytes());
out.write(("Proxy-Agent: JavaProxy/1.0\r\n").getBytes());
out.write(("\r\n").getBytes());
out.flush();
Thread clientToServer = new Thread(() -> {
try {
forwardData(clientSocket.getInputStream(), forwardSocket.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
});
Thread serverToClient = new Thread(() -> {
try {
forwardData(forwardSocket.getInputStream(), clientSocket.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
});
clientToServer.start();
serverToClient.start();
try {
clientToServer.join();
serverToClient.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void forwardData(InputStream inputStream, OutputStream outputStream) {
try (BufferedInputStream in = new BufferedInputStream(inputStream);
BufferedOutputStream out = new BufferedOutputStream(outputStream)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
out.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void handleStaticFileRequest(String requestLine, OutputStream out, Map<String, String> headers, String timestamp) throws IOException {
String[] requestParts = requestLine.split(" ");
String filePath = "." + requestParts[1];
File file = new File(filePath);
StringBuilder responseBody = new StringBuilder();
if (file.exists() && !file.isDirectory()) {
String contentType = getContentType(filePath);
try (BufferedInputStream fileInput = new BufferedInputStream(new FileInputStream(file))) {
out.write(("HTTP/1.1 200 OK\r\n").getBytes());
out.write(("Content-Type: " + contentType + "\r\n").getBytes());
out.write(("Content-Length: " + file.length() + "\r\n").getBytes());
out.write(("\r\n").getBytes());
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = fileInput.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
responseBody.append(new String(buffer, 0, bytesRead));
}
out.flush();
}
logger.logRequest(protocol, filePath, timestamp, headers, "", responseBody.toString());
} else {
responseBody.append("404 Not Found");
out.write(("HTTP/1.1 404 Not Found\r\n").getBytes());
out.write(("\r\n").getBytes());
out.flush();
logger.logRequest(protocol, filePath, timestamp, headers, "", responseBody.toString());
}
}
private String getContentType(String filePath) {
if (filePath.endsWith(".html")) {
return "text/html";
} else if (filePath.endsWith(".css")) {
return "text/css";
} else if (filePath.endsWith(".js")) {
return "application/javascript";
} else if (filePath.endsWith(".png")) {
return "image/png";
} else if (filePath.endsWith(".jpg") || filePath.endsWith(".jpeg")) {
return "image/jpeg";
} else if (filePath.endsWith(".gif")) {
return "image/gif";
} else {
return "application/octet-stream";
}
}
private void forwardRequest(String targetUrl, String method, Map<String, String> headers, String requestBody, OutputStream out, String timestamp) {
HttpURLConnection connection = null;
try {
URL url = new URL(targetUrl);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod(method);
for (Map.Entry<String, String> header : headers.entrySet()) {
connection.setRequestProperty(header.getKey(), header.getValue());
}
if (!requestBody.isEmpty() && (method.equals("POST") || method.equals("PUT") || method.equals("PATCH"))) {
connection.setDoOutput(true);
try (OutputStream os = connection.getOutputStream()) {
os.write(requestBody.getBytes());
}
}
int responseCode = connection.getResponseCode();
out.write(("HTTP/1.1 " + responseCode + " " + connection.getResponseMessage() + "\r\n").getBytes());
for (Map.Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
if (header.getKey() != null) {
out.write((header.getKey() + ": " + String.join(", ", header.getValue()) + "\r\n").getBytes());
}
}
out.write(("\r\n").getBytes());
InputStream responseStream;
if (responseCode >= 200 && responseCode < 400) {
responseStream = connection.getInputStream();
} else {
responseStream = connection.getErrorStream();
}
if (responseStream != null) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = responseStream.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}
out.flush();
// 记录转发的请求和响应内容
Map<String, String> headerMap = new HashMap<>();
for (Map.Entry<String, List<String>> entry : connection.getHeaderFields().entrySet()) {
headerMap.put(entry.getKey(), entry.getValue().get(0));
}
//logger.logRequest(protocol, targetUrl, timestamp, headers, requestBody.toString(), responseBody.toString());
try {
url = new URL(targetUrl);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
int requestSize = targetUrl.length();
for (Map.Entry<String, List<String>> header : connection.getRequestProperties().entrySet()) {
requestSize += header.getKey().length() + String.join(", ", header.getValue()).length();
}
long startTime = System.currentTimeMillis();
responseCode = connection.getResponseCode();
Map<String, List<String>> header = connection.getHeaderFields();
StringBuilder responseBody = new StringBuilder();
int responseSize = 0;
try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
String line;
while ((line = in.readLine()) != null) {
responseBody.append(line);
responseSize += line.length();
}
}
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
// 根据请求判断 HTTPS or HTTP
if (targetUrl.startsWith("https")) {
logger.logRequest("HTTPS", "/redirect="+targetUrl, timestamp, headersToMap(header), "", responseBody.toString());
} else {
logger.logRequest("HTTP", "/redirect="+targetUrl, timestamp, headersToMap(header), "", responseBody.toString());
}
synchronized (ProxyClientHandler.trafficData) {
ProxyClientHandler.trafficData.add((long) (requestSize + responseSize));
ProxyClientHandler.trafficTimestamps.add(endTime);
}
} catch (Exception e) {
e.printStackTrace();
}
} catch (SocketException e) {
System.err.println("连接被客户端中断: " + e.getMessage());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
private Map<String, String> headersToMap(Map<String, List<String>> headers) {
Map<String, String> map = new HashMap<>();
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
map.put(entry.getKey(), String.join(", ", entry.getValue()));
}
return map;
}
public static void addIPToBlacklist(String ip) {
blacklistedIPs.add(ip);
}
public static void removeIPFromBlacklist(String ip) {
blacklistedIPs.remove(ip);
}
public static Set<String> getBlacklistedIPs() {
return blacklistedIPs;
}
public static List<Long> getTrafficData() {
return new ArrayList<>(trafficData);
}
public static List<Long> getTrafficTimestamps() {
return new ArrayList<>(trafficTimestamps);
}
}
3.RequestLoggerGUI.java:
package view;
import model.Request;
import handler.ProxyClientHandler;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.List;
import java.util.Timer;
public class RequestLoggerGUI {
private JFrame frame;
private JTable table;
private DefaultTableModel tableModel;
private JTextField searchField;
private JTextField urlField;
private JTextArea detailsArea;
private List<Request> requests;
public RequestLoggerGUI() {
requests = new ArrayList<>();
frame = new JFrame("HTTP 代理服务器");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(1000, 600);
frame.setLocationRelativeTo(null);
searchField = new JTextField();
searchField.setPreferredSize(new Dimension(200, 30));
JButton searchButton = new JButton("搜索");
searchButton.addActionListener(e -> searchRequests());
urlField = new JTextField();
urlField.setPreferredSize(new Dimension(200, 30));
JButton sendButton = new JButton("发送请求");
sendButton.addActionListener(e -> sendHttpRequest());
JButton blacklistButton = new JButton("管理黑名单");
blacklistButton.addActionListener(e -> manageBlacklist());
JButton trafficButton = new JButton("流量数据");
trafficButton.addActionListener(e -> monitorTraffic());
JPanel searchPanel = new JPanel();
searchPanel.add(searchField);
searchPanel.add(searchButton);
searchPanel.add(urlField);
searchPanel.add(sendButton);
searchPanel.add(blacklistButton);
searchPanel.add(trafficButton);
tableModel = new DefaultTableModel(new Object[]{"请求协议", "请求地址", "时间"}, 0);
table = new JTable(tableModel);
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
int selectedRow = table.getSelectedRow();
if (selectedRow >= 0) {
displayRequestDetails(selectedRow);
}
}
});
TableColumn column;
column = table.getColumnModel().getColumn(0);
column.setPreferredWidth(100); // Protocol column
column = table.getColumnModel().getColumn(1);
column.setPreferredWidth(500); // URL column
column = table.getColumnModel().getColumn(2);
column.setPreferredWidth(200); // Timestamp column
JScrollPane tableScrollPane = new JScrollPane(table);
detailsArea = new JTextArea();
detailsArea.setEditable(false);
JScrollPane detailsScrollPane = new JScrollPane(detailsArea);
detailsScrollPane.setPreferredSize(new Dimension(800, 200));
frame.setLayout(new BorderLayout());
frame.add(searchPanel, BorderLayout.NORTH);
frame.add(tableScrollPane, BorderLayout.CENTER);
frame.add(detailsScrollPane, BorderLayout.SOUTH);
frame.setVisible(true);
}
public void logRequest(String protocol, String url, String timestamp, Map<String, String> headers, String requestBody, String responseBody) {
requests.add(new Request(protocol, url, timestamp, headers, requestBody, responseBody));
tableModel.addRow(new Object[]{protocol, url, timestamp});
}
private void searchRequests() {
String keyword = searchField.getText().toLowerCase();
tableModel.setRowCount(0);
for (Request request : requests) {
if (request.getUrl().toLowerCase().contains(keyword)) {
tableModel.addRow(new Object[]{request.getProtocol(), request.getUrl(), request.getTimestamp()});
}
}
}
private void displayRequestDetails(int index) {
Request request = requests.get(index);
StringBuilder details = new StringBuilder();
details.append("Protocol: ").append(request.getProtocol()).append("\n")
.append("URL: ").append(request.getUrl()).append("\n")
.append("Timestamp: ").append(request.getTimestamp()).append("\n")
.append("Headers: ").append("\n");
for (Map.Entry<String, String> entry : request.getHeaders().entrySet()) {
details.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");
}
details.append("Request Body: ").append(request.getRequestBody()).append("\n")
.append("Response Body: ").append(request.getResponseBody()).append("\n");
detailsArea.setText(details.toString());
}
private void sendHttpRequest() {
String urlStr = urlField.getText();
if (urlStr.isEmpty()) {
JOptionPane.showMessageDialog(frame, "URL不能为空", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
try {
URL url = new URL(urlStr);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
int requestSize = urlStr.length();
for (Map.Entry<String, List<String>> header : connection.getRequestProperties().entrySet()) {
requestSize += header.getKey().length() + String.join(", ", header.getValue()).length();
}
long startTime = System.currentTimeMillis();
int responseCode = connection.getResponseCode();
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
Map<String, List<String>> headers = connection.getHeaderFields();
StringBuilder responseBody = new StringBuilder();
int responseSize = 0;
try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
String line;
while ((line = in.readLine()) != null) {
responseBody.append(line);
responseSize += line.length();
}
}
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
// 根据请求判断 HTTPS or HTTP
if (urlStr.startsWith("https")) {
logRequest("HTTPS", urlStr, timestamp, headersToMap(headers), "", responseBody.toString());
} else {
logRequest("HTTP", urlStr, timestamp, headersToMap(headers), "", responseBody.toString());
}
synchronized (ProxyClientHandler.trafficData) {
ProxyClientHandler.trafficData.add((long) (requestSize + responseSize));
ProxyClientHandler.trafficTimestamps.add(endTime);
}
} catch (Exception e) {
e.printStackTrace();
JOptionPane.showMessageDialog(frame, "发送请求失败", "错误", JOptionPane.ERROR_MESSAGE);
}
}
private Map<String, String> headersToMap(Map<String, List<String>> headers) {
Map<String, String> map = new HashMap<>();
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
map.put(entry.getKey(), String.join(", ", entry.getValue()));
}
return map;
}
private void manageBlacklist() {
JFrame blacklistFrame = new JFrame("黑名单管理");
blacklistFrame.setSize(400, 300);
blacklistFrame.setLocationRelativeTo(frame);
DefaultListModel<String> listModel = new DefaultListModel<>();
JList<String> list = new JList<>(listModel);
Set<String> blacklistedIPs = ProxyClientHandler.getBlacklistedIPs();
for (String ip : blacklistedIPs) {
listModel.addElement(ip);
}
JTextField ipField = new JTextField();
ipField.setPreferredSize(new Dimension(200, 30));
JButton addButton = new JButton("添加");
addButton.addActionListener(e -> {
String ip = ipField.getText();
if (!ip.isEmpty() && !blacklistedIPs.contains(ip)) {
ProxyClientHandler.addIPToBlacklist(ip);
listModel.addElement(ip);
saveBlacklistToFile();
}
});
JButton removeButton = new JButton("删除");
removeButton.addActionListener(e -> {
String selectedIP = list.getSelectedValue();
if (selectedIP != null) {
ProxyClientHandler.removeIPFromBlacklist(selectedIP);
listModel.removeElement(selectedIP);
saveBlacklistToFile();
}
});
JPanel controlPanel = new JPanel();
controlPanel.add(ipField);
controlPanel.add(addButton);
controlPanel.add(removeButton);
blacklistFrame.setLayout(new BorderLayout());
blacklistFrame.add(new JScrollPane(list), BorderLayout.CENTER);
blacklistFrame.add(controlPanel, BorderLayout.SOUTH);
blacklistFrame.setVisible(true);
}
private void saveBlacklistToFile() {
try (PrintWriter writer = new PrintWriter(new FileWriter("blacklist.txt"))) {
for (String ip : ProxyClientHandler.getBlacklistedIPs()) {
writer.println(ip);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void monitorTraffic() {
JFrame trafficFrame = new JFrame("流量监控");
trafficFrame.setSize(400, 200);
trafficFrame.setLocationRelativeTo(frame);
JPanel controlPanel = new JPanel();
String[] timeOptions = {"1 分钟", "5 分钟", "10 分钟"};
JComboBox<String> timeSelector = new JComboBox<>(timeOptions);
controlPanel.add(new JLabel("选择时间范围 "));
controlPanel.add(timeSelector);
JLabel trafficLabel = new JLabel("平均流量: ");
controlPanel.add(trafficLabel);
timeSelector.addActionListener(e -> updateTrafficLabel(trafficLabel, (String) timeSelector.getSelectedItem()));
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
updateTrafficLabel(trafficLabel, (String) timeSelector.getSelectedItem());
}
}, 0, 1000);
trafficFrame.setLayout(new BorderLayout());
trafficFrame.add(controlPanel, BorderLayout.CENTER);
trafficFrame.setVisible(true);
}
private void updateTrafficLabel(JLabel trafficLabel, String timeRange) {
List<Long> trafficData = ProxyClientHandler.getTrafficData();
List<Long> timestamps = ProxyClientHandler.getTrafficTimestamps();
long currentTime = System.currentTimeMillis();
long timeRangeMillis;
switch (timeRange) {
case "1 分钟":
timeRangeMillis = 60 * 1000;
break;
case "5 分钟":
timeRangeMillis = 5 * 60 * 1000;
break;
case "10 分钟":
timeRangeMillis = 10 * 60 * 1000;
break;
default:
timeRangeMillis = 60 * 1000;
break;
}
List<Long> filteredData = new ArrayList<>();
for (int i = 0; i < trafficData.size(); i++) {
long timestamp = timestamps.get(i);
if (currentTime - timestamp <= timeRangeMillis) {
filteredData.add(trafficData.get(i));
}
}
long totalTraffic = 0;
for (Long data : filteredData) {
totalTraffic += data;
}
long averageTraffic = filteredData.isEmpty() ? 0 : totalTraffic * 1000 / timeRangeMillis;
trafficLabel.setText("平均流量为: " + averageTraffic + " bytes/s");
}
}
4.Request.java:
package model;
import java.util.Map;
public class Request {
private String protocol;
private String url;
private String timestamp;
private Map<String, String> headers;
private String requestBody;
private String responseBody;
public Request(String protocol, String url, String timestamp, Map<String, String> headers, String requestBody, String responseBody) {
this.protocol = protocol;
this.url = url;
this.timestamp = timestamp;
this.headers = headers;
this.requestBody = requestBody;
this.responseBody = responseBody;
}
public String getProtocol() {
return protocol;
}
public String getUrl() {
return url;
}
public String getTimestamp() {
return timestamp;
}
public Map<String, String> getHeaders() {
return headers;
}
public String getRequestBody() {
return requestBody;
}
public String getResponseBody() {
return responseBody;
}
}
五、项目演示
图2 服务器可视化图
图3 请求记录图
图4 请求代理
图5 请求详情图
图6 黑名单配置图
图7黑名单警告拦截图
图8 流量统计图
https://localhost:8443/redirect=https://pic.rmb.bdstatic.com/bjh/gallery/03e17cba710868d9153176b50a5fca0d1090.jpeg
图9安全性警告图
六、心得体会
在这个项目中,我实现了一个简易的多线程HTTP服务器,并对其进行了详细的记录和可视化展示。通过开发这个项目,我深入了解了HTTP协议的工作原理和网络编程的基础。我学会了如何使用Java的Socket类进行网络通信,并通过多线程技术实现服务器对多个客户端请求的并发处理。这使我能够高效地处理和记录HTTP请求,为后续的分析和优化提供了基础。
通过开发这个项目,我学会了如何使用Swing库创建图形用户界面,并在界面中展示HTTP请求的详细信息。我实现了请求记录的展示、详细信息的查看、搜索功能等。这让我认识到用户界面的设计和实现是一个既有挑战性又有趣的过程,需要不断优化和改进,以提升用户体验。
在项目的过程中,我遇到了一些问题,例如如何高效地解析HTTP请求头和请求体,如何在多线程环境下管理共享数据等。通过查阅文档、搜索和尝试不同的解决方案,我逐渐克服了这些问题,并成功地完成了项目。这锻炼了我的实践和问题解决能力,让我在解决实际问题时更加自信和独立。
在尝试实现HTTPS代理时,我遇到了更大的挑战。由于HTTPS使用SSL/TLS协议对数据进行加密,代理服务器无法直接读取或修改HTTPS请求和响应内容。尽管我成功实现了通过CONNECT方法建立隧道,并能处理静态内容的代理,但在处理动态内容时遇到了困难。由于HTTPS的加密特性,动态内容的代理需要更多的处理和优化,这涉及到复杂的SSL/TLS协议细节和数据转发机制。
尽管在代理动态内容方面遇到了失败,但通过这个过程,我深入理解了HTTPS代理的复杂性和难点。我意识到,处理加密流量需要更多的安全和性能考虑,必须确保数据在传输过程中不被篡改或泄露。
通过这个项目,我不仅学到了许多有关网络编程和GUI开发的技术知识,还深入了解了HTTP和HTTPS请求的处理和记录方式。我相信这个项目为我今后在网络通信和用户界面设计领域的学习和发展奠定了坚实的基础。我期待将来能够在更复杂和真实的网络应用项目中应用所学,为业务决策和问题解决提供更有洞察力的数据支持。虽然HTTPS代理的实现尚未完美,但我会继续研究和改进,以期在未来的项目中取得更好的成果。
这个项目不仅提升了我的技术能力,还让我深刻体会到解决实际问题的过程和挑战。通过不断的学习和实践,我相信自己能够在网络编程和用户界面设计方面取得更大的进步,并为未来的工作和学习打下坚实的基础。