I am trying to send the below JSON via WebClient to an endpoint. I tried sending it as a POJO, a Map, and a JsonNode for the request body but the array is converted to the index-based format.
JSON to be sent;
{
"jediId":"23",
"name":luke,
"master":"yoda",
"address":[
{
"planet":"tatooine",
"city":"mos eisley"
}
],
"filters":{
"male":1,
"female":1,
"padawan":0,
"master":1
}
}
On the other side, the address is converted to address[0].planet=tatooine, address[0].city=mos eisley i.e the format changes
I created a JsonNode with the Jackson and printed the exact format but the array changes in the response.
public Jedi sendRequest(String url, JsonNode request) {
return webClient.post()
.uri(url)
//.bodyValue(request)
.body(BodyInserters.fromValue(request))
.retrieve()
.bodyToMono(Jedi.class)
.blockOptional()
.orElseThrow();
}
}
A similar format change happens to "filters" as well. For example, filters.male=1
I tried both body and bodyValue functions but the result is the same. I am missing something but could not figure out what it is. Any help would be great.
Thanks!
You need to send it as a String that holds valid JSON format. SpringBoot uses JSON Jackson lib and will parse it. It is a responsibility of your SpringBoot end point to parse it. So, really you should ask person who developed the endpoint what info is expected. But in most cases it is String that holds valid json. It might be required that json conforms to particular schema but that is the info that should be included in your endpoint requirments
Thanks to Michael for stating the point of using String in the request. I converted the whole JSON into a String but what I should have done was convert the "filters" and "address" into a String as well.
private ObjectNode setRequestWithMapper() throws JsonProcessingException {
ObjectNode request = objectMapper.createObjectNode();
//other key values ..
String AddressArrayStr = objectMapper.writeValueAsString(addressArray);
String filterStr = objectMapper.writeValueAsString(filters);
request.put("address", AddressArrayStr );
request.put("filters", filtersStr);
return request;
}
And for the body;
public Jedi sendRequest(String url, JsonNode request) {
return webClient.post()
.uri(url)
.bodyValue(request)
.retrieve()
.bodyToMono(Jedi.class)
.blockOptional()
.orElseThrow();
}
}
Related
I'm trying to use Spring WebClient to make some basic REST API calls. I'm getting an error that the request is malformed, but I can't tell exactly why. Is there any way to easily log the contents of the request (really, just the request body)? Everything I find online is super complicated. Here's what I have:
LinkedMultiValueMap params = new LinkedMultiValueMap();
params.add("app_id", getOneSignalAppId());
params.add("included_segments", inSegment);
params.add("content_available", true);
params.add("contents", new LinkedMultiValueMap() {{
add("en", inTitle);
}});
BodyInserters.MultipartInserter inserter = BodyInserters.fromMultipartData(params);
WebClient client = WebClient.builder()
.baseUrl("https://onesignal.com")
.defaultHeader(HttpHeaders.AUTHORIZATION, "Basic " + getOneSignalKey())
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.build();
Mono<NotificationResponse> result = client
.post()
.uri("/api/v1/notifications")
.body(inserter)
.retrieve()
.bodyToMono(NotificationResponse.class);
I just want a string of the JSON that will be inserted into the request body.
You can create your own wrapper/proxy class around the JSON encoder (assuming you're using JSON) and intercept the serialized body before it is sent into the intertubes.
If your request is going to send JSON.
Specifically, you would extend the encodeValue method (or encodeValues in case of streaming data) of Jackson2JsonEncoder (the default encoder). Then you can do with that data what you wish, such as logging etc. And you could even do this conditionally based on environment/profile.
This custom logging-encoder can be specified when creating the WebClient, by providing it as a codec:
CustomBodyLoggingEncoder bodyLoggingEncoder = new CustomBodyLoggingEncoder();
WebClient.builder()
.codecs(clientDefaultCodecsConfigurer -> {
clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonEncoder(bodyLoggingEncoder);
clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(new ObjectMapper(), MediaType.APPLICATION_JSON));
})
...
I made a blog post about this. You might be able to find the encoder for Multipart data and apply similar principles
For completeness, the encoder might look something like this:
public class CustomBodyLoggingEncoder extends Jackson2JsonEncoder {
#Override
public DataBuffer encodeValue(final Object value, final DataBufferFactory bufferFactory,
final ResolvableType valueType, #Nullable final MimeType mimeType, #Nullable final Map<String, Object> hints) {
// Encode/Serialize data to JSON
final DataBuffer data = super.encodeValue(value, bufferFactory, valueType, mimeType, hints);
// This is your code:
SomethingAmazing.doItWithThisData(extractBytes(data));
// Return the data as normal
return data;
}
private byte[] extractBytes(final DataBuffer data) {
final byte[] bytes = new byte[data.readableByteCount()];
data.read(bytes);
// We've copied the data above to our array, but must reset the buffer for actual usage
data.readPosition(0);
return bytes;
}
}
Hope that helps somehow!
i'm trying to send a POST request with body data as described here: https://scrapyrt.readthedocs.io/en/stable/api.html#post.
Here's what i've tried to do but it gives me HTTP code 500
String uri = "http://localhost:3000";
WebClient webClient = WebClient.builder()
.baseUrl(uri)
.build();
LinkedMultiValueMap map = new LinkedMultiValueMap();
String q = "\"url\": \"https://blog.trendmicro.com/trendlabs-security-intelligence\",\"meta\":{\"latestDate\" : \"18-05-2020\"}}";
map.add("request", q);
map.add("spider_name", "blog");
BodyInserter<MultiValueMap<String, Object>, ClientHttpRequest> inserter2
= BodyInserters.fromMultipartData(map);
Mono<ItemsList> result = webClient.post()
.uri(uriBuilder -> uriBuilder
.path("/crawl.json")
.build())
.body(inserter2)
.retrieve()
.bodyToMono(ItemsList.class);
ItemsList tempItems = result.block();
Here's what i've tried to do but it gives me HTTP code 500
Most likely because you're sending the wrong data in a mixture of wrong formats with the wrong type:
You're using multipart form data, not JSON
You're then setting the request parameter as a JSON string (q)
The JSON string you're using in q isn't even valid (it's at least missing an opening curly brace) - and handwriting JSON is almost universally a bad idea, leverage a framework to do it for you instead.
Instead, the normal thing to do would be to create a POJO structure that maps to your request, so:
public class CrawlRequest {
private CrawlInnerRequest request;
#JsonProperty("spider_name")
private String spiderName;
//....add the getters / setters
}
public class CrawlInnerRequest {
private String url;
private String callback;
#JsonProperty("dont_filter")
private String dontFilter;
//....add the getters / setters
}
...then simply create a CrawlRequest, set the values as you wish, then in your post call use:
.body(BodyInserters.fromValue(crawlRequest))
This is a rather fundamental, basic part of using a WebClient. I'd suggest reading around more widely to give yourself a better understanding of the fundamentals, it will help tremendously in the long run.
For me following code worked:
public String wcPost(){
Map<String, String> bodyMap = new HashMap();
bodyMap.put("key1","value1");
WebClient client = WebClient.builder()
.baseUrl("domainURL")
.build();
String responseSpec = client.post()
.uri("URI")
.headers(h -> h.setBearerAuth("token if any"))
.body(BodyInserters.fromValue(bodyMap))
.exchange()
.flatMap(clientResponse -> {
if (clientResponse.statusCode().is5xxServerError()) {
clientResponse.body((clientHttpResponse, context) -> {
return clientHttpResponse.getBody();
});
return clientResponse.bodyToMono(String.class);
}
else
return clientResponse.bodyToMono(String.class);
})
.block();
return responseSpec;
}
I need help in the coding of a program in spring which takes in a JSON String(key-value Pair ) as an input and gives out the Updated JSON string
I'm assuming you are asking in terms of REST, if yes then refer below snippet -
#Controller
public class JSONTest {
// Below method will take iput as JSON
#RequestMapping(value="/getData",consumes = MediaType.APPLICATION_JSON_VALUE)
public void consumeJson(#RequestBody String s) {
System.out.println("json body : " + s);
}
// Below method will produce output as JSON
#RequestMapping(value="/sendData",produces= MediaType.APPLICATION_JSON_VALUE)
public String sendJson() {
//Create jsonObject
//do some things with jsonObject, put some header information in jsonObject
return jsonObject.toString();
}
}
The Spring method I wanna test
#RequestMapping(value="/files", method=RequestMethod.GET)
#ResponseBody
public List<FileListRequest> get() {
return getMainController().getAllFiles();
}
I want to be assured all calls to /files are responded with an List[FileListRequest]. How?
This is the method in which the test is supposed to be.
#Test
public void testGetAll() throws Exception {
this.mockMvc.perform(get("/files").accept("application/json"))
.andExpect(status().isOk())
.andExpect(content().contentType(SOMETHING);
}
Can I simply replace the SOMETHING or am I totally wrong?
Can I run assert methods on the object returned by perform()?
Edit:
MvcResult result = this.mockMvc.perform(get("/files").accept("application/json"))
.andExpect(status().isOk())
.andReturn();
String content = result.getResponse().getContentAsString();
// Convert json String to Respective object by using Gson or Jackson
ObjectMapper mapper = new ObjectMapper();
TypeFactory typeFactory=objectMapper.getTypeFactory();
List<SomeClass> someClassList =mapper.readValue(content , typeFactory.constructCollectionType(List.class, SomeClass.class));
//Assert here with your list
You could use Json Path to check if specific data exist in your response
a code snipper from by old project
mockMvc.perform(get("/rest/blogs")) .contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.blogs[*].title",
hasItems(endsWith("Title A"), endsWith("Title B"))))
.andExpect(status().isOk());
You cannot use contentType to check the class of instances. The Content-Type is to determine the format of text sent/returned in a HTTP(S) request/response, and has nothing to do with programmatic type-check. It only regulates that the request/response is in json/text-plain/xml, etc.
To check the type of the objects returned in the response, let's assume that the response is in format JSON(built-in Jackson in Spring boot will do the (un)marshalling), and we just use org.hamcrest.Matchers.instanceOf(Class<?> type) to check the class of first item in the list, with jsonPath.
A working snippet:
import static org.hamcrest.Matchers.instanceOf;
...
#Test
public void testBinInfoControllerInsertBIN() throws Exception {
when(this.repository.save(mockBinInfo)).thenReturn(mockBinInfo);
this.mockMvc.perform(post("/insert")
.content("{\"id\":\"42\", \"bin\":\"touhou\", \"json_full\":\"{is_json:true}\", \"createAt\":\"18/08/2018\"}")
.accept(MediaType.APPLICATION_JSON_UTF8_VALUE)
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
)
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andExpect(status().isCreated())
.andExpect(jsonPath("$[0]", instanceOf(BinInfo.class)))
.andExpect(jsonPath("$[0].bin", is("touhou")));
}
If you want to check every item in the list... maybe it is redundant? I haven't seen code examining each and every item in the list because you have to iterate. There is way, of course.
I am new to web programming in general, especially in Java, so I just learned what a header and body is.
I'm writing RESTful services using Spring MVC. I am able to create simple services with the #RequestMapping in my controllers. I need help understanding how to get HTTP header information from a request that comes to my method in my REST service controller. I would like to parse out the header and get some attributes from it.
Could you explain how I go about getting that information?
When you annotate a parameter with #RequestHeader, the parameter retrieves the header information. So you can just do something like this:
#RequestHeader("Accept")
to get the Accept header.
So from the documentation:
#RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(#RequestHeader("Accept-Encoding") String encoding,
#RequestHeader("Keep-Alive") long keepAlive) {
}
The Accept-Encoding and Keep-Alive header values are provided in the encoding and keepAlive parameters respectively.
And no worries. We are all noobs with something.
You can use the #RequestHeader annotation with HttpHeaders method parameter to gain access to all request headers:
#RequestMapping(value = "/restURL")
public String serveRest(#RequestBody String body, #RequestHeader HttpHeaders headers) {
// Use headers to get the information about all the request headers
long contentLength = headers.getContentLength();
// ...
StreamSource source = new StreamSource(new StringReader(body));
YourObject obj = (YourObject) jaxb2Mashaller.unmarshal(source);
// ...
}
My solution in Header parameters with example is user="test" is:
#RequestMapping(value = "/restURL")
public String serveRest(#RequestBody String body, #RequestHeader HttpHeaders headers){
System.out.println(headers.get("user"));
}
You can use HttpEntity to read both Body and Headers.
#RequestMapping(value = "/restURL")
public String serveRest(HttpEntity<String> httpEntity){
MultiValueMap<String, String> headers =
httpEntity.getHeaders();
Iterator<Map.Entry<String, List<String>>> s =
headers.entrySet().iterator();
while(s.hasNext()) {
Map.Entry<String, List<String>> obj = s.next();
String key = obj.getKey();
List<String> value = obj.getValue();
}
String body = httpEntity.getBody();
}