文章目录

  • 前言
  • 一、nginx版本和安装
  • 二、nginx配置说明
  • 三、java代码示例
  • 1、引入依赖
  • 2、代码演示
  • 四、报错说明



更新记录
2024-01-11
在“章节一”增加nginx需要编译proxy_connect模块的提示和参考博客。
2024-01-11
在“章节四”增加502 Bad Gateway的情况处理。

前言

        前段时间,在搞nginx正向代理,实现一个端口代理http和https的能力,因为之前都是用反向代理的模式配置第三方地址,这样导致nginx会启动很多端口,然后还要开通这么多端口的防火墙策略,所以为了一切从简,决定捣鼓一番。
        查了很多博客,也感谢各位开发者的共享,不过在查找资料中,也觉得现有的教程或示例多少存在不足之处,或是只能代理标准端口80和443,或是能代理所有端口,但是没有演示代码怎么写,所以博主在整合百家之长后写下一点心得和示例,方便有需要的人白嫖。
        最好能给博主点个免费的赞支持一下,如果有更好的想法或其他疑问,也可以在评论里说明,博主会和你一起探讨。


一、nginx版本和安装

        nginx的安装本文不做说明,C站有很多详细的安装教程博客。
        nginx版本和安装的模块,在使用正向代理功能时,nginx需要编译ngx_http_proxy_connect_module模块:

./nginx -V

nginx version: nginx/1.14.2
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-44) (GCC) 
built with OpenSSL 1.1.1q  5 Jul 2022
TLS SNI support enabled
configure arguments: --prefix=/home/test/app/nginx --with-openssl=/home/test/nginx/openssl-1.1.1q --with-pcre=/home/test/nginx/pcre-8.45 --with-zlib=/home/test/nginx/zlib-1.2.12 --with-http_stub_status_module --with-http_ssl_module --with-stream


二、nginx配置说明

        重要参数均以注释形式给出,各位也可以与其他博客对比下配置差异,也可以深究各个配置的差异影响。

# http模块
http {
    # mime类型,根据自己实际需求配置,不是本文重点
    include       mime.types;
    # 默认类型
    default_type  application/octet-stream;
    # log格式,根据自己实际需求配置,不是本文重点
    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;
    # 开启sendfile,根据自己实际需求配置,不是本文重点
    sendfile        on;

    # 超时时间,根据自己实际需求配置,不是本文重点
    keepalive_timeout  65;
    # 开启gzip压缩,根据自己实际需求配置,不是本文重点
    #gzip  on;

    server {
        # 监听端口
        listen       10086;
        # DNS解析 有效时间300秒 支持IPv6协议
        resolver 114.114.114.114 valid=300s ipv6=on;
        # 开启代理
        proxy_connect;
        # 代理白名单端口,允许所有端口
        proxy_connect_allow all;

        # 核心配置
        location / {
    		# scheme http和https 
    		# http_host 目标地址和端口
    		# request_uri 接口请求地址
            proxy_pass $scheme://$http_host$request_uri;
            # 使用http 1.1
            proxy_http_version 1.1;
            # 开启携带域名,防止目标存在SNI情况
            proxy_ssl_server_name on;
            # 请求头携带目标地址
            proxy_set_header Host $http_host;
        }
    }
}

三、java代码示例

        前言中博主也有提到,少有将配置和代码结合的博文,也可能其他开发者使用的非java开发语言,本文只演示java如何使用上述配置发送post请求。

1、引入依赖

<!-- httpclient 会自动引入 httpcore commons-codec commons-logging -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <!-- 高版本测试通过,低版本未测试,请自行测试 -->
            <version>4.5.3</version>
        </dependency>

2、代码演示

package org.example;

import org.apache.http.HttpHost;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;

/**
 * @author 系统异常
 * @version 1.0
 * @date 2024/1/2 10:17
 * @description 代理测试类
 */
public class HttpProxyTest {
    public static void main(String[] args) {
        // https标准443端口,根据自身情况修改
        String url = "https://www.baidu.com";
        // http标准80端口,根据自身情况修改
        url = "http://www.baidu.com";
        // https非标准443端口,根据自身情况修改
        url = "https://www.baidu.com:8080";
        // http非标准80端口,根据自身情况修改
        url = "http://www.baidu.com:8080";

        // 测试报文
        String request = "{\"test\":\"123456\"}";
        try {
            // 信任所有证书,如果各位调用的地址都是标准的,则不需要此构造方法,直接使用 HttpClients.createDefault() 即可
            SSLConnectionSocketFactory scsf = new SSLConnectionSocketFactory(SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build(), NoopHostnameVerifier.INSTANCE);
            CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(scsf).build();

            // 请求配置
            int connTimeOut = 10000;
            int readTimeOut = 10000;
            String proxyIp = "192.168.109.137";
            int proxyPort = 10086;
            RequestConfig.Builder customReqConf = getBuilder(connTimeOut, readTimeOut, proxyIp, proxyPort);

            // 构建HttpPost请求
            HttpPost httpPost = new HttpPost(url);
            StringEntity stringEntity = new StringEntity(request);
            httpPost.setEntity(stringEntity);
            httpPost.setConfig(customReqConf.build());

            // 执行请求
            CloseableHttpResponse response = httpClient.execute(httpPost);
            if (HttpStatus.SC_OK == response.getStatusLine().getStatusCode()) {
                System.out.println("请求成功");
            } else {
                System.out.println("请求失败");
            }

            // 获取响应报文
            String result = EntityUtils.toString(response.getEntity());
            System.out.println("响应 " + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取自定义的RequestConfig.Builder对象
     *
     * @param connTimeOut 连接超时时间,单位为毫秒
     * @param readTimeOut 读取超时时间,单位为毫秒
     * @param proxyIp     代理服务器的IP地址
     * @param proxyPort   代理服务器的端口号
     * @return 自定义的RequestConfig.Builder对象
     */
    private static RequestConfig.Builder getBuilder(int connTimeOut, int readTimeOut, String proxyIp, int proxyPort) {
        RequestConfig.Builder customReqConf = RequestConfig.custom();
        // 连接超时
        customReqConf.setConnectTimeout(connTimeOut);
        // 读取超时
        customReqConf.setSocketTimeout(readTimeOut);

        // 代理
        if (proxyIp != null && proxyPort > 0) {
            HttpHost proxy = new HttpHost(proxyIp, proxyPort, "http");
            customReqConf.setProxy(proxy);
        }
        return customReqConf;
    }
}

        各位还可以测试下低版本的依赖,以及其他端口或形式的url,如果不兼容的情况,欢迎在评论区指出。


四、报错说明

        在捣鼓过程中,出现过几种报错也列举在此,方便各位开发者对照。
1、

javx.netssL.SLHndshakexception: sn.security,validator.validatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to mequested tanget

2、

javax.net.ssl.SSLPeerUnverifiedException: Certificate for <xxx.xxx.xx.xx> doesn't match any of the subject alternative names: [*., ]

3、

curl -vvv -d '' -x 192.168.109.137:10086 https://xxx./query
* About to connect() to proxy 192.168.109.137 port 10086 (#0)
*   Trying 192.168.109.137...
* Connected to 192.168.109.137 (192.168.109.137) port 10086 (#0)
* Establish HTTP proxy tunnel to xxx.:443
> CONNECT xxx.:443 HTTP/1.1
> Host: xxx.:443
> User-Agent: curl/7.29.0
> Proxy-Connection: Keep-Alive
> 
< HTTP/1.1 502 Bad Gateway
< Server: nginx/1.14.2
< Date: Thu, 11 Jan 2024 08:04:16 GMT
< Content-Type: text/html
< Content-Length: 173
< Connection: close
< 
* Received HTTP code 502 from proxy after CONNECT
* Connection #0 to host 192.168.109.137 left intact
curl: (56) Received HTTP code 502 from proxy after CONNECT

1和2这两种都是由于SSL忽略证书的写法有问题(错误的代码没有保留,尴尬);第三种是可能是由于ipv6解析打开的问题,只需要把上面的DNS解析调整一下(resolver 114.114.114.114 valid=300s ipv6=off;)就可以了