概括
我遇到了一个独特的场景,当实例中的变量不是时,对 httpcomponentsHttpUriRequest
方法实例的调用getURI()
正在返回。在这种情况下,不可能返回 a ,但这就是正在发生的事情。null
null
HttpUriRequest.getURI()
null
代码演练
这是代码演练。它演示了该HttpUriRequest.getURI()
方法如何不可能返回 a null
,但它正在返回null
。
使用的相关库是
- com.sun.jersey.contribs:jersey-apache-client4:jar:1.19.4
- org.apache.httpcomponents:httpclient:jar:4.5.12
我们的内部应用程序调用第三方代码,这些代码用于jersey-apache-client4
通过apache httpcomponents httpclient
.
就本问题而言,请求从ApacheHttpClient4Handler.handle(ClientRequest)
以下来源的调用开始。
- 编号的内嵌注释将指导演练。
ApacheHttpClient4Handler
类v1.19.4的相关来源
package com.sun.jersey.client.apache4;
// ... irrelevant code removed
public final class ApacheHttpClient4Handler extends TerminatingClientHandler {
private final HttpClient client;
private final boolean preemptiveBasicAuth;
// ... irrelevant code removed
public ClientResponse handle(final ClientRequest cr) throws ClientHandlerException {
// {1} this is where the HttpUriRequest instance is created.
// See source walkthrough of getUriHttpRequest(ClientRequest) below.
final HttpUriRequest request = getUriHttpRequest(cr);
// {8} - calling request.getURI() here returns not null, and logging produces the expected "https://{serverpath}" on toString()
writeOutBoundHeaders(cr.getHeaders(), request);
// {11} - calling request.getURI() here returns not null, and logging produces the expected "https://{serverpath}" on toString()
try {
HttpResponse response;
if(preemptiveBasicAuth) {
// this code block is never entered as preemptiveBasicAuth is always false for the scenario
// ... irrelevant code removed
} else {
// {12} - calling request.getURI() here returns not null, and logging produces the expected "https://{serverpath}" on toString()
// the following line throws the unexpected NullPointerException originating from the call to getHost(request).
// See source walkthrough of getHost(HttpUriRequest) below.
response = getHttpClient().execute(getHost(request), request);
}
// from here to the catch is never reached due to the NPE two lines above.
ClientResponse r = new ClientResponse(response.getStatusLine().getStatusCode(),
getInBoundHeaders(response),
new HttpClientResponseInputStream(response),
getMessageBodyWorkers());
if (!r.hasEntity()) {
r.bufferEntity();
r.close();
}
return r;
} catch (Exception e) {
// {15} - NPE is caught here and rethrown.
throw new ClientHandlerException(e);
}
}
// ... irrelevant code removed
// this is the method where the unexpected NPE is thrown.
private HttpHost getHost(final HttpUriRequest request) {
// {13} - calling request.getURI() here returns null.
// It is not possible for this to be happening.
// {14} - since getURI() returns null, a NPE will be thrown because .getHost() can't be called on a null return.
// WHY IS getURI() returning null ???!!!???
return new HttpHost(request.getURI().getHost(), request.getURI().getPort(), request.getURI().getScheme());
}
// {2} - this is called by handle(ClientRequest)
private HttpUriRequest getUriHttpRequest(final ClientRequest cr) {
final String strMethod = cr.getMethod();
final URI uri = cr.getURI();
// {3} - uri is not null, and logging produces the expected "https://{serverpath}" on toString()
final Boolean bufferingEnabled = isBufferingEnabled(cr);
final HttpEntity entity = getHttpEntity(cr, bufferingEnabled);
final HttpUriRequest request;
// {4} - uri is not null, and logging produces the expected "https://{serverpath}" on toString()
// even odds for a HttpGet, HttpPost, HttpPut, HttpDelete; HttpHead and HttpOptions do not get referenced by this scenario.
// it does not matter which class the request is, the uri will end up being null.
if (strMethod.equals("GET")) {
request = new HttpGet(uri);
} else if (strMethod.equals("POST")) {
request = new HttpPost(uri);
} else if (strMethod.equals("PUT")) {
request = new HttpPut(uri);
} else if (strMethod.equals("DELETE")) {
request = new HttpDelete(uri);
} else if (strMethod.equals("HEAD")) {
request = new HttpHead(uri);
} else if (strMethod.equals("OPTIONS")) {
request = new HttpOptions(uri);
} else {
// this block of code is never hit by the requests for this scenario.
request = new HttpEntityEnclosingRequestBase() {
@Override
public String getMethod() {
return strMethod;
}
@Override
public URI getURI() {
return uri;
}
};
}
// {5} - checking uri shows it is not null, and logging produces the expected "https://{serverpath}" on toString()
// calling request.getURI() here returns not null, and logging produces the expected "https://{serverpath}" on toString()
// {6} - sometimes this is called, sometimes it is not; regardless, the error scenario still happens
if(entity != null && request instanceof HttpEntityEnclosingRequestBase) {
((HttpEntityEnclosingRequestBase) request).setEntity(entity);
} else if (entity != null) {
throw new ClientHandlerException("Adding entity to http method " + cr.getMethod() + " is not supported.");
}
// ... irrelevant code removed
// {7} - calling request.getURI() here returns not null, and logging produces the expected "https://{serverpath}" on toString()
return request;
}
// ... irrelevant code removed
private void writeOutBoundHeaders(final MultivaluedMap<String, Object> headers, final HttpUriRequest request) {
// {9} - none of this code is relevant; it does not change the value of the uri
for (Map.Entry<String, List<Object>> e : headers.entrySet()) {
List<Object> vs = e.getValue();
if (vs.size() == 1) {
request.addHeader(e.getKey(), ClientRequest.getHeaderValue(vs.get(0)));
} else {
StringBuilder b = new StringBuilder();
for (Object v : e.getValue()) {
if (b.length() > 0) {
b.append(',');
}
b.append(ClientRequest.getHeaderValue(v));
}
request.addHeader(e.getKey(), b.toString());
}
}
// {10} - calling request.getURI() here returns not null, and logging produces the expected "https://{serverpath}" on toString()
}
// ... irrelevant code removed
}
接口HttpUriRequest
v4.5.12相关源码
package org.apache.http.client.methods;
import java.net.URI;
import org.apache.http.HttpRequest;
public interface HttpUriRequest extends HttpRequest {
// ... irrelevant code removed
URI getURI();
// ... irrelevant code removed
}
抽象类HttpRequestBase
v4.5.12 的相关源代码(HttpGet
,HttpPost
等扩展了这个)
package org.apache.http.client.methods;
import java.net.URI;
// ... irrelevant code removed
public abstract class HttpRequestBase extends AbstractExecutionAwareRequest
implements HttpUriRequest, Configurable {
// ... irrelevant code removed
private URI uri;
// ... irrelevant code removed
@Override
public URI getURI() {
return this.uri;
}
// ... irrelevant code removed
public void setURI(final URI uri) {
this.uri = uri;
}
}
HttpGet
v4.5.12类的相关源代码(HttpPost
等实现类似)
package org.apache.http.client.methods;
import java.net.URI;
public class HttpGet extends HttpRequestBase {
public final static String METHOD_NAME = "GET";
public HttpGet() {
super();
}
public HttpGet(final URI uri) {
super();
setURI(uri);
}
/**
* @throws IllegalArgumentException if the uri is invalid.
*/
public HttpGet(final String uri) {
super();
setURI(URI.create(uri));
}
@Override
public String getMethod() {
return METHOD_NAME;
}
}
鉴于源演练和证明 Object 实例状态的可行性的支持性评论,调用如何request.getURI()
返回null
?
环境
- 此代码在 Linux 服务器 (JVM 8.0-5.40) 上的 IBM Liberty 下运行。
- 此代码还在 Windows 服务器 (JVM 1.7) 上的 Wildfly 下运行。
- 两种系统类型的代码相同。
- 该代码在 Windows 服务器上从未出现上述问题。
- 该代码在安装了 Liberty 管理控制台的 Liberty 服务器上不存在上述问题。
观察结果
- 前2-3次直通
ApacheHttpClient4Hander.handle(ClientRequest)
成功不request.getURI()
返回null
;它仅在第 4 次和后续请求时才返回null
。
调查
以下是我迄今为止采取的一些调查步骤:
1 - 我在 SO 上创建了这个案例以获取群体思维洞察力。
2 - 我向 IBM 开了一个案例。
3 - 自定义ApacheHttpClient4Handler
- 我通过 SL4J 向所有方法添加调试日志记录。
结果
- 日志输出证明对的调用仅在进入
HttpUriRequest.getURI()
后才开始返回。null
ApacheHttpClient4Handler.getHost(final HttpUriRequest request)
getHost(HttpUriRequest)
在显示getURI()
返回非空之前的任何调用。- 在添加调试日志之后,而不是在 2-3 和后续请求(每个 OBSERVATIONS)之后,
HttpUriRequest.getURI()
现在在 1-2(和后续)请求之后返回 null。我怀疑这是由于 debug logging 主动调用HttpUriRequest.getURI()
,但我无法确认这一点,也不知道为什么场景现在更短了。
ApacheHttpClient4Handler
4 -进一步定制
HttpUriRequest
更改对的所有引用HttpRequestBase
结果
- 这样做并没有解决问题;
getURI()
仍然返回null
。
5 - 自定义HttpRequestBase
- 将调试日志添加到
getURI()
/setURI(...)
方法
结果
- 应用程序开始按设计工作,除了大量的调试日志!!!
- 请参阅下面的主要更新部分。
6 - 自定义HttpGet
,HttpPut
等。
- 向构造函数添加调试日志记录。
结果
- 请参阅下面的主要更新部分。
7 - 将 IBM JVM 从 8.0-5.40 更新到 8.0-6.11
- IBM 发行说明中与 NPE 相关的错误修复列表
- IJ17563 | 调用具有大偏移量的 JAVA.LANG.STRING.INDEXOF API 时的 NPE
- IJ19669 | 使用 JAVA 8 读取空的 PKCS12 密钥库时发生 NPE
- IJ20528 | 原始包装类型对象的方法调用的 NPE 或 SEGFAULT
- IJ23079 | 由不正确的 JIT 编译器优化导致的意外 NPE
- TODO:结果将在更新中发布
结论
这里发生了什么?
Liberty 使用的 JVM 的 IBM 实现是否滥用了此代码,还是有其他原因?
根据代码和演练,没有办法HttpUriRequest.getURI()
返回null
.
重大更新
根据 INVESTIGATION 中的第 5 步,我httpclient
使用来自全球发行版的源代码定制了调试日志记录。将 JAR 添加到我的项目后,它开始工作。我回滚了我的更改,从未修改的源代码重建了 JAR,将其添加到我的项目中,然后应用程序开始工作。
我使用全球分布httpclient-4.5.12.jar
的 My application failed 再次重新部署。
同样,我没有对代码进行任何更改;我httpclient-4.5.12-sources.jar
使用 .Eclipse 并在 Eclipse 中编译它并使用我新命名的 JAR 重新部署,httpclient-4.5.12.joshdm.jar
. 它有效。
我使用 JAD 反编译了两个 JAR 来检查代码。差异很小,仅在标签中,这意味着编译器不同。我比较了两个 JAR 的 MANIFEST.MF;Build-Jdk 条目不同。我使用 Jdk 1.8.0_191 编译。全局的使用 Jdk 1.8.0_1 8 1 编译。
我已经向 IBM 报告了这个奇怪的差异,提供了编译后的 JAR 和源 JAR,我正在等待回复为什么我的 JVM 可以与他们的 JVM 一起工作,以及为什么全局的 JAR 不能。我现在 99% 确定这是 IBM JVM 或服务器配置问题,与部署的应用程序无关。
更新#2
添加以下配置并回滚到全局 JAR 工作(也适用于重新编译的 JAR)。
-Xshareclasses:none
- 禁用共享类缓存和 AOT 编译。
-Xnojit
- 禁用 JIT 编译
在 Liberty 上禁用共享类缓存、禁用 AOT 编译和禁用 JIT 编译似乎已经解决了这个问题。一旦我从 IBM 收到为什么需要上述设置,我会写一个实际的答案。