URLConnection не следует за редиректом

Я не могу понять, почему Java HttpURLConnection не следует за редиректом Я использую следующий код, чтобы получить эту страницу:

import java.net.URL;
import java.net.HttpURLConnection;
import java.io.InputStream;

public class Tester {

    public static void main(String argv[]) throws Exception{
        InputStream is = null;

        try {
            String bitlyUrl = "http://bit.ly/4hW294";
            URL resourceUrl = new URL(bitlyUrl);
            HttpURLConnection conn = (HttpURLConnection)resourceUrl.openConnection();
            conn.setConnectTimeout(15000);
            conn.setReadTimeout(15000);
            conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 6.0; ru; rv:1.9.0.11) Gecko/2009060215 Firefox/3.0.11 (.NET CLR 3.5.30729)");
            conn.connect();
            is = conn.getInputStream();
            String res = conn.getURL().toString();
            if (res.toLowerCase().contains("bit.ly"))
                System.out.println("bit.ly is after resolving: "+res);
       }
       catch (Exception e) {
           System.out.println("error happened: "+e.toString());
       }
       finally {
            if (is != null) is.close(); 
        }
    }
}

Более того, я получаю следующий ответ (это кажется абсолютно правильным!):

GET /4hW294 HTTP/1.1
Host: bit.ly
Connection: Keep-Alive
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; ru-RU; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)
HTTP/1.1 301 Moved
Server: nginx/0.7.42
Date: Thu, 10 Dec 2009 20:28:44 GMT
Content-Type: text/html; charset=utf-8
Connection: keep-alive
Location: https://www.myganocafe.com/CafeMacy
MIME-Version: 1.0
Content-Length: 297

К сожалению, res переменная содержит тот же URL, а поток содержит следующее (очевидно, HttpURLConnection не следует перенаправлять!):

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<HTML>
<HEAD>
<TITLE>Moved</TITLE>
</HEAD>
<BODY>
<H2>Moved</H2>
<A HREF="https://www.myganocafe.com/CafeMacy">The requested URL has moved here.</A>
<P ALIGN=RIGHT><SMALL><I>AOLserver/4.5.1 on http://127.0.0.1:7400</I></SMALL></P>
</BODY>
</HTML>

6 ответов

Решение

Я не думаю, что он будет автоматически перенаправлять с HTTP на HTTPS (или наоборот).

Хотя мы знаем, что он отражает HTTP, с точки зрения протокола HTTP, HTTPS - это просто какой-то другой, совершенно другой, неизвестный протокол. Было бы небезопасно следовать перенаправлению без одобрения пользователя.

Например, предположим, что приложение настроено для автоматической аутентификации клиента. Пользователь ожидает анонимного серфинга, потому что он использует HTTP. Но если его клиент следует HTTPS без запроса, его личность раскрывается на сервере.

HttpURLConnection по своему дизайну не будет автоматически перенаправлять с HTTP на HTTPS (или наоборот). После перенаправления могут возникнуть серьезные последствия для безопасности. SSL (следовательно, HTTPS) создает сеанс, который является уникальным для пользователя. Этот сеанс может быть повторно использован для нескольких запросов. Таким образом, сервер может отслеживать все запросы, сделанные от одного человека. Это слабая форма идентичности и ее можно использовать. Кроме того, SSL рукопожатие может запросить сертификат клиента. Если отправлено на сервер, то идентификатор клиента передается на сервер.

Как указывает erickson, предположим, что приложение настроено для автоматической аутентификации клиента. Пользователь ожидает анонимного серфинга, потому что он использует HTTP. Но если его клиент следует HTTPS без запроса, его личность раскрывается на сервере.

С этим поняли, вот код, который будет следовать перенаправлениям.

  URL resourceUrl, base, next;
  HttpURLConnection conn;
  String location;

  ...

  while (true)
  {
     resourceUrl = new URL(url);
     conn        = (HttpURLConnection) resourceUrl.openConnection();

     conn.setConnectTimeout(15000);
     conn.setReadTimeout(15000);
     conn.setInstanceFollowRedirects(false);   // Make the logic below easier to detect redirections
     conn.setRequestProperty("User-Agent", "Mozilla/5.0...");

     switch (conn.getResponseCode())
     {
        case HttpURLConnection.HTTP_MOVED_PERM:
        case HttpURLConnection.HTTP_MOVED_TEMP:
           location = conn.getHeaderField("Location");
           location = URLDecoder.decode(location, "UTF-8");
           base     = new URL(url);               
           next     = new URL(base, location);  // Deal with relative URLs
           url      = next.toExternalForm();
           continue;
     }

     break;
  }

  is = conn.openStream();
  ...

Есть что-то под названием HttpURLConnection.setFollowRedirects(false) случайно?

Вы всегда можете позвонить

conn.setInstanceFollowRedirects(true);

если вы хотите убедиться, что вы не влияете на остальную часть поведения приложения.

Как уже упоминалось некоторыми из вас выше, setFollowRedirect и setInstanceFollowRedirects работают автоматически, только если перенаправленный протокол одинаков. то есть с http на http и https на https.

setFolloRedirect находится на уровне класса и устанавливает его для всех экземпляров соединения url, тогда как setInstanceFollowRedirects только для данного экземпляра. Таким образом, мы можем иметь разное поведение для разных экземпляров.

Я нашел очень хороший пример здесь http://www.mkyong.com/java/java-httpurlconnection-follow-redirect-example/

Другим вариантом может быть использование Apache HttpComponents Client:

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
</dependency>

Образец кода:

CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("https://media-hearth.cursecdn.com/avatars/330/498/212.png");
CloseableHttpResponse response = httpclient.execute(httpget);
final HttpEntity entity = response.getEntity();
final InputStream is = entity.getContent();

HTTPUrlConnection не несет ответственности за обработку ответа объекта. Это ожидаемая производительность, она захватывает содержимое запрошенного URL. Пользователь функциональности может интерпретировать ответ. Он не может прочитать намерения разработчика без спецификации.

Другие вопросы по тегам