1. 概述

本文将展示如何使用 HttpClient展开短链接

一个简单的例子是,原始链接只被缩短过一次——比如通过 bit.ly 这样的服务。

更复杂的情况是,链接被不同的服务多次缩短,需要多次解析才能得到原始完整链接。

如果你还想深入了解 HttpClient 的其他酷炫用法,可以移步到 **HttpClient 主教程**。

2. 展开单层短链接

我们先从一个简单的场景开始:展开只被缩短过一次的链接。

首先,我们需要一个不会自动跟随重定向的 HTTP 客户端:

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

这是必要的,因为我们需要手动拦截重定向响应并从中提取信息。

我们向短链接发送请求——得到的响应会是 301 Moved Permanently

然后,我们需要提取指向下一个链接(也是最终链接)的 Location 头信息

private String expandSingleLevel(final String url) throws IOException {
    try {
        HttpHead request = new HttpHead(url);
        String expandedUrl = httpClient.execute(request, response -> {
            final int statusCode = response.getCode();
            if (statusCode != 301 && statusCode != 302) {
                return url;
            }
            final Header[] headers = response.getHeaders(HttpHeaders.LOCATION);
            Preconditions.checkState(headers.length == 1);

            return headers[0].getValue();
        });
        return expandedUrl;
    } catch (final IllegalArgumentException uriEx) {
        return url;
    }
}

最后,用一个未缩短的链接进行简单测试(这里我们测试一个实际缩短的链接):

@Test
public final void givenShortenedOnce_whenUrlIsExpanded_thenCorrectResult() throws IOException {
    final String expectedResult = "https://www.baeldung.com/rest-versioning";
    final String actualResult = expandSingleLevel("http://bit.ly/3LScTri");
    assertThat(actualResult, equalTo(expectedResult));
}

3. 处理多层短链接

短链接的问题在于它们可能被多次缩短,而且每次都是不同的服务。展开这样的链接需要多次解析才能到达原始链接。

我们将使用前面定义的 expandSingleLevel 基础操作,简单地迭代所有中间链接,直到最终目标

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;
}

现在,用这个新的多层链接展开机制来测试:

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

这次,短链接 http://t.co/e4rDDbnzmk 实际上被缩短了两次——一次通过 bit.ly,另一次通过 t.co 服务——被正确地展开为原始链接。

4. 检测重定向循环

最后,有些链接无法展开,因为它们形成了重定向循环。这种问题通常会被 HttpClient 检测到,但因为我们关闭了自动跟随重定向,所以它不再处理。

链接展开机制的最终一步是检测重定向循环,并在发生循环时快速失败。

为了实现这一点,我们需要从之前定义的 expandSingleLevel 方法中获取更多信息——主要是,我们还需要返回响应的状态码。

由于 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 机制能够展开经过任意多次缩短服务的链接,同时正确地在重定向循环时快速失败。

5. 总结

本教程讨论了如何在 Java 中展开短链接——使用 Apache HttpClient

我们从只被缩短一次的链接的简单用例开始,然后实现了一个更通用的机制,能够处理多层重定向并在过程中检测重定向循环。

这些示例的实现可以在 GitHub 上找到。


原始标题:Expand Shortened URLs with Apache HttpClient