Я хочу скачать некоторые файлы из интернета, используя Java.
Файлы за редиректом 302 и некоторой безопасностью После аутентификации перенаправления указывают на корзину Amazon S3.
Например, https://example.com/getFile?Name=TYPEA проверяет подлинность, затем перенаправляет на https://exampleA.s3.amazonaws.com/TYPEA/TYPEA.file.gz;
https://example.com/getFile?Name=TYPEB проверяет подлинность, затем перенаправляет на https://exampleB.s3.amazonaws.com/TYPEB/TYPEB.file.gz;
Если я перехожу к версии http: //, она перенаправляется на версию https: //, затем продолжается, как описано выше (добавляя дополнительный редирект).
Я написал фрагмент кода Java, который загружает один файл. Этот код запускается в одном потоке на файл при запуске моего приложения (например, 2 файла = 2 потока, выполняющих один и тот же код). Общий код для каждого файла выполняет следующие действия:
- Помещает основную информацию об авторизации (в кодировке) в заголовок
- Открывает соединение
- Проверяет код ответа
- Если 301 или 302 или 303, заголовок «Местоположение» считывается, и вышеприведенные циклы циклически повторяются, пока не будет найден файл (ответ 200).
Моя первоначальная проблема заключалась в том, что мне нужно было удалить заголовок авторизации, как только мы добрались до части перенаправлений Amazon, потому что Amazon не хотела базовую авторизацию, а также свой собственный ключ авторизации подписи. Затем код работал для одного файла в одном потоке; но как только я запустил его с двумя потоками, заголовок местоположения смешивал имена хостов url - так что два заголовка "Location" были бы либо:
или (похоже, случайным образом)
Что, конечно, приводит к ошибке 404 для одного файла и 200 к успеху для другого.
Если я оставлю пробел, чтобы исходные URL-адреса не были доступны одновременно, эта проблема не возникнет.
У кого-нибудь есть предложения относительно того, почему в HTTP-ответах указан неверный хост в заголовке Location?
В коде нет статических переменных (например, для местоположения файла), которые могли бы вызвать это.
- Общий код (MCVE - где fileDownloadURLHeaderHostName
= exampleA.s3.amazonaws.com или exampleB.s3.amazonaws.com): -
InputStream inputStream = null;
OutputStream outputStream = null;
URL url;
URI uri;
HttpURLConnection conn;
try
{
String authString = openFeedsUser + ":" + openFeedsPass;
byte[] authEncBytes = Base64.getEncoder().encode(authString.getBytes());
String authStringEnc = new String(authEncBytes);
CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ALL));
url = new URL(fileDownloadURL);
uri = new URI(url.getProtocol(), url.getUserInfo(), IDN.toASCII(url.getHost()), url.getPort(), url.getPath(), url.getQuery(), url.getRef());
String correctEncodedURL=uri.toASCIIString();
url = new URL(correctEncodedURL);
conn = (HttpURLConnection)url.openConnection();
conn.setRequestProperty("Authorization", "Basic " + authStringEnc);
conn.setRequestMethod("GET");
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36");
conn.setRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3");
conn.setRequestProperty("Accept-Encoding", "gzip, deflate, br");
conn.setRequestProperty("Upgrade-Insecure-Requests", "1");
conn.setRequestProperty("Host", fileDownloadURLHeaderHostName);
conn.setRequestProperty("Accept-Language","en-US,en;q=0.9");
conn.setRequestProperty("Cache-Control","max-age=0");
conn.setRequestProperty("Connection","keep-alive");
// Don't automatically follow redirects, we'll do it manually because we need to set the cookies again once we have the Amazon auth settings; before following the redirect
conn.setInstanceFollowRedirects(false);
HttpURLConnection.setFollowRedirects(false);
boolean redirect = false;
int redirectCount = 0;
int redirectLimit = 10;
// follow all redirects until the limit, or we get to a final destination
do
{
// get the response code, and act accordingly
int status = conn.getResponseCode();
if (status != HttpURLConnection.HTTP_OK)
{
if (status == HttpURLConnection.HTTP_MOVED_TEMP
|| status == HttpURLConnection.HTTP_MOVED_PERM
|| status == HttpURLConnection.HTTP_SEE_OTHER)
{
redirect = true;
redirectCount = redirectCount + 1;
// get redirect url from "location" header field
String newUrl = conn.getHeaderField("Location");
url = new URL(newUrl);
uri = new URI(url.getProtocol(), url.getUserInfo(), IDN.toASCII(url.getHost()), url.getPort(), url.getPath(), url.getQuery(), url.getRef());
correctEncodedURL=uri.toASCIIString();
url = new URL(correctEncodedURL);
// get the cookie if need, for login
String cookies = conn.getHeaderField("Set-Cookie");
// open the new connnection again
url = new URL(newUrl);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36");
conn.setRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3");
conn.setRequestProperty("Accept-Encoding", "gzip, deflate, br");
conn.setRequestProperty("Upgrade-Insecure-Requests", "1");
conn.setRequestProperty("Host", fileDownloadURLHeaderHostName);
conn.setRequestProperty("Accept-Language","en-US,en;q=0.9");
conn.setRequestProperty("Cache-Control","max-age=0");
conn.setRequestProperty("Connection","keep-alive");
// only put the basic auth in the header, if the Amazon Signature query string is not present
String query = url.getQuery();
if (!query.contains("Signature"))
{
conn.setRequestProperty("Authorization", "Basic " + authStringEnc);
}
// set the cookies to be the same as the previous request
conn.setRequestProperty("Cookie", cookies);
}
else // something went wrong
{
redirect = false;
InputStream errorStream = conn.getErrorStream();
StringBuilder errorMessage = new StringBuilder();
String line = null;
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(errorStream, StandardCharsets.UTF_8)))
{
while ((line = bufferedReader.readLine()) != null)
{
errorMessage.append(line);
}
}
Log("error received while attempting to download file. Details: " + errorMessage.toString());
return false;
}
}
else // success
{
redirect = false;
inputStream = conn.getInputStream();
// etc...
return true;
}
}while (redirect == true && redirectCount < redirectLimit);
if (redirectCount >= redirectLimit)
{
Log( "Too many redirects, so not getting file.");
}
return false; //default
}
catch (IOException e)
{
e.printStackTrace();
return false;
}
catch (URISyntaxException e)
{
e.printStackTrace();
return false;
}
finally
{
try
{
if (inputStream != null)
{
inputStream.close();
}
if (outputStream != null)
{
outputStream.close();
}
}
catch (IOException ioe)
{
// nothing to see here
}
}
- Edit -
Я обнаружил, что заголовок Host
не был установлен, поэтому я добавил System.setProperty("sun.net.http.allowRestrictedHeaders", "true")
, что ... изменило проблему, так что теперь я получаю 403 (ошибка подписи амазонки) на тот, который не работать вместо ошибки 404.
- Правка 2--
Команда curl curl -L -u user:pass -o file.gz "https://example.com/getFile?Name=TYPEA"
работает и загружает файл. Так что же Java делает по-другому с простой командой curl?
- Правка 3--
Несмотря на то, что работает один завиток, замена приведенного выше http-кода вызовами ProcessBuilder вызывает ту же проблему - поэтому это выглядит как проблема на стороне сервера, где два соединения с одного IP-адреса, которые находятся близко друг к другу, заставляют его перепутать хост , Могу ли я что-нибудь сделать в Java, чтобы помочь дифференцировать сервер? Есть ли какие-то настройки для сессий, как-нибудь?
код скручивания:
String authString = openFeedsUser + ":" + openFeedsPass;
String authParam = "-u";
File downloadFile = new File(fileDownloadDirectory + File.separator + fileDownloadFileName);
downloadFile.createNewFile();
String canonicalDownloadFilePath = downloadFile.getCanonicalPath();
String downloadPathParam = "-o";
ProcessBuilder processBuilder = new ProcessBuilder("curl","-L",authParam, authString, downloadPathParam, canonicalDownloadFilePath, fileDownloadURL);
Log(className, "getting file from URL : " + fileDownloadURL);
Process curl = processBuilder.start();
// wait for the process to end
while(curl.isAlive())
{
}
Log(className, "CURL Exit code: " + curl.exitValue());
Log(className, "File Downloaded.");