Tomcat 代理配置
Proxy Configuration
概述
代理配置是Tomcat在企业环境中的重要应用场景,包括正向代理、透明代理和代理服务器集成。本文详细介绍Tomcat代理配置的各种方法、安全设置和性能优化技巧。
1. HTTP代理基础配置
1.1 正向代理配置
<!-- server.xml - HTTP代理连接器 -->
<Connector port="8080"
protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
<!-- 代理相关配置 -->
proxyName="proxy.example.com"
proxyPort="80"
scheme="http"
secure="false" />
1.2 HTTPS代理配置
<!-- HTTPS代理连接器配置 -->
<Connector port="8443"
protocol="org.apache.coyote.http11.Http11NioProtocol"
SSLEnabled="true"
scheme="https"
secure="true"
<!-- HTTPS代理设置 -->
proxyName="secure.example.com"
proxyPort="443"
<!-- SSL配置 -->
keystoreFile="conf/keystore.jks"
keystorePass="changeit" />
2. 透明代理实现
2.1 代理Servlet
// ProxyServlet.java
package com.example.proxy;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import java.io.*;
import java.net.*;
import java.util.Enumeration;
public class ProxyServlet extends HttpServlet {
private static final int BUFFER_SIZE = 8192;
private static final int CONNECT_TIMEOUT = 5000;
private static final int READ_TIMEOUT = 30000;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
handleProxyRequest(request, response, "GET");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
handleProxyRequest(request, response, "POST");
}
private void handleProxyRequest(HttpServletRequest request, HttpServletResponse response,
String method) throws ServletException, IOException {
String targetUrl = getTargetUrl(request);
if (targetUrl == null) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid target URL");
return;
}
try {
URL url = new URL(targetUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 配置连接
connection.setRequestMethod(method);
connection.setConnectTimeout(CONNECT_TIMEOUT);
connection.setReadTimeout(READ_TIMEOUT);
connection.setDoInput(true);
connection.setDoOutput("POST".equals(method) || "PUT".equals(method));
// 复制请求头
copyRequestHeaders(request, connection);
// 复制请求体
if ("POST".equals(method) || "PUT".equals(method)) {
copyRequestBody(request, connection);
}
// 获取响应
int responseCode = connection.getResponseCode();
response.setStatus(responseCode);
// 复制响应头和响应体
copyResponseHeaders(connection, response);
copyResponseBody(connection, response);
} catch (Exception e) {
log("Proxy error for URL: " + targetUrl, e);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Proxy error: " + e.getMessage());
}
}
private String getTargetUrl(HttpServletRequest request) {
String pathInfo = request.getPathInfo();
String queryString = request.getQueryString();
if (pathInfo == null || pathInfo.length() <= 1) {
return null;
}
String targetUrl = pathInfo.substring(1);
if (!targetUrl.startsWith("http://") && !targetUrl.startsWith("https://")) {
targetUrl = "http://" + targetUrl;
}
if (queryString != null) {
targetUrl += "?" + queryString;
}
return targetUrl;
}
// 辅助方法实现...
}
2.2 代理配置
<!-- web.xml - 代理Servlet配置 -->
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="3.1">
<servlet>
<servlet-name>ProxyServlet</servlet-name>
<servlet-class>com.example.proxy.ProxyServlet</servlet-class>
<init-param>
<param-name>allowedHosts</param-name>
<param-value>*.example.com,*.trusted.org</param-value>
</init-param>
<init-param>
<param-name>maxRequestSize</param-name>
<param-value>10485760</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>ProxyServlet</servlet-name>
<url-pattern>/proxy/*</url-pattern>
</servlet-mapping>
</web-app>
3. 代理安全配置
3.1 安全过滤器
// ProxySecurityFilter.java
package com.example.proxy;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;
public class ProxySecurityFilter implements Filter {
private Set<String> allowedHosts = new HashSet<>();
private Set<String> blockedHosts = new HashSet<>();
private long maxRequestSize = 10 * 1024 * 1024;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
String allowed = filterConfig.getInitParameter("allowedHosts");
if (allowed != null) {
for (String host : allowed.split(",")) {
allowedHosts.add(host.trim());
}
}
String blocked = filterConfig.getInitParameter("blockedHosts");
if (blocked != null) {
for (String host : blocked.split(",")) {
blockedHosts.add(host.trim());
}
}
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 检查请求大小
if (httpRequest.getContentLengthLong() > maxRequestSize) {
httpResponse.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE);
return;
}
// 检查目标URL
String pathInfo = httpRequest.getPathInfo();
if (pathInfo != null && !isAllowedUrl(pathInfo.substring(1))) {
httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
chain.doFilter(request, response);
}
private boolean isAllowedUrl(String url) {
try {
if (!url.startsWith("http://") && !url.startsWith("https://")) {
url = "http://" + url;
}
URL targetUrl = new URL(url);
String host = targetUrl.getHost();
if (blockedHosts.contains(host)) {
return false;
}
if (!allowedHosts.isEmpty() && !allowedHosts.contains(host)) {
return false;
}
return true;
} catch (Exception e) {
return false;
}
}
}
4. 代理缓存配置
4.1 缓存过滤器
// ProxyCacheFilter.java
package com.example.proxy;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.concurrent.ConcurrentHashMap;
public class ProxyCacheFilter implements Filter {
private static final ConcurrentHashMap<String, CacheEntry> cache = new ConcurrentHashMap<>();
private long defaultTtl = 300; // 5分钟
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String cacheKey = generateCacheKey(httpRequest);
// 检查缓存
CacheEntry entry = cache.get(cacheKey);
if (entry != null && !entry.isExpired()) {
serveCachedResponse(entry, httpResponse);
return;
}
// 处理请求并缓存响应
CacheResponseWrapper wrapper = new CacheResponseWrapper(httpResponse);
chain.doFilter(request, wrapper);
if (wrapper.getStatus() == 200 && isCacheable(httpRequest)) {
storeCachedResponse(cacheKey, wrapper);
}
}
private String generateCacheKey(HttpServletRequest request) {
return request.getMethod() + ":" + request.getRequestURL() +
(request.getQueryString() != null ? "?" + request.getQueryString() : "");
}
private boolean isCacheable(HttpServletRequest request) {
return "GET".equals(request.getMethod());
}
// 缓存相关类和方法实现...
}
5. 代理监控与统计
5.1 统计Servlet
// ProxyStatsServlet.java
package com.example.proxy;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.concurrent.atomic.AtomicLong;
public class ProxyStatsServlet extends HttpServlet {
private static final AtomicLong totalRequests = new AtomicLong(0);
private static final AtomicLong successfulRequests = new AtomicLong(0);
private static final AtomicLong failedRequests = new AtomicLong(0);
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("application/json");
PrintWriter out = response.getWriter();
out.println("{");
out.println(" \"total_requests\": " + totalRequests.get() + ",");
out.println(" \"successful_requests\": " + successfulRequests.get() + ",");
out.println(" \"failed_requests\": " + failedRequests.get());
out.println("}");
}
public static void recordRequest() { totalRequests.incrementAndGet(); }
public static void recordSuccess() { successfulRequests.incrementAndGet(); }
public static void recordFailure() { failedRequests.incrementAndGet(); }
}
6. 代理管理脚本
6.1 代理配置脚本
#!/bin/bash
# proxy-manager.sh
TOMCAT_HOME="/opt/tomcat9"
PROXY_CONFIG_DIR="$TOMCAT_HOME/conf/proxy"
create_proxy_config() {
local proxy_name=$1
local target_host=$2
local target_port=$3
mkdir -p "$PROXY_CONFIG_DIR"
cat > "$PROXY_CONFIG_DIR/${proxy_name}.properties" << EOF
target.host=$target_host
target.port=$target_port
proxy.timeout=30000
proxy.max.connections=100
proxy.cache.enabled=true
proxy.security.enabled=true
EOF
echo "代理配置已创建: $proxy_name"
}
enable_proxy() {
local proxy_name=$1
# 创建代理应用目录
mkdir -p "$TOMCAT_HOME/webapps/$proxy_name"
# 部署代理应用
echo "代理已启用: $proxy_name"
}
disable_proxy() {
local proxy_name=$1
rm -rf "$TOMCAT_HOME/webapps/$proxy_name"
echo "代理已禁用: $proxy_name"
}
show_proxy_stats() {
echo "=== 代理统计信息 ==="
curl -s "http://localhost:8080/proxy-stats" | \
python -m json.tool 2>/dev/null || echo "无法获取统计信息"
}
case "$1" in
"create") create_proxy_config "$2" "$3" "$4" ;;
"enable") enable_proxy "$2" ;;
"disable") disable_proxy "$2" ;;
"stats") show_proxy_stats ;;
*)
echo "用法: $0 {create|enable|disable|stats}"
echo " create <name> <host> <port> - 创建代理配置"
echo " enable <name> - 启用代理"
echo " disable <name> - 禁用代理"
echo " stats - 显示统计信息"
;;
esac
7. 性能优化配置
7.1 连接池配置
// ProxyConnectionManager.java
package com.example.proxy;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
public class ProxyConnectionManager {
private static final ConcurrentHashMap<String, ConcurrentLinkedQueue<HttpURLConnection>>
connectionPools = new ConcurrentHashMap<>();
private static final int MAX_CONNECTIONS_PER_HOST = 20;
public static HttpURLConnection getConnection(URL url) throws Exception {
String host = url.getHost();
ConcurrentLinkedQueue<HttpURLConnection> pool =
connectionPools.computeIfAbsent(host, k -> new ConcurrentLinkedQueue<>());
HttpURLConnection connection = pool.poll();
if (connection == null) {
connection = createNewConnection(url);
}
return connection;
}
public static void returnConnection(HttpURLConnection connection) {
if (connection != null) {
String host = connection.getURL().getHost();
ConcurrentLinkedQueue<HttpURLConnection> pool = connectionPools.get(host);
if (pool != null && pool.size() < MAX_CONNECTIONS_PER_HOST) {
pool.offer(connection);
} else {
connection.disconnect();
}
}
}
private static HttpURLConnection createNewConnection(URL url) throws Exception {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(5000);
connection.setReadTimeout(30000);
connection.setRequestProperty("Connection", "keep-alive");
return connection;
}
}
小结
通过本文学习,你应该掌握:
- Tomcat HTTP/HTTPS代理的基础配置
- 透明代理服务器的实现方法
- 代理安全控制和访问限制
- 代理缓存机制和优化技术
- 代理监控和统计功能
- 代理性能优化和连接池管理
- 代理配置管理脚本
下一篇文章将介绍Tomcat反向代理集成技术。