I've successfully used the Micronaut HTTP Client with several different external services in the past. 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.
The client and response type are defined in the same groovy file.
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
When I debug, I can inspect the HttpResponse object and see all the correct headers, so I know I'm making the request successfully. I just can't bind the response.
I tried changing the client to bind to a 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.
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. I talked with Jeff Scott Brown about this, and that's just how Micronaut is designed. If there's no content-type
header in the response, the client won't know how to parse the response body.
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();
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.