Problems about comsuming request body for multiple times - java

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());
}

Related

Writing the payload back to HTTPRequest object in Java Filter after changing some value in the payload

In our application, I have to write a Filter which will read the HttpServletRequest containing xml as body. I have read the xml and now i want to remove a specific tag from the xml as it is base64 encoded content and it is taking up alot of memory while processing. I have removed the tag from xml after reading the request. Now i want this new xml string to again get added to the HttpServletRequest so that it can be processed further.
I am not able to figure out how do i add back the new string to the HttpServletRequest as body and not as parameters. I am not looking for Interceptor solution as i am facing another issue with Interceptor in conversion.
Below is my Filter :
#Component
public class UploadDocumentFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException { // TODO Auto-generated method stub
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
ByteArrayOutputStream result = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
InputStream stream = request.getInputStream();
for (int length; (length = stream.read(buffer)) != -1;) {
result.write(buffer, 0, length);
} // StandardCharsets.UTF_8.name() > JDK 7 String test=
String test=result.toString("UTF-8");
//TODO add back this string back to HttpServletRequest
chain.doFilter(request, response);
}
#Override
public void destroy() { // TODO Auto-generated method stub
}
}
Have you tried javax.servlet.ServletRequestWrapper?

How to add Location header to the http response?

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 ....
}

Log Request and response in Spring API

I want to implement Rest logging for API using Spring. I tried this:
public static String readPayload(final HttpServletRequest request) throws IOException {
String payloadData = null;
ContentCachingRequestWrapper contentCachingRequestWrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
if (null != contentCachingRequestWrapper) {
byte[] buf = contentCachingRequestWrapper.getContentAsByteArray();
if (buf.length > 0) {
payloadData = new String(buf, 0, buf.length, contentCachingRequestWrapper.getCharacterEncoding());
}
}
return payloadData;
}
public static String getResponseData(final HttpServletResponse response) throws IOException {
String payload = null;
ContentCachingResponseWrapper wrapper =
WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
if (wrapper != null) {
byte[] buf = wrapper.getContentAsByteArray();
if (buf.length > 0) {
payload = new String(buf, 0, buf.length, wrapper.getCharacterEncoding());
wrapper.copyBodyToResponse();
}
}
return payload;
}
#PostMapping(value = "/v1", consumes = { MediaType.APPLICATION_XML_VALUE,
MediaType.APPLICATION_JSON_VALUE }, produces = { MediaType.APPLICATION_XML_VALUE,
MediaType.APPLICATION_JSON_VALUE })
public PaymentResponse handleMessage(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest requestCacheWrapperObject = new ContentCachingRequestWrapper(request);
requestCacheWrapperObject.getParameterMap();
.raw_request(readPayload(requestCacheWrapperObject))
.raw_response(getResponseData(response))
}
But I get NULL for request and response.
Do you know what is the proper way to get the payload from the request and the response?
So you just need to have your own interceptor.
#Component
public class HttpRequestResponseLoggingInterceptorAdapter extends HandlerInterceptorAdapter {
#Autowired
private LoggingUtils loggingutils;
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
loggingutils.preHandle(request, response);
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
#Nullable ModelAndView modelAndView) {
try {
loggingutils.postHandle(request, response);
} catch(Exception e) {
System.out.println("Exception while logging outgoing response");
}
}
}
Once that is done, you need to bind your new interceptor to existing interceptors.
#Configuration
public class InterceptorConfig implements WebMvcConfigurer {
#Autowired
private HttpRequestResponseLoggingInterceptorAdapter httpRequestResponseLoggingInterceptorAdapter;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(httpRequestResponseLoggingInterceptorAdapter);
}
}
Once that is done, your incoming requests for handlemessage method will be intercepted, and can do whatever pre/post processing you want to have.
Logging in this case.
Let me know if this helps.
Sounds like your usecase would be best suited with a class extending spring's org.springframework.web.servlet.handler.HandlerInterceptorAdapter.
Custom interceptors can override preHandle and postHandle - both of which it sounds like you are inclined to use.
EDIT:
// add to wherevere your source code is
public class CustomInterceptor extends HandlerInterceptorAdapter {
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// TODO: use 'request' from param above and log whatever details you want
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// TODO: use 'response' from param above and log whatever details you want
}
}
// add to your context
<mvc:interceptors>
<bean id="customInterceptor" class="your.package.CustomInterceptor"/>
</mvc:interceptors>

Logging Spring REST APIs

I have custom Filter and I want to log body from request.
But when I use ContentCachingRequestWrapper and try to call getContentAsByteArray() I always get an empty array.
#Component
public class CustomFilter implements Filter {
private final Logger log = LoggerFactory.getLogger(CustomFilter.class);
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest requestToCache = new ContentCachingRequestWrapper(request);
chain.doFilter(req, res);
log.info(getRequestData(requestToCache));
}
#Override
public void init(FilterConfig filterConfig) {
}
#Override
public void destroy() {
}
public static String getRequestData(final HttpServletRequest request) throws UnsupportedEncodingException {
String payload = null;
ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
if (wrapper != null) {
byte[] buf = wrapper.getContentAsByteArray();
if (buf.length > 0) {
payload = new String(buf, 0, buf.length, wrapper.getCharacterEncoding());
}
}
return payload;
}
}
I also tried create Interceptor, but had the same problem.
What am I doing wrong?
Thanks for help.
You can use the existing spring implementation by just registering this bean in a #Configuration annotated class:
#Bean
public static Filter requestLoggingFilter() {
final CommonsRequestLoggingFilter loggingFilter = new CommonsRequestLoggingFilter();
loggingFilter.setIncludePayload(true);
loggingFilter.setMaxPayloadLength(512);
return loggingFilter;
}
Whilst I'd recommend going with NiVer's answer, I've been looking into why this issue occurs and I can finally give you an answer.
When you create a new ContentCachingRequestWrapper, the internal ByteArrayOutputStream is initialized but no data is copied to it. The body is only written to the ByteArrayOutputStream when you call getParameter, getParameterMap(), getParameterNames() or getParameterValues(String name) methods, and even then the data is only copied if the content type contains application/x-www-form-urlencoded.

Servlet filter "proxy" that only acts on response from remote endpoint

I have a need where certain HTTP requests must be redirected to a Spring Boot web app/service, but that on the request-side, the Spring app does nothing and acts as a passthrough between the HTTP client (another service) and the request's true destination. But when the response comes back to the Spring app (from that destination), I need the Spring app to be able to inspect the response and possibly take action on it if need be. So:
HTTP client makes a request to, say, http://someapi.example.com
Network magic routes the request to my Spring app at, say, http://myproxy.example.com
On the request, this app/proxy does nothing, and so the request is forwarded on http://someapi.example.com
The service endpoint at http://someapi.example.com returns an HTTP response back to the proxy
The proxy at http://myproxy.example.com inspects this response, and possibly sends an alert before returning the response back to the original client
So essentially, a filter that acts as a pass-through on the request, and only really does anything after the remote service has executed and returned a response.
My best attempt thus far has been to setup a servlet filter:
#Override
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(request, response)
// How and where do I put my code?
if(responseContainsFizz(response)) {
// Send an alert (don't worry about this code)
}
}
Is this possible to do? If so, where do I put the code that inspects and acts upon the response? With my code the way it is I get exceptions thrown when trying to hit a controller from a browser:
java.lang.IllegalStateException: STREAM
at org.eclipse.jetty.server.Response.getWriter(Response.java:910) ~[jetty-server-9.2.16.v20160414.jar:9.2.16.v20160414]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_92]
rest of stack trace omitted for brevity
Any ideas?
Per the Servlet API documentation, the reason you are getting the IllegalStateException is because you are attempting to call ServletResponse.getWriter after ServletResponse.getOutputStream has already been called on the response. So it appears that the method you need to call is ServletResponse.getOutputStream().
However, if you are trying to access the body of the response, the best solution is to wrap the response in a ServletResponseWrapper so that you can capture the data:
public class MyFilter implements Filter
{
#Override
public void init(FilterConfig filterConfig) throws ServletException
{
}
#Override
public void destroy()
{
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
{
MyServletResponseWrapper responseWrapper = new MyServletResponseWrapper((HttpServletResponse) response);
chain.doFilter(request, responseWrapper);
if (evaluateResponse(responseWrapper)) {
// Send an alert
}
}
private boolean evaluateResponse(MyServletResponseWrapper responseWrapper) throws IOException
{
String body = responseWrapper.getResponseBodyAsText();
// Perform business logic on the body text
return true;
}
private static class MyServletResponseWrapper extends HttpServletResponseWrapper
{
private ByteArrayOutputStream copyOutputStream;
private ServletOutputStream wrappedOutputStream;
public MyServletResponseWrapper(HttpServletResponse response)
{
super(response);
}
public String getResponseBodyAsText() throws IOException
{
String encoding = getResponse().getCharacterEncoding();
return copyOutputStream.toString(encoding);
}
#Override
public ServletOutputStream getOutputStream() throws IOException
{
if (wrappedOutputStream == null) {
wrappedOutputStream = getResponse().getOutputStream();
copyOutputStream = new ByteArrayOutputStream();
}
return new ServletOutputStream()
{
#Override
public boolean isReady()
{
return wrappedOutputStream.isReady();
}
#Override
public void setWriteListener(WriteListener listener)
{
wrappedOutputStream.setWriteListener(listener);
}
#Override
public void write(int b) throws IOException
{
wrappedOutputStream.write(b);
copyOutputStream.write(b);
}
#Override
public void close() throws IOException
{
wrappedOutputStream.close();
copyOutputStream.close();
}
};
}
}
}
The response can be easy manipulated/replaced/extended e with a filter and a response wrapper.
In the filter before the call chain.doFilter(request, wrapper) you prepare a PrintWriter for the new response content and the wrapper object.
After the call chain.doFilter(request, wrapper) is the actuall response manipulation.
The wrapper is used to get access to the response as String.
The Filter:
#WebFilter(filterName = "ResponseAnalysisFilter", urlPatterns = { "/ResponseFilterTest/*" })
public class ResponseFilter implements Filter {
public ResponseFilter() {}
#Override
public void init(FilterConfig filterConfig) throws ServletException {}
#Override
public void destroy() {}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
PrintWriter out = response.getWriter();
CharResponseWrapper wrapper = new CharResponseWrapper((HttpServletResponse) response);
chain.doFilter(request, wrapper);
String oldResponseString = wrapper.toString();
if (oldResponseString.contains("Fizz")) {
// replace something
String newResponseString = oldResponseString.replaceAll("Fizz", "Cheers");
// show alert with a javascript appended in the head tag
newResponseString = newResponseString.replace("</head>",
"<script>alert('Found Fizz, replaced with Cheers');</script></head>");
out.write(newResponseString);
response.setContentLength(newResponseString.length());
}
else { //not changed
out.write(oldResponseString);
}
// the above if-else block could be replaced with the code you need.
// for example: sending notification, writing log, etc.
out.close();
}
}
The Response Wrapper:
public class CharResponseWrapper extends HttpServletResponseWrapper {
private CharArrayWriter output;
public String toString() {
return output.toString();
}
public CharResponseWrapper(HttpServletResponse response) {
super(response);
output = new CharArrayWriter();
}
public PrintWriter getWriter() {
return new PrintWriter(output);
}
}
The Test Servlet:
#WebServlet("/ResponseFilterTest/*")
public class ResponseFilterTest extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
response.getWriter().append(
"<html><head><title>replaceResponse filter</title></head><body>");
if (request.getRequestURI().contains("Fizz")) {
response.getWriter().append("Fizz");
}
else {
response.getWriter().append("Limo");
}
response.getWriter().append("</body></html>");
}
}
Test Urls:
https://yourHost:8181/contextPath/ResponseFilterTest/Fizz (Trigger response Replacement)
https://yourHost:8181/contextPath/ResponseFilterTest/ (response unchanged)
More Info and examples about filters:
http://www.oracle.com/technetwork/java/filters-137243.html#72674
http://www.leveluplunch.com/java/tutorials/034-modify-html-response-using-filter/
https://punekaramit.wordpress.com/2010/03/16/intercepting-http-response-using-servlet-filter/

Categories

Resources