簡體   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