SpringBoot 2.6.2 and form data - spring-boot

I am experimenting with a controller endpoint that looks like this:
#PostMapping("login")
fun login(
#RequestParam username: String,
#RequestParam password: String): ResponseEntity<LoginResponse> {
// ...
}
The request is send from a HTML form looking like this:
<form action="../api/login" method="POST">
<input id="username" type="text" placeholder="Enter Username" name="username" required=""><br>
<input id="password" type="password" placeholder="Enter Password" name="password" required=""><br>
<button type="submit">Login</button>
</form>
This works perfectly will with spring boot version 2.6.1. But after an upgrade to version 2.6.2 and adding spring cloud gateway it all of a sudden does not work any longer.
The log would look like this:
2022-01-11 14:33:09,618 [reactor-http-nio-2] DEBUG o.s.web.method.HandlerMethod - [3d97dc1a-1, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:13027] Could not resolve parameter [0] in public org.springframework.http.ResponseEntity<com.example.models.LoginResponse> com.example.login(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String): 400 BAD_REQUEST "Required String parameter 'username' is not present"
2022-01-11 14:33:09,656 [reactor-http-nio-2] DEBUG org.springframework.web.HttpLogging - [3d97dc1a-1, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:13027] Resolved [ServerWebInputException: 400 BAD_REQUEST "Required String parameter 'username' is not present"] for HTTP POST /api/login
I tried various things like:
#PostMapping(value = ["login"], consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE])
fun login(
#RequestParam paramMap: MultiValueMap<String,String>
): ResponseEntity<LoginResponse> {
//...
}
But also this fails with the following log:
2022-01-11 14:10:11,589 [reactor-http-nio-2] DEBUG o.s.w.s.a.HttpWebHandlerAdapter - [656327b1-1, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:11772] HTTP POST "/api/login"
2022-01-11 14:10:11,601 [reactor-http-nio-2] DEBUG o.s.w.r.r.m.a.RequestMappingHandlerMapping - [656327b1-1, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:11772] Mapped to com.example.ApiController#login(MultiValueMap)
2022-01-11 14:10:12,945 [reactor-http-nio-2] DEBUG o.s.w.r.r.m.a.RequestBodyMethodArgumentResolver - Form data is accessed via ServerWebExchange.getFormData() in WebFlux.
2022-01-11 14:10:21,640 [reactor-http-nio-2] DEBUG o.s.web.method.HandlerMethod - [656327b1-1, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:11772] Could not resolve parameter [0] in public org.springframework.http.ResponseEntity<com.example.models.LoginResponse> com.example.ApiController.login(org.springframework.util.MultiValueMap<java.lang.String, java.lang.String>): 415 UNSUPPORTED_MEDIA_TYPE
I would guess the error message with 415 UNSUPPORTED_MEDIA_TYPE is just misleading and it somehow fails to map the form-data. What can I do to get the API again accept form-data?
Trying something like:
#PostMapping(value = ["login"], consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE])
fun login(#RequestParam paramMap: Map<String,String>): ResponseEntity<LoginCodeResponse> {
// ...
}
Actually get's called but paramMap is always empty.
What actually works is the following:
#RestController
#RequestMapping("api")
class HelloWorldController(){
#GetMapping("hello")
fun helloName(#RequestParam name: String): String {
return "Hello $name!"
}
}
So for a normal get request #RequestParam works as expected.
Update
I seems to boil down to the following. With spring-boot-starter-webflux it seems the #RequestParam for form-data does not work. This seem to be a known issue.
implementation("org.springframework.boot:spring-boot-starter-webflux")
With spring-boot-starter-web it #RequestParam for form-data works.
implementation("org.springframework.boot:spring-boot-starter-web")
But this starter is not compatible with spring cloud. Using both spring-boot-starter-web with setting spring.main.web-application-type=reactive makes spring cloud gateway start with spring-boot-starter-web but still #RequestParam for form-data not working.

To get from data with POST working with webflux I did the following using the nicely provided awaitFormData method:
#PostMapping("authorize", consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE])
suspend fun login(exchange: ServerWebExchange): ResponseEntity<LoginResponse> {
val formData = exchange.awaitFormData()
val username = formData["username"]?.get(0)!!
val password = formData["password"]?.get(0)!!
That is just a sketch of the essentials of the solution. If you want to use this you should also add some proper error handling to check whether parameters are there otherwise you will just have an error 500 which does not tell a lot.

Related

Spring-boot Api endpoint for uploading file not working after adding 'spring-boot-starter-hateoas' dependency

I have a simple API function to upload a file similar to:
#PostMapping(value = "/documents",
consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public Mono<ResponseEntity<String>> uploadDocument(#RequestPart Mono<FilePart> file){
return storeDocumentService
.upload(file)
.map(fileLocation->ResponseEntity.ok(fileLocation))
}
The code works ok and uploads the file. The problem comes when I want to make the response a bit better by returning the link to the uploaded file. For this I want to use HATEOAS 'org.springframework.boot:spring-boot-starter-hateoas'. As soon as I add the dependency 'org.springframework.boot:spring-boot-starter-hateoas' to my 'build.gradle' the endpoint stops working and I get a response:
{
"timestamp": "2023-02-20T04:28:10.620+00:00",
"status": 415,
"error": "Unsupported Media Type",
"path": "/documents"
}
and also I get in the logs:
2023-02-20T05:28:10.618+01:00 WARN 2993 --- [nio-8080-exec-4] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content-Type 'application/pdf' is not supported]
It is important to point out that I upload a ".pdf" file with a header "Content-Type:multipart/form-data". And most important the only change in the working code and not working code is that i just add the dependency for HATEOAS 'org.springframework.boot:spring-boot-starter-hateoas'
For Uploading File We can easily use the type MultiPartFile , This will handles all the types of files and we can easily retrive the fileInputStream(data) from it.
The following code may helps you!..
#PostMapping("uploadExcelData")
public ResponseEntity<?> uploadExcelData(#RequestParam MultipartFile file) throws IOException {
List<...> dataList = fileHandling.convertFileAsJson(file);
if (!dataList.isEmpty()) {
return ....
} else {
return ResponseEntity.ok("No Records found !!");
}
}
I hope the above code will helps you to handle the File in the Endpoint.

how to pass a parameter in the Header with multipartFormData

#PostMapping(value = "/uploadCV" , consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public ResponseEntity<?> uploadCV(#RequestHeader("token") String token,
#RequestParam("file") MultipartFile cvFile) throws Exception {
log.info(token + cvFile.getOriginalFilename()));
return curriculumService.addCurriculum(token, pdfFile);
}
If i use MediaType.MULTIPART_FORM_DATA_VALUE don't get the value "token" :
2022-11-11 16:13:28.040 WARN 7283 --- [nio-8080-exec-4] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MissingRequestHeaderException: Required request header 'token' for method parameter type String is not present]
If i use MediaType.MULTIPART_MIXED_VALUE don't get file:
org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found
how can i do?
The code that you have written with MediaType.MULTIPART_FORM_DATA_VALUE is absolutely correct. It is very clear from error message what is the issue.
2022-11-11 16:13:28.040 WARN 7283 --- [nio-8080-exec-4] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MissingRequestHeaderException: Required request header 'token' for method parameter type String is not present]
Issue:
Code is expecting header name with key token should present in the request but it is not present.
Solution:
Please add header name with key token like this and try again.

rSocket websocket postman testing mime types and endpoints

I am using spring-boot-starter-webflux and spring-boot-starter-rsocket version 2.7.1
The rSocket transport is set to websocket like this:
spring.rsocket.server.transport=websocket
spring.rsocket.server.mapping-path=/rsocket
# this setting has no effect when transport==WEBSOCKET
spring.rsocket.server.port=7000
There's a spring #Controller endpoint #MessageMapping setup for a simple string like:
#MessageMapping("test")
String test() {
Logs.Info("*** Received test ***");
return "tested";
}
I want to get a successful test done with Postman. Run the spring boot app locally and connect to ws://localhost:7000 using mime types
dataMimeType: 'application/json'
metadataMimeType: 'message/x.rsocket.routing.v0'
Like this:
The rsocket websocket connects, but I can't hit the endpoint test
With error 1005 No Status Received: Missing status code even though one was expected
On the server the error is
DEBUG [reactor-http-nio-2] debug: [c4e97d34-1, L:/127.0.0.1:7000 - R:/127.0.0.1:2051] Cancelling Websocket inbound. Closing Websocket
DEBUG [reactor-http-nio-2] debug: [c4e97d34, L:/127.0.0.1:7000 - R:/127.0.0.1:2051] Removed handler: PongHandler, pipeline: DefaultChannelPipeline{(wsencoder = io.netty.handler.codec.http.websocketx.WebSocket13FrameEncoder), (wsdecoder = io.netty.handler.codec.http.websocketx.WebSocket13FrameDecoder), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
DEBUG [reactor-http-nio-2] debug: [c4e97d34, L:/127.0.0.1:7000 ! R:/127.0.0.1:2051] An outbound error could not be processed
java.nio.channels.ClosedChannelException
at reactor.core.publisher.MonoErrorSupplied.call(MonoErrorSupplied.java:61)
at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:228)
at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onComplete(MonoIgnoreThen.java:203)
at reactor.core.publisher.SinkEmptyMulticast$VoidInner.complete(SinkEmptyMulticast.java:238)
at reactor.core.publisher.SinkEmptyMulticast.tryEmitEmpty(SinkEmptyMulticast.java:70)
at reactor.core.publisher.SinkEmptySerialized.tryEmitEmpty(SinkEmptySerialized.java:46)
What's the incorrect setting in Postman?
The answer is don't use postman. Rsocket is a binary protocol, Even though based on Websocket, There are many tools test it.
use spring message write a unit test
#Autowired
private RSocketRequester rSocketRequester;
StepVerifier.create(rSocketRequester
.route("test")
.retrieveMono(String.class))
.expectNext("tested")
.verifyComplete();
RSocket Client CLI (RSC)
rsc --request --route=test --debug ws://localhost:7000/rsocket
Actually the following message was received:
{
"data":"test",
"metadata":4
}
Per screenshot
But now the error on the server side is:
DEBUG [reactor-http-nio-6] lambda$receive$0: receiving ->
Frame => Stream ID: 2064452128 Type: REQUEST_N Flags: 0b100000 Length: 42
RequestN: 539124833
Data:
DEBUG [reactor-http-nio-6] sendErrorAndClose: sending -> InvalidSetupException: SETUP or RESUME frame must be received before any others

Adding body to Http request with Spock

I'm developing a Spring Boot application and I'm trying to do some authorization/authentication testing using Spock and groovyx.net.http.RESTClient. I'm trying to pass username and password inside body block like this:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
class AuthorizationTest extends Specification {
#Shared
def client = new RESTClient("http://localhost:8080")
def "something should happen"() {
when:
def response = client.post(
path: "/login",
body: [ password : "1234", username : "admin"],
requestContentType: ContentType.JSON
)
then:
response.status == 200
}
Sadly, something's not working, and when I'm debugging I don't see the two parameters (username and password) inside the request.
What am I doing wrong?
It turned out I need to use different encoding, requestContentType: ContentType.URLENC, which is of type application/x-www-form-urlencoded.

#RequestHeader behaviour not as expected; working without required User-Agent

I have a project set up using Spring Boot with Kotlin to make REST APIs.
I'm trying to use the #RequestHeader to recognize the User-Agent. The said header is required=true -
#PostMapping("details", produces = ["application/json"])
fun addInfo(#RequestHeader(name = "User-Agent", required = true) userAgent: String,
#Valid #RequestBody podEntity: PodEntity): ResponseEntity<String> {
pod.addPod(podcastEntity)
return ResponseEntity<String>("{ \"status\":\"Added\" }", HttpStatus.CREATED)
}
Problems -
However, even if I'm not sending the User-Agent header, the API is working and adding data. But, if I change the header to any other non default names, like, user or request-source, the required=true requirement is enforced to and the API does not work. Does it mean that default headers cannot be made mandatory using the required tag?
The other problem is that in the case of custom header, when the required header is missing for the request, the API fails by giving 400 error code but does not throw any exception. I was expecting HttpClientErrorException for my junit test case but on checking the console, I see no exception. Adding #Throws is also not helping. How do enable my function to throw an exception when the required header is missing?
Unit test -
#Test
fun test_getAll_fail_missingHeaders() {
val url = getRootUrl() + "/details/all"
val headers = HttpHeaders()
val request = HttpEntity(pod, headers)
try {
restTemplate!!.postForEntity(url, request, String::class.java)
fail()
} catch (ex: HttpClientErrorException) {
assertEquals(400, ex.rawStatusCode);
assertEquals(true, ex.responseBodyAsString.contains("Missing request header"))
}
}

Resources