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;
    }
}

小结

通过本文学习,你应该掌握:

  1. Tomcat HTTP/HTTPS代理的基础配置
  2. 透明代理服务器的实现方法
  3. 代理安全控制和访问限制
  4. 代理缓存机制和优化技术
  5. 代理监控和统计功能
  6. 代理性能优化和连接池管理
  7. 代理配置管理脚本

下一篇文章将介绍Tomcat反向代理集成技术。

powered by Gitbook© 2025 编外计划 | 最后修改: 2025-08-29 15:40:15

results matching ""

    No results matching ""