I have a Java project and I'm using Servlet in order to handle http requests.
I also using Spring
When I receive a request to create a new object (for example an account), I would like also to return the “location” header with the GET URL of the newly created object.
for example: location: /accounts/1000
I understand the header are added to the Servlet filter (correct me if Im wrong)
public class ApiLogFilter implements Filter {
private static final Logger LOGGER = LoggerFactory.getLogger("apilogger");
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpServletResponse httpServletResponse = ((HttpServletResponse) servletResponse);
httpServletResponse.addHeader( "Location","the location value");
try {
filterChain.doFilter(servletRequest, servletResponse);
} finally {
String queryString = httpServletRequest.getQueryString() != null ? httpServletRequest.getQueryString() : "N/A";
String logMessage = "URL: " + httpServletRequest.getRequestURL() + ", Query String: " + queryString + ", Response Status: " + httpServletResponse.getStatus() ;
LOGGER.info(logMessage);
}
}
#Override
public void destroy() {
}
}
But I don't understand how to get the location value from the API
#RequestMapping("/accounts")
public class IgnoreRuleController {
private AccountService accountService;
public void setIgnoreRuleService(IgnoreRuleService ignoreRuleService) {
this.accountService = ignoreRuleService;
}
#RequestMapping(method = RequestMethod.POST)
#ResponseBody
public String createAccount(#RequestBody Account account) {
return new Gson().toJson(accountService.createAccount(account));
}
}
I found solution here
http://learningviacode.blogspot.com/2013/07/post-with-location.html
you didn't need to do anything with the filter.
in the api itself:
#RequestMapping(method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<String> createIgnoreRule(#RequestBody IgnoreRule ignoreRule) {
String response = new Gson().toJson(ignoreRuleService.createIgnoreRule(ignoreRule));
final URI location = ServletUriComponentsBuilder
.fromCurrentServletMapping().path("/ignore_rules/{id}").build()
.expand(ignoreRule.getId()).toUri();
final HttpHeaders headers = new HttpHeaders();
headers.setLocation(location);
final ResponseEntity<String> entity = new ResponseEntity<>(response, headers, HttpStatus.CREATED);
return entity;
}
It's very simple, you can pass the header directly throw your method signature:
#RequestMapping(value="/create-account", method = RequestMethod.POST)
#ResponseBody
public String createAccount(#RequestHeader HttpHeaders httpHeader, #RequestBody Account account) {
var s = httpHeader.get("Location");
System.out.println(s.get(0));
return ...
}
In fact you can pass the whole request also which contains everything (Headers, Body, ...):
#RequestMapping(value="/create-account", method = RequestMethod.POST)
#ResponseBody
public String createAccount(HttpServletRequest httpRequest, #RequestBody Account account) {
var s = httpRequest.getHeader("Location");
System.out.println(s);
return ....
}
Related
I'm a new javaer. Recently I'm working on a new springboot project, and I want to print request body before it enter mvc controller. (To be exact, I want to print request body of post request with contentType:"application/json")
I use a requestWrapper as below.
public class MyRequestWrapper extends HttpServletRequestWrapper {
private byte[] cachedBody = new byte[]{};
private InputStream input = null;
public MyRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
if (request.getContentType() != null && (request.getContentType().contains("multipart/") ||
request.getContentType().contains("/x-www-form-urlencoded"))) {
cachedBody = new byte[]{};
input = request.getInputStream();
} else {
cachedBody = StreamUtils.copyToByteArray(request.getInputStream());
input = new ByteArrayInputStream(cachedBody);
}
}
#Override
public ServletInputStream getInputStream() {
return new ServletInputStream() {
#Override
public int read() throws IOException {
return input.read();
}
}
}
public String getBody() {
return new String(cachedBody);
}
Then, I use a filter to print the request content.
#WebFilter(filterName = "RequestResponseFilter", urlPatterns = "/*", asyncSupported = true)
public class RequestResponseFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
MyRequestWrapper requestWrapper = (MyRequestWrapper) request;
......
System.out.println(requestWrapper.getBody());
......
chain.doFilter(requestWrapper, response);
}
}
Below is my controller.
#PostMapping(value="/test")
public ResponseData<String> test(
#RequestParam("id") String id,
#RequestParam("value") String value) {
ResponseData<String> result = new ResponseData<>();
result.setData(id + value);
result.setCode(Constants.CODE_SUCCESS);
return result;
}
However, when I use postman to test my code, it didn't work well. If I use post method and pass param with content-type:"application/x-www-form-urlencoded", it throws "org.springframework.web.bind.MissingServletRequestParameterException".
What confuse me is that, if I pass param with content-type:"multipart/form-data", it work well.
Besides, I have tried CachedBodyHttpServletRequest which provided by spring. But it couldn't get request content until the request enter controller.
Why the mvc controller failed to get param with annotation #RequestParam? And how can I fix it?
u can get param & body like this
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// param
request.getParameterMap().forEach((k, v) -> System.out.println(k + " : " + v[0]));
// body
byte[] array = StreamUtils.copyToByteArray(request.getInputStream());
System.out.println(new String(array, StandardCharsets.UTF_8));
}
but if u upload file with multipart/form-data then file content can't cast to String, u need tools to resolve it, something like this
if (contentType.startsWith("multipart/form-data")) {
StandardServletMultipartResolver resolver = new StandardServletMultipartResolver();
StandardMultipartHttpServletRequest req = (StandardMultipartHttpServletRequest)resolver.resolveMultipart((HttpServletRequest) request);
System.out.println(req.getMultiFileMap());
System.out.println(req.getParameterMap());
}
There exists two common parameters data1 and data2 in my request, and I need decrypt them to 10 parameters, and use them in my controllers.
I tried replace the HttpServletRequest param to My Custom HttpServletRequestWrapper instance in filter. But I got a DefaultMultipartHttpServletRequest instance instead of my HttpServletRequestWrapper instance. So I can't get the decrypted parameters in my controllers.
I also found that there is cannot change request param in HandlerInterceptorAdapter.
public class SecurityFilter extends OncePerRequestFilter {
private Logger logger = LoggerFactory.getLogger(this.getClass());
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
//todo: convert data1,data2 to param1, ... param10
logger.info("doFilterInternal 1 " + httpServletRequest);
filterChain.doFilter(new CultureRequestWrapper(httpServletRequest), httpServletResponse);
logger.info("doFilterInternal 2 " + httpServletRequest);
}
}
public class CultureRequestWrapper extends HttpServletRequestWrapper {
public CultureRequestWrapper(HttpServletRequest request) {
super(request);
}
#Override
public String[] getParameterValues(String name) {
//todo:convert
return super.getParameterValues(name);
}
}
#RequestMapping(value = {"/req1"})
#ResponseBody
public JsonResult testreq(HttpServletRequest request) {
logger.info("request:" + request);//get DefaultMultipartHttpServletRequest
logger.info("request param size:" + request.getParameterMap().size());//get 2 (data1,data2)
String param1 = request.getParameter("param1");
...
String param10 = request.getParameter("param10");
}
I would suggest the following:
1) Create a POJO which would reflect your 10 decoded values.
2) Decode the params in the filter/interceptor and populate the POJO with them.
3) Set the POJO as an attribute of the request:
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, FilterChain filterChain){
//convert data1,data2 to param1, ... param10 into a POJO
httpServletRequest.setAttribute("decodedData", pojo);
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
4) Use the #RequestAttribute annotation on the controller methods in order to retrieve the POJO wherever you need to:
#RequestMapping(value = {"/req1"})
#ResponseBody
public JsonResult testreq(#RequestAttribute("decodedData") PojoWith10Values pojo) {
Currently, we are building uri references using this straightforward code:
#RestController
#RequestMapping("/fitxers")
public class DocumentController {
#RequestMapping(value = "/create", method = RequestMethod.GET)
private ResponseEntity<String> createReferenceURI(String fileName) {
String uri = ServletUriComponentsBuilder.fromCurrentContextPath()
.path("/fitxers/download/")
.path(fileName)
.toUriString();
HttpHeaders headersResp = new HttpHeaders();
headersResp.setLocation(URI.create(uri));
return new ResponseEntity<String>(uri, headersResp, HttpStatus.CREATED);
}
#RequestMapping(value = "/download/{id}", method = RequestMethod.GET)
void getStreamingFile(HttpServletResponse response, String id) throws IOException {}
}
I'm sure, there have to be another more elegant way to get it.
We are creating spring-boot services.
Any ideas?
Just write a filter, something like this:
#Component
public class HeadersFilter implements Filter {
#Override
public void destroy() {
System.out.println("Filter will be destroyed");
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws
IOException, ServletException {
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader(HttpHeaders.LOCATION, ((RequestFacade) request).getRequestURL().toString());
chain.doFilter(request, response);
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("Filter initialized");
}
}
I have a form with a three text fields and a file upload field.
When I reach the MaxUploadSizeExceededException exception I can handle with a class that implements HandlerExceptionResolver.
I have my custom handler class with
resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception exception){ ... }
My problem is that I need a way to pass some variables to Exception handler (the values of other fields in the form) so I can return a ModelAndView that contains these variables. I don't want to redirect to an error page, I want to return to my Form, without losing inserted values.
I've also a "Validator" that validates other fields and it works, but I don't know how to integrate it with MaxUploadSizeExceededException exception.
My controller implements HandlerExceptionResolver
#Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception exception)
{
Map<String, Object> model = new HashMap<String, Object>();
if (exception instanceof MaxUploadSizeExceededException)
{
// this is empty!
Map<String,String[]> paramMap = request.getParameterMap();
// model.put("ticketForm", new TicketForm());
// ticketForm.setId();
model.put("err", exception.getMessage());
return new ModelAndView(inserisciticket", model);
} else
{
model.put("err", "Unexpected error: " + exception.getMessage());
return new ModelAndView("error", model);
}
}
This is the function that is called from the form:
#RequestMapping(value = "/inseriscinuovoticket", method = RequestMethod.POST)
#ResponseBody
public String inseriscinuovoticket(
#RequestParam(value = "idArgomento", required = true, defaultValue = "") String idArgomento,
#RequestParam(value = "oggetto", required = true, defaultValue = "") String oggetto,
#RequestParam(value = "descrizione", required = true, defaultValue = "") String descrizione,
#RequestParam(value = "fileuploaded", required = false) MultipartFile fileuploaded,
#ModelAttribute("ticket") TicketForm ticketForm, BindingResult result, Model model, HttpServletRequest request,
Locale locale) throws IOException { .... }
Can you help me?
------------- EDIT 2 --------------------
I tried the method suggested here
public class MultipartExceptionHandler extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} catch (MaxUploadSizeExceededException e) {
handle(request, response, e);
} catch (ServletException e) {
if(e.getRootCause() instanceof MaxUploadSizeExceededException) {
handle(request, response, (MaxUploadSizeExceededException) e.getRootCause());
} else {
throw e;
}
}
}
private void handle(HttpServletRequest request,
HttpServletResponse response, MaxUploadSizeExceededException e) throws ServletException, IOException {
// null
TicketForm t = (TicketForm)request.getAttribute("ticket");
// null
String idArgomento = (String)request.getAttribute("idArgomento");
response.sendRedirect("inserisciticket");
}
}
But also in the handle and in the filter I CAN'T read form parameters (post data).
How can I do???
Thank you.
The following changes resolved the fileUpload size issues(MaxUploadSizeExceededException) in my application.
Do the following changes in "application.yml" to fix this issue. Setting -1 means, unlimited size allowed.
Spring:
servlet:
multipart:
max-file-size: -1
Spring:
servlet:
multipart:
max-request-size: -1
I'm using Spring Boot with #ResponseBody based approach like the following:
#RequestMapping(value = VIDEO_DATA_PATH, method = RequestMethod.GET)
public #ResponseBody Response getData(#PathVariable(ID_PARAMETER) long id, HttpServletResponse res) {
Video video = null;
Response response = null;
video = videos.get(id - 1);
if (video == null) {
// TODO how to return 404 status
}
serveSomeVideo(video, res);
VideoSvcApi client = new RestAdapter.Builder()
.setEndpoint("http://localhost:8080").build().create(VideoSvcApi.class);
response = client.getData(video.getId());
return response;
}
public void serveSomeVideo(Video v, HttpServletResponse response) throws IOException {
if (videoDataMgr == null) {
videoDataMgr = VideoFileManager.get();
}
response.addHeader("Content-Type", v.getContentType());
videoDataMgr.copyVideoData(v, response.getOutputStream());
response.setStatus(200);
response.addHeader("Content-Type", v.getContentType());
}
I tried some typical approaches as:
res.setStatus(HttpStatus.NOT_FOUND.value());
new ResponseEntity(HttpStatus.BAD_REQUEST);
but I need to return Response.
How to return here 404 status code if video is null?
This is very simply done by throwing org.springframework.web.server.ResponseStatusException:
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "entity not found"
);
It's compatible with #ResponseBody and with any return value. Requires Spring 5+
Create a NotFoundException class with an #ResponseStatus(HttpStatus.NOT_FOUND) annotation and throw it from your controller.
#ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "video not found")
public class VideoNotFoundException extends RuntimeException {
}
Your original method can return ResponseEntity (doesn't change your method behavior):
#RequestMapping(value = VIDEO_DATA_PATH, method = RequestMethod.GET)
public ResponseEntity getData(#PathVariable(ID_PARAMETER) long id, HttpServletResponse res{
...
}
and return the following:
return new ResponseEntity(HttpStatus.NOT_FOUND);
You can just set responseStatus on res like this:
#RequestMapping(value = VIDEO_DATA_PATH, method = RequestMethod.GET)
public ResponseEntity getData(#PathVariable(ID_PARAMETER) long id,
HttpServletResponse res) {
...
res.setStatus(HttpServletResponse.SC_NOT_FOUND);
// or res.setStatus(404)
return null; // or build some response entity
...
}