![](/img/trans.png)
[英]REST Web Service: Acceptable HTTP response content-type when responding with status 4XX (Client Error)
[英]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
調試時,我可以檢查 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
與成功響應的內容長度完美匹配。
我真的很茫然,所以我很感激你能提供任何幫助。
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.