我正在将我的代码迁移到Java 20。
在这个版本中,java.net.URL #URL(java.lang.String)被弃用了。不幸的是,我有一个类,在那里我发现没有旧的URL构造函数的替代品。
package com.github.bottomlessarchive.loa.url.service.encoder;
import io.mola.galimatias.GalimatiasParseException;
import org.springframework.stereotype.Service;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Optional;
/**
* This service is responsible for encoding existing {@link URL} instances to valid
* <a href="https://en.wikipedia.org/wiki/Internationalized_Resource_Identifier">resource identifiers</a>.
*/
@Service
public class UrlEncoder {
/**
* Encodes the provided URL to a valid
* <a href="https://en.wikipedia.org/wiki/Internationalized_Resource_Identifier">resource identifier</a> and return
* the new identifier as a URL.
*
* @param link the url to encode
* @return the encoded url
*/
public Optional<URL> encode(final String link) {
try {
final URL url = new URL(link);
// We need to further validate the URL because the java.net.URL's validation is inadequate.
validateUrl(url);
return Optional.of(encodeUrl(url));
} catch (GalimatiasParseException | MalformedURLException | URISyntaxException e) {
return Optional.empty();
}
}
private void validateUrl(final URL url) throws URISyntaxException {
// This will trigger an URISyntaxException. It is needed because the constructor of java.net.URL doesn't always validate the
// passed url correctly.
new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef());
}
private URL encodeUrl(final URL url) throws GalimatiasParseException, MalformedURLException {
return io.mola.galimatias.URL.parse(url.toString()).toJavaURL();
}
}
幸运的是,我也为这个类做了测试:
package com.github.bottomlessarchive.loa.url.service.encoder;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
class UrlEncoderTest {
private final UrlEncoder underTest = new UrlEncoder();
@ParameterizedTest
@CsvSource(
value = {
"http://www.example.com/?test=Hello world,http://www.example.com/?test=Hello%20world",
"http://www.example.com/?test=ŐÚőúŰÜűü,http://www.example.com/?test=%C5%90%C3%9A%C5%91%C3%BA%C5%B0%C3%9C%C5%B1%C3%BC",
"http://www.example.com/?test=random word £500 bank $,"
+ "http://www.example.com/?test=random%20word%20%C2%A3500%20bank%20$",
"http://www.aquincum.hu/wp-content/uploads/2015/06/Aquincumi-F%C3%BCzetek_14_2008.pdf,"
+ "http://www.aquincum.hu/wp-content/uploads/2015/06/Aquincumi-F%C3%BCzetek_14_2008.pdf",
"http://www.aquincum.hu/wp-content/uploads/2015/06/Aquincumi-F%C3%BCzetek_14 _2008.pdf,"
+ "http://www.aquincum.hu/wp-content/uploads/2015/06/Aquincumi-F%C3%BCzetek_14%20_2008.pdf"
}
)
void testEncodeWhenUsingValidUrls(final String urlToEncode, final String expected) throws MalformedURLException {
final Optional<URL> result = underTest.encode(urlToEncode);
assertThat(result)
.contains(new URL(expected));
}
@ParameterizedTest
@CsvSource(
value = {
"http://промкаталог.рф/PublicDocuments/05-0211-00.pdf"
}
)
void testEncodeWhenUsingInvalidUrls(final String urlToEncode) {
final Optional<URL> result = underTest.encode(urlToEncode);
assertThat(result)
.isEmpty();
}
}
它使用的唯一依赖项是galamatias URL库。
有没有人知道如何删除new URL(link)
代码片段,同时保持功能不变?
我尝试了各种方法,例如使用java.net.URI#create
,但它并没有产生与以前的解决方案完全相同的结果。例如,包含非编码字符(如http://www.example.com/?test=Hello world
中的空格)的URL导致IllegalArgumentException。这是由URL类解析的,没有给出错误。(我的数据中包含了很多这样的链接)。另外,像http://промкаталог.рф/PublicDocuments/05-0211-00.pdf
这样的URL转换失败的链接可以通过URI.create成功转换为URI。
1条答案
按热度按时间enxuqcxy1#
问题是
主要的问题似乎是
UrlEncoder
服务处理的是编码、未编码和部分编码的URL的混合。这会导致歧义,因为某些字符在编码和未编码时可能具有不同的含义。例如,给定一个部分编码的URL,判断一个字符(如
'&'
)是查询参数的一部分(因此应该编码)还是作为分隔符(因此不应该编码)并不容易:雪上加霜的是,由于历史/向后兼容性的原因,Java的
URI
实现偏离了RFC 3986和RFC 3987。下面是关于URI的一些怪癖的有趣阅读:Updating URI support for RFC 3986 and RFC 3987 in the JDK .在不了解原始URL的情况下,通过重新编码来“修复”错误编码的URL并不是一个简单的问题。使用充满怪癖的编码器和解码器来修复错误编码的URL甚至更难。一个足够好的“最大努力”启发式将是我个人的建议。
简单的尽力而为解决方案
所以好消息是我已经设法实现了一个通过上述所有测试的解决方案。该解决方案利用了Spring Web
UriUtils
和UriComponentsBuilder
。蛋糕上的樱桃是你可能不再需要galimatias了。代码如下:
以下是它的要点:
reencode
→通过解码和重新编码来“修复”URL编码的最佳尝试parseServerAuthority()
→作为以前validateUrl(url);
方法的替代。对与号和其他特殊字符进行双重编码
如前所述,虽然上面的代码通过了所有测试,但由于模糊性,很容易产生无法通过的URL。例如,通过编码器运行上面的URL将导致:
这是一个完全有效的URL,但可能不是人们要寻找的。
这是一个危险的领域,但是可以实现一个更“自以为是”的重新编码算法。例如,下面的代码通过确保
%26
不被解码来处理&字符:上面的解决方案可以被修改以处理其他转义序列(例如,处理所有RFC 3986的保留字符),以及使用更复杂的试探法(例如,对查询参数做一些与路径参数不同的事情)。
然而,作为一个曾经经历过这个兔子洞的人,我可以告诉你,一旦你知道你正在处理错误编码的URL,就没有完美的“修复”。