HttpClient 在重定向后不处理正文



我正在使用HttpClient(micronaut 2.2.0的一部分)对从控制器重定向的资源执行GET操作,如下所示:

@Controller
public class ExampleController {
@Client("https://www.vrt.be/") @Inject
RxHttpClient client;
@Get
public String getIt() {
return client.toBlocking().retrieve("/");
}
}

我正在使用具有 lambda 函数和 API 网关的模板通过 SAM CLI 运行此应用程序。发出curl http://127.0.0.1:3000/时,它会引发异常。将模板部署到 AWS 本身时也会发生此异常。

19:38:58.007 [default-nioEventLoopGroup-1-1] DEBUG i.m.h.client.netty.DefaultHttpClient - Sending HTTP Request: GET /
19:38:58.008 [default-nioEventLoopGroup-1-1] DEBUG i.m.h.client.netty.DefaultHttpClient - Chosen Server: www.vrt.be(-1)
19:38:58.023 [default-nioEventLoopGroup-1-1] TRACE i.m.h.client.netty.DefaultHttpClient - host: www.vrt.be
19:38:58.023 [default-nioEventLoopGroup-1-1] TRACE i.m.h.client.netty.DefaultHttpClient - connection: close
19:38:58.873 [default-nioEventLoopGroup-1-1] TRACE i.m.h.client.netty.DefaultHttpClient - HTTP Client Response Received for Request: GET https://www.vrt.be/
19:38:58.873 [default-nioEventLoopGroup-1-1] TRACE i.m.h.client.netty.DefaultHttpClient - Status Code: 301 Moved Permanently
19:38:58.874 [default-nioEventLoopGroup-1-1] TRACE i.m.h.client.netty.DefaultHttpClient - Content-Type: text/html; charset=iso-8859-1
19:38:58.874 [default-nioEventLoopGroup-1-1] TRACE i.m.h.client.netty.DefaultHttpClient - Content-Length: 230
19:38:58.874 [default-nioEventLoopGroup-1-1] TRACE i.m.h.client.netty.DefaultHttpClient - Connection: close
19:38:58.874 [default-nioEventLoopGroup-1-1] TRACE i.m.h.client.netty.DefaultHttpClient - Date: Tue, 01 Dec 2020 20:19:06 GMT
19:38:58.874 [default-nioEventLoopGroup-1-1] TRACE i.m.h.client.netty.DefaultHttpClient - Cache-Control: max-age=604800
19:38:58.875 [default-nioEventLoopGroup-1-1] TRACE i.m.h.client.netty.DefaultHttpClient - Location: https://www.vrt.be/nl/
19:38:58.875 [default-nioEventLoopGroup-1-1] TRACE i.m.h.client.netty.DefaultHttpClient - Vary: Accept-Encoding
19:38:58.875 [default-nioEventLoopGroup-1-1] TRACE i.m.h.client.netty.DefaultHttpClient - X-Cache: Hit from cloudfront
...
19:38:58.876 [default-nioEventLoopGroup-1-1] TRACE i.m.h.client.netty.DefaultHttpClient - Response Body
19:38:58.876 [default-nioEventLoopGroup-1-1] TRACE i.m.h.client.netty.DefaultHttpClient - ----
19:38:58.876 [default-nioEventLoopGroup-1-1] TRACE i.m.h.client.netty.DefaultHttpClient - <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>301 Moved Permanently</title>
</head><body>
<h1>Moved Permanently</h1>
<p>The document has moved <a href="https://www.vrt.be/nl/">here</a>.</p>
</body></html>
19:38:58.877 [default-nioEventLoopGroup-1-1] TRACE i.m.h.client.netty.DefaultHttpClient - ----
19:38:58.989 [main] ERROR i.m.f.a.p.AbstractLambdaContainerHandler - Error while handling request
io.micronaut.http.client.exceptions.HttpClientResponseException: Error decoding HTTP response body: No request present
at io.micronaut.http.client.netty.DefaultHttpClient$12.channelRead0(DefaultHttpClient.java:2148)
at io.micronaut.http.client.netty.DefaultHttpClient$12.channelRead0(DefaultHttpClient.java:2021)
at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:99)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)

我不确定我使用 HttpClient 是否有问题,或者这是否是 HttpClient 实现中的实际问题。

TL;博士

创建文件

src/main/resources/META-INF/services/io.micronaut.http.HttpResponseFactory

包含以下内容:

io.micronaut.http.simple.SimpleHttpResponseFactory

它将注册一个额外的HttpResponseFactory,该将由任何内部 HTTP 请求使用。

为什么存在这个问题?

默认情况下,Micronaut 函数应用程序注册一个名为MicronautAwsProxyResponseFactory的单个HttpResponseFactory服务。它旨在处理 AWS API 响应,并将所有其他响应委托给存储在ALTERNATE静态字段中的备用HttpResponseFactory。此字段由检查是否有任何其他实例注册在任何META-INF/services/io.micronaut.http.HttpResponseFactory文件中的SoftServiceLoader初始化。

示例应用程序的问题始于DefaultHttpClient行 2072。在这里,我们可以找到表示重定向的redirectExchange可流动对象。它被发布到事件循环,并定义(使用first()方法)如果重定向没有发出任何值作为回报,它应该返回重定向请求或HttpStatus.notFound()的结果。

现在,如果您查看堆栈跟踪的底部,您会发现以下内容:

Caused by: io.micronaut.http.server.exceptions.InternalServerException: No request present
at io.micronaut.function.aws.proxy.model.factory.MicronautAwsProxyResponseFactory.status(MicronautAwsProxyResponseFactory.java:79)
at io.micronaut.http.HttpResponseFactory.status(HttpResponseFactory.java:74)
at io.micronaut.http.HttpResponse.notFound(HttpResponse.java:105)
at io.micronaut.http.client.netty.DefaultHttpClient$12.channelRead0(DefaultHttpClient.java:2059)
... 51 more

它显示了导致异常的确切原因 - 它是io.micronaut.http.HttpResponse.notFound().如果我们查看该方法的实现,我们会发现:

static <T> MutableHttpResponse<T> notFound() {
return HttpResponseFactory.INSTANCE.status(HttpStatus.NOT_FOUND);
}

HttpResponseFactory.INSTANCE变量返回服务加载程序注册的工厂,在我们的例子中是MicronautAwsProxyResponseFactory

现在让我们来看看MicronautAwsProxyResponseFactory如何实现status(status,reason)方法:

@Override
public <T> MutableHttpResponse<T> status(HttpStatus status, String reason) {
final HttpRequest<Object> req = ServerRequestContext.currentRequest().orElse(null);
if (req instanceof MicronautAwsProxyRequest) {
final MicronautAwsProxyResponse<T> response = (MicronautAwsProxyResponse<T>) ((MicronautAwsProxyRequest<Object>) req).getResponse();
return response.status(status, reason);
} else {
if (ALTERNATE != null) {
return ALTERNATE.status(status, reason);
} else {
throw new InternalServerException("No request present");
}
}
}

当前请求不是MicronautAwsProxyRequest的实例,因此流转到else分支。在这里,我们得到未设置ALTERNATE,这就是抛出异常的原因。

在服务加载器中注册io.micronaut.http.simple.SimpleHttpResponseFactory时,上述流不会抛出异常,会返回ALTERNATE.status(status,reason)的结果。

这种极端情况不容易检测,尤其是其他模块(如http-server-netty)注册自己的HttpResponseFactory,并且一切正常。例如,如果您将http-server-netty依赖项添加到项目中,它将毫无问题地工作,因为它注册了处理所有状态而不会引发异常io.micronaut.http.server.netty.NettyHttpResponseFactory

我想像micronaut-http-client这样的模块可能会为开箱即用的服务加载器注册此io.micronaut.http.simple.SimpleHttpResponseFactory,但这是Micronaut开发团队的问题。但是,它可能会引入一些副作用,因此我只会在应用程序中为服务加载程序注册此工厂。

PS:如果要调试问题,可以使用调试器运行如下所示的单元测试。

package demo;
import com.amazonaws.serverless.exceptions.ContainerInitializationException;
import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder;
import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext;
import com.amazonaws.serverless.proxy.model.AwsProxyResponse;
import com.amazonaws.services.lambda.runtime.Context;
import io.micronaut.context.ApplicationContext;
import io.micronaut.function.aws.proxy.MicronautLambdaContainerHandler;
import io.micronaut.http.HttpMethod;
import org.junit.jupiter.api.Test;
class ExampleControllerTest {
static MicronautLambdaContainerHandler handler;
static Context lambdaContext = new MockLambdaContext();
static {
try {
handler = new MicronautLambdaContainerHandler(
ApplicationContext.build()
);
} catch (ContainerInitializationException e) {
e.printStackTrace();
}
}
@Test
void test() {
AwsProxyRequestBuilder builder = new AwsProxyRequestBuilder("/", HttpMethod.GET.toString());
AwsProxyResponse response = handler.proxy(builder.build(), lambdaContext);
System.out.println(response.getBody());
}
}

最新更新