[英]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.我真的很茫然,所以我很感激你能提供任何帮助。
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
标头,客户端将不知道如何解析响应正文。
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
}
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.