/* * Copyright (c) 2016 Saugo360. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.tsdr.restconf.collector; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.invoke.MethodHandles; import java.util.Arrays; import java.util.regex.Pattern; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class is responsible for intercepting restconf requests and logging them if they conform to the criteria * specified in the configuration. Much of the code in the class is added to create an input stream that can be consumed * again by other ServletFilters or by the Restconf Servlet itself. More information about this issue could be found * here: http://wetfeetblog.com/servlet-filer-to-log-request-and-response-details-and-payload/431 * * @author <a href="mailto:a.alhamali93@gmail.com">AbdulRahman AlHamali</a> * * Created: Dec 16th, 2016 * */ public class TSDRRestconfCollectorFilter implements Filter { /** * the logger of the class. */ private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); /** * a reference to the restconf collector logger singleton. */ private TSDRRestconfCollectorLogger tsdrRestconfCollectorLogger; /** * a reference to the restconf collector config singleton. */ private TSDRRestconfCollectorConfig tsdrRestconfCollectorConfig; /** * called when the filter is first initialized, it obtains the instances of the restconf collector logger and * the restconf collector config. * @param filterConfig the configuration of the filter (not used) */ @Override public void init(FilterConfig filterConfig) { tsdrRestconfCollectorLogger = TSDRRestconfCollectorLogger.getInstance(); tsdrRestconfCollectorConfig = TSDRRestconfCollectorConfig.getInstance(); } /** * called each time a request is sent to the restconf, the function checks whether the request satisfies the * criteria specified in the configuration, and decides whether to log it or not, then passes the request to the * rest of the chain. * @param request the request received * @param response the response from the servlet * @param chain the chain of filters registered on the servlet */ @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { try { HttpServletRequest httpServletRequest = (HttpServletRequest)request; BufferedRequestWrapper bufferedRequest = new BufferedRequestWrapper(httpServletRequest); String method = httpServletRequest.getMethod(); String path = httpServletRequest.getPathInfo(); String address = httpServletRequest.getRemoteAddr(); String body = bufferedRequest.getRequestBody(); if (requestPassesCriteria(method, path, address, body)) { tsdrRestconfCollectorLogger.insertLog(method, path, address, body); } chain.doFilter(bufferedRequest, response); } catch (IOException e) { LOG.error(e.toString()); } catch (ServletException e) { LOG.error(e.toString()); } } /** * called when the filter is destroyed to clean up. */ @Override public void destroy() { } /** * called with the parameters of the request to check if the match the criteria specified in the configuration. * @param method the http method * @param path the relative url of the request * @param address the address from which the request originated * @param body the content of the request * @return returns true if the request passes the criteria */ private boolean requestPassesCriteria(String method, String path, String address, String body) { String methods = tsdrRestconfCollectorConfig.getProperty("METHODS_TO_LOG"); if (!Arrays.asList(methods.split(",")).contains(method)) { return false; } String paths = tsdrRestconfCollectorConfig.getProperty("PATHS_TO_LOG"); if (!Pattern.compile(paths).matcher(path).matches()) { return false; } String addresses = tsdrRestconfCollectorConfig.getProperty("REMOTE_ADDRESSES_TO_LOG"); if (!Pattern.compile(addresses).matcher(address).matches()) { return false; } String content = tsdrRestconfCollectorConfig.getProperty("CONTENT_TO_LOG"); if (!Pattern.compile(content).matcher(body).matches()) { return false; } return true; } /** * a wrapper for the request that provides a body that could be consumed multiple times. */ private static final class BufferedRequestWrapper extends HttpServletRequestWrapper { /** * a byte array that caches the body of the request. */ private byte[] buffer = null; /** * when the constructor is called, it reads the body of the request and caches it in the byte buffer. */ BufferedRequestWrapper(HttpServletRequest req) throws IOException { super(req); // Read InputStream and store its content in a buffer. InputStream is = req.getInputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buf = new byte[1024]; int letti; while ((letti = is.read(buf)) > 0) { baos.write(buf, 0, letti); } this.buffer = baos.toByteArray(); } /** * whenever getInputStream is called, it returns a new stream, filled with the data of the byte buffer, that * way the input stream will be reusable by other filters on the way. */ @Override public ServletInputStream getInputStream() { return new BufferedServletInputStream(new ByteArrayInputStream(this.buffer)); } /** * reads the input stream and stores it in a string that it returns. * @return returns a string containing the body of the request */ String getRequestBody() throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(this.getInputStream())); String line = null; StringBuilder inputBuffer = new StringBuilder(); do { line = reader.readLine(); if (line != null) { inputBuffer.append(line.trim()); } } while (line != null); reader.close(); return inputBuffer.toString().trim(); } } /** * represents a reusable input stream. */ private static final class BufferedServletInputStream extends ServletInputStream { /** * the internal stream. */ private ByteArrayInputStream bais; BufferedServletInputStream(ByteArrayInputStream bais) { this.bais = bais; } /** * when called, called the same function of the internal stream. */ @Override public int available() { return this.bais.available(); } /** * when called, called the same function of the internal stream. */ @Override public int read() { return this.bais.read(); } /** * when called, called the same function of the internal stream. */ @Override public int read(byte[] buf, int off, int len) { return this.bais.read(buf, off, len); } } }