I need to create an interceptor that will intercept HTTP requests and responses, but it seems to me that I'm doing something wrong, can someone tell me what I should change or add?
public class HttpInterceptor extends HandlerInterceptorAdapter implements ClientHttpRequestInterceptor
{
#Override
public ClientHttpResponse intercept(final HttpRequest httpRequest, final byte[] bytes, final ClientHttpRequestExecution clientHttpRequestExecution) throws IOException
{
RestTemplate restTemplate = new RestTemplate();
final ClientHttpResponse response = clientHttpRequestExecution.execute(httpRequest, bytes);
final String httpResponseName = response.toString();
final HttpHeaders httpHeaders = response.getHeaders();
final HttpStatus httpStatusCode = response.getStatusCode();
final String statusText = response.getStatusText();
final String body = httpHeaders.toString() + httpStatusCode.toString() + statusText;
//And then i will put body to DB
return response;
}
xml
<bean id="httpInterceptor" class="HttpInterceptor"/>
<bean id="httpInterceptor" class="de.hybris.platform.servicelayer.interceptor.impl.InterceptorMapping">
<property name="interceptor" ref="httpInterceptor"/>
<property name="typeCode" value="Message"/>
</bean>
I understood that you try to create service (it can be rest, soap or any other). If I am right, you need to create controller for handling http request.
#Controller("MyController")
public class MyController extends AbstractController {
#RequestMapping(value = "/mymethod/{id}", method = RequestMethod.GET)
public void myMethod(#PathVariable final String id, final HttpServletRequest request, final HttpServletResponse out) throws Exception {
try {
if (StringUtils.isEmpty(id))
throw new UnknownIdentifierException("id is null!");
out.setContentType(MediaType.APPLICATION_TXT_VALUE);
IOUtils.copy(myStream, out.getOutputStream());
} catch (UnknownIdentifierException ex) {
out.setStatus(HttpServletResponse.SC_BAD_REQUEST);
out.setContentType(MediaType.TEXT_PLAIN_VALUE);
String message = "My error text!";
IOUtils.copy(new ByteArrayInputStream(message.getBytes()), out.getOutputStream());
}
}
I recommande to implements Filter to transform or use the information contained in the requests or responses. and not Interceptor , it provide more information then Interceptor Here's an exemple of the use of Filter for logging :
#Component
public class HttpLoggingFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(HttpLoggingFilter.class);
#Value("${output.trace.actif}")
private boolean isOutputActif;
private 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;
}
private 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;
}
#Override
public void init(FilterConfig filterConfig) {
logger.info("start http filter");
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
ContentCachingRequestWrapper requestToCache = new ContentCachingRequestWrapper(httpServletRequest);
ContentCachingResponseWrapper responseToCache = new ContentCachingResponseWrapper(httpServletResponse);
HttpUtil.majMDCRestInfo(httpServletRequest);
long start = System.currentTimeMillis();
chain.doFilter(requestToCache, responseToCache);
long elapsedTime = System.currentTimeMillis() - start;
String requestBody = new String(requestToCache.getContentAsByteArray());
String responseBody = new String(responseToCache.getContentAsByteArray());
final StringBuilder logMessage = new StringBuilder().append("[METHOD:").append(httpServletRequest.getMethod())
.append("] [PARAMS:")
.append(httpServletRequest.getQueryString()).append("] [BODY:").append(requestBody).append("]");
if (isOutputActif) {
String respContent = responseBody;
if (respContent.equals("")) {
respContent = "no data";
}
logMessage.append(" [RESPONSE:").append(respContent).append("]");
}
logMessage.append(" [STATUS:").append(responseToCache.getStatus()).append("] [Time:").append(elapsedTime).append("ms]");
String[] nonLoggingPaths = {"/api/"};
String urlPath = httpServletRequest.getRequestURL().toString();
if ((Arrays.stream(nonLoggingPaths).parallel().anyMatch(urlPath::contains))) {
logger.info("{}", logMessage);
}
getRequestData(requestToCache);
getResponseData(responseToCache);
}
}
Related
I am working on a spring boot application. I want to modify the response of the request by request body field "Id".
I have implemented below, but still getting just the name in the output while implementing.Any suggestions on implementing below would be helpful:
Below is the requestBody:
{
"id" : "123"
}
In response, I want to append that field to response id(fieldname from request body).
responseBody:
{
"name" : "foo123" //name + id from request
}
MyCustomFilter:
public class TestFilter implements Filter {
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse res = (HttpServletResponse) response;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
final PrintStream ps = new PrintStream(baos);
MultiReadHttpServletRequest wrapper = new MultiReadHttpServletRequest((HttpServletRequest) request);
MyRequestWrapper req = new MyRequestWrapper(wrapper);
String userId = req.getId();
chain.doFilter(wrapper, new HttpServletResponseWrapper(res) {
#Override
public ServletOutputStream getOutputStream() throws IOException {
return new DelegatingServletOutputStream(new TeeOutputStream(super.getOutputStream(), ps)
);
}
#Override
public PrintWriter getWriter() throws IOException {
return new PrintWriter(new DelegatingServletOutputStream(new TeeOutputStream(super.getOutputStream(), ps))
);
}
});
String responseBody = baos.toString();
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(responseBody);
String name = node.get("name").astext();
((ObjectNode) node1).put("name", name + userId);
chain.doFilter(wrapper, res);
}
MyRequestWrapper:
public class MyRequestWrapper extends HttpServletRequestWrapper {
private ServletInputStream input;
public MyRequestWrapper(ServletRequest request) {
super((HttpServletRequest)request);
}
public String getId() throws IOException {
if (input == null) {
try {
JSONObject jsonObject = new JSONObject(IOUtils.toString(super.getInputStream()));
String userId = jsonObject.getString("id");
userId = userId.replaceAll("\\D+","");
return userId;
} catch (JSONException e) {
e.printStackTrace();
}
}
return null;
}
}
MultiReadHttpServletRequest.java
public class MultiReadHttpServletRequest extends HttpServletRequestWrapper {
private byte[] body;
public MultiReadHttpServletRequest(HttpServletRequest request) {
super(request);
try {
body = IOUtils.toByteArray(request.getInputStream());
} catch (IOException ex) {
body = new byte[0];
}
}
#Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream(), getCharacterEncoding()));
}
#Override
public ServletInputStream getInputStream() throws IOException {
return new ServletInputStream() {
ByteArrayInputStream wrapperStream = new ByteArrayInputStream(body);
#Override
public boolean isFinished() {
return false;
}
#Override
public boolean isReady() {
return false;
}
#Override
public void setReadListener(ReadListener readListener) {
}
#Override
public int read() throws IOException {
return wrapperStream.read();
}
};
}
}
Any suggestions are appreciated. TIA.
Nte: After update i am not able to see the updated response as output. I am still seeing just the name but not id appended to it.
The one issue I see with your own implementation of ServletRequest is that you call super.getInputStream() instead of request.getInputStream(). Your own request is empty by default, that's why you're getting time out exception. You have to delegate a call to the actual request:
public class MyRequestWrapper extends HttpServletRequestWrapper {
private ServletInputStream input;
public MyRequestWrapper(ServletRequest request) {
super((HttpServletRequest)request);
}
public String getId() throws IOException {
if (input == null) {
try {
JSONObject jsonObject = new JSONObject(IOUtils.toString(/*DELETEGATE TO ACTUAL REQUEST*/request.getInputStream()));
String userId = jsonObject.getString("id");
userId = userId.replaceAll("\\D+","");
return userId;
} catch (JSONException e) {
e.printStackTrace();
}
}
return null;
}
}
I have a spring boot application. And now I need to read request and response in interceptor.I use a HttpServletRequestWrapper replace the request in DispatcherServlet
#Component("dispatcherServlet")
public class FofDisPatcherServlet extends DispatcherServlet {
#Override
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
MultiReadHttpServletRequest requestWrapper = null;
try {
requestWrapper = new MultiReadHttpServletRequest(request);
super.doDispatch(requestWrapper, response);
} catch (Exception e) {
super.doDispatch(request,response);
}
}
}
And in my interceptor , I can read the request body. But when I want to read the response body, it doesn't works.when I replace the response in the CustomerDispatcherServlet I got nothing response.I have tried ContentCachingResponseWrapper , but I got the payload with "".
It's a old question.and I have search some questions but didn't find a suitable solution.
I know I can solve the problem with AOP.But I want to know how can I do it in the interceptor?
here is my interceptor code
public class CustomerInterceptor extends HandlerInterceptorAdapter{
#Override
public void postHandle(...){
MultiReadHttpServletRequest req = (MultiReadHttpServletRequest) request;
ContentCachingResponseWrapper res = new ContentCachingResponseWrapper(response);
Byte[] body = res. getContentAsByteArray();
...
}
}
the body I got is [].
After few days .I find the answer.In the CustomerDispatcherServlet I should add responseWrapper.copyBodyToResponse()
the CustomerDIspatcherServlet like this:
#Component("dispatcherServlet")
public class FofDisPatcherServlet extends DispatcherServlet {
#Override
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
MultiReadHttpServletRequest requestWrapper = null;
try {
requestWrapper = new MultiReadHttpServletRequest(request);
if (!(response instanceof ContentCachingResponseWrapper)) {
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
super.doDispatch(requestWrapper, responseWrapper);
responseWrapper.copyBodyToResponse();
}else {
super.doDispatch(requestWrapper, response);
}
} catch (Exception e) {
super.doDispatch(request, response);
}
}
}
Try this:
#Component("dispatcherServlet")
public class FofDisPatcherServlet extends DispatcherServlet {
#Override
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
MultiReadHttpServletRequest requestWrapper = null;
try {
requestWrapper = new MultiReadHttpServletRequest(request);
super.doDispatch(requestWrapper, new ContentCachingResponseWrapper(request));
} catch (Exception e) {
super.doDispatch(request,response);
}
}
}
.
public class CustomerInterceptor extends HandlerInterceptorAdapter{
#Override
public void postHandle(..., HttpServletResponse response){
if (response instanceof ContentCachingResponseWrapper) {
Byte[] body = ((ContentCachingResponseWrapper)response). getContentAsByteArray();
}
...
}
}
The error is in your code
public class CustomerInterceptor extends HandlerInterceptorAdapter{
#Override
public void postHandle((HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView){
MultiReadHttpServletRequest req = (MultiReadHttpServletRequest) request;
ContentCachingResponseWrapper res = new ContentCachingResponseWrapper(response);
Byte[] body = res. getContentAsByteArray();
...
}
}
You are passing request in ContentCachingResponseWrapper.
See this question very similar problem .
I am attempting to change the Content-Type header in a request and change it to "application/json" before it reaches my spring rest controller. I have created a servlet request wrapper to change the values, but when the request reaches the controller it is still "text/plain". The logging shows that the header value has been changed before hitting doFilter();
Here is my class extending HttpServletRequestWrapper
class HttpServletRequestWritableWrapper extends HttpServletRequestWrapper {
private final Logger logger = org.slf4j.LoggerFactory.getLogger(HttpServletRequestWritableWrapper.class);
private final ByteArrayInputStream decryptedBody;
HttpServletRequestWritableWrapper(HttpServletRequest request, byte[] decryptedData) {
super(request);
decryptedBody = new ByteArrayInputStream(decryptedData);
}
#Override
public String getHeader(String name) {
String headerValue = super.getHeader(name);
if("Accept".equalsIgnoreCase(name))
{
logger.debug("Accept header changing :");
return headerValue.replaceAll(
MediaType.TEXT_PLAIN_VALUE, MediaType.APPLICATION_JSON_VALUE
);
}
else if ("Content-Type".equalsIgnoreCase(name))
{
logger.debug("Content type change: ");
return headerValue.replaceAll(MediaType.TEXT_PLAIN_VALUE, MediaType.APPLICATION_JSON_VALUE);
}
return headerValue;
}
#Override
public Enumeration<String> getHeaderNames() {
return super.getHeaderNames();
}
#Override
public String getContentType() {
String contentTypeValue = super.getContentType();
if (MediaType.TEXT_PLAIN_VALUE.equalsIgnoreCase(contentTypeValue)) {
logger.debug("Changing on getContentType():");
return MediaType.APPLICATION_JSON_VALUE;
}
return contentTypeValue;
}
#Override
public BufferedReader getReader() throws UnsupportedEncodingException {
return new BufferedReader(new InputStreamReader(decryptedBody, UTF_8));
}
#Override
public ServletInputStream getInputStream() throws IOException {
return new ServletInputStream() {
#Override
public int read() {
return decryptedBody.read();
}
};
}
And here is my filter:
#WebFilter(displayName = "EncryptionFilter", urlPatterns = "/*")
public class EncryptionFilter implements Filter {
private final Logger logger = org.slf4j.LoggerFactory.getLogger(EncryptionFilter.class);
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
byte[] data = "{\"currentClientVersion\":{\"majorElement\":\"1\",\"minorElement\":\"2\"}}".getBytes();
logger.debug("data string " + data.toString());
logger.debug("Content-type before: " + servletRequest.getContentType());
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletRequestWritableWrapper requestWrapper = new HttpServletRequestWritableWrapper(request, data);
//logger.debug("Accept Header: " + requestWrapper.getHeader("Accept"));
//logger.debug("Content-Type: " + requestWrapper.getHeader("Content-Type"));
//logger.debug("Contenttype" + requestWrapper.getContentType());
filterChain.doFilter(requestWrapper, servletResponse);
}
#Override
public void destroy() {
}
}
It appears that the getHeaders method was being called somewhere else after my filter and not returning the headers with my updated values.
I added this override in my HttpServletRequestWrapper and it is now working:
#Override
public Enumeration<String> getHeaders(String name) {
List<String> headerVals = Collections.list(super.getHeaders(name));
int index = 0;
for (String value : headerVals) {
if ("Content-Type".equalsIgnoreCase(name)) {
logger.debug("Content type change: ");
headerVals.set(index, MediaType.APPLICATION_JSON_VALUE);
}
index++;
}
return Collections.enumeration(headerVals);
}
I have the following problem: I try to get body of a POST request before it is handled by a spring controller. For that I am using the HandlerInterceptorAdapter's preHandle() method.
As stated in this discussion Spring REST service: retrieving JSON from Request I also use the HttpServletRequestWrapper. With this wrapper I managed to print the body of the first POST request, but the second POST throws an IOException: StreamClosed.
Do you have any ideas on how I can get the body of all POST requests?
Here is the preHandle() method from the interceptor:
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
System.out.println(request.getMethod());
MyRequestWrapper w = new MyRequestWrapper(request);
BufferedReader r = w.getReader();
System.out.println(r.readLine());
return super.preHandle(request, response, handler);
}
The HttpServletRequestWrapper:
public class MyRequestWrapper extends HttpServletRequestWrapper {
private ByteArrayOutputStream cachedBytes;
private HttpServletRequest request;
public MyRequestWrapper(HttpServletRequest request) {
super(request);
this.request = request;
}
#Override
public ServletInputStream getInputStream() throws IOException {
cachedBytes = new ByteArrayOutputStream();
if (request.getMethod().equals("POST"))
cacheInputStream();
return new CachedServletInputStream();
}
#Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
private void cacheInputStream() throws IOException {
/*
* Cache the inputstream in order to read it multiple times. For
* convenience, I use apache.commons IOUtils
*/
ServletInputStream inputStream = super.getInputStream();
if (inputStream == null) {
return;
}
IOUtils.copy(inputStream, cachedBytes);
}
/* An inputstream which reads the cached request body */
public class CachedServletInputStream extends ServletInputStream {
private ByteArrayInputStream input;
public CachedServletInputStream() {
/* create a new input stream from the cached request body */
input = new ByteArrayInputStream(cachedBytes.toByteArray());
}
#Override
public int read() throws IOException {
return input.read();
}
}
}
The console output:
2014-10-15 12:13:00 INFO [http-nio-8080-exec-1] org.springframework.web.servlet.DispatcherServlet - FrameworkServlet 'dispatcherServlet': initialization completed in 9 ms
GET
null
GET
null
POST
{"long":null,"owner":{"__type":"Owner","id":20,"version":1,"md5Password":""},"string":"ws","tool":{"__type":"Tool","id":33,"version":1}}
POST
2014-10-15 12:13:00 ERROR [http-nio-8080-exec-3] org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet] - Servlet.service() for servlet dispatcherServlet threw exception
java.io.IOException: Stream closed
You're attempting to read from the original request in your Wrapper, but after this, the original request is still being read - hence the request input stream has been consumed and cannot be read from again.
Instead of using an Interceptor, consider using a javax.servlet.Filter. In the doFilter method, you can pass the wrapped request on down the chain.
I've used filter that implements Filter & interceptor that extends HandlerInterceptorAdapter (because in the filter all fields are nullable and I can't save anything to DB. see Autowired Null Pointer Exception) to retreive request and response body and save them to DB. If your filter works fine then use only filter.
filter. Here I wrap a request and a response to read from them not only once. You can use ContentCachingRequestWrapper and ContentCachingResponseWrapper for that.
#Component
public class RequestLogFilter implements Filter {
private final Logger logger = LoggerFactory.getLogger(RequestLogFilter.class);
#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 {
logger.info("======================> FILTER <======================");
HttpServletRequest requestToCache = new ContentCachingRequestWrapper((HttpServletRequest) request);
HttpServletResponse responseToCache = new ContentCachingResponseWrapper((HttpServletResponse) response);
// before method
chain.doFilter(requestToCache, responseToCache);
// after method
// your logic(save to DB, logging...)
getRequestData(request);
getResponseData(response);
}
#Override
public void destroy() {
}
}
-
#Component
public class RequestLogInterceptor extends HandlerInterceptorAdapter {
private final Logger logger = LoggerFactory.getLogger(RequestLogInterceptor.class);
#Autowired
private InboundRequestLogStore inboundRequestLogStore;
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
logger.info("====================> INTERCEPTOR <========================");
try {
if (request.getAttribute(InboundRequestAspect.INBOUND_LOG_MARKER) != null) {
InboundRequestLogRecord logRecord = new InboundRequestLogRecord();
logRecord.setIpAddress(request.getRemoteAddr());
// getting request and response body
logRecord.setRequestBody(getRequestData(request));
logRecord.setResponseBody(getResponseData(response));
logRecord.setResponseCode(((HttpServletResponse) response).getStatus());
String uri = request.getScheme() + "://" + request.getServerName()
+ ("http".equals(request.getScheme()) && request.getServerPort() == 80
|| "https".equals(request.getScheme()) && request.getServerPort() == 443 ? ""
: ":" + request.getServerPort())
+ request.getRequestURI()
+ (request.getQueryString() != null ? "?" + request.getQueryString() : "");
logRecord.setUrl(uri);
inboundRequestLogStore.add(logRecord); // save to DB
} else {
((ContentCachingResponseWrapper) response).copyBodyToResponse(); // in other case you send null to the response
}
} catch (Exception e) {
logger.error("error ", e);
try {
((ContentCachingResponseWrapper) response).copyBodyToResponse(); // in other case you send null to the response
} catch (Exception e2) {
// TODO Auto-generated catch block
logger.error("error ", e2);
}
}
}
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;
}
public static String getResponseData(final HttpServletResponse response) throws UnsupportedEncodingException, 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(); // in other case you send null to the response
}
return payload;
}
}
add to servlet-context.xml
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<beans:bean class="path.to.RequestLogInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
namespaces:
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
ContentCachingRequestWrapper - http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/util/ContentCachingRequestWrapper.html
ContentCachingResponseWrapper - http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/util/ContentCachingResponseWrapper.html
I need to render ModelAndView in my controller manually in order to put it inside JSON object. If I pass the whole ModelAndView object into to JSON I get " no serializer found for class javassistlazyinitializer" exception because jackson can't work properly with LAZY-objects.
Thank you
public class JSONView implements View {
/**
* Logger for this class
*/
private static final Logger logger = Logger.getLogger(JSONView.class);
private String contentType = "application/json";
public void render(Map map, HttpServletRequest request, HttpServletResponse response)
throws Exception {
if(logger.isDebugEnabled()) {
logger.debug("render(Map, HttpServletRequest, HttpServletResponse) - start");
}
JSONObject jsonObject = new JSONObject(map);
PrintWriter writer = response.getWriter();
writer.write(jsonObject.toString());
if(logger.isDebugEnabled()) {
logger.debug("render(Map, HttpServletRequest, HttpServletResponse) - end");
}
}
public String getContentType() {
return contentType;
}
}
ModelAndView returnModelAndView = new ModelAndView(new JSONView(), model);