450855 - GZipFilter declaration order can result in MIGHT_COMPRESS
+ Adding testcase GzipFilterLayeredTest to demonstrate this bug + Some cleanup of Gzip testing behavior to be easier to follow (less reliance on GzipTester utility class)
This commit is contained in:
parent
e4cc9ea5de
commit
9e8a776c3e
|
@ -34,11 +34,16 @@ import org.eclipse.jetty.server.LocalConnector;
|
|||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.util.Attributes;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
|
||||
public class ServletTester extends ContainerLifeCycle
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(ServletTester.class);
|
||||
|
||||
private final Server _server=new Server();
|
||||
private final LocalConnector _connector=new LocalConnector(_server);
|
||||
private final ServletContextHandler _context;
|
||||
|
@ -163,8 +168,6 @@ public class ServletTester extends ContainerLifeCycle
|
|||
_context.setResourceBase(resourceBase);
|
||||
}
|
||||
|
||||
private final ServletHandler _handler;
|
||||
|
||||
public ServletTester()
|
||||
{
|
||||
this("/",ServletContextHandler.SECURITY|ServletContextHandler.SESSIONS);
|
||||
|
@ -178,7 +181,6 @@ public class ServletTester extends ContainerLifeCycle
|
|||
public ServletTester(String contextPath,int options)
|
||||
{
|
||||
_context=new ServletContextHandler(_server,contextPath,options);
|
||||
_handler=_context.getServletHandler();
|
||||
_server.setConnectors(new Connector[]{_connector});
|
||||
addBean(_server);
|
||||
}
|
||||
|
@ -190,25 +192,40 @@ public class ServletTester extends ContainerLifeCycle
|
|||
|
||||
public String getResponses(String request) throws Exception
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("Request: {}",request);
|
||||
}
|
||||
return _connector.getResponses(request);
|
||||
}
|
||||
|
||||
public String getResponses(String request, long idleFor,TimeUnit units) throws Exception
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("Request: {}",request);
|
||||
}
|
||||
return _connector.getResponses(request, idleFor, units);
|
||||
}
|
||||
|
||||
public ByteBuffer getResponses(ByteBuffer request) throws Exception
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("Request (Buffer): {}",BufferUtil.toUTF8String(request));
|
||||
}
|
||||
return _connector.getResponses(request);
|
||||
}
|
||||
|
||||
public ByteBuffer getResponses(ByteBuffer requestsBuffer,long idleFor,TimeUnit units) throws Exception
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("Requests (Buffer): {}",BufferUtil.toUTF8String(requestsBuffer));
|
||||
}
|
||||
return _connector.getResponses(requestsBuffer, idleFor, units);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Create a port based connector.
|
||||
* This methods adds a port connector to the server
|
||||
* @return A URL to access the server via the connector.
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.servlets;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.servlet.DispatcherType;
|
||||
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpTester;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.servlet.FilterHolder;
|
||||
import org.eclipse.jetty.servlets.gzip.AsyncManipFilter;
|
||||
import org.eclipse.jetty.servlets.gzip.GzipTester;
|
||||
import org.eclipse.jetty.servlets.gzip.GzipTester.ContentMetadata;
|
||||
import org.eclipse.jetty.servlets.gzip.TestServletLengthStreamTypeWrite;
|
||||
import org.eclipse.jetty.toolchain.test.TestTracker;
|
||||
import org.eclipse.jetty.toolchain.test.TestingDir;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameter;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
/**
|
||||
* Test the GzipFilter support when under several layers of Filters.
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class GzipFilterLayeredTest
|
||||
{
|
||||
@Rule
|
||||
public final TestTracker tracker = new TestTracker();
|
||||
|
||||
private static final HttpConfiguration defaultHttp = new HttpConfiguration();
|
||||
private static final int LARGE = defaultHttp.getOutputBufferSize() * 8;
|
||||
private static final int SMALL = defaultHttp.getOutputBufferSize() / 4;
|
||||
private static final int TINY = AsyncGzipFilter.DEFAULT_MIN_GZIP_SIZE / 2;
|
||||
private static final boolean EXPECT_COMPRESSED = true;
|
||||
|
||||
@Parameters(name = "{0} bytes - {1} - compressed: {2}")
|
||||
public static List<Object[]> data()
|
||||
{
|
||||
List<Object[]> ret = new ArrayList<Object[]>();
|
||||
|
||||
ret.add(new Object[] { 0, "empty.txt", !EXPECT_COMPRESSED });
|
||||
ret.add(new Object[] { TINY, "file-tiny.txt", !EXPECT_COMPRESSED });
|
||||
ret.add(new Object[] { SMALL, "file-small.txt", EXPECT_COMPRESSED });
|
||||
ret.add(new Object[] { LARGE, "file-large.txt", EXPECT_COMPRESSED });
|
||||
ret.add(new Object[] { LARGE, "file-large.mp3", !EXPECT_COMPRESSED });
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Parameter(0)
|
||||
public int fileSize;
|
||||
@Parameter(1)
|
||||
public String fileName;
|
||||
@Parameter(2)
|
||||
public boolean expectCompressed;
|
||||
|
||||
@Rule
|
||||
public TestingDir testingdir = new TestingDir();
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testGzipDosNormal() throws Exception
|
||||
{
|
||||
GzipTester tester = new GzipTester(testingdir, GzipFilter.GZIP);
|
||||
|
||||
// Add Gzip Filter first
|
||||
FilterHolder gzipHolder = new FilterHolder(AsyncGzipFilter.class);
|
||||
gzipHolder.setAsyncSupported(true);
|
||||
tester.addFilter(gzipHolder,"*.txt",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
|
||||
tester.addFilter(gzipHolder,"*.mp3",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
|
||||
gzipHolder.setInitParameter("mimeTypes","text/plain");
|
||||
|
||||
// Add (DoSFilter-like) manip filter (in chain of Gzip)
|
||||
FilterHolder manipHolder = new FilterHolder(AsyncManipFilter.class);
|
||||
manipHolder.setAsyncSupported(true);
|
||||
tester.addFilter(manipHolder,"/*",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
|
||||
|
||||
// Add normal content servlet
|
||||
tester.setContentServlet(TestServletLengthStreamTypeWrite.class);
|
||||
|
||||
try
|
||||
{
|
||||
File testFile = tester.prepareServerFile("GzipDosNormal-" + fileName,fileSize);
|
||||
|
||||
tester.start();
|
||||
|
||||
HttpTester.Response response = tester.issueRequest("GET","/" + testFile.getName(), 2, TimeUnit.SECONDS);
|
||||
|
||||
assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
|
||||
|
||||
if (expectCompressed)
|
||||
{
|
||||
// Must be gzip compressed
|
||||
assertThat("Content-Encoding",response.get("Content-Encoding"),containsString(GzipFilter.GZIP));
|
||||
}
|
||||
|
||||
// Uncompressed content Size
|
||||
ContentMetadata content = tester.getResponseMetadata(response);
|
||||
assertThat("(Uncompressed) Content Length", content.size, is((long)fileSize));
|
||||
}
|
||||
finally
|
||||
{
|
||||
tester.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDosGzipNormal() throws Exception
|
||||
{
|
||||
GzipTester tester = new GzipTester(testingdir, GzipFilter.GZIP);
|
||||
|
||||
// Add (DoSFilter-like) manip filter
|
||||
FilterHolder manipHolder = new FilterHolder(AsyncManipFilter.class);
|
||||
manipHolder.setAsyncSupported(true);
|
||||
tester.addFilter(manipHolder,"/*",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
|
||||
|
||||
// Add Gzip Filter first (in chain of DosFilter)
|
||||
FilterHolder gzipHolder = new FilterHolder(AsyncGzipFilter.class);
|
||||
gzipHolder.setAsyncSupported(true);
|
||||
tester.addFilter(gzipHolder,"*.txt",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
|
||||
tester.addFilter(gzipHolder,"*.mp3",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
|
||||
gzipHolder.setInitParameter("mimeTypes","text/plain");
|
||||
|
||||
// Add normal content servlet
|
||||
tester.setContentServlet(TestServletLengthStreamTypeWrite.class);
|
||||
|
||||
try
|
||||
{
|
||||
File testFile = tester.prepareServerFile("DosGzipNormal-" + fileName,fileSize);
|
||||
|
||||
tester.start();
|
||||
|
||||
HttpTester.Response response = tester.issueRequest("GET",testFile.getName(),2,TimeUnit.SECONDS);
|
||||
|
||||
assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
|
||||
|
||||
if (expectCompressed)
|
||||
{
|
||||
// Must be gzip compressed
|
||||
assertThat("Content-Encoding",response.get("Content-Encoding"),containsString(GzipFilter.GZIP));
|
||||
} else
|
||||
{
|
||||
assertThat("Content-Encoding",response.get("Content-Encoding"),not(containsString(GzipFilter.GZIP)));
|
||||
}
|
||||
|
||||
// Uncompressed content Size
|
||||
ContentMetadata content = tester.getResponseMetadata(response);
|
||||
assertThat("(Uncompressed) Content Length", content.size, is((long)fileSize));
|
||||
}
|
||||
finally
|
||||
{
|
||||
tester.stop();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.servlets.gzip;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.AsyncEvent;
|
||||
import javax.servlet.AsyncListener;
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
/**
|
||||
* Filter that merely manipulates the AsyncContext.
|
||||
* <p>
|
||||
* The pattern of manipulation is modeled after how DOSFilter behaves. The purpose of this filter is to test arbitrary filter chains that could see unintended
|
||||
* side-effects of async context manipulation.
|
||||
*/
|
||||
public class AsyncManipFilter implements Filter, AsyncListener
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(AsyncManipFilter.class);
|
||||
private static final String MANIP_KEY = AsyncManipFilter.class.getName();
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
|
||||
{
|
||||
LOG.debug("doFilter() - {}", chain);
|
||||
AsyncContext ctx = (AsyncContext)request.getAttribute(MANIP_KEY);
|
||||
if (ctx == null)
|
||||
{
|
||||
LOG.debug("Initial pass through: {}", chain);
|
||||
ctx = request.startAsync();
|
||||
ctx.addListener(this);
|
||||
ctx.setTimeout(1000);
|
||||
LOG.debug("AsyncContext: {}", ctx);
|
||||
request.setAttribute(MANIP_KEY,ctx);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.debug("Second pass through: {}", chain);
|
||||
chain.doFilter(request,response);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(AsyncEvent event) throws IOException
|
||||
{
|
||||
LOG.debug("onComplete() {}",event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimeout(AsyncEvent event) throws IOException
|
||||
{
|
||||
LOG.debug("onTimeout() {}",event.getAsyncContext());
|
||||
event.getAsyncContext().dispatch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(AsyncEvent event) throws IOException
|
||||
{
|
||||
LOG.debug("onError()",event.getThrowable());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartAsync(AsyncEvent event) throws IOException
|
||||
{
|
||||
LOG.debug("onTimeout() {}",event);
|
||||
}
|
||||
}
|
|
@ -18,13 +18,8 @@
|
|||
|
||||
package org.eclipse.jetty.servlets.gzip;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
@ -37,6 +32,7 @@ import java.security.DigestOutputStream;
|
|||
import java.security.MessageDigest;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Enumeration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
@ -51,6 +47,7 @@ import javax.servlet.http.HttpServletResponse;
|
|||
import org.eclipse.jetty.http.DateGenerator;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpTester;
|
||||
import org.eclipse.jetty.http.HttpTester.Response;
|
||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||
import org.eclipse.jetty.servlet.FilterHolder;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
|
@ -67,8 +64,20 @@ import org.junit.Assert;
|
|||
public class GzipTester
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(GzipTester.class);
|
||||
|
||||
private Class<? extends Filter> gzipFilterClass = GzipFilter.class;
|
||||
|
||||
public static class ContentMetadata
|
||||
{
|
||||
public final long size;
|
||||
public final String sha1;
|
||||
|
||||
public ContentMetadata(long size, String sha1checksum)
|
||||
{
|
||||
this.size = size;
|
||||
this.sha1 = sha1checksum;
|
||||
}
|
||||
}
|
||||
|
||||
private Class<? extends Filter> gzipFilterClass = null;
|
||||
private String encoding = "ISO8859_1";
|
||||
private String userAgent = null;
|
||||
private final ServletTester tester = new ServletTester();;
|
||||
|
@ -80,14 +89,14 @@ public class GzipTester
|
|||
{
|
||||
this.testdir = testingdir;
|
||||
this.compressionType = compressionType;
|
||||
this.accept=accept;
|
||||
this.accept = accept;
|
||||
}
|
||||
|
||||
|
||||
public GzipTester(TestingDir testingdir, String compressionType)
|
||||
{
|
||||
this.testdir = testingdir;
|
||||
this.compressionType = compressionType;
|
||||
this.accept=compressionType;
|
||||
this.accept = compressionType;
|
||||
}
|
||||
|
||||
public int getOutputBufferSize()
|
||||
|
@ -95,11 +104,88 @@ public class GzipTester
|
|||
return tester.getConnector().getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().getOutputBufferSize();
|
||||
}
|
||||
|
||||
public HttpTester.Response issueRequest(String method, String path) throws Exception
|
||||
{
|
||||
return issueRequest(method, path, 2, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public HttpTester.Response issueRequest(String method, String path, int idleFor, TimeUnit idleUnit) throws Exception
|
||||
{
|
||||
HttpTester.Request request = HttpTester.newRequest();
|
||||
|
||||
request.setMethod(method);
|
||||
request.setVersion("HTTP/1.1");
|
||||
request.setHeader("Host","tester");
|
||||
request.setHeader("Accept-Encoding",accept);
|
||||
request.setHeader("Connection","close");
|
||||
|
||||
if (this.userAgent != null)
|
||||
{
|
||||
request.setHeader("User-Agent",this.userAgent);
|
||||
}
|
||||
|
||||
request.setURI("/context/" + path);
|
||||
|
||||
// Issue the request
|
||||
return HttpTester.parseResponse(tester.getResponses(request.generate(),idleFor,idleUnit));
|
||||
}
|
||||
|
||||
public ContentMetadata getResponseMetadata(Response response) throws Exception
|
||||
{
|
||||
long size = response.getContentBytes().length;
|
||||
|
||||
String contentEncoding = response.get("Content-Encoding");
|
||||
|
||||
ByteArrayInputStream bais = null;
|
||||
InputStream in = null;
|
||||
DigestOutputStream digester = null;
|
||||
ByteArrayOutputStream uncompressedStream = null;
|
||||
try
|
||||
{
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA1");
|
||||
bais = new ByteArrayInputStream(response.getContentBytes());
|
||||
|
||||
if (contentEncoding == null)
|
||||
{
|
||||
LOG.debug("No response content-encoding");
|
||||
in = new PassThruInputStream(bais);
|
||||
}
|
||||
else if (contentEncoding.contains(GzipFilter.GZIP))
|
||||
{
|
||||
in = new GZIPInputStream(bais);
|
||||
}
|
||||
else if (contentEncoding.contains(GzipFilter.DEFLATE))
|
||||
{
|
||||
in = new InflaterInputStream(bais,new Inflater(true));
|
||||
}
|
||||
else
|
||||
{
|
||||
assertThat("Unexpected response content-encoding", contentEncoding, isEmptyOrNullString());
|
||||
}
|
||||
|
||||
uncompressedStream = new ByteArrayOutputStream((int)size);
|
||||
|
||||
digester = new DigestOutputStream(uncompressedStream,digest);
|
||||
IO.copy(in,digester);
|
||||
|
||||
byte output[] = uncompressedStream.toByteArray();
|
||||
String actualSha1Sum = Hex.asHex(digest.digest());
|
||||
return new ContentMetadata(output.length,actualSha1Sum);
|
||||
}
|
||||
finally
|
||||
{
|
||||
IO.close(digester);
|
||||
IO.close(in);
|
||||
IO.close(bais);
|
||||
IO.close(uncompressedStream);
|
||||
}
|
||||
}
|
||||
|
||||
public HttpTester.Response assertIsResponseGzipCompressed(String method, String filename) throws Exception
|
||||
{
|
||||
return assertIsResponseGzipCompressed(method,filename,filename,-1);
|
||||
}
|
||||
|
||||
|
||||
public HttpTester.Response assertIsResponseGzipCompressed(String method, String filename, long ifmodifiedsince) throws Exception
|
||||
{
|
||||
return assertIsResponseGzipCompressed(method,filename,filename,ifmodifiedsince);
|
||||
|
@ -109,7 +195,6 @@ public class GzipTester
|
|||
{
|
||||
return assertIsResponseGzipCompressed(method,requestedFilename,serverFilename,-1);
|
||||
}
|
||||
|
||||
|
||||
public HttpTester.Response assertNonStaticContentIsResponseGzipCompressed(String method, String path, String expected) throws Exception
|
||||
{
|
||||
|
@ -120,25 +205,25 @@ public class GzipTester
|
|||
request.setVersion("HTTP/1.0");
|
||||
request.setHeader("Host","tester");
|
||||
request.setHeader("Accept-Encoding",accept);
|
||||
|
||||
|
||||
if (this.userAgent != null)
|
||||
request.setHeader("User-Agent", this.userAgent);
|
||||
request.setHeader("User-Agent",this.userAgent);
|
||||
request.setURI("/context/" + path);
|
||||
|
||||
// Issue the request
|
||||
response = HttpTester.parseResponse(tester.getResponses(request.generate()));
|
||||
|
||||
|
||||
int qindex = compressionType.indexOf(";");
|
||||
if (qindex < 0)
|
||||
Assert.assertThat("Response.header[Content-Encoding]",response.get("Content-Encoding"),containsString(compressionType));
|
||||
else
|
||||
Assert.assertThat("Response.header[Content-Encoding]", response.get("Content-Encoding"),containsString(compressionType.substring(0,qindex)));
|
||||
Assert.assertThat("Response.header[Content-Encoding]",response.get("Content-Encoding"),containsString(compressionType.substring(0,qindex)));
|
||||
|
||||
ByteArrayInputStream bais = null;
|
||||
InputStream in = null;
|
||||
ByteArrayOutputStream out = null;
|
||||
String actual = null;
|
||||
|
||||
ByteArrayInputStream bais = null;
|
||||
InputStream in = null;
|
||||
ByteArrayOutputStream out = null;
|
||||
String actual = null;
|
||||
|
||||
try
|
||||
{
|
||||
bais = new ByteArrayInputStream(response.getContentBytes());
|
||||
|
@ -148,7 +233,7 @@ public class GzipTester
|
|||
}
|
||||
else if (compressionType.startsWith(GzipFilter.DEFLATE))
|
||||
{
|
||||
in = new InflaterInputStream(bais, new Inflater(true));
|
||||
in = new InflaterInputStream(bais,new Inflater(true));
|
||||
}
|
||||
out = new ByteArrayOutputStream();
|
||||
IO.copy(in,out);
|
||||
|
@ -162,12 +247,12 @@ public class GzipTester
|
|||
IO.close(in);
|
||||
IO.close(bais);
|
||||
}
|
||||
|
||||
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public HttpTester.Response assertIsResponseGzipCompressed(String method, String requestedFilename, String serverFilename, long ifmodifiedsince) throws Exception
|
||||
|
||||
public HttpTester.Response assertIsResponseGzipCompressed(String method, String requestedFilename, String serverFilename, long ifmodifiedsince)
|
||||
throws Exception
|
||||
{
|
||||
HttpTester.Request request = HttpTester.newRequest();
|
||||
HttpTester.Response response;
|
||||
|
@ -176,39 +261,36 @@ public class GzipTester
|
|||
request.setVersion("HTTP/1.0");
|
||||
request.setHeader("Host","tester");
|
||||
request.setHeader("Accept-Encoding",compressionType);
|
||||
if (ifmodifiedsince>0)
|
||||
if (ifmodifiedsince > 0)
|
||||
request.setHeader(HttpHeader.IF_MODIFIED_SINCE.asString(),DateGenerator.formatDate(ifmodifiedsince));
|
||||
if (this.userAgent != null)
|
||||
request.setHeader("User-Agent", this.userAgent);
|
||||
request.setHeader("User-Agent",this.userAgent);
|
||||
request.setURI("/context/" + requestedFilename);
|
||||
|
||||
// Issue the request
|
||||
response = HttpTester.parseResponse(tester.getResponses(request.generate()));
|
||||
|
||||
|
||||
// Assert the response headers
|
||||
// Assert.assertThat("Response.status",response.getStatus(),is(HttpServletResponse.SC_OK));
|
||||
|
||||
|
||||
// Response headers should have either a Transfer-Encoding indicating chunked OR a Content-Length
|
||||
/* TODO need to check for the 3rd option of EOF content. To do this properly you might need to look at both HTTP/1.1 and HTTP/1.0 requests
|
||||
String contentLength = response.get("Content-Length");
|
||||
String transferEncoding = response.get("Transfer-Encoding");
|
||||
|
||||
boolean chunked = (transferEncoding != null) && (transferEncoding.indexOf("chunk") >= 0);
|
||||
if(!chunked) {
|
||||
Assert.assertThat("Response.header[Content-Length]",contentLength,notNullValue());
|
||||
} else {
|
||||
Assert.assertThat("Response.header[Transfer-Encoding]",transferEncoding,notNullValue());
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
* TODO need to check for the 3rd option of EOF content. To do this properly you might need to look at both HTTP/1.1 and HTTP/1.0 requests String
|
||||
* contentLength = response.get("Content-Length"); String transferEncoding = response.get("Transfer-Encoding");
|
||||
*
|
||||
* boolean chunked = (transferEncoding != null) && (transferEncoding.indexOf("chunk") >= 0); if(!chunked) {
|
||||
* Assert.assertThat("Response.header[Content-Length]",contentLength,notNullValue()); } else {
|
||||
* Assert.assertThat("Response.header[Transfer-Encoding]",transferEncoding,notNullValue()); }
|
||||
*/
|
||||
|
||||
int qindex = compressionType.indexOf(";");
|
||||
if (qindex < 0)
|
||||
Assert.assertThat("Response.header[Content-Encoding]",response.get("Content-Encoding"),containsString(compressionType));
|
||||
else
|
||||
Assert.assertThat("Response.header[Content-Encoding]", response.get("Content-Encoding"),containsString(compressionType.substring(0,qindex)));
|
||||
Assert.assertThat("Response.header[Content-Encoding]",response.get("Content-Encoding"),containsString(compressionType.substring(0,qindex)));
|
||||
|
||||
Assert.assertThat(response.get("ETag"),Matchers.startsWith("W/"));
|
||||
|
||||
|
||||
// Assert that the decompressed contents are what we expect.
|
||||
File serverFile = testdir.getFile(serverFilename);
|
||||
String expected = IO.readToString(serverFile);
|
||||
|
@ -226,7 +308,7 @@ public class GzipTester
|
|||
}
|
||||
else if (compressionType.startsWith(GzipFilter.DEFLATE))
|
||||
{
|
||||
in = new InflaterInputStream(bais, new Inflater(true));
|
||||
in = new InflaterInputStream(bais,new Inflater(true));
|
||||
}
|
||||
out = new ByteArrayOutputStream();
|
||||
IO.copy(in,out);
|
||||
|
@ -240,11 +322,10 @@ public class GzipTester
|
|||
IO.close(in);
|
||||
IO.close(bais);
|
||||
}
|
||||
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
public HttpTester.Response assertIsResponseNotModified(String method, String requestedFilename, long ifmodifiedsince) throws Exception
|
||||
{
|
||||
HttpTester.Request request = HttpTester.newRequest();
|
||||
|
@ -254,10 +335,10 @@ public class GzipTester
|
|||
request.setVersion("HTTP/1.0");
|
||||
request.setHeader("Host","tester");
|
||||
request.setHeader("Accept-Encoding",compressionType);
|
||||
if (ifmodifiedsince>0)
|
||||
if (ifmodifiedsince > 0)
|
||||
request.setHeader(HttpHeader.IF_MODIFIED_SINCE.asString(),DateGenerator.formatDate(ifmodifiedsince));
|
||||
if (this.userAgent != null)
|
||||
request.setHeader("User-Agent", this.userAgent);
|
||||
request.setHeader("User-Agent",this.userAgent);
|
||||
request.setURI("/context/" + requestedFilename);
|
||||
|
||||
// Issue the request
|
||||
|
@ -265,47 +346,45 @@ public class GzipTester
|
|||
|
||||
Assert.assertThat(response.getStatus(),Matchers.equalTo(304));
|
||||
Assert.assertThat(response.get("ETag"),Matchers.startsWith("W/"));
|
||||
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Makes sure that the response contains an unfiltered file contents.
|
||||
* <p>
|
||||
* This is used to test exclusions and passthroughs in the GzipFilter.
|
||||
* <p>
|
||||
* An example is to test that it is possible to configure GzipFilter to not recompress content that shouldn't be
|
||||
* compressed by the GzipFilter.
|
||||
* An example is to test that it is possible to configure GzipFilter to not recompress content that shouldn't be compressed by the GzipFilter.
|
||||
*
|
||||
* @param requestedFilename
|
||||
* the filename used to on the GET request,.
|
||||
* @param testResourceSha1Sum
|
||||
* the sha1sum file that contains the SHA1SUM checksum that will be used to verify that the response
|
||||
* contents are what is intended.
|
||||
* the sha1sum file that contains the SHA1SUM checksum that will be used to verify that the response contents are what is intended.
|
||||
* @param expectedContentType
|
||||
*/
|
||||
public void assertIsResponseNotGzipFiltered(String requestedFilename, String testResourceSha1Sum, String expectedContentType) throws Exception
|
||||
{
|
||||
assertIsResponseNotGzipFiltered(requestedFilename, testResourceSha1Sum, expectedContentType,null);
|
||||
assertIsResponseNotGzipFiltered(requestedFilename,testResourceSha1Sum,expectedContentType,null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Makes sure that the response contains an unfiltered file contents.
|
||||
* <p>
|
||||
* This is used to test exclusions and passthroughs in the GzipFilter.
|
||||
* <p>
|
||||
* An example is to test that it is possible to configure GzipFilter to not recompress content that shouldn't be
|
||||
* compressed by the GzipFilter.
|
||||
* An example is to test that it is possible to configure GzipFilter to not recompress content that shouldn't be compressed by the GzipFilter.
|
||||
*
|
||||
* @param requestedFilename
|
||||
* the filename used to on the GET request,.
|
||||
* @param testResourceSha1Sum
|
||||
* the sha1sum file that contains the SHA1SUM checksum that will be used to verify that the response
|
||||
* contents are what is intended.
|
||||
* the sha1sum file that contains the SHA1SUM checksum that will be used to verify that the response contents are what is intended.
|
||||
* @param expectedContentType
|
||||
* @param expectedContentEncoding can be non-null in some circumstances, eg when dealing with pre-gzipped .svgz files
|
||||
* @param expectedContentEncoding
|
||||
* can be non-null in some circumstances, eg when dealing with pre-gzipped .svgz files
|
||||
*/
|
||||
public void assertIsResponseNotGzipFiltered(String requestedFilename, String testResourceSha1Sum, String expectedContentType, String expectedContentEncoding) throws Exception
|
||||
public void assertIsResponseNotGzipFiltered(String requestedFilename, String testResourceSha1Sum, String expectedContentType, String expectedContentEncoding)
|
||||
throws Exception
|
||||
{
|
||||
HttpTester.Request request = HttpTester.newRequest();
|
||||
HttpTester.Response response;
|
||||
|
@ -315,7 +394,7 @@ public class GzipTester
|
|||
request.setHeader("Host","tester");
|
||||
request.setHeader("Accept-Encoding",compressionType);
|
||||
if (this.userAgent != null)
|
||||
request.setHeader("User-Agent", this.userAgent);
|
||||
request.setHeader("User-Agent",this.userAgent);
|
||||
request.setURI("/context/" + requestedFilename);
|
||||
|
||||
// Issue the request
|
||||
|
@ -328,14 +407,14 @@ public class GzipTester
|
|||
Assert.assertThat(prefix + ".status",response.getStatus(),is(HttpServletResponse.SC_OK));
|
||||
Assert.assertThat(prefix + ".header[Content-Length]",response.get("Content-Length"),notNullValue());
|
||||
Assert.assertThat(prefix + ".header[Content-Encoding] (should not be recompressed by GzipFilter)",response.get("Content-Encoding"),
|
||||
expectedContentEncoding == null? nullValue() : notNullValue());
|
||||
expectedContentEncoding == null?nullValue():notNullValue());
|
||||
if (expectedContentEncoding != null)
|
||||
Assert.assertThat(prefix + ".header[Content-Encoding]",response.get("Content-Encoding"),is(expectedContentEncoding));
|
||||
Assert.assertThat(prefix + ".header[Content-Type] (should have a Content-Type associated with it)",response.get("Content-Type"),notNullValue());
|
||||
Assert.assertThat(prefix + ".header[Content-Type]",response.get("Content-Type"),is(expectedContentType));
|
||||
|
||||
Assert.assertThat(response.get("ETAG"),Matchers.startsWith("W/"));
|
||||
|
||||
|
||||
ByteArrayInputStream bais = null;
|
||||
DigestOutputStream digester = null;
|
||||
try
|
||||
|
@ -358,7 +437,7 @@ public class GzipTester
|
|||
|
||||
private void dumpHeaders(String prefix, HttpTester.Message message)
|
||||
{
|
||||
LOG.debug("dumpHeaders: {}", prefix);
|
||||
LOG.debug("dumpHeaders: {}",prefix);
|
||||
Enumeration<String> names = message.getFieldNames();
|
||||
while (names.hasMoreElements())
|
||||
{
|
||||
|
@ -379,21 +458,20 @@ public class GzipTester
|
|||
}
|
||||
|
||||
/**
|
||||
* Asserts that the requested filename results in a properly structured GzipFilter response, where the content is
|
||||
* not compressed, and the content-length is returned appropriately.
|
||||
* Asserts that the requested filename results in a properly structured GzipFilter response, where the content is not compressed, and the content-length is
|
||||
* returned appropriately.
|
||||
*
|
||||
* @param filename
|
||||
* the filename used for the request, and also used to compare the response to the server file, assumes
|
||||
* that the file is suitable for {@link Assert#assertEquals(Object, Object)} use. (in other words, the
|
||||
* contents of the file are text)
|
||||
* the filename used for the request, and also used to compare the response to the server file, assumes that the file is suitable for
|
||||
* {@link Assert#assertEquals(Object, Object)} use. (in other words, the contents of the file are text)
|
||||
* @param expectedFilesize
|
||||
* the expected filesize to be specified on the Content-Length portion of the response headers. (note:
|
||||
* passing -1 will disable the Content-Length assertion)
|
||||
* the expected filesize to be specified on the Content-Length portion of the response headers. (note: passing -1 will disable the Content-Length
|
||||
* assertion)
|
||||
* @throws Exception
|
||||
*/
|
||||
public HttpTester.Response assertIsResponseNotGzipCompressed(String method, String filename, int expectedFilesize, int status) throws Exception
|
||||
{
|
||||
String uri = "/context/"+filename;
|
||||
String uri = "/context/" + filename;
|
||||
HttpTester.Response response = executeRequest(method,uri);
|
||||
assertResponseHeaders(expectedFilesize,status,response);
|
||||
// Assert that the contents are what we expect.
|
||||
|
@ -405,23 +483,23 @@ public class GzipTester
|
|||
String actual = readResponse(response);
|
||||
Assert.assertEquals("Expected response equals actual response",expectedResponse,actual);
|
||||
}
|
||||
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Asserts that the request results in a properly structured GzipFilter response, where the content is
|
||||
* not compressed, and the content-length is returned appropriately.
|
||||
* Asserts that the request results in a properly structured GzipFilter response, where the content is not compressed, and the content-length is returned
|
||||
* appropriately.
|
||||
*
|
||||
* @param expectedResponse
|
||||
* the expected response body string
|
||||
* @param expectedFilesize
|
||||
* the expected filesize to be specified on the Content-Length portion of the response headers. (note:
|
||||
* passing -1 will disable the Content-Length assertion)
|
||||
* the expected filesize to be specified on the Content-Length portion of the response headers. (note: passing -1 will disable the Content-Length
|
||||
* assertion)
|
||||
* @throws Exception
|
||||
*/
|
||||
public void assertIsResponseNotGzipCompressedAndEqualToExpectedString(String method,String expectedResponse, int expectedFilesize, int status) throws Exception
|
||||
public void assertIsResponseNotGzipCompressedAndEqualToExpectedString(String method, String expectedResponse, int expectedFilesize, int status)
|
||||
throws Exception
|
||||
{
|
||||
String uri = "/context/";
|
||||
HttpTester.Response response = executeRequest(method,uri);
|
||||
|
@ -432,15 +510,15 @@ public class GzipTester
|
|||
}
|
||||
|
||||
/**
|
||||
* Asserts that the request results in a properly structured GzipFilter response, where the content is
|
||||
* not compressed, and the content-length is returned appropriately.
|
||||
* Asserts that the request results in a properly structured GzipFilter response, where the content is not compressed, and the content-length is returned
|
||||
* appropriately.
|
||||
*
|
||||
* @param expectedFilesize
|
||||
* the expected filesize to be specified on the Content-Length portion of the response headers. (note:
|
||||
* passing -1 will disable the Content-Length assertion)
|
||||
* the expected filesize to be specified on the Content-Length portion of the response headers. (note: passing -1 will disable the Content-Length
|
||||
* assertion)
|
||||
* @throws Exception
|
||||
*/
|
||||
public void assertIsResponseNotGzipCompressed(String method,int expectedFilesize, int status) throws Exception
|
||||
public void assertIsResponseNotGzipCompressed(String method, int expectedFilesize, int status) throws Exception
|
||||
{
|
||||
String uri = "/context/";
|
||||
HttpTester.Response response = executeRequest(method,uri);
|
||||
|
@ -454,16 +532,16 @@ public class GzipTester
|
|||
if (expectedFilesize != (-1))
|
||||
{
|
||||
Assert.assertEquals(expectedFilesize,response.getContentBytes().length);
|
||||
String cl=response.get("Content-Length");
|
||||
if (cl!=null)
|
||||
String cl = response.get("Content-Length");
|
||||
if (cl != null)
|
||||
{
|
||||
int serverLength = Integer.parseInt(response.get("Content-Length"));
|
||||
Assert.assertEquals(serverLength,expectedFilesize);
|
||||
}
|
||||
|
||||
if (status>=200 && status<300)
|
||||
Assert.assertThat(response.get("ETAG"),Matchers.startsWith("W/"));
|
||||
|
||||
|
||||
if (status >= 200 && status < 300)
|
||||
Assert.assertThat(response.get("ETAG"),Matchers.startsWith("W/"));
|
||||
|
||||
}
|
||||
Assert.assertThat("Response.header[Content-Encoding]",response.get("Content-Encoding"),not(containsString(compressionType)));
|
||||
}
|
||||
|
@ -478,7 +556,7 @@ public class GzipTester
|
|||
request.setHeader("Host","tester");
|
||||
request.setHeader("Accept-Encoding",compressionType);
|
||||
if (this.userAgent != null)
|
||||
request.setHeader("User-Agent", this.userAgent);
|
||||
request.setHeader("User-Agent",this.userAgent);
|
||||
|
||||
request.setURI(uri);
|
||||
response = HttpTester.parseResponse(tester.getResponses(request.generate()));
|
||||
|
@ -492,11 +570,11 @@ public class GzipTester
|
|||
ByteArrayOutputStream out = null;
|
||||
try
|
||||
{
|
||||
byte[] content=response.getContentBytes();
|
||||
if (content!=null)
|
||||
actual=new String(response.getContentBytes(),encoding);
|
||||
byte[] content = response.getContentBytes();
|
||||
if (content != null)
|
||||
actual = new String(response.getContentBytes(),encoding);
|
||||
else
|
||||
actual="";
|
||||
actual = "";
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@ -506,7 +584,6 @@ public class GzipTester
|
|||
return actual;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate string content of arbitrary length.
|
||||
*
|
||||
|
@ -579,7 +656,7 @@ public class GzipTester
|
|||
finally
|
||||
{
|
||||
IO.close(in);
|
||||
IO.close(fos);
|
||||
IO.close(fos);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -611,9 +688,17 @@ public class GzipTester
|
|||
ServletHolder servletHolder = tester.addServlet(servletClass,"/");
|
||||
servletHolder.setInitParameter("baseDir",testdir.getDir().getAbsolutePath());
|
||||
servletHolder.setInitParameter("etags","true");
|
||||
FilterHolder holder = tester.addFilter(gzipFilterClass,"/*",EnumSet.of(DispatcherType.REQUEST));
|
||||
holder.setInitParameter("vary","Accept-Encoding");
|
||||
return holder;
|
||||
|
||||
if (gzipFilterClass != null)
|
||||
{
|
||||
FilterHolder holder = tester.addFilter(gzipFilterClass,"/*",EnumSet.of(DispatcherType.REQUEST));
|
||||
holder.setInitParameter("vary","Accept-Encoding");
|
||||
return holder;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Class<? extends Filter> getGzipFilterClass()
|
||||
|
@ -635,22 +720,41 @@ public class GzipTester
|
|||
{
|
||||
this.userAgent = ua;
|
||||
}
|
||||
|
||||
public void addMimeType (String extension, String mimetype)
|
||||
|
||||
public void addMimeType(String extension, String mimetype)
|
||||
{
|
||||
this.tester.getContext().getMimeTypes().addMimeMapping(extension, mimetype);
|
||||
this.tester.getContext().getMimeTypes().addMimeMapping(extension,mimetype);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an arbitrary filter to the test case.
|
||||
*
|
||||
* @param holder
|
||||
* the filter to add
|
||||
* @param pathSpec
|
||||
* the path spec for this filter
|
||||
* @param dispatches
|
||||
* the set of {@link DispatcherType} to associate with this filter
|
||||
*/
|
||||
public void addFilter(FilterHolder holder, String pathSpec, EnumSet<DispatcherType> dispatches) throws IOException
|
||||
{
|
||||
tester.addFilter(holder,pathSpec,dispatches);
|
||||
}
|
||||
|
||||
public void start() throws Exception
|
||||
{
|
||||
Assert.assertThat("No servlet defined yet. Did you use #setContentServlet()?",tester,notNullValue());
|
||||
tester.dump();
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
tester.dumpStdErr();
|
||||
}
|
||||
tester.start();
|
||||
}
|
||||
|
||||
public void stop()
|
||||
{
|
||||
// NOTE: Do not cleanup the testdir. Failures can't be diagnosed if you do that.
|
||||
// NOTE: Do not cleanup the testdir. Failures can't be diagnosed if you do that.
|
||||
// IO.delete(testdir.getDir()):
|
||||
try
|
||||
{
|
||||
|
@ -664,4 +768,5 @@ public class GzipTester
|
|||
e.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.servlets.gzip;
|
||||
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* A simple pass-through input stream.
|
||||
* <p>
|
||||
* Used in some test cases where a proper resource open/close is needed for
|
||||
* some potentially optional layers of the input stream.
|
||||
*/
|
||||
public class PassThruInputStream extends FilterInputStream
|
||||
{
|
||||
public PassThruInputStream(InputStream in)
|
||||
{
|
||||
super(in);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
||||
#org.eclipse.jetty.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.servlets.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.servlet.ServletTester.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.servlets.GzipFilter.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.servlets.QoSFilter.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.servlets.DoSFilter.LEVEL=DEBUG
|
||||
|
|
Loading…
Reference in New Issue