Using #RequestBody and forwarding to another endpoint throws exception Stream closed - java

My Java spring REST API controller looks like this:
public void signup(#RequestBody RequestBody requestBody) throws IOException, ServletException {
I get this exception:
Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: Stream closed; nested exception is java.io.IOException: Stream closed
This happens because I want to cast the request body to RequestBody class (which opens the request input stream and finishes it), and also forward/redirect it to another endpoint.
The actual controller is:
#RequestMapping(value = "/signup", method = RequestMethod.POST)
public void signup(#RequestBody CustomUserDetails user, HttpServletRequest request, HttpServletResponse response) {
String userName = user.getUsername();
logger.debug("User signup attempt with username: " + userName);
try{
if(customUserDetailsService.exists(userName))
{
logger.debug("Duplicate username " + userName);
userName + " already exists");
String newUrl = "login";
RequestDispatcher view = request.getRequestDispatcher(newUrl);
view.forward(request, response);
} else {
customUserDetailsService.save(user);
authenticateUserAndSetSession(user, response);
}
} catch(Exception ex) {
}
}
How should I handle this ?

You can forward to login page in a ExceptionHandler,like this:
#RequestMapping(value = "/signup", method = RequestMethod.POST)
public void signup(#RequestBody CustomUserDetails user, HttpServletResponse response) {
String userName = user.getUsername();
logger.debug("User signup attempt with username: " + userName);
//try{
if (customUserDetailsService.exists(userName)) {
logger.debug("Duplicate username " + userName);
throw new SignupException(userName + " already exists");
} else {
customUserDetailsService.save(user);
authenticateUserAndSetSession(user, response);
}
/*} catch(Exception ex) {
}*/
}
define a ExceptionHandler in the same Controller:
#ExceptionHandler(SignupException.class)
public String duplicateName() {
return "login";
}
and the SignupException could be like this:
public class SignupException extends RuntimeException {
public SignupException(String message) {
super(message);
}
public SignupException() {
}
}

The request body object is a stream which can be read only once. So forwarding it is not very trivial. One way around this is to create a filter which reads the input steam and replace the input stream to something which can be read multiple times. Example can be found at another answer:
How can I read request body multiple times in Spring 'HandlerMethodArgumentResolver'?
As for your method, there is also another problem:
public void signup(#RequestBody RequestBody requestBody)
As far as I know, RequestBody is an annotation and you can't map it like that. But to get the raw data, you can map it as String.
public void signup(#RequestBody String requestBody)
And then you can just manually make an REST call to the api you want to forward it to using the String request body. Just make sure you set the content-type as the original one, which I assume in this case would be JSON.

Root of your problem is using #RequestBody RequestBody requestBody together with HttpServletRequest request.
Opening input stream twice on the same request is not allowed. In your case a system should open a input stream to extract request body and then propagate in forward to reuse.
To handle it you should avoid multiple usage of the same request stream. Possible solutions are:
Wrap request
Copy request body
Use spring native forward

I think you are trying to forward to a url with the RequestBody, please have a check
Spring 3.2 forward request with new object for the answers.
Create the object and add it to the request as an attribute in the first controller,
request.setAttribute("user",user),
return "forward:/login";

Try putting in request mapping consumes= {" application/json"}, produces={"application/json"}

RequestBody is an annotation to process your request object as expected class deserialization. They help you avoid boilerplate code by extracting the logic of messageconversion and making it an aspect.
You can not get RequestBody directly as an object in any controller. It was not designed to use this way.
Though it is not possible to get RequestBody in a RestController so you can't judge whether it is good or bad practice.
If you have to use new controller/endpoint to process.Then i think the better approach is to get the CustomUserDetails as #RequestBody in the controller then process it. Then call nested method or service to further process instead of thinking forwarding to another controller. Then return response from the controller.

Related

How to pick values from a redirected_url using Springboot

I want to be able to fetch a param from the redirect url whenever it is automated. I am having difficulties doing this as I am getting a bad request after I created another endpoint to effect this.
I have an endpoint that works fine. The endpoint is a get method. Loading the endpoint takes a user to a page where they need to provide some necessary details. Once these details have been verified, the user is redirected to my redirecr_uri. The redirect_uri now contains important information like session_id, code, etc. The most important thing I need is the code. I need to pass the code into yet another endpoint which will return an access token.
I have manually done this process and it works but I want it to be done automatically because I can't keep doing that when I push the code to staging or production.
Here is the endpoint that redirects as well as the method.
#GetMapping("/get-token")
public RedirectView getBvn() throws UnirestException {
return nibss.getAccessToken();
}
This is the method that the controller calls
public RedirectView getAccessToken() throws UnirestException {
String url = "https://idsandbox.nibss-plc.com.ng/oxauth/authorize.htm?scope=profile&acr_values=otp&response" +
"_type=code&redirect_uri=https://www.accionmfb.com/&client_id=0915cd00-67f2-4768-99ac-1b2ff9f1da2e";
RedirectView redirectView = new RedirectView();
redirectView.setUrl(url);
return redirectView;
}
When the user provides the right information they are redirected to something like this
https://www.accionmfb.com/?code=9ad91f13-4698-4030-8a8f-a857e6a9907e&acr_values=otp&scope=profile&session_state=fa525cabc5b62854c73315d0322fd830c12a5941b89fd8e6e518da369e386572.b78a3d21-e98e-4e9a-8d60-afca779d9fad&sid=fd60ab92-ef37-4a5b-99b9-f8f52321985d
It is important to state that this 3rd party API I am trying to consume uses oath2.0 client authentication.
I created this endpoint to get the code from the redirected_uri
#GetMapping("/redirect-url")
public void handleRedirect(#RequestParam("code") String code) throws UnirestException {
if(Objects.nonNull(code) || !code.isEmpty()){
nibss.getToken(code);
log.info("Code is not being passed {}", code);
} else {
log.info("Code is not being passed {}", code);
}
}
public String getToken(String code) throws UnirestException {
log.info("This is the code here oooooooooo {}", code);
String url = "https://idsandbox.nibss-plc.com.ng/oxauth/restv1/token";
String parameters = "client_id=0915cd00-67f2-4768-99ac-1b2ff9f1da2e&code="+code+"&redirect_uri=https://www.accionmfb.com/&grant_type=authorization_code";
HttpResponse<String> apiResponse = Unirest.post(url)
.header("Content-Type", "application/x-www-form-urlencoded")
.header("Authorization", "Basic MDkxNWNkMDAtNjdmMi00NzY4LTk5YWMtMWIyZmY5ZjFkYTJlOlRVRnEwcGFBQXRzbzBZOEcxMkl2WFZHUmx6WG5zaERiaGt1dzI1YUM=")
.body(parameters)
.asString();
//JSONObject apiJson = apiResponse.getBody().getObject();
//return apiJson.getString("access_token");
JSONObject json = new JSONObject(apiResponse.getBody());
String accessToken = json.getString("access_token");
log.info(accessToken);
return accessToken;
}
But this is not working, I get 400 whenever I hit the second endpoint. What am I doing wrong?
The redirect_uri that you are passing to the OAuth server is https://www.accionmfb.com which does not include the path /redirect-url so the redirect never hits your method.
Either register and pass a callback uri like redirect_uri=https://www.accionmfb.com/redirect-url
Or change #GetMapping("/redirect-url") to #GetMapping("/")

How to code restcontroller for google actions?

I wish to code the Rest Controller in spring-boot for my webhook. I am creating a google action, with simple actions.
This is a boilerplate: https://github.com/actions-on-google/dialogflow-webhook-boilerplate-java/blob/master/src/main/java/com/example/ActionsServlet.java.
I want to do the same, only in spring-boot. I want to manipulate JSON body as input, but not sure how to do this.
#RestController
public class indexController extends HttpServlet {
#Autowired
private App actionsApp;
//handle all incoming requests to URI "/"
// #GetMapping("/")
// public String sayHello() {
// return "Hi there, this is a Spring Boot application";}
private static final Logger LOG = LoggerFactory.getLogger(MyActionsApp.class);
//handles post requests at URI /googleservice
#PostMapping(path = "/", consumes = "application/json", produces = "application/json")
public ResponseEntity<String> getPost(#RequestBody String payload,
#RequestHeader String header, HttpServletResponse response) throws IOException {
//Not sure what to do here.
System.out.println(jsonData);
return ResponseEntity.ok(HttpStatus.OK);
try {
//writeResponse(response, jsonResponse);
//String med request body og object that has all request header entries
String jsonResponse = actionsApp.handleRequest(body, listAllHeaders(header)).get();
return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED);
} catch (
InterruptedException e) {
System.out.println("Something wrong happened, interupted");
} catch (
ExecutionException e) {
System.out.println("Something wrong happened, execution error");
}
}
First, there is an error in your code. There might be a wrong "return" before your function logic.
return ResponseEntity.ok(HttpStatus.OK);
Second, as you are using Spring Framework, and you use "#RequestBody String payload" in the method, the Spring Framework will take the request body and set it to payload. If you set payload as a specific type. The framework will deserialize the body to it.
Finally, you can directly use payload in your code. The value of it would be the request body.
If you want to decode the json string. You can use org.json library.
JSONObject obj = new JSONObject(payload);
String name = obj.optString("name");
The code will get the value of name in the json.

How to request origin and body of a request in Spring controller

I'm building a REST API using Java and Spring and I need to handle a POST request in my controller, but I need to extract the body from that request which is a JSON and also the "origin" of that request,
#RequestMapping(value = "/create", method = RequestMethod.POST)
public XXX createMyObject(#RequestBody String data, YYY){
MyObject mo = new MyObject();
mo.setData = data;
mo.setOrigin = yyy;
myRepository.save(mo);
return XXX;
}
I have a few questions: First is how can I obtain the origin of that request( which I guess is an url that travels in the header?), is there a similar annotation as the #RequestBody for that?.
My second question is what is usually the proper object to return in these kind of post methods as a response.
To answer your questions:
If you include HttpServletRequest in your method parameters you will be able to get the origin information from there. eg.
public XXX createMyObject(#Requestbody String data, HttpServletRequest request) {
String origin = request.getHeader(HttpHeaders.ORIGIN);
//rest of code...
}
For rest responses you will need to return a representation of the object (json) or the HttpStatus to notify the clients whether the call wass successful or not. eg
Return ResponseEntity<>(HttpStatus.ok);
You should be able to get headers and uris from HttpServletRequest object
public XXX createMyObject(#RequestBody String data, HttpServletRequest request)
As for response I'd say return String which would be a view name to which you can pass some attributes saying that operation was successful or not, or ModelAndView.
#Autowired
private HttpServletRequest servletRequest;
You can declare request object and then access in method to get Uri
Try this:
#RequestMapping(value = "/create", method = RequestMethod.POST)
public XXX createMyObject(HttpServletRequest request, #RequestBody String body) {
String origin = URI.create(request.getRequestURL().toString()).getHost();
System.out.println("Body: " + body + " Origin:" + origin);
return XXX;
}

Request Header is getting as null in spring mvc

I am having two mapping methods in my controller. one is redirecting to other.
Before redirecting I'm setting a header in response. But I getting the request header as null.
These are my methods in controller. both are in same controller.
#RequestMapping(value="/testStart", method=RequestMethod.POST)
public String testStart(HttpServletRequest request, HttpServletResponse response){
String token = "126712810-1289291":
response.addHeader("authToken", token);
return "redirect:/test";
}
#RequestMapping(value="/test", method={ RequestMethod.POST, RequestMethod.GET })
public String getTestPage(Model model, HttpServletRequest request, HttpServletResponse response){
String token = request.getHeader("authToken");
System.out.println(token); //prints null
model.addAttribute("Testtoken", token);
System.out.println("Test page about to load ..");
return "test";
}
I'm using postman client to test this api. It is hitting the url and redirecting to other url. but the header is null.
I don't know what's wrong. can any one help me to figure this out? Thanks
With Redirect method web app tells browser to load the page which you want to redirect. So this makes new http request from browser, the original requests are not reachable at this moment.
So your problem can be solved with Forward method. Web app forwards all request data to another handler method internally
return "forward:/test";
Additionally , please change your
String token = request.getHeader("authToken");
with
String token = response.getHeader("authToken");
because you are adding your authToken to the response object.(from comments HttpServletResponse -> getHeader(String name) works since Servlet 3.0)
EDIT :
this code will be your complete solution "/test" method supports forwarded reuqest and also supports request from browser.
(You want to get token info from request becuase you want to call /test method without forwarding, so it works in this way, but when forwarding you cant add header so you tried to add in response and get it from request but that doesnt work in that way so you need to resolve token according to dispatcher's type so check the code )
#RequestMapping(value="/testStart", method=RequestMethod.POST)
public String testStart(HttpServletRequest request, HttpServletResponse response){
String token = "126712810-1289291";
request.setAttribute("authToken", token);
return "forward:/test";
}
#RequestMapping(value="/test", method={ RequestMethod.POST, RequestMethod.GET })
public String getTestPage(Model model, HttpServletRequest request, HttpServletResponse response)
{
//-----------------
//resolving token
String token = null;
DispatcherType type = request.getDispatcherType();
if(type == DispatcherType.FORWARD)
{
token = (String) request.getAttribute("authToken");
}
else if(type == DispatcherType.REQUEST)
{
token = (String) request.getHeader("authToken");
}
//-----------------
System.out.println(token); //prints the value
model.addAttribute("Testtoken", token);
System.out.println("Test page about to load ..");
return "test";
}
Use RedirectAttributes to pass parameters with redirect URL:
#RequestMapping(value="/test1", method=GET)
public String test(RedirectAttributes redirectAttributes){
redirectAttributes.addAttribute("authToken", "val");
return "redirect:/test";
}
or if you can use forward:/test you can go with request.setAttribute and request.getAttribute
Still if you want to add in header then use RestTemplate and HTTPHeaders and get the response String

Redirecting to a page using restful methods?

I've created a page which asks user to fill some form fields and when he submits, the form is sent to a Restful method which you can see below:
#POST
#Path("addUser")
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void addUser(#FormParam("username") String username,
#FormParam("password") String password,
#FormParam("id") String id,
#FormParam("group_name") String groupName,
#FormParam("authority_name") String authorityName,
#FormParam("authority_id") String authorityId
)
{
//Something will be done here
}
How can I redirect the user at the end of this function to (let's say) index.jsp?
change your code like this, the addUser() should return a Response Object
public Response addUser(#FormParam("username") String username,
#FormParam("password") String password,
#FormParam("id") String id,
#FormParam("group_name") String groupName,
#FormParam("authority_name") String authorityName,
#FormParam("authority_id") String authorityId
)
{
//Something will be done here
java.net.URI location = new java.net.URI("../index.jsp?msg=A_User_Added");
return Response.temporaryRedirect(location).build()
}
Create a URI using javax.ws.rs.core.UriBuilder that maps the parameters and other data you want to preserve. Then use Response.temporaryRedirect to return a redirect to the client and pass it the URI you’ve built.
Finally I come to this conclusion that there are no other way than what I did:
So here is my solution:
try {
java.net.URI location = new java.net.URI("../index.jsp?msg=A_User_Added");
throw new WebApplicationException(Response.temporaryRedirect(location).build());
} catch (URISyntaxException e) {
e.printStackTrace();
}
By adding this block to my code, I got what I needed. Hope it helps you as well.
See below the usage of redirecting in web services:
public class LoginWebService {
#POST
#Path("/check")
public Response checkDetails(#FormParam("name") String name,#FormParam("pass") String pass ) throws URISyntaxException {
URI uri = new URI("/login/success");
URI uri2= new URI("http://localhost:9090/NewWebServiceproject/new/login/failure");
if(name.equals("admin") && pass.equals("pass"))
//#Path("http://localhost:8010/NewWebServiceproject/new/login/success");
{
return Response.temporaryRedirect(uri).build();
//Response.seeOther(uri);
//return Response.status(200).entity("user successfully login").build();
}
else
{
return Response.temporaryRedirect(uri2).build();
//Response.seeOther(uri2);
//return Response.status(200).entity("user logon failed").build();
}
}
#POST
#Path("/success")
public Response successpage()
{
return Response.status(200).entity("user successfully login").build();
}
#POST
#Path("/failure")
public Response failurepage()
{
return Response.status(200).entity("user logon failed").build();
}
}
It is not good idea to use the "WebApplicationException" in order to redirect the request. in Jersey (2.4.1) you should be able to redirect the request via the normal servlet way, (request.getServletContext().getRequestDispatcher().forward() or just response.sendRedirect())
The following is how Jersey process the request
org.glassfish.jersey.servlet.ServletContainer.service(HttpServletRequest request, HttpServletResponse response)
requestScope.runInScope
final ContainerResponse response = endpoint.apply(data)
methodHandler.invoke(resource, method, args);
Responder.process(ContainerResponse);
That methodHandler is your REST service class, method is the function in that service class.
The step to redirect page become straitforward
Get the (request, response) through Jersey injection (#Context HttpServletRequest request, #Context HttpServletResponse response) in class field or function parameter
Call request.getServletContext().getRequestDispatcher() to get the dispatcher for "forward"
or use Response.sendRedirect(url)
Once you application is returned (just null), Jersey will try to process the result in the "Responder.process(ContainerResponse)". In this step, it will use response to set status (204 no contents for your null return).
So the key point here is you must finalize/close response object before return from your service function. Otherwise, Jersey may overwrite your output.
Small tips on why "WebApplicationException" can overwrite Jersey repsponse. It is because org.glassfish.jersey.server.ServerRuntime.mapException() will use the "webApplicationException.getResponse()" as the return response result.

Categories