I'm currently asking you a question because I'm using a spring to write a test code that I don't know how to solve.
The problem i'm trying to solve now is that i don't know how to send DTO to send Form Post Function using BodyInserters.
The codes below are my DTO and test codes.
StudentInfo studentInfo = StudentInfo.builder()
.studentId(2014l)
.name("s1")
.email("a#b.c")
.phoneNumber("1234")
.universityCode("cbnu")
.build();
ContestInfo contestInfo = ContestInfo.builder()
.name("contest 1")
.description("easy contest")
.activeTime(LocalDateTime.now())
.inActiveTime(LocalDateTime.now())
.startTime(LocalDateTime.now())
.endTime(LocalDateTime.now())
.freezeTime(LocalDateTime.now())
.unFreezeTime(LocalDateTime.now())
.build();
WebTestClient.ResponseSpec responseSpec = webTestClient.post()
.uri("/questions")
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(BodyInserters.fromFormData("contest", AcceptanceTestUtils.ConvertObjectToMap(contestInfo).toString())
.with("student", AcceptanceTestUtils.ConvertObjectToMap(studentInfo).toString())
.with("activeTime", String.valueOf(LocalDateTime.now()))
.with("problemNumber", String.valueOf(1L))
.with("content", "test")
.with("response", " ")
.with("createTime", String.valueOf(LocalDateTime.now())))
.exchange()
.expectStatus()
.isCreated()
.expectHeader().valueMatches("location", "/questions/[1-9]+[0-9]*");
Especially among the parts of the test code.
.body ("contest", AcceptanceTestUtils.ConvertObjectToMap").toString()
.with ("student", AcceptanceTestUtils.ConvertObjectToMap).toString()
These are the two lines that I've been thinking about.
I thought I could convert the DTO into a map and deliver the string again, but there is a problem that keeps not being delivered.
contest=%7BactiveTime%3D2020-05-20T12%3A31%3A01.718%2C+name%3Dcontest+1%2C+description%3Deasy+contest%2C+startTime%3D2020-05-20T12%3A31%3A01.719%2C+freezeTime%3D2020-05-20T12%3A31%3A01.719%2C+endTime%3D2020-05-20T12%3A31%3A01.719%2C+unFreezeTime%3D2020-05-20T12%3A31%3A01.719%2C+inActiveTime%3D2020-05-20T12%3A31%3A01.719%7D&student=%7BstudentId%3D2014%2C+phoneNumber%3D1234%2C+name%3Ds1%2C+universityCode%3Dcbnu%2C+email%3Da%40b.c%7D&activeTime=2020-05-20T12%3A31%3A01.749&problemNumber=1&content=test&response=+&createTime=2020-05-20T12%3A31%3A01.749
The values transferred are as above. How can I fix this?
Related
I want to be able to extract the List<Payload> from the Mono<List<Payload>> to pass it to a downstream service for processing (or maybe return from the read(RequestParams params) method, instead of it returning void):
#PostMapping("/subset")
public void read(#RequestBody RequestParams params){
Mono<List<Payload>> result = reader.read(params.getDate(), params.getAssetClasses(), params.getFirmAccounts(), params.getUserId(), params.getPassword());
....
}
where reader.read(...) is a method on an autowired Spring service utilizing a webClient to get the data from external web service API:
public Mono<List<Payload>> read(String date, String assetClasses, String firmAccounts, String id, String password) {
Flux<Payload> nodes = client
.get()
.uri(uriBuilder -> uriBuilder
.path("/api/subset")
.queryParam("payloads", true)
.queryParam("date", date)
.queryParam("assetClasses", assetClasses)
.queryParam("firmAccounts", firmAccounts)
.build())
.headers(header -> header.setBasicAuth("abc123", "XXXXXXX"))
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> {
System.out.println("4xx error");
return Mono.error(new RuntimeException("4xx"));
})
.onStatus(HttpStatus::is5xxServerError, response -> {
System.out.println("5xx error");
return Mono.error(new RuntimeException("5xx"));
})
.bodyToFlux(Payload.class);
Mono<List<Payload>> records = nodes
.collectList();
return records;
}
Doing a blocking result.block() is not allowed in WebFlux and throws an exception:
new IllegalStateException("block()/blockFirst()/blockLast() are blocking, which is not supported in thread ..." ;
What is a proper way to extract the contents of a Mono in WebFlux?
Is it some kind of a subscribe()? What would be the syntax?
Thank you in advance.
There is no "proper way" and that is the entire point. To get the value you need to block, and blocking is bad in webflux for many reasons (that I won't go into right now).
What you should do is to return the publisher all the way out to the calling client.
One of the things that many usually have a hard time understanding is that webflux works with a producer (Mono or Flux) and a subscriber.
Your entire service is also a producer, and the calling client can be seen as the subscriber.
Think of it as a long chain, that starts at the datasource, and ends up in the client showing the data.
A simple rule of thumb is that whomever is the final consumer of the data is the subscriber, everyone else is a producer.
So in your case, you just return the Mono<List<T> out to the calling client.
#PostMapping("/subset")
public Mono<List<Payload>> read(#RequestBody RequestParams params){
Mono<List<Payload>> result = reader.read(params.getDate(), params.getAssetClasses(), params.getFirmAccounts(), params.getUserId(), params.getPassword());
return result;
}
While the following does return the value of the Mono observable in the logs:
#PostMapping("/subset")
#ResponseBody
public Mono<ResponseEntity<List<Payload>>> read1(#RequestBody RequestParams params){
Mono<List<Payload>> result = reader.read(params.getDate(), params.getAssetClasses(), params.getFirmAccounts(), params.getUserId(), params.getPassword());
return result
.map(e -> new ResponseEntity<List<PayloadByStandardBasis>>(e, HttpStatus.OK));
}
the understanding I was seeking was a proper way to compose a chain of calls, with WebFlux, whereby a response from one of its operators/legs (materialized as as a result from a webclient call, producing a set of records, as above) could be passed downstream to another operator/leg to facilitate a side effect of saving those records in a DB, or something to that effect.
It would probably be a good idea to model each of those steps as a separate REST endpoint, and then have another endpoint for a composition operation which internally calls each independent endpoint in the right order, or would other design choices be more preferred?
That is ultimately the understanding I was looking for, so if anyone wants to share an example code as well as opinions to better implement the set of steps described above, I'm willing to accept the most comprehensive answer.
Thank you.
I am a newbie to reactive programming in Java. I plan to use spring-webclient instead of restclient as the latter is being decommissioned. I have a situation when I make several http post requests to different endpoints and the response structure is identical. With webclient code as below,
List<Mono<CommonResponse>> monolist = new ArrayList<>();
for(String endpoint : endpoints) {
Mono<CommonResponse> mono = webClient.post()
.uri(URI.create(endPoint))
.body(Mono.just(requestData), RequestData.class)
.retrieve()
.bodyToMono(CommonResponse.class);
monolist.add(mono);
}
I get a mono per request. As the response is common, I would like each mono to be subscribed a common method, but how can I distinguish the endpoints, assuming that the response data is not helping.
Can I pass additional arguments to the method while subscribing?
You can do this in following way. If you have many monos you can treat team as flux which actually means that you have many of Mono. Then you can subscribe all of them with single method. To pass to subscribing method some extra arguments like information about endpoint, you can create extra object with additional information.
Flux<ResponseWithEndpoint> commonResponseFlux = Flux.fromIterable(endpoints)
.flatMap(endpoint -> webClient.post()
.uri(URI.create(endpoint))
.body(Mono.just(requestData), RequestData.class)
.retrieve()
.bodyToMono(CommonResponse.class)
.map(response -> new ResponseWithEndpoint(response, endpoint)));
...
class ResponseWithEndpoint {
CommonResponse commonResponse;
String endpoint;
public ResponseWithEndpoint(CommonResponse commonResponse, String endpoint) {
this.commonResponse = commonResponse;
this.endpoint = endpoint;
}
}
I have the following controller method:
#PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, path = "/upload")
public Mono<SomeResponse> saveEnhanced(#RequestPart("file") Mono<FilePart> file) {
return documentService.save(file);
}
which calls a service method where I try to use a WebClient to put some data in another application:
public Mono<SomeResponse> save(Mono<FilePart> file) {
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.asyncPart("file", file, FilePart.class);
bodyBuilder.part("identifiers", "some static content");
return WebClient.create("some-url").put()
.uri("/remote-path")
.syncBody(bodyBuilder.build())
.retrieve()
.bodyToMono(SomeResponse.class);
}
but I get the error:
org.springframework.core.codec.CodecException: No suitable writer found for part: file
I tried all variants of the MultipartBodyBuilder (part, asyncpart, with or without headers) and I cannot get it to work.
Am I using it wrong, what am I missing?
Regards,
Alex
I found the solution after getting a reply from one of the contributes on the Spring Framework Github issues section.
For this to work:
The asyncPart method is expecting actual content, i.e. file.content(). I'll update it to unwrap the part content automatically.
bodyBuilder.asyncPart("file", file.content(), DataBuffer.class)
.headers(h -> {
h.setContentDispositionFormData("file", file.name());
h.setContentType(file.headers().getContentType());
});
If both headers are not set then the request will fail on the remote side, saying it cannot find the form part.
Good luck to anyone needing this!
I am developing corda application using kotlin. I am on webserver spring boot. My requirment is to recieve(postman or any API from outside) XML in requestBody and create IOU(pass) that xml as it is to Flow but i am not able to so. when i define it as a string and pass that xml i am able to do it. but for xml i am struggling. Can anyone help please. Below is my code. am doing anything wrong here. The problem is- i dont get error but it just doesnt work.
#PostMapping(value = ["createTransaction"],consumes = [MediaType.APPLICATION_XML_VALUE],produces = [ MediaType.TEXT_PLAIN_VALUE])
private fun TransactionOne(#RequestBody ()employee:Document, #RequestParam(value = "payload") payload: String, #RequestParam(value = "partyName") partyName: String): ResponseEntity<String> {
val partyX500Name = CordaX500Name.parse(partyName)
val otherParty = proxy.wellKnownPartyFromX500Name(partyX500Name) ?: return ResponseEntity.badRequest().body("Party named $partyName cannot be found.\n")
return try {
val signedTx = proxy.startTrackedFlow(::IOUFlow, employee, otherParty).returnValue.getOrThrow()
ResponseEntity.status(HttpStatus.CREATED).body("Transaction id {$signedTx} committed to ledger.\n")
} catch (ex: Throwable) {
logger.error(ex.message, ex)
ResponseEntity.badRequest().body(ex.message!!)
}
}
This is not clear what does it mean "just doesn't work". You don't have response from "startTrackedFlow"?
What do you see in logs of your nodes? I think the answer is there.
There is not enough information to help you...
I am new to RESTful services and their implementation on Spring 3. I would like your opinion on the best practices for returning type when a client creates a new resource in my server.
#RequestMapping(method = RequestMethod.POST,
value = "/organisation",
headers = "content-type=application/xml")
#ResponseStatus(HttpStatus.CREATED)
public ??? createOrganisation(#RequestBody String xml)
{
StreamSource source = new StreamSource(new StringReader(xml));
Organisation organisation = (Organisation) castorMarshaller.unmarshal(source);
// save
return ???;
}
A simple choice would be javax.ws.rs.core.Response, found in the Java EE's own restful services package. It - simply - tells what the web server should answer to the HTTP request.
For instance:
if (organisation != null)
return Response.ok().build();
else
return Response.serverError().build();
Custom response headers and other exotic things like that are possible with that return type too, but I don't think that would match with "best practices".
uh, I missed that #ResponseStatus(HttpStatus.CREATED)... I guess my answer was not much of help.
Maybe this will help instead: How to return generated ID in RESTful POST?
I would go for a ResponseEntity<byte[]> and you would have take care of the marshalling of your response on your controller method. Notice that you are basically scrapping the V in MVC, there is a MarshallingView on Spring but from experience I consider the previous solution much more flexible and easier to understand.
It is a good idea to return the newly created entity(with the generated id) wrapped in ResponseEntity. You can also set the HttpStatus in ResponseEntity based on the result of the operation.
#RequestMapping(method = RequestMethod.POST,
value = "/organization",
headers = "content-type=application/xml")
public ResponseEntity<Organization> createOrganisation(#RequestBody String xml) {
StreamSource source = new StreamSource(new StringReader(xml));
Organization organisation = (Organization) castorMarshaller.unmarshal(source);
// save
return new ResponseEntity<Organization>(organization, HttpStatus.OK);
}