简体   繁体   English

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

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

I've successfully used the Micronaut HTTP Client with several different external services in the past.过去,我已经成功地将 Micronaut HTTP 客户端与几个不同的外部服务一起使用。 However, I'm really struggling with one external service.但是,我真的在为一项外部服务而苦苦挣扎。 I think it might be related to the fact that the response from the external service does not contain a Content-Type header, but I'm not sure.我认为这可能与来自外部服务的响应不包含Content-Type标头这一事实有关,但我不确定。

The client and response type are defined in the same groovy file.客户端和响应类型在同一个 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
}

I'm testing it with a simple controller that just calls the client and renders the response status and body.我正在使用一个简单的控制器对其进行测试,该控制器只调用客户端并呈现响应状态和正文。

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"
    }

}

However, the result I get is但是,我得到的结果是

status: OK body: null

Making the same request in Postman results in the correct response在 Postman 中发出相同的请求会得到正确的响应邮递员请求

When I debug, I can inspect the HttpResponse object and see all the correct headers, so I know I'm making the request successfully.调试时,我可以检查 HttpResponse 对象并查看所有正确的标头,因此我知道我正在成功发出请求。 I just can't bind the response.我只是无法绑定响应。 在此处输入图像描述

I tried changing the client to bind to a String我尝试将客户端更改为绑定到String

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

and I got the following response我得到了以下回复

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

This was interesting because the widx: 176, cap: 176/176 perfectly matched the content length of the successful response.这很有趣,因为widx: 176, cap: 176/176与成功响应的内容长度完美匹配。

I am really at a loss, so I would appreciate any help you can give.我真的很茫然,所以我很感激你能提供任何帮助。

Thanks in advance for your help!在此先感谢您的帮助!

TL;DR: The Micronaut HTTP Client is not designed to work for this API TL;DR:Micronaut HTTP 客户端不适用于此 API

The Micronaut HTTP Client cannot consume APIs that do not include a content-type header in the response. Micronaut HTTP 客户端无法使用响应中不包含content-type标头的 API。 I talked with Jeff Scott Brown about this, and that's just how Micronaut is designed.我与 Jeff Scott Brown 讨论过这个问题,这就是 Micronaut 的设计方式。 If there's no content-type header in the response, the client won't know how to parse the response body.如果响应中没有content-type标头,客户端将不知道如何解析响应正文。

Work-Around解决方法

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
}

JUST FYI: Before I found the above workaround, I tried this and it also does not work.仅供参考:在找到上述解决方法之前,我尝试了这个,但它也不起作用。

    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
    }

Having the same problem, this is the best solution I found so far:遇到同样的问题,这是我迄今为止找到的最佳解决方案:

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