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 上找到。