Java如何实现通过代理服务器的HTTP请求? - AnderRV


向HTTP请求添加代理的情况有很多,例如为了安全性或匿名性。但是在任何情况下,Java 库(通常)都会使添加代理变得复杂。
在 Java 中执行 HTTP 调用没有简单的内置解决方案。
我们将使用Apache HttpComponents项目fluent.Request的一部分实现:

import org.apache.hc.client5.http.fluent.Request;
public class TestRequest {
    public static void main(final String... args) throws Exception {
        String url = "http://httpbin.org/anything";
        String proxy = "http://XXX.代理IP.XXX:8123"; // Free proxy
        String response = Request.get(url)
                .viaProxy(proxy) // will set the passed proxy
                .execute().returnContent().asString();
        System.out.println(response);
    }
}

在我们的例子中,只要我们不需要身份验证,我们就可以直接使用带有代理 URL的viaProxy。


具有认证功能的代理
付费或私人代理供应商--如ZenRows--经常在每次调用中使用认证。有时它是通过IP允许列表完成的,但经常使用其他手段,如代理授权头。
在没有适当的auth方法的情况下调用代理将导致错误:

Exception in thread "main" org.apache.hc.client5.http.HttpResponseException: status code: 407, reason phrase: Proxy Authentication Required.

按照这个例子,我们将需要两样东西:授权和把代理作为一个Host传递。
Proxy-Authorization包含用户和密码的base64编码。

然后,我们需要改变viaProxy获取代理的方式,因为它不允许带有用户和密码的URL。
为此,我们将创建一个新的HttpHost,传入整个URL。它将在内部处理这个问题并省略不需要的部分。

import java.net.URI;
import java.util.Base64;
import org.apache.hc.client5.http.fluent.Request;
import org.apache.hc.core5.http.HttpHost;
public class TestRequest {
    public static void main(final String... args) throws Exception {
        String url = "http://httpbin.org/anything";
        // Proxy URL as given by the provider
        URI proxyURI = new URI("http://YOUR_API_KEY:@proxy.zenrows.com:8001");
        String basicAuth = new String(
            Base64.getEncoder() // get the base64 encoder
            .encode(
                // get user and password from the proxy URL
                proxyURI.getUserInfo().getBytes()
            ));
        String response = Request.get(url)
                .addHeader("Proxy-Authorization", "Basic " + basicAuth) // add auth 
                // will set the passed proxy as a host
                .viaProxy(HttpHost.create(proxyURI))
                .execute().returnContent().asString();
        System.out.println(response);
    }
}

忽略SSL证书
在为SSL(https)连接添加代理时,库往往会提出关于证书的警告/错误。从安全的角度来看,这是很好的! 我们避免被显示或重定向到我们喜欢避免的网站。
但是,如果强迫我们通过自己的代理进行连接呢?在这些情况下没有安全风险,所以我们想忽略这些警告。这在Java中又不是一件容易的事。
错误如下:

 Exception in thread "main" javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target.

在这种情况下,我们将修改目标URL,将其切换为https。
同时,调用我们接下来要创建的一个辅助方法。在主函数上没有其他变化。

public class TestRequest {
    public static void main(final String... args) throws Exception {
        ignoreCertWarning(); // new method that will ignore certificate warnings
        String url = "https://httpbin.org/anything"; // switch to https
        // ...
    }
}

现在是复杂和冗长的部分。我们需要创建一个SSL上下文和假证书。正如你所看到的,证书管理器和它的方法什么都不做。它将只是绕过内部工作,从而避免问题。最后,用创建的假证书初始化上下文并将其设置为默认值。然后我们就可以开始了

import java.security.cert.X509Certificate;
import javax.net.ssl.*;
public class TestRequest {
    // ...
    private static void ignoreCertWarning() {
        SSLContext ctx = null;
        TrustManager[] trustAllCerts = new X509TrustManager[] { new X509TrustManager() {
            public X509Certificate[] getAcceptedIssuers() {return null;}
            public void checkClientTrusted(X509Certificate[] certs, String authType) {}
            public void checkServerTrusted(X509Certificate[] certs, String authType) {}
        } };
        try {
            ctx = SSLContext.getInstance("SSL");
            ctx.init(null, trustAllCerts, null);
            SSLContext.setDefault(ctx);
        } catch (Exception e) {}
    }
}


结论
在Java中访问数据(或搜刮)可能会变得复杂和冗长。但有了正确的工具和库,我们就能驯服它的冗长性。