zip文件下载适用于apache httpclient v4.3.6,但其他版本

时间:2016-11-16 15:41:16

标签: java apache apache-httpclient-4.x

这有点奇怪,我已经做了足够的研究,找到了这个问题的原因和解决方法。我的目标是从安全的URL下载一个也需要登录的zip文件。 当我使用版本4.3.6的apache httpClient maven依赖时,一切都很完美。但是,我不能使用这个版本,因为我的aws-sdk-java-core maven依赖项也有httpclient依赖,并且使用v4.3.6使得aws-sdk-java抱怨NoSuchMethod运行时异常。我理解这个问题。原因是apache httpclient v4.3.6依赖项在maven依赖树中比aws-sdk-java-core依赖项使用的版本(4.5.1)更接近。无论如何,我会减少更多细节,因为我很确定我应该使一个版本的maven依赖项工作,而不是使用相同jar的多个版本。 回到原始问题。由于我不能使用v4.3.6,我告诉我的代码使用v4.5.1,那就是当文件下载代码开始出现问题时。当我使用httpclient v4.5.1时,响应给了我以下html内容,而不是给我我在请求的https网址上的zip文件。

<html>
<HEAD><META HTTP-EQUIV='PRAGMA' CONTENT='NO-CACHE'><META HTTP-EQUIV='CACHE-    
CONTROL' CONTENT='NO-CACHE'>
<TITLE>SAML 2.0 Auto-POST form</TITLE>
</HEAD>
<body onLoad="document.forms[0].submit()">
<NOSCRIPT>Your browser does not support JavaScript.  Please click the    
'Continue' button below to proceed. <br><br>
</NOSCRIPT>
<form action="https://githubext.deere.com/saml/consume" method="POST">
<input type="hidden" name="SAMLResponse"  value="PFJlc3BvbnNlIHhtbG5zPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIERl">
<input type="hidden" name="RelayState" value="2F1HpzrUy5FdX">
<NOSCRIPT><INPUT TYPE="SUBMIT" VALUE="Continue"></NOSCRIPT>
</form>
</body>
</html>

当我使用v4.3.6时,响应为我提供了zip文件作为预期的响应。我尝试通过添加更多代码手动提交此html内容,但响应保持不变。 下面提供了我用于文件下载的原始代码。

@Component
public class FileDAO {

    public static void main(String args[]) throws Exception{
        new FileDAO().loadFile("https://some_url.domain.com/zipball/master","myfile.zip");
    }


    public String loadFile(String url, String fileName) throws ClientProtocolException, IOException {

        HttpClient client = login();
        HttpResponse response = client.execute(new HttpGet(url));
        int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode == 200) {
            String unzipToFolderName = fileName.replace(".", "_");
            FileOutputStream outputStream = new FileOutputStream(new File(fileName));
            writeToFile(outputStream, response.getEntity().getContent());            
            return unzipToFolderName;
        } else {
            throw new RuntimeException("error downloading file, HTTP Status code: " + statusCode);
        }
    }

    private void writeToFile(FileOutputStream outputStream, InputStream inputStream)  {
        try {
            int read = 0;
            byte[] bytes = new byte[1024];
            while ((read = inputStream.read(bytes)) != -1) {
                outputStream.write(bytes, 0, read);
            }
        } catch (Exception ex) {
            throw new RuntimeException("error writing zip file, error message : " + ex.getMessage(), ex);
        } finally {
            try {
                outputStream.close();
                inputStream.close();
            } catch (Exception ex) {}
        }
    }

    private HttpClient login() throws IOException {
        HttpClient client = getHttpClient();

        HttpResponse response = client.execute(new HttpGet("https://some_url.domain.com"));
        String responseBody = EntityUtils.toString(response.getEntity());
        Document doc = Jsoup.parse(responseBody);
        org.jsoup.select.Elements inputs = doc.getElementsByTag("input");
        int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode == 200) {
            HttpPost httpPost = new HttpPost("https://some_url.domain.com/saml/consume");
            List<NameValuePair> data = new ArrayList<NameValuePair>();
            data.add(new BasicNameValuePair("SAMLResponse", doc.select("input[name=SAMLResponse]").val()));
            data.add(new BasicNameValuePair("RelayState", doc.select("input[name=RelayState]").val()));
            httpPost.setEntity(new UrlEncodedFormEntity(data));
            HttpResponse logingResponse = client.execute(httpPost);
            int loginStatusCode = logingResponse.getStatusLine().getStatusCode();
            if (loginStatusCode != 302) {
                throw new RuntimeException("clone repo dao. error during login, HTTP Status code: " + loginStatusCode);
            }
        }
        return client;
    }

    private HttpClient getHttpClient() {
        CredentialsProvider provider = new BasicCredentialsProvider();
        UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("userId", "password");
        provider.setCredentials(AuthScope.ANY, credentials);
        return HttpClientBuilder.create().setDefaultCredentialsProvider(provider).build();
    }
}

我仍在分析除4.3.6之外的apache httpclient版本出了什么问题。相同的代码适用于4.3.6,但不适用于4.3.6以上的版本。 任何帮助都非常感谢。谢谢大家。

1 个答案:

答案 0 :(得分:1)

问题已解决。在经过严格调试日志的apache httpclient文档之后,我可以解决这个问题。我必须创建两个服务器日志,一个用于v4.3.6,另一个用于v4.5.2。我开始比较服务器日志,发现罪魁祸首是cookie类型。旧版本中的Cookie类型(自动)配置为BEST_MATCH并且它正在工作。但是,对于v4.5.2,已从apache弃用BEST_MATCH cookie类型。我在添加更多代码后尝试使用cookie设置,但服务器响应发送的cookie与我在客户端代码中配置的DEFAULT cookie类型不匹配。这导致cookie没有正确设置,这就是为什么响应返回SAML响应(再次登录页面)而不是zip文件。

Apache cookie spec为cookie规范说明了这一点:

默认:默认Cookie策略是一种综合策略,它根据与HTTP响应一起发送的cookie的属性(例如版本属性,现在,选择RFC 2965,RFC 2109或Netscape草案兼容的实现)过时)。在HttpClient的下一个次要版本中,将弃用此策略以支持标准(符合RFC 6265)实现。 标准严格:符合RFC 6265第4节定义的良好行为规范的语法和语义的状态管理策略。

我将Cookie配置更新为 STANDARD_STRICT 模式,所有内容都开始使用最新版本4.5.2。

这是更新的getHttpClient()方法:

private CloseableHttpClient getHttpClient() {
    CredentialsProvider provider = new BasicCredentialsProvider();
    UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(gitUserId, gitPassword);
    provider.setCredentials(AuthScope.ANY, credentials);
    RequestConfig config = RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD_STRICT).build();
    return HttpClientBuilder.create().setDefaultCredentialsProvider(provider).setDefaultRequestConfig(config).setRedirectStrategy(new LaxRedirectStrategy()).build();
}