'WebFlux WebClient: consume a REST service, that returns a JSON body with Content-Type "text/string"
I'm consuming a REST service, that returns a JSON formatted string, but in the header gives the content-type "text/string". I tried to go to the source of the problem, but the code seems to no longer be maintained. If there is no better solution, I need to fork the spaCy Rest Services project and put it in a container of some sort myself...
I have tried all of these solutions already, but the methods used are either deprecated or the solutions don't work for some other reason:
Spring reactive WebClient GET json response with Content-Type "text/plain;charset=UTF-8"
Reactive WebClient GET Request with text/html response
The exception I receive is always: org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'text/string' not supported
My code is this:
public class SpacyClient {
WebClient webClient;
public SpacyClient() {
webClient = WebClient.builder()
.baseUrl("http://localhost:8081")
.build();
}
public List<NamedEntity> spacyNamedEntities(String text) {
HashMap<String, String> requestBody = new HashMap<>();
requestBody.put("text", text);
requestBody.put("model", "en_core_web_lg");
Mono<List<NamedEntity>> responseMono = webClient.post()
.uri("/ent")
.accept(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(requestBody))
.exchangeToMono( response -> {
Mono<List<NamedEntity>> = response.bodyToMono(new ParameterizedTypeReference<List<NamedEntity>>() {});
return lalala;
});
List<NamedEntity> entities = responseMono.block();
return entities;
}
}
Solution 1:[1]
You can register a specific decoder for a custom Mime type on your WebClient instance by using the ClientCodecConfigurer.
WebClient webClient = WebClient.builder()
.baseUrl("http://localhost:8090")
.codecs(clientCodecConfigurer -> clientCodecConfigurer.customCodecs()
.register(new Jackson2JsonDecoder(new ObjectMapper(), new MimeType("text", "string"))))
.build();
Further details on both the client and server codecs provided by spring can be found in the reference documentation
Solution 2:[2]
Here's the workaround I did, just deserializing the string explicitly using Jackson. It's not pretty, but it does work.
public List<NamedEntity> spacyNamedEntities(String text) {
HashMap<String, String> requestBody = new HashMap<>();
requestBody.put("text", text);
requestBody.put("model", "en_core_web_lg");
Mono<String> responseMono = webClient.post()
.uri("/ent")
.body(BodyInserters.fromValue(requestBody))
.retrieve()
.bodyToMono(String.class);
String json = responseMono.block();
ObjectMapper objectMapper = new ObjectMapper();
List<NamedEntity> entities = new ArrayList<>();
try {
entities = objectMapper.readerForListOf(NamedEntity.class).readValue(json);
} catch (JsonProcessingException e) {
// ToDo: show information in frontend
}
return entities;
}
Solution 3:[3]
The reason your function isn't tail-recursive is that a tail-recursive function must return the result of the recursive call as is, without further processing. Your call requests further processing: the xsl:value-of instruction asks for the result to be converted to a text node.
Saxon can sometimes work out that you didn't really want this conversion, but it can only do that if you declare the type of the function result.
So you should make two improvements to your code, both of which are standard good coding practice: (a) use xsl:sequence rather than xsl:value-of to return function results, and (b) use an as attribute on xsl:function and xsl:param to declare the types of the function parameters and result.
Furthermore, as Martin points out, it's a good idea to use xsl:for-each-group or xsl:iterate rather than recursive functions where it makes sense: it usually makes the code more readable and it's often more efficient.
Solution 4:[4]
Assuming XSLT 3 (supported since 9.8) it seems to identify the "chains" you can use group-adjacent:
<xsl:template match="events">
<xsl:copy>
<xsl:for-each-group select="event" composite="yes" group-adjacent="@end => substring-after('T') => xs:integer() - position(), @start => substring-after('T') => xs:integer() - (position() + 1)">
<chain>
<xsl:sequence select="current-group()"/>
</chain>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
https://xsltfiddle.liberty-development.net/nbspVax
A more general and precise declarative grouping implementation might be possible in XSLT using break-when, currently supported by SaxonCS and hopefully soon by Saxon 11:
<xsl:template match="events">
<xsl:copy>
<xsl:for-each-group select="event" break-when="$next/@start => substring-after('T') => xs:integer() gt $group[last()]/@end => substring-after('T') => xs:integer()">
<chain>
<xsl:sequence select="current-group()"/>
</chain>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | Chris |
| Solution 3 | Michael Kay |
| Solution 4 |
