diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java index 4402d193a93..3127269d971 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java @@ -21,8 +21,13 @@ 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.util.ResourceBundle; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @@ -56,6 +61,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; @@ -130,6 +138,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable private int _bufferSize; private int _commitSize; private WriteListener _writeListener; + private CharsetEncoder _encoder; private volatile Throwable _onError; /* ACTION OPEN ASYNC READY PENDING UNREADY CLOSED @@ -551,6 +560,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 +684,109 @@ 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())); + // TODO would a Threadlocal pool be better? + String charset = _channel.getResponse().getCharacterEncoding(); + if (_encoder==null || !_encoder.charset().name().equals(charset)) + _encoder = Charset.forName(charset).newEncoder(); + 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, false); + 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)); } /** diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java index 6ef9136c453..e5f89919704 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java @@ -578,6 +578,30 @@ 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); + + assertThat(session,not(nullValue())); + assertTrue(session.isNew()); + + response.getOutputStream().println("ABC"); + response.getOutputStream().println("XYZ"); + response.getOutputStream().close(); + assertEquals("ABC\r\nXYZ\r\n",BufferUtil.toString(_content)); + } + + @Test public void testContentTypeWithOther() throws Exception {