在 URL 查询参数中使用某些字符时出现 Java 9 HttpClient 异常

Posted

技术标签:

【中文标题】在 URL 查询参数中使用某些字符时出现 Java 9 HttpClient 异常【英文标题】:Java 9 HttpClient exception when using certain characters in URL query parameters 【发布时间】:2018-11-24 03:17:22 【问题描述】:

这是我的示例代码。查询编码为 UTF-8:

HttpRequest request = HttpRequest.newBuilder()
    .header("content-type", "application/json;charset=UTF-8")
    .uri(URI.create("http://localhost:8080/test?param1=test%C5%84"))
    .GET()
    .build();

HttpClient.newBuilder()
    .version(HttpClient.Version.HTTP_2)
    .build()
    .send(request, HttpResponse.BodyHandler.asString(Charset.forName("UTF-8")));

运行此示例后,出现以下异常:

java.lang.IllegalArgumentException: char=324
at jdk.incubator.httpclient/jdk.incubator.http.internal.hpack.Huffman.codeOf(Huffman.java:559)
at jdk.incubator.httpclient/jdk.incubator.http.internal.hpack.Huffman.lengthOf(Huffman.java:524)
at jdk.incubator.httpclient/jdk.incubator.http.internal.hpack.StringWriter.configure(StringWriter.java:79)
at jdk.incubator.httpclient/jdk.incubator.http.internal.hpack.StringWriter.configure(StringWriter.java:62)
at jdk.incubator.httpclient/jdk.incubator.http.internal.hpack.IndexNameValueWriter.value(IndexNameValueWriter.java:64)
at jdk.incubator.httpclient/jdk.incubator.http.internal.hpack.Encoder.literal(Encoder.java:422)
at jdk.incubator.httpclient/jdk.incubator.http.internal.hpack.Encoder.header(Encoder.java:245)
at jdk.incubator.httpclient/jdk.incubator.http.internal.hpack.Encoder.header(Encoder.java:198)
at jdk.incubator.httpclient/jdk.incubator.http.Http2Connection.encodeHeadersImpl(Http2Connection.java:927)
at jdk.incubator.httpclient/jdk.incubator.http.Http2Connection.encodeHeaders(Http2Connection.java:878)
at jdk.incubator.httpclient/jdk.incubator.http.Http2Connection.encodeHeaders(Http2Connection.java:951)
at jdk.incubator.httpclient/jdk.incubator.http.Http2Connection.sendFrame(Http2Connection.java:984)
at jdk.incubator.httpclient/jdk.incubator.http.Stream.sendHeadersAsync(Stream.java:547)
at jdk.incubator.httpclient/jdk.incubator.http.Exchange.lambda$responseAsyncImpl0$8(Exchange.java:322)
at java.base/java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1072)
at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:506)
at java.base/java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:2073)
at jdk.incubator.httpclient/jdk.incubator.http.internal.common.SSLFlowDelegate.setALPN(SSLFlowDelegate.java:164)
at jdk.incubator.httpclient/jdk.incubator.http.internal.common.SSLFlowDelegate.access$200(SSLFlowDelegate.java:81)
at jdk.incubator.httpclient/jdk.incubator.http.internal.common.SSLFlowDelegate$Reader.processData(SSLFlowDelegate.java:340)
at jdk.incubator.httpclient/jdk.incubator.http.internal.common.SSLFlowDelegate$Reader$ReaderDownstreamPusher.run(SSLFlowDelegate.java:215)
at jdk.incubator.httpclient/jdk.incubator.http.internal.common.SequentialScheduler$SynchronizedRestartableTask.run(SequentialScheduler.java:175)
at jdk.incubator.httpclient/jdk.incubator.http.internal.common.SequentialScheduler$CompleteRestartableTask.run(SequentialScheduler.java:147)
at jdk.incubator.httpclient/jdk.incubator.http.internal.common.SequentialScheduler$TryEndDeferredCompleter.complete(SequentialScheduler.java:315)
at jdk.incubator.httpclient/jdk.incubator.http.internal.common.SequentialScheduler$CompleteRestartableTask.run(SequentialScheduler.java:149)
at jdk.incubator.httpclient/jdk.incubator.http.internal.common.SequentialScheduler$SchedulableTask.run(SequentialScheduler.java:198)
at jdk.incubator.httpclient/jdk.incubator.http.internal.common.SequentialScheduler.runOrSchedule(SequentialScheduler.java:271)
at jdk.incubator.httpclient/jdk.incubator.http.internal.common.SequentialScheduler.runOrSchedule(SequentialScheduler.java:224)
at jdk.incubator.httpclient/jdk.incubator.http.internal.common.SSLFlowDelegate$Reader.incoming(SSLFlowDelegate.java:242)
at jdk.incubator.httpclient/jdk.incubator.http.internal.common.SubscriberWrapper.incomingCaller(SubscriberWrapper.java:388)
at jdk.incubator.httpclient/jdk.incubator.http.internal.common.SubscriberWrapper.onNext(SubscriberWrapper.java:343)
at jdk.incubator.httpclient/jdk.incubator.http.internal.common.SubscriberWrapper.onNext(SubscriberWrapper.java:58)
at jdk.incubator.httpclient/jdk.incubator.http.SocketTube$InternalReadPublisher$InternalReadSubscription.read(SocketTube.java:739)
at jdk.incubator.httpclient/jdk.incubator.http.SocketTube$SocketFlowTask.run(SocketTube.java:171)
at jdk.incubator.httpclient/jdk.incubator.http.internal.common.SequentialScheduler$SchedulableTask.run(SequentialScheduler.java:198)
at jdk.incubator.httpclient/jdk.incubator.http.internal.common.SequentialScheduler.runOrSchedule(SequentialScheduler.java:271)
at jdk.incubator.httpclient/jdk.incubator.http.internal.common.SequentialScheduler.runOrSchedule(SequentialScheduler.java:224)
at jdk.incubator.httpclient/jdk.incubator.http.SocketTube$InternalReadPublisher$InternalReadSubscription.signalReadable(SocketTube.java:675)
at jdk.incubator.httpclient/jdk.incubator.http.SocketTube$InternalReadPublisher$ReadEvent.signalEvent(SocketTube.java:829)
at jdk.incubator.httpclient/jdk.incubator.http.SocketTube$SocketFlowEvent.handle(SocketTube.java:243)
at jdk.incubator.httpclient/jdk.incubator.http.HttpClientImpl$SelectorManager.handleEvent(HttpClientImpl.java:769)
at jdk.incubator.httpclient/jdk.incubator.http.HttpClientImpl$SelectorManager.run(HttpClientImpl.java:731)

char=324 表示从查询中解码ń

当我阅读堆栈跟踪时,我在类中发现了jdk.incubator.http.Stream<T> 这个方法:

private void setPseudoHeaderFields() 
    HttpHeadersImpl hdrs = requestPseudoHeaders;
    String method = request.method();
    hdrs.setHeader(":method", method);
    URI uri = request.uri();
    hdrs.setHeader(":scheme", uri.getScheme());
    // TODO: userinfo deprecated. Needs to be removed
    hdrs.setHeader(":authority", uri.getAuthority());
    // TODO: ensure header names beginning with : not in user headers
    String query = uri.getQuery();
    String path = uri.getPath();
    if (path == null || path.isEmpty()) 
        if (method.equalsIgnoreCase("OPTIONS")) 
            path = "*";
         else 
            path = "/";
        
    
    if (query != null) 
        path += "?" + query;
    
    hdrs.setHeader(":path", path);

在这个方法中使用了uri.getQuery(),它为我们提供了解码后的查询并导致上述异常。

当我在调试模式下使用uri.getRawQuery()(它为我们提供了编码查询)时,一切都很好。

我的问题是:这是错误还是故意使用?如果不是bug,如何避免异常?

【问题讨论】:

test%C5%84 也许(额外的 %)?如"...?param=" + URLEncoder.encode("ń", "UTF-8") 对不起,这是我的错误。应该有test%C5%84,但在方法setPseudoHeaderFields() 中我得到...testń,以及未来的异常。 似乎 Huffman 压缩使用简单的字符到字节转换 à la ISO-8859-1。因此,就我非常有限的经验而言,这似乎是一个错误。您可能会恢复为 Base64:"...?param1=" + Base64.getEncoder().encode("ń"),但您也需要自己解码参数。 【参考方案1】:

这是一个错误:

使用某些 UTF-8 字符时带有 jdk.incubator.httpclient 的 java.lang.IllegalArgumentException 某些 UTF-8 字符(如“š”)不能与 HttpClient 一起使用。然而 其他类似“å”的可以。

见:https://bugs.openjdk.java.net/browse/JDK-8201238


Java 9/10 HttpClient 已孵化。它尚未准备好生产。它在包名称中非常清楚地说明了这一点。因此,它很可能包含错误。

它将作为 JEP 321 的一部分在 Java 11 (currently scheduled for release 25th September 2018) 中正确发布。

【讨论】:

注意:HTTP 客户端 API 将在 Java 11 中标准化,并将移至名为 java.net.http 的新包(和模块)中。 (JEP 321) @Slaw 我为此感到非常高兴,终于在 jdk 本身中有一个标准的 http 客户端! 您可以计划migrate to Java-11 for using the standardized http module.。我可以确认code in question 可以与 JDK/11 (jdk-11-ea+17) 一起正常工作。

以上是关于在 URL 查询参数中使用某些字符时出现 Java 9 HttpClient 异常的主要内容,如果未能解决你的问题,请参考以下文章

url参数中带有中文时出现字符乱码

用java获取URL路径时出现非法字符

在配置单元中发出选择查询时出现异常'java.lang.IllegalArgumentException(无法从空字符串创建路径)'

wordpress - 在 cpanel 中删除某些文件管理器时出现错误 URL

在我的 URL 末尾添加 /?name=someonesname 时出现 Wordpress 404 错误

在mysql查询中使用参数时出现mysql语法错误