Buscar este blog

viernes, 25 de marzo de 2016

Java servlet download log file

This is a basic servlet to download the log files of  a web app.
The servlet has the following config params:
  • logsConfigLocation. The base dir in which the log files are stored. It is not allowed to read files outside this directory.
  • logsMaxSize. The maximum size of the log file allowed to download
The config params can be configured usen Spring Expression Language, so a valid value could be ${my.config.property:/home/jboss/logs}
If none is specified, the default values are jboss.server.log.dir and 10MB respectively.


In order to prevent public access to this files, the servlet can be configured to use basic authentication. In this case, you need to configure the appropriate login mechanism in the app, form example, in web.xml:
<login-config>
 <auth-method>BASIC</auth-method>
 <realm-name>default</realm-name>
</login-config>

The servlet receives a request param called "path" which contains the relative route of the requested log file.

LoggerServlet:
package es.sisifo.jee6.servlet;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.HttpConstraint;
import javax.servlet.annotation.ServletSecurity;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.util.ServletContextPropertyUtils;


@WebServlet(urlPatterns = { "/loggerServlet" })
@ServletSecurity(@HttpConstraint(rolesAllowed = { "admin" }))
public class LoggerServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    public static final String CONFIG_LOCATION_PARAM = "logsConfigLocation";
    public static final String CONFIG_MAXSIXE_PARAM = "logsMaxSize";

    private String logsLocation = System.getProperty("jboss.server.log.dir");
    private int maxFileSize = 10485760; // 10MB

    @Override
    public void init(final ServletConfig servletConfig) throws ServletException {
        super.init(servletConfig);

        if (servletConfig.getInitParameter(CONFIG_LOCATION_PARAM) != null) {
            logsLocation = ServletContextPropertyUtils.resolvePlaceholders(
                    servletConfig.getInitParameter(CONFIG_LOCATION_PARAM), servletConfig.getServletContext());
        }

        if (servletConfig.getInitParameter(CONFIG_MAXSIXE_PARAM) != null) {
            maxFileSize = Integer.valueOf(ServletContextPropertyUtils.resolvePlaceholders(
                    servletConfig.getInitParameter(CONFIG_MAXSIXE_PARAM), servletConfig.getServletContext()));
        }
    }


    @Override
    protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException,
            IOException {

        final String path = request.getParameter("path");
        if (!isValidPath(path)) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid path");
            return;
        }

        final File logFile = new File(logsLocation + File.separator + path);
        if (!isValidFile(logFile)) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid file");
            return;
        }

        downloadFile(logFile, response);
    }


    private void downloadFile(final File logFile, final HttpServletResponse response) throws IOException {
        final FileInputStream inStream = new FileInputStream(logFile);

        response.setContentType(getServletContext().getMimeType(logFile.getAbsolutePath()));
        response.setContentLength((int) logFile.length());
        response.setHeader("Content-Disposition", String.format("attachment; filename=\"%s\"", logFile.getName()));


        final OutputStream outStream = response.getOutputStream();

        final byte[] buffer = new byte[4096];
        int bytesRead = -1;
        while ((bytesRead = inStream.read(buffer)) != -1) {
            outStream.write(buffer, 0, bytesRead);
        }

        inStream.close();
        outStream.close();
    }


    private boolean isValidPath(final String path) {
        return path != null && !path.contains("..");
    }


    private boolean isValidFile(final File logFile) {
        return logFile.exists() && logFile.isFile() && logFile.length() <= maxFileSize;
    }
}

domingo, 13 de marzo de 2016

Tomcat - Custom request dump filter - Log request and response body

Tomcat 7 has two components to intercept server requests:
Both are almost the same, with the difference that valves are tomcat specific.
In previous versions, tomcat had a valve to log http traffic, but in the last releases it was eliminated and substituted by a filter called Request_Dumper_Filter (https://tomcat.apache.org/tomcat-7.0-doc/config/filter.html#Request_Dumper_Filter).
You can configure this filter in tomcat server.xml, so it will intercept all requests for all the applications hosted by the server (well, you can specify a URL pattern). In this way, if you want to monitor a concrete app, you dont need to change it, just reconfigure tomcat.

The problem with Request_Dumper_Filter is that it does not work properly with application/x-www-form-urlencoded POST requests. So it may make the back-end servlets to fail, because when you read the request parameters, you are consuming its input stream.

The solution is to create a custom Dumper Filter to solve this. I found two interesting partial implementations here and here, based in request and response wrappers, and unify them in a single filter.

The code is in https://github.com/evazquezma/tomcat/tree/master/custom-tomcat-utils.

In order to use this filter you need to do as follows:
1) Copy the jar to the ${CATALINA_HOME}/lib

2) Edit ${CATALINA_HOME}/conf/server.xml and declare the filter:
<filter>
 <filter-name>requestdumper</filter-name>
 <filter-class>es.sisifo.tomcatutil.filters.SimpleRequestDumperFilter</filter-class>
</filter>
<filter-mapping>
 <filter-name>requestdumper</filter-name>
 <url-pattern>*</url-pattern>
</filter-mapping>

3) Edit ${CATALINA_HOME}/conf/loggin.properties and configure the traces:
handlers = (...), 5request-dumper.org.apache.juli.FileHandler
(....)
5request-dumper.org.apache.juli.FileHandler.level = INFO
5request-dumper.org.apache.juli.FileHandler.directory = ${catalina.base}/logs
5request-dumper.org.apache.juli.FileHandler.prefix = request-dumper.
5request-dumper.org.apache.juli.FileHandler.formatter = org.apache.juli.VerbatimFormatter
es.sisifo.tomcatutil.filters.SimpleRequestDumperFilter.level = INFO
es.sisifo.tomcatutil.filters.SimpleRequestDumperFilter.handlers = \
5request-dumper.org.apache.juli.FileHandler

The result will be something like this (this is a log from this app):
http-bio-8080-exec-4 ******************=********************************************
http-bio-8080-exec-4 START TIME       a =13-mar-2016 12:07:02
http-bio-8080-exec-4         requestURI=/afirma-server-triphase-signer/SignatureService
http-bio-8080-exec-4           authType=null
http-bio-8080-exec-4  characterEncoding=null
http-bio-8080-exec-4      contentLength=2259
http-bio-8080-exec-4        contentType=application/x-www-form-urlencoded
http-bio-8080-exec-4        contextPath=/afirma-server-triphase-signer
http-bio-8080-exec-4             header=accept=*/*
http-bio-8080-exec-4             header=content-type=application/x-www-form-urlencoded
http-bio-8080-exec-4             header=user-agent=Java/1.8.0_60
http-bio-8080-exec-4             header=host=localhost:8080
http-bio-8080-exec-4             header=connection=keep-alive
http-bio-8080-exec-4             header=content-length=2259
http-bio-8080-exec-4             locale=es_ES
http-bio-8080-exec-4             method=POST
http-bio-8080-exec-4          parameter=op=pre
http-bio-8080-exec-4          parameter=cop=sign
http-bio-8080-exec-4          parameter=format=pades
http-bio-8080-exec-4          parameter=algo=SHA512withRSA
http-bio-8080-exec-4          parameter=cert=MIIC6jCCAdKgAwIBAgIEVtGLlDANBgkqhkiG9w0BAQsFADA9MRswGQYDVQQLDBJKYm9zcyBBcnF1aXRlY3R1cmExHjAcBgNVBAMMFUNBLUpCT1NTIEFycXVpdGVjdHVyYTAgFw0xNjAyMjcxMTQyMzNaGA8yMTE1MDIyNzExNDIzM1owLzENMAsGA1UECgwEdGVzdDENMAsGA1UECwwEdGVzdDEPMA0GA1UEAwwGc2lzaWZvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6GJwUNgmPFoE48U-8CU2jdTPRTl-GthiGyk7ggo-V3MYMgQ_7imW7WzAm7rGVncwtJ_-JYiHSG-Kry1Fb558ux2_9yLY6cfOvQroid9kpH2lRoZ-pBBl1Ww7GO3Z0FZKVf0TJIIDSv__NuPFIV0IxEYezWWDCW9NfzKqjrkbdNgJB07T8M7cWUuojW8LcbV_i3Z-m0pLif51cbdyLjowtbqKN_b04tP74PjQt5hI9tmBRIL51bRebAn6xc2C34_fiIf9AEETpRV_YLh80eClueeb4qiiaSWkYwo11FCswZE0eHSBTpwYrHX1IWMdZ64-IUYAwSvz4Ypx0Ma_JEiFJQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBtI5LSsXlCK4u3PRjYXdD-lruCEIpw1YNow9SYIe_zyg_W060R0Ir-Xr3wEt6Buf1vjfOSg5J1hF3xxoRt-Dvj1La6xWNnYiF8g2mozNFs-FeL8m1Tv7Ll8LKShmOyJXDzkEpBcK4eZp92mgBH2JuOF8emhrP6BJqb2C4fR6KL3um4OlgIynGWYcdZvXcUt3k5rU6QShrCNxpfraPzeGzDifG2zmSI4GraoB7FwTiMLDjg8_20kmxot_w5Tm31D79F-XL2_qfP9TQL_qhV7F-5WR-bMSU1oa3hI0rJYvRuNF8KsPEaRUoLvRpEpW8dJ-XEgUL9etVAuSEk3obPFzJF,MIIC-DCCAeCgAwIBAgIEVMp9_TANBgkqhkiG9w0BAQsFADA9MRswGQYDVQQLDBJKYm9zcyBBcnF1aXRlY3R1cmExHjAcBgNVBAMMFUNBLUpCT1NTIEFycXVpdGVjdHVyYTAgFw0xNTAxMjkxODM4MTFaGA8yMTE0MDEyOTE4MzgxMVowPTEbMBkGA1UECwwSSmJvc3MgQXJxdWl0ZWN0dXJhMR4wHAYDVQQDDBVDQS1KQk9TUyBBcnF1aXRlY3R1cmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCBnpiP835iOWmqrDdgBWyp8yaG6pe_PVI944YqU_AKlrsa_WZFxMRy6Uri0TGuT5hlmUJsw6e7fDR9bf7F4K5wE4ZSEVG_njlDShjlsJ4CgvVfneK-kOva9KzlpUM3do-Bm-3WbfvHl0d8eujc_sHD4uSyH1yI04r9YrHN0OeY1qFfsowS3EzPdTsw9oeoBAyW4nVZQG-VqXwBSnkwxQOEERN_-3l9tSxEV12gRDieUJj5y3USdfxHruqNPbmAu6yc2rXIR7yeeq0CCrbnvTlW81lCVWvSVV2o9fFt-irdP4P20e-ecR-24VzIV7Brm4LE3snmExDb-aLF6XmqiokTAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAAcsXzdYsrAnUwd269PMUNa4cSBnDk7g9YY36KHIDLo72XTJOgbeb-pQXWEwr5A5vuH1MeT_CHYMJkPvqM4kaFVN4zQVOhi3XKzTSAsLCERprvwU0bg2Nk65TeE6SIELWfWkcdko8x3Hn0uwEniJXA275G_kT15aBQ1tiyEGhYwPpO3pdKeMRmtFZfyvuernVAko9-FCBauH7KFZGIpne-VzA2rIFa0kTta1HesVeoAmetltgvxGdNrwnQJZfg8PNTF3ceQSyK0quCJv4nxcy_pwV-tQRXXLScvIFRuXdu6782PSDFa17TYY9FOPmyMKiwsz-yjCu8JB9xuPCrzRnuE=
http-bio-8080-exec-4          parameter=doc=cHJ1ZWJhLnBkZg==
http-bio-8080-exec-4          parameter=params=Iw0KI1N1biBNYXIgMTMgMTI6MDc6MDIgQ0VUIDIwMTYNCnNlcnZlclVybD1odHRwXDovL2xvY2FsaG9zdFw6ODA4MC9hZmlybWEtc2VydmVyLXRyaXBoYXNlLXNpZ25lci9TaWduYXR1cmVTZXJ2aWNlDQo=
http-bio-8080-exec-4           pathInfo=null
http-bio-8080-exec-4           protocol=HTTP/1.1
http-bio-8080-exec-4        queryString=null
http-bio-8080-exec-4         remoteAddr=127.0.0.1
http-bio-8080-exec-4         remoteHost=127.0.0.1
http-bio-8080-exec-4         remoteUser=null
http-bio-8080-exec-4 requestedSessionId=null
http-bio-8080-exec-4             scheme=http
http-bio-8080-exec-4         serverName=localhost
http-bio-8080-exec-4         serverPort=8080
http-bio-8080-exec-4        servletPath=/SignatureService
http-bio-8080-exec-4           isSecure=false
http-bio-8080-exec-4           authType=null
http-bio-8080-exec-4         remoteUser=null
http-bio-8080-exec-4 ------------------=--------------------------------------------
http-bio-8080-exec-4 ------------------=--------------------------------------------
http-bio-8080-exec-4        contentType=text/plain;charset=utf-8
http-bio-8080-exec-4             header=Access-Control-Allow-Origin=*
http-bio-8080-exec-4             status=200
http-bio-8080-exec-4 Response body=PHhtbD4KIDxmaXJtYXM-CiAgPGZpcm1hIElkPSI5MzQ2YTllYi00NDNlLTRhYjAtODVhZC03ODE1YjViMjFhM2IiPgogICA8cGFyYW0gbj0iTkVFRF9QUkUiPnRydWU8L3BhcmFtPgogICA8cGFyYW0gbj0iVElNRSI-MTQ1Nzg2NzIyMjY3ODwvcGFyYW0-CiAgIDxwYXJhbSBuPSJQUkUiPk1ZSUJJekFZQmdrcWhraUc5dzBCQ1FNeEN3WUpLb1pJaHZjTkFRY0JNRThHQ1NxR1NJYjNEUUVKQkRGQ0JFQk1NdHpSTTNIV3F0R1NEYWlBQjBHalFKNTd0dXh3bGVIV2pBcmhTV2ViRVltd0dFcFpyYjFndGIwSUZKbGhmMmVITmkzYnpKVUlzMHBTSFg0OHhFVU5NSUcxQmdzcWhraUc5dzBCQ1JBQ0x6R0JwVENCb2pDQm56Q0JuREFOQmdsZ2hrZ0JaUU1FQWdNRkFBUkEvRWNydW1KSFhzaSt5Y2xZa05WODNlQ0RUWWZLM3VuTkhHeEpTdmRaanBwR1RIVW5lUkpmLyszVW9yV3FZdlZGelJzMkNORGlsQTFTVnVFOHN3dEtWREJKTUVHa1B6QTlNUnN3R1FZRFZRUUxEQkpLWW05emN5QkJjbkYxYVhSbFkzUjFjbUV4SGpBY0JnTlZCQU1NRlVOQkxVcENUMU5USUVGeWNYVnBkR1ZqZEhWeVlRSUVWdEdMbEE9PTwvcGFyYW0-CiAgIDxwYXJhbSBuPSJQSUQiPld6eGhPREpqWVdVMU1qZGtaR015WW1SalpURmpNR1JpTXpneU5EUTROakkxTXo0OE4yTTRNemxqT0RobE9EaGhZakU0TURKallqTm1PRE14TWpneE5XTmtOR0krWFE9PTwvcGFyYW0-CiAgPC9maXJtYT4KIDwvZmlybWFzPgo8L3htbD4=
http-bio-8080-exec-4 END TIME          =13-mar-2016 12:07:02
http-bio-8080-exec-4 ===============================================================