«1. Обзор

В этой статье мы собираемся показать, как сократить URL-адреса с помощью HttpClient.

Простой пример: исходный URL-адрес был сокращен один раз — такой службой, как bit.ly.

Более сложный пример: URL-адрес был сокращен несколько раз разными службами, и для получения исходного полного URL-адреса требуется несколько проходов.

Если вы хотите копнуть глубже и узнать о других интересных вещах, которые можно делать с помощью HttpClient, перейдите к основному руководству по HttpClient.

2. Сокращение URL-адреса один раз

Давайте начнем с простого – сократите URL-адрес, который был передан через службу сокращения URL-адресов только один раз.

Первое, что нам нужно, это http-клиент, который не следует автоматически за перенаправлениями:

CloseableHttpClient client = 
  HttpClientBuilder.create().disableRedirectHandling().build();

Это необходимо, потому что нам нужно будет вручную перехватывать ответ о перенаправлении и извлекать из него информацию.

Мы начнем с отправки запроса на сокращенный URL-адрес — ответ, который мы получим, будет 301 Moved Permanently.

Затем нам нужно извлечь заголовок Location, указывающий на следующий, и в данном случае — на конечный URL:

public String expandSingleLevel(String url) throws IOException {
    HttpHead request = null;
    try {
        request = new HttpHead(url);
        HttpResponse httpResponse = client.execute(request);

        int statusCode = httpResponse.getStatusLine().getStatusCode();
        if (statusCode != 301 && statusCode != 302) {
            return url;
        }
        Header[] headers = httpResponse.getHeaders(HttpHeaders.LOCATION);
        Preconditions.checkState(headers.length == 1);
        String newUrl = headers[0].getValue();
        return newUrl;
    } catch (IllegalArgumentException uriEx) {
        return url;
    } finally {
        if (request != null) {
            request.releaseConnection();
        }
    }
}

Наконец, простой живой тест расширения URL:

@Test
public void givenShortenedOnce_whenUrlIsUnshortened_thenCorrectResult() throws IOException {
    String expectedResult = "/rest-versioning";
    String actualResult = expandSingleLevel("http://bit.ly/13jEoS1");
    assertThat(actualResult, equalTo(expectedResult));
}

3 Обработка нескольких уровней URL

Проблема с короткими URL заключается в том, что они могут многократно сокращаться совершенно разными службами. Для расширения такого URL-адреса потребуется несколько проходов, чтобы перейти к исходному URL-адресу.

Мы собираемся применить примитивную операцию expandSingleLevel, определенную ранее, чтобы просто перебрать все промежуточные URL-адреса и добраться до конечной цели:

public String expand(String urlArg) throws IOException {
    String originalUrl = urlArg;
    String newUrl = expandSingleLevel(originalUrl);
    while (!originalUrl.equals(newUrl)) {
        originalUrl = newUrl;
        newUrl = expandSingleLevel(originalUrl);
    }
    return newUrl;
}

Теперь, с новым механизмом расширения нескольких уровней URL-адресов, давайте определите тест и заставьте это работать:

@Test
public void givenShortenedMultiple_whenUrlIsUnshortened_thenCorrectResult() throws IOException {
    String expectedResult = "/rest-versioning";
    String actualResult = expand("http://t.co/e4rDDbnzmk");
    assertThat(actualResult, equalTo(expectedResult));
}

На этот раз короткий URL-адрес — http://t.co/e4rDDbnzmk — который на самом деле сокращен дважды — один раз через bit.ly и второй раз через сервис t.co — корректно расширяется до исходного URL.

4. Обнаружение циклов перенаправления

Наконец, некоторые URL-адреса нельзя расширить, поскольку они образуют цикл перенаправления. Этот тип проблемы будет обнаружен HttpClient, но поскольку мы отключили автоматическое следование перенаправлениям, это больше не происходит.

Последним шагом в механизме расширения URL будет обнаружение циклов перенаправления и быстрый сбой в случае возникновения такого цикла.

Чтобы это было эффективно, нам нужна дополнительная информация из метода expandSingleLevel, который мы определили ранее — в основном нам нужно также вернуть код состояния ответа вместе с URL-адресом.

Поскольку java не поддерживает множественные возвращаемые значения, мы собираемся обернуть информацию в объект org.apache.commons.lang3.tuple.Pair — новая сигнатура метода теперь будет: ~~ ~

public Pair<Integer, String> expandSingleLevelSafe(String url) throws IOException {

И, наконец, давайте включим обнаружение цикла редиректа в основной механизм расширения:

public String expandSafe(String urlArg) throws IOException {
    String originalUrl = urlArg;
    String newUrl = expandSingleLevelSafe(originalUrl).getRight();
    List<String> alreadyVisited = Lists.newArrayList(originalUrl, newUrl);
    while (!originalUrl.equals(newUrl)) {
        originalUrl = newUrl;
        Pair<Integer, String> statusAndUrl = expandSingleLevelSafe(originalUrl);
        newUrl = statusAndUrl.getRight();
        boolean isRedirect = statusAndUrl.getLeft() == 301 || statusAndUrl.getLeft() == 302;
        if (isRedirect && alreadyVisited.contains(newUrl)) {
            throw new IllegalStateException("Likely a redirect loop");
        }
        alreadyVisited.add(newUrl);
    }
    return newUrl;
}

И все — механизм expandSafe может сокращать URL-адреса, проходящие через произвольное количество служб сокращения URL-адресов, в то время как корректно терпит неудачу в циклах перенаправления.

5. Заключение

В этом руководстве обсуждалось, как расширять короткие URL-адреса в java — с помощью Apache HttpClient.

Мы начали с простого варианта использования с URL-адресом, который был сокращен только один раз, а затем реализовали более общий механизм, способный обрабатывать несколько уровней перенаправления и обнаруживать циклы перенаправления в процессе.

Реализацию этих примеров можно найти в проекте github — это проект на основе Eclipse, поэтому его легко импортировать и запускать как есть.