Merge remote-tracking branch 'origin/jetty-10.0.x' into jetty-10.0.x-3578-epl2

This commit is contained in:
Joakim Erdfelt 2020-01-09 15:59:36 -06:00
commit f4fc78ac66
No known key found for this signature in database
GPG Key ID: 2D0E1FB8FE4B68B4
137 changed files with 392 additions and 61 deletions

View File

@ -25,11 +25,6 @@
<artifactId>jetty-io</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-servlet-api</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
@ -40,14 +35,6 @@
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>
@{argLine} ${jetty.surefire.argLine} --add-modules jetty.servlet.api
</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>

View File

@ -26,9 +26,6 @@ module org.eclipse.jetty.http
requires transitive org.eclipse.jetty.io;
// Only required if using the MultiPart classes.
requires static jetty.servlet.api;
uses HttpFieldPreEncoder;
provides HttpFieldPreEncoder with Http1FieldPreEncoder;

View File

@ -276,7 +276,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
public static final String DEFAULT_DATE_FORMAT = "dd/MMM/yyyy:HH:mm:ss ZZZ";
public static final String NCSA_FORMAT = "%{client}a - %u %t \"%r\" %s %O";
public static final String EXTENDED_NCSA_FORMAT = "%{client}a - %u %t \"%r\" %s %O \"%{Referer}i\" \"%{User-Agent}i\"";
public static final String EXTENDED_NCSA_FORMAT = NCSA_FORMAT + " \"%{Referer}i\" \"%{User-Agent}i\"";
private static ThreadLocal<StringBuilder> _buffers = ThreadLocal.withInitial(() -> new StringBuilder(256));
@ -287,6 +287,21 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
private final MethodHandle _logHandle;
private final String _formatString;
public CustomRequestLog()
{
this(new Slf4jRequestLogWriter(), EXTENDED_NCSA_FORMAT);
}
public CustomRequestLog(String file)
{
this(file, EXTENDED_NCSA_FORMAT);
}
public CustomRequestLog(String file, String format)
{
this(new RequestLogWriter(file), format);
}
public CustomRequestLog(RequestLog.Writer writer, String formatString)
{
_formatString = formatString;
@ -303,16 +318,6 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
}
}
public CustomRequestLog(String file)
{
this(file, EXTENDED_NCSA_FORMAT);
}
public CustomRequestLog(String file, String format)
{
this(new RequestLogWriter(file), format);
}
@ManagedAttribute("The RequestLogWriter")
public RequestLog.Writer getWriter()
{

View File

@ -366,6 +366,18 @@ public class HttpOutput extends ServletOutputStream implements Runnable
return wake;
}
private int maximizeAggregateSpace()
{
// If no aggregate, we can allocate one of bufferSize
if (_aggregate == null)
return getBufferSize();
// compact to maximize space
BufferUtil.compact(_aggregate);
return BufferUtil.space(_aggregate);
}
public void softClose()
{
synchronized (_channelState)
@ -711,6 +723,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
@Override
public void write(byte[] b, int off, int len) throws IOException
{
if (LOG.isDebugEnabled())
LOG.debug("write(array {})", BufferUtil.toDetailString(ByteBuffer.wrap(b, off, len)));
boolean last;
boolean aggregate;
boolean flush;
@ -721,7 +736,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
{
checkWritable();
long written = _written + len;
int space = _aggregate == null ? getBufferSize() : BufferUtil.space(_aggregate);
int space = maximizeAggregateSpace();
last = _channel.getResponse().isAllContentWritten(written);
// Write will be aggregated if:
// + it is smaller than the commitSize
@ -765,7 +780,12 @@ public class HttpOutput extends ServletOutputStream implements Runnable
// return if we are not complete, not full and filled all the content
if (!flush)
{
if (LOG.isDebugEnabled())
LOG.debug("write(array) {} aggregated !flush {}",
stateString(), BufferUtil.toDetailString(_aggregate));
return;
}
// adjust offset/length
off += filled;
@ -773,6 +793,10 @@ public class HttpOutput extends ServletOutputStream implements Runnable
}
}
if (LOG.isDebugEnabled())
LOG.debug("write(array) {} last={} agg={} flush=true async={}, len={} {}",
stateString(), last, aggregate, async, len, BufferUtil.toDetailString(_aggregate));
if (async)
{
// Do the asynchronous writing from the callback
@ -789,9 +813,10 @@ public class HttpOutput extends ServletOutputStream implements Runnable
channelWrite(_aggregate, last && len == 0);
// should we fill aggregate again from the buffer?
if (len > 0 && !last && len <= _commitSize && len <= BufferUtil.space(_aggregate))
if (len > 0 && !last && len <= _commitSize && len <= maximizeAggregateSpace())
{
BufferUtil.append(_aggregate, b, off, len);
onWriteComplete(false, null);
return;
}
}
@ -917,7 +942,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
{
checkWritable();
long written = _written + 1;
int space = _aggregate == null ? getBufferSize() : BufferUtil.space(_aggregate);
int space = maximizeAggregateSpace();
last = _channel.getResponse().isAllContentWritten(written);
flush = last || space == 1;
@ -1554,7 +1579,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
private final ByteBuffer _buffer;
private final ByteBuffer _slice;
private final int _len;
volatile boolean _completed;
private boolean _completed;
AsyncWrite(byte[] b, int off, int len, boolean last)
{
@ -1591,7 +1616,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
}
// Can we just aggregate the remainder?
if (!_last && _len < BufferUtil.space(_aggregate) && _len < _commitSize)
if (!_last && _aggregate != null && _len < maximizeAggregateSpace() && _len < _commitSize)
{
int position = BufferUtil.flipToFill(_aggregate);
BufferUtil.put(_buffer, _aggregate);
@ -1790,32 +1815,16 @@ public class HttpOutput extends ServletOutputStream implements Runnable
private class WriteCompleteCB implements Callback
{
final Callback _callback;
WriteCompleteCB()
{
this(null);
}
WriteCompleteCB(Callback callback)
{
_callback = callback;
}
@Override
public void succeeded()
{
onWriteComplete(true, null);
if (_callback != null)
_callback.succeeded();
}
@Override
public void failed(Throwable x)
{
onWriteComplete(true, x);
if (_callback != null)
_callback.succeeded();
}
}
}

View File

@ -16,7 +16,7 @@
// ========================================================================
//
package org.eclipse.jetty.http;
package org.eclipse.jetty.server;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;

View File

@ -16,13 +16,15 @@
// ========================================================================
//
package org.eclipse.jetty.http;
package org.eclipse.jetty.server;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.EnumSet;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpParser.RequestHandler;
import org.eclipse.jetty.http.HttpTokens;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.SearchPattern;
import org.eclipse.jetty.util.Utf8StringBuilder;

View File

@ -81,7 +81,6 @@ import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.MultiPartFormInputStream;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
import org.eclipse.jetty.io.RuntimeIOException;

View File

@ -375,7 +375,7 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
int len = slice.remaining();
_crc.update(array, off, len);
_deflater.setInput(array, off, len); // TODO use ByteBuffer API in Jetty-10
BufferUtil.clear(slice);
slice.position(slice.position() + len);
if (_last && BufferUtil.isEmpty(_content))
_deflater.finish();
}

View File

@ -18,11 +18,14 @@
package org.eclipse.jetty.server;
import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.AsyncContext;
@ -31,6 +34,7 @@ import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.server.HttpOutput.Interceptor;
import org.eclipse.jetty.server.LocalConnector.LocalEndPoint;
import org.eclipse.jetty.server.handler.AbstractHandler;
@ -54,6 +58,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
*/
public class HttpOutputTest
{
public static final int OUTPUT_AGGREGATION_SIZE = 1024;
public static final int OUTPUT_BUFFER_SIZE = 4096;
private Server _server;
private LocalConnector _connector;
private ContentHandler _handler;
@ -64,10 +70,25 @@ public class HttpOutputTest
{
_server = new Server();
_server.addBean(new ByteBufferPool()
{
@Override
public ByteBuffer acquire(int size, boolean direct)
{
return direct ? BufferUtil.allocateDirect(size) : BufferUtil.allocate(size);
}
@Override
public void release(ByteBuffer buffer)
{
}
});
HttpConnectionFactory http = new HttpConnectionFactory();
http.getHttpConfiguration().setRequestHeaderSize(1024);
http.getHttpConfiguration().setResponseHeaderSize(1024);
http.getHttpConfiguration().setOutputBufferSize(4096);
http.getHttpConfiguration().setOutputBufferSize(OUTPUT_BUFFER_SIZE);
http.getHttpConfiguration().setOutputAggregationSize(OUTPUT_AGGREGATION_SIZE);
_connector = new LocalConnector(_server, http, null);
_server.addConnector(_connector);
@ -670,6 +691,318 @@ public class HttpOutputTest
assertThat(response, containsString("400\tTHIS IS A BIGGER FILE"));
}
@Test
public void testAggregation() throws Exception
{
AggregateHandler handler = new AggregateHandler();
_swap.setHandler(handler);
handler.start();
String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n");
assertThat(response, containsString("HTTP/1.1 200 OK"));
assertThat(response, containsString(handler.expected.toString()));
}
static class AggregateHandler extends AbstractHandler
{
ByteArrayOutputStream expected = new ByteArrayOutputStream();
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
HttpOutput out = (HttpOutput)response.getOutputStream();
// Add interceptor to check aggregation is done
HttpOutput.Interceptor interceptor = out.getInterceptor();
out.setInterceptor(new AggregationChecker(interceptor));
int bufferSize = baseRequest.getHttpChannel().getHttpConfiguration().getOutputBufferSize();
int len = bufferSize * 3 / 2;
byte[] data = new byte[AggregationChecker.MAX_SIZE];
int fill = 0;
while (expected.size() < len)
{
Arrays.fill(data, (byte)('A' + (fill++ % 26)));
expected.write(data);
out.write(data);
}
}
}
@Test
public void testAsyncAggregation() throws Exception
{
AsyncAggregateHandler handler = new AsyncAggregateHandler();
_swap.setHandler(handler);
handler.start();
String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n");
assertThat(response, containsString("HTTP/1.1 200 OK"));
assertThat(response, containsString(handler.expected.toString()));
}
static class AsyncAggregateHandler extends AbstractHandler
{
ByteArrayOutputStream expected = new ByteArrayOutputStream();
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
HttpOutput out = (HttpOutput)response.getOutputStream();
// Add interceptor to check aggregation is done
HttpOutput.Interceptor interceptor = out.getInterceptor();
out.setInterceptor(new AggregationChecker(interceptor));
int bufferSize = baseRequest.getHttpChannel().getHttpConfiguration().getOutputBufferSize();
int len = bufferSize * 3 / 2;
AsyncContext async = request.startAsync();
out.setWriteListener(new WriteListener()
{
int fill = 0;
@Override
public void onWritePossible() throws IOException
{
byte[] data = new byte[AggregationChecker.MAX_SIZE];
while (out.isReady())
{
if (expected.size() >= len)
{
async.complete();
return;
}
Arrays.fill(data, (byte)('A' + (fill++ % 26)));
expected.write(data);
out.write(data);
}
}
@Override
public void onError(Throwable t)
{
}
});
}
}
private static class AggregationChecker implements Interceptor
{
static final int MAX_SIZE = OUTPUT_AGGREGATION_SIZE / 2 - 1;
private final Interceptor interceptor;
public AggregationChecker(Interceptor interceptor)
{
this.interceptor = interceptor;
}
@Override
public void write(ByteBuffer content, boolean last, Callback callback)
{
if (content.remaining() <= MAX_SIZE)
throw new IllegalStateException("Not Aggregated!");
interceptor.write(content, last, callback);
}
@Override
public Interceptor getNextInterceptor()
{
return interceptor;
}
}
@Test
public void testAggregateResidue() throws Exception
{
AggregateResidueHandler handler = new AggregateResidueHandler();
_swap.setHandler(handler);
handler.start();
String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n");
assertThat(response, containsString("HTTP/1.1 200 OK"));
assertThat(response, containsString(handler.expected.toString()));
}
static class AggregateResidueHandler extends AbstractHandler
{
ByteArrayOutputStream expected = new ByteArrayOutputStream();
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
HttpOutput out = (HttpOutput)response.getOutputStream();
int bufferSize = baseRequest.getHttpChannel().getHttpConfiguration().getOutputBufferSize();
int commitSize = baseRequest.getHttpChannel().getHttpConfiguration().getOutputAggregationSize();
char fill = 'A';
// write data that will be aggregated
byte[] data = new byte[commitSize - 1];
Arrays.fill(data, (byte)(fill++));
expected.write(data);
out.write(data);
int aggregated = data.length;
// write data that will almost fill the aggregate buffer
while (aggregated < (bufferSize - 1))
{
data = new byte[Math.min(commitSize - 1, bufferSize - aggregated - 1)];
Arrays.fill(data, (byte)(fill++));
expected.write(data);
out.write(data);
aggregated += data.length;
}
// write data that will not be aggregated
data = new byte[bufferSize + 1];
Arrays.fill(data, (byte)(fill++));
expected.write(data);
out.write(data);
}
}
@Test
public void testPrint() throws Exception
{
ByteArrayOutputStream bout = new ByteArrayOutputStream();
PrintWriter exp = new PrintWriter(bout);
_swap.setHandler(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
response.setCharacterEncoding("UTF8");
HttpOutput out = (HttpOutput)response.getOutputStream();
exp.print("\u20AC\u0939\uD55C");
out.print("\u20AC\u0939\uD55C");
exp.print("zero");
out.print("zero");
exp.print(1);
out.print(1);
exp.print(2L);
out.print(2L);
exp.print(3.0F);
out.print(3.0F);
exp.print('4');
out.print('4');
exp.print(5.0D);
out.print(5.0D);
exp.print(true);
out.print(true);
exp.println("zero");
out.println("zero");
exp.println(-1);
out.println(-1);
exp.println(-2L);
out.println(-2L);
exp.println(-3.0F);
out.println(-3.0F);
exp.println('4');
out.println('4');
exp.println(-5.0D);
out.println(-5.0D);
exp.println(false);
out.println(false);
}
});
_swap.getHandler().start();
String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n");
assertThat(response, containsString("HTTP/1.1 200 OK"));
assertThat(response, containsString(bout.toString()));
}
@Test
public void testReset() throws Exception
{
ByteArrayOutputStream exp = new ByteArrayOutputStream();
_swap.setHandler(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
HttpOutput out = (HttpOutput)response.getOutputStream();
Interceptor interceptor = out.getInterceptor();
out.setInterceptor(new Interceptor()
{
@Override
public void write(ByteBuffer content, boolean last, Callback callback)
{
interceptor.write(content, last, callback);
}
@Override
public Interceptor getNextInterceptor()
{
return interceptor;
}
});
out.setBufferSize(128);
out.println("NOT TO BE SEEN!");
out.resetBuffer();
byte[] data = "TO BE SEEN\n".getBytes(StandardCharsets.ISO_8859_1);
exp.write(data);
out.write(data);
out.flush();
data = "Not reset after flush\n".getBytes(StandardCharsets.ISO_8859_1);
exp.write(data);
try
{
out.resetBuffer();
}
catch (IllegalStateException e)
{
out.write(data);
}
}
});
_swap.getHandler().start();
String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n");
assertThat(response, containsString("HTTP/1.1 200 OK"));
assertThat(response, containsString(exp.toString()));
}
@Test
public void testZeroLengthWrite() throws Exception
{
_swap.setHandler(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
response.setContentLength(0);
AsyncContext async = request.startAsync();
response.getOutputStream().setWriteListener(new WriteListener()
{
@Override
public void onWritePossible() throws IOException
{
response.getOutputStream().write(new byte[0]);
async.complete();
}
@Override
public void onError(Throwable t)
{
}
});
}
});
_swap.getHandler().start();
String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n");
assertThat(response, containsString("HTTP/1.1 200 OK"));
}
private static String toUTF8String(Resource resource)
throws IOException
{
@ -682,8 +1015,6 @@ public class HttpOutputTest
{
}
;
void setNext(Interceptor interceptor);
}

View File

@ -16,7 +16,7 @@
// ========================================================================
//
package org.eclipse.jetty.http;
package org.eclipse.jetty.server;
import java.io.BufferedReader;
import java.io.IOException;

View File

@ -16,7 +16,7 @@
// ========================================================================
//
package org.eclipse.jetty.http;
package org.eclipse.jetty.server;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@ -31,7 +31,7 @@ import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.Part;
import org.eclipse.jetty.http.MultiPartFormInputStream.MultiPart;
import org.eclipse.jetty.server.MultiPartFormInputStream.MultiPart;
import org.eclipse.jetty.util.IO;
import org.junit.jupiter.api.Test;

View File

@ -16,14 +16,15 @@
// ========================================================================
//
package org.eclipse.jetty.http;
package org.eclipse.jetty.server;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import org.eclipse.jetty.http.MultiPartParser.State;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.server.MultiPartParser.State;
import org.eclipse.jetty.util.BufferUtil;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;

Some files were not shown because too many files have changed in this diff Show More