I have a use case where I need to return a PDF to a user which is generated for us. It seems that what I need to do is utilize the ResponseEntity in this case, but I have a couple of things which are not very clear.
How can I redirect the user -- let's pretend they don't have the permissions to access this page? How can I redirect them to a separate controller?
Am I able to set the response encoding?
Can I achieve either of these two without bringing in the HttpResponse as a parameter to my RequestMapping?
I'm using Spring 3.0.5. Example code below:
#Controller
#RequestMapping("/generate/data/pdf.xhtml")
public class PdfController {
#RequestMapping
public ResponseEntity<byte []> generatePdf(#RequestAttribute("key") Key itemKey) {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.valueOf("application/pdf"));
if (itemKey == null || !allowedToViewPdf(itemKey)) {
//How can I redirect here?
}
//How can I set the response content type to UTF_8 -- I need this
//for a separate controller
return new ResponseEntity<byte []>(PdfGenerator.generateFromKey(itemKey),
responseHeaders,
HttpStatus.CREATED);
}
I'd really like to not pull in the Response... None of my controllers have done so thus far, and I'd hate to have to bring it in at all.
Note, this works in Spring 3.1, not sure about spring 3.0.5 as asked in the original question.
In your return ResponseEntity statement where you want to handle the redirect, just add in a "Location" header to the ResponseEntity, set the body to null and set the HttpStatus to FOUND (302).
HttpHeaders headers = new HttpHeaders();
headers.add("Location", "http://stackoverflow.com");
return new ResponseEntity<byte []>(null,headers,HttpStatus.FOUND);
This will keep you from having to change the return type of the controller method.
Regarding the redirect, all you need to do is change the return type to Object:
#Controller
#RequestMapping("/generate/data/pdf.xhtml")
public class PdfController {
#RequestMapping
public Object generatePdf(#RequestAttribute("key") Key itemKey) {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.valueOf("application/pdf"));
if (itemKey == null || !allowedToViewPdf(itemKey)) {
return "redirect:/some/path/to/redirect"
}
//How can I set the response content type to UTF_8 -- I need this
//for a separate controller
return new ResponseEntity<byte []>(PdfGenerator.generateFromKey(itemKey),
responseHeaders,
HttpStatus.CREATED);
}
Redirects are easy - for your handler method's return String, just prepend with redirect:, as in return "redirect:somewhere else".
Not sure why you're objecting to the Response object. Is there a reason? Otherwise, if you just stream the PDF as an OutputStream on the HttpServletResponse object, then you don't actually need to return the PDF from your handler method - you just need to set the PDF stream on the response, which you can add to your handler method's signature. See http://www.exampledepot.com/egs/javax.servlet/GetImage.html for an example.
Instead of dealing with redirecting (these are instances which we open in new windows / tabs) anyhow we decided to just display the error message they would have received.
This likely won't work for all, but with the way we add error / status messages we were unable to get those messages to persist on the view upon exception occurring.
Related
There is a small method mymethod that gets the response from the external api data in json format, which will be Dto and
then there is a getEnums method that selects which data to leave and returns a list of strings and as a result mymethod itself returns a list of strings.
I tried to write a test, but I get :
Expected :200
Actual :302
As I understand the problem is in the redirection, but how to solve it I do not understand, maybe you have an answer?
controller
#GetMapping(value = "api/mymethod", produces = "application/json")
public ResponseEntity<List<String>> mymethod(#RequestHeader(value = "Enum") String Enum,
#RequestParam(value = "Type") String Type) {
URI uri = UriComponentsBuilder.fromUriString("...url...").build().toUri();
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(null, headers);
ResponseEntity<myDto> response =
restTemplate.exchange(uri, HttpMethod.GET, entity, myDto.class);
List<String> currencies =
getEnums(response, Type); // <- custom method
return new ResponseEntity<>(currencies, HttpStatus.OK);
}
// (The test is not written in full, left exactly the problem area)
test
#Test
public void mytest() throws Exception{
ResultActions getResult = mockMvc.perform(get("/api/mymethod")
.header("Enum", "OOL")
.param("Type", "Counter"))
.andExpect(status().isOk());
}
The problem with testing against an external service is that you do not manage it's state. Therefore your test cases may show varying results even if you did not change anything.
Usually you'd create a mock of the REST api your test object would access. Then you can send requests to your test object and check in the mocked api whether the expected requests did come in. You can also fake success or error responses and check how your test object reacts.
To finally answer your question: How do you want your client to treat a redirection? Should it follow or error out? Looking at the meaning of status 302 it means the resource has moved temporarily or at least was found at a new location. This might mean a valid redirect if the server is trying to loadbalance or tries to point out a url that is closer to you network-wise. Therefore I believe the client should follow a 302.
I'm handling a MethodArgumentNotValidException thrown after a failed validation of a request object. All the usual stuff is in place: #Valid, #ControllerAdvice, and an extended ResponseEntityExceptionHandler, in which I override handleMethodArgumentNotValid().
As it happens, I need to access that same request object in order to form a customized error response. One way would be to intercept the request before it hits the controller and create a #RequestScope bean with the needed fields in case validation fails later.
Is there a better way?
Thanks to a suggestion from a colleague, I've found that the BindingResult within MethodArgumentNotValidException has a method named getTarget() that returns the validated object. As seen from the method signature (Object getTarget()), the return value needs a cast.
You should have the error fields in the MethodArgumentNotValidException class. Your handleMethodArgumentNotValid function might look like something as follows.
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
#ResponseBody
public CustomInputErrorResponse handleMethodArgumentNotValid(MethodArgumentNotValidException e) {
String message = "Invalid inputs";
ArrayList<String> fieldNames = new ArrayList<String>();
for (FieldError fieldError : e.getBindingResult().getFieldErrors()) {
fieldNames.add(fieldError.getField());
}
return new CustomInputErrorResponse(message, fieldNames);
}
Considering you have a CustomInputErrorResponse class that takes two arguments for a custom message and error field names.
I am trying to pass the List of String from one server to the another server in spring boot.
How i can get that list at the another server?
The code i have tried-
public void addNewMostPopular(List<String> totalList){
try {
HttpHeaders httpHeaders = getHttpHeaders();
HttpEntity<String> httpEntity = new HttpEntity<String>(null, httpHeaders);
ResponseEntity responseEntity = restTemplate.exchange(
BASE_URL + "addMostPopular/"+new ArrayList<>(totalList), HttpMethod.POST, httpEntity,TrendingCategoryDTO.class);
}
and at server side i tried to get like-
#RequestMapping(value="/addMostPopular/[{totalList}]", method = RequestMethod.POST)
public void addMostPopularProduct( #PathVariable List<String> totalList) {}
Past long object in the url is a bad praxis, thats because spring url interpreter has a maximun lenght, so if you pass more than 2048 or 4096 char in some cases your request will return Response 400 bad request and won't execute anycode on your spring server.
After this aclaration, is there any option to pass a list? Yes, of course! But we need use #RequestBodylike this:
#PostMapping("/addMostPopular")
public void addMostPopularProduct(#RequestBody List<String> totalList) {
// Your function
}
Now we need to add to our other server the List we want to pass to this request in the body of the request.
If you like to pass a List of values in the url one possibility is to pass them as url parameters.
You have to create a link similar to the followings:
http://youserver/youraction?param=first¶m=second¶m=third
or
http://youserver/youraction?param=first,second,third
Your controller in spring must be something like
#Controller
public class MyController {
#GetMapping("/youraction")
public String yourAction(#RequestParam("param") List<String> params) {
// Here params is tre list with the values first, second, third
}
}
This action is able to handle both kind of requests that I wrote before.
There are many ways to pass infomation between servers.
The simple way is to initiate an http request, based on your request method get or post put the parameters to the appropriate location : reuqest header or request body. You can do like #Davide Lorenzo MARINO.
Or use a message queue, like ActiveMq.
In the case of the same registry center, you can also use #feign to resolve it.
This is my method for creating Response with header parameters and body:
public Response sendOKResponse(request req)
{
ResponseBuilderImpl builder = new ResponseBuilderImpl();
// set the header params.
for(int index =0; index<req.headerParameters.size(); index++)
{
builder.header(req.headerParameters.get(index).getName(), req.headerParameters.get(index).getBody());
}
// set the body and response code
builder.status(Response.Status.OK).entity(req.getBody());
Response r = builder.build();
return r;
}
And this is how i return the Response:
Response response;
response = sendBadMesseage();
return response;
This code returns code 204(No content) instead of 200.
Any ideas why?
You shouldn't be instantiating your response builder with new, the whole point of the JAX-RS abstraction layer is to hide implementation details away from calling clients. This is what makes it possible to have various vendor implementations which can be interchanged at will. Also, if you are using JEE6, or hope to migrate to it, this code will almost certainly fail. Most JEE6 vendor implementations utilize CDI, which is concept-incompatible with usage of new. But, closer to the topic, the JAX-RS implementation specifies that a 204 status code be returned if a responses wrapped entity is null. You might want to verify this is not the case in any of your methods. Also, you might want to make some changes to your code:
public Response sendOKResponse(request req) {
ResponseBuilder response = Response.ok();
// set the header params.
for(Header h: req.headerParameters()) {
builder = builder.header(h.getName(), h.getValue());
}
// set the body and response code
builder = builder.entity(req.getBody());
return builder.build();
}
Your sendBadMessage method should also look similar to above. You can log your entity before adding it to the builder, to verify that you only get a 204 when it's null.
I want to set the Expires header for all image/* and text/css. I'm doing this in a Filter. However:
before calling chain.doFilter(..) the Content-type is not yet "realized"
after calling chain.doFilter(..) the Content-type is set, but so is content-length, which forbids adding new headers (at least in Tomcat implementation)
I can use the extensions of the requested resource, but since some of the css files are generated by richfaces by taking them from inside jar-files, the name of the file isn't x.css, but is /xx/yy/zz.xcss/DATB/....
So, is there a way to get the Content-type before the response is committed.
Yes, implement HttpServletResponseWrapper and override setContentType().
class AddExpiresHeader extends HttpServletResponseWrapper {
private static final long ONE_WEEK_IN_MILLIS = 604800000L;
public AddExpiresHeader(HttpServletResponse response) {
super(response);
}
public void setContentType(String type) {
if (type.startsWith("text") || type.startsWith("image")) {
super.setDateHeader("Expires", System.currentTimeMillis() + ONE_WEEK_IN_MILLIS);
}
super.setContentType(type);
}
}
and use it as follows:
chain.doFilter(request, new AddExpiresHeader((HttpServletResponse) response));
You should subclass HttpServletResponseWrapper and override addHeader and setHeader to add the newly desired header when "Content-Type" is passed in as the header name. Make sure to not forget to call super in those overridden methods too. Wrap the Response sent in the doFilter method argument with this new Wrapper and pass the Wrapper to the call to doFilter.