Merge pull request #3208 from eclipse/jetty-9.4.x-3207-async-servlet-print

Issue #3207 async compliant print methods
This commit is contained in:
Greg Wilkins 2018-12-22 14:16:08 +11:00 committed by GitHub
commit ad8d3ea54d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 152 additions and 1 deletions

View File

@ -21,8 +21,14 @@ package org.eclipse.jetty.server;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritePendingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
import java.util.ResourceBundle;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@ -56,6 +62,9 @@ import org.eclipse.jetty.util.log.Logger;
*/
public class HttpOutput extends ServletOutputStream implements Runnable
{
private static final String LSTRING_FILE = "javax.servlet.LocalStrings";
private static ResourceBundle lStrings = ResourceBundle.getBundle(LSTRING_FILE);
/**
* The HttpOutput.Interceptor is a single intercept point for all
* output written to the HttpOutput: via writer; via output stream;
@ -119,6 +128,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
}
private static Logger LOG = Log.getLogger(HttpOutput.class);
private final static ThreadLocal<CharsetEncoder> _encoder = new ThreadLocal<>();
private final HttpChannel _channel;
private final SharedBlockingCallback _writeBlocker;
@ -551,6 +561,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable
public void write(ByteBuffer buffer) throws IOException
{
// This write always bypasses aggregate buffer
// Async or Blocking ?
while (true)
{
@ -673,11 +685,116 @@ public class HttpOutput extends ServletOutputStream implements Runnable
@Override
public void print(String s) throws IOException
{
print(s,false);
}
@Override
public void println(String s) throws IOException
{
print(s,true);
}
private void print(String s, boolean eoln) throws IOException
{
if (isClosed())
throw new IOException("Closed");
write(s.getBytes(_channel.getResponse().getCharacterEncoding()));
String charset = _channel.getResponse().getCharacterEncoding();
CharsetEncoder encoder = _encoder.get();
if (encoder==null || !encoder.charset().name().equalsIgnoreCase(charset))
{
encoder = Charset.forName(charset).newEncoder();
encoder.onMalformedInput(CodingErrorAction.REPLACE);
encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
_encoder.set(encoder);
}
else
{
encoder.reset();
}
CharBuffer in = CharBuffer.wrap(s);
CharBuffer crlf = eoln?CharBuffer.wrap("\r\n"):null;
ByteBuffer out = getHttpChannel().getByteBufferPool().acquire((int)(1+(s.length()+2)*encoder.averageBytesPerChar()),false);
BufferUtil.flipToFill(out);
for(;;)
{
CoderResult result;
if (in.hasRemaining())
{
result = encoder.encode(in, out, crlf==null);
if (result.isUnderflow())
if (crlf==null)
break;
else
continue;
}
else if (crlf.hasRemaining())
{
result = encoder.encode(crlf, out, true);
if (result.isUnderflow())
{
if (!encoder.flush(out).isUnderflow())
result.throwException();
break;
}
}
else
break;
if (result.isOverflow())
{
BufferUtil.flipToFlush(out,0);
ByteBuffer bigger = BufferUtil.ensureCapacity(out,out.capacity()+s.length()+2);
getHttpChannel().getByteBufferPool().release(out);
BufferUtil.flipToFill(bigger);
out = bigger;
continue;
}
result.throwException();
}
BufferUtil.flipToFlush(out,0);
write(out.array(),out.arrayOffset(),out.remaining());
getHttpChannel().getByteBufferPool().release(out);
}
@Override
public void println(boolean b) throws IOException
{
println(lStrings.getString(b? "value.true":"value.false"));
}
@Override
public void println(char c) throws IOException
{
println(String.valueOf(c));
}
@Override
public void println(int i) throws IOException
{
println(String.valueOf(i));
}
@Override
public void println(long l) throws IOException
{
println(String.valueOf(l));
}
@Override
public void println(float f) throws IOException
{
println(String.valueOf(f));
}
@Override
public void println(double d) throws IOException
{
println(String.valueOf(d));
}
/**

View File

@ -578,6 +578,40 @@ public class ResponseTest
assertEquals("foo2/bar2;charset=utf-8", response.getContentType());
}
@Test
public void testPrintln() throws Exception
{
Response response = getResponse();
Request request = response.getHttpChannel().getRequest();
SessionHandler session_handler = new SessionHandler();
session_handler.setServer(_server);
session_handler.setUsingCookies(true);
session_handler.start();
request.setSessionHandler(session_handler);
HttpSession session = request.getSession(true);
response.setCharacterEncoding(UTF_8.name());
assertThat(session,not(nullValue()));
assertTrue(session.isNew());
String expected = "";
response.getOutputStream().print("ABC");
expected += "ABC";
response.getOutputStream().println("XYZ");
expected += "XYZ\r\n";
String s="";
for (int i=0; i<100; i++)
s += "\u20AC\u20AC\u20AC\u20AC\u20AC\u20AC\u20AC\u20AC\u20AC\u20AC";
response.getOutputStream().println(s);
expected += s +"\r\n";
response.getOutputStream().close();
assertEquals(expected,BufferUtil.toString(_content, UTF_8));
}
@Test
public void testContentTypeWithOther() throws Exception
{