繁体   English   中英

Micronaut HTTP 客户端无法绑定缺少 Content-Type 标头的响应

[英]Micronaut HTTP Client Fails to Bind Response that is Missing Content-Type Header

过去,我已经成功地将 Micronaut HTTP 客户端与几个不同的外部服务一起使用。 但是,我真的在为一项外部服务而苦苦挣扎。 我认为这可能与来自外部服务的响应不包含Content-Type标头这一事实有关,但我不确定。

客户端和响应类型在同一个 groovy 文件中定义。

package us.cloudcard.api.transact

import groovy.transform.ToString
import io.micronaut.http.HttpResponse
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Body
import io.micronaut.http.annotation.Post
import io.micronaut.http.annotation.Produces
import io.micronaut.http.client.annotation.Client

import javax.validation.constraints.NotNull

@Client('${transact.url}')
interface TransactAuthenticationClient {

    @Post
    @Produces(MediaType.TEXT_PLAIN)
    HttpResponse<TransactAuthenticationResponse> authenticate(@NotNull @Body String token)
}

@ToString
class TransactAuthenticationResponse {
    Boolean Expired
    String InstitutionId
    String UserName
    String customCssUrl
    String email
    String role
}

我正在使用一个简单的控制器对其进行测试,该控制器只调用客户端并呈现响应状态和正文。

package us.cloudcard.api

import grails.compiler.GrailsCompileStatic
import grails.converters.JSON
import grails.plugin.springsecurity.annotation.Secured
import io.micronaut.http.HttpResponse
import org.springframework.beans.factory.annotation.Autowired
import us.cloudcard.api.transact.TransactAuthenticationClient
import us.cloudcard.api.transact.TransactAuthenticationResponse

@GrailsCompileStatic
@Secured("permitAll")
class MyController {
    static responseFormats = ['json', 'xml']

    @Autowired
    TransactAuthenticationClient transactAuthenticationClient

    def show(String id) {
        String goodToken = "5753D...REDACTED...647F"
        HttpResponse response = transactAuthenticationClient.authenticate(goodToken)
        TransactAuthenticationResponse authenticationResponse = response.body()
        log.error("status: ${response.status()} body: $authenticationResponse")
        render "status: ${response.status()} body: $authenticationResponse"
    }

}

但是,我得到的结果是

status: OK body: null

在 Postman 中发出相同的请求会得到正确的响应邮递员请求

调试时,我可以检查 HttpResponse 对象并查看所有正确的标头,因此我知道我正在成功发出请求。 我只是无法绑定响应。 在此处输入图像描述

我尝试将客户端更改为绑定到String

    @Post
    @Produces(MediaType.TEXT_PLAIN)
    HttpResponse<String> authenticate(@NotNull @Body String token)

我得到了以下回复

status: OK body: PooledSlicedByteBuf(ridx: 0, widx: 176, cap: 176/176, unwrapped: PooledUnsafeDirectByteBuf(ridx: 484, widx: 484, cap: 513))

这很有趣,因为widx: 176, cap: 176/176与成功响应的内容长度完美匹配。

我真的很茫然,所以我很感激你能提供任何帮助。

在此先感谢您的帮助!

TL;DR:Micronaut HTTP 客户端不适用于此 API

Micronaut HTTP 客户端无法使用响应中不包含content-type标头的 API。 我与 Jeff Scott Brown 讨论过这个问题,这就是 Micronaut 的设计方式。 如果响应中没有content-type标头,客户端将不知道如何解析响应正文。

解决方法

package us.cloudcard.api.transact

import groovy.json.JsonSlurper
import groovy.transform.ToString
import org.apache.http.client.methods.CloseableHttpResponse
import org.apache.http.client.methods.HttpPost
import org.apache.http.entity.StringEntity
import org.apache.http.impl.client.CloseableHttpClient
import org.apache.http.impl.client.HttpClientBuilder
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component

@Component
class TransactAuthenticationClient {

    @Value('${transact.url}')
    String transactAuthenticationUrl

    TransactAuthenticationResponse workaround2(String token) {
        HttpPost post = new HttpPost(transactAuthenticationUrl)
        post.addHeader("content-type", "text/plain")
        post.setEntity(new StringEntity(token))

        CloseableHttpClient client = HttpClientBuilder.create().build()
        CloseableHttpResponse response = client.execute(post)

        def bufferedReader = new BufferedReader(new InputStreamReader(response.getEntity().getContent()))
        def json = bufferedReader.getText()
        println "response: \n" + json

        def resultMap = new JsonSlurper().parseText(json)
        return new TransactAuthenticationResponse(resultMap)
    }
}

@ToString(includeNames = true)
class TransactAuthenticationResponse {
    Boolean Expired
    String InstitutionId
    String UserName
    String customCssUrl
    String email
    String role
}

仅供参考:在找到上述解决方法之前,我尝试了这个,但它也不起作用。

    TransactAuthenticationResponse thisAlsoDoesNotWork (String token) {
        String baseUrl = "https://example.com"
        HttpClient client = HttpClient.create(baseUrl.toURL())
        HttpRequest request = HttpRequest.POST("/path/to/endpoint", token)
        HttpResponse<String> resp = client.toBlocking().exchange(request, String)
        String json = resp.body()
        println "json: $json"
        ObjectMapper objectMapper = new ObjectMapper()
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
        TransactAuthenticationResponse response = objectMapper.readValue(json, TransactAuthenticationResponse)
        return response
    }

遇到同样的问题,这是我迄今为止找到的最佳解决方案:

import io.micronaut.core.convert.ConversionService;
import io.micronaut.core.convert.TypeConverter;
import io.netty.buffer.ByteBuf;

// (...)

ConversionService.SHARED.addConverter(ByteBuf.class, String.class, new TypeConverter<ByteBuf, String>() {
      @Override
      public Optional<String> convert(ByteBuf object, Class<String> targetType, ConversionContext context) {
          return Optional.ofNullable(object).map(bb -> bb.toString(StandardCharsets.UTF_8));
      }
});
HttpRequest<String> req = HttpRequest.POST("<url>", "<body>");
// res is instance of io.micronaut.http.client.netty.FullNettyClientHttpResponse which uses the shared conversion service as "last chance" to convert the response body
HttpResponse<String> res = httpClient.toBlocking().exchange(req);
String responseBody = res.getBody(String.class).get();

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM