Несколько параллельных подключений к серверам amazon s3 с разными хостами после редиректа периодически дают неверный хост в заголовке Location - PullRequest
0 голосов
/ 26 апреля 2019

Я хочу скачать некоторые файлы из интернета, используя 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 потока, выполняющих один и тот же код). Общий код для каждого файла выполняет следующие действия:

  1. Помещает основную информацию об авторизации (в кодировке) в заголовок
  2. Открывает соединение
  3. Проверяет код ответа
  4. Если 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.");
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...