OutputBuffer.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.catalina.connector;

  18. import java.io.IOException;
  19. import java.io.Writer;
  20. import java.nio.Buffer;
  21. import java.nio.ByteBuffer;
  22. import java.nio.CharBuffer;
  23. import java.nio.charset.Charset;
  24. import java.security.AccessController;
  25. import java.security.PrivilegedActionException;
  26. import java.security.PrivilegedExceptionAction;
  27. import java.util.HashMap;
  28. import java.util.Map;

  29. import jakarta.servlet.WriteListener;
  30. import jakarta.servlet.http.HttpServletResponse;

  31. import org.apache.catalina.Globals;
  32. import org.apache.coyote.ActionCode;
  33. import org.apache.coyote.CloseNowException;
  34. import org.apache.coyote.Response;
  35. import org.apache.tomcat.util.buf.B2CConverter;
  36. import org.apache.tomcat.util.buf.C2BConverter;
  37. import org.apache.tomcat.util.res.StringManager;

  38. /**
  39.  * The buffer used by Tomcat response. This is a derivative of the Tomcat 3.3 OutputBuffer, with the removal of some of
  40.  * the state handling (which in Coyote is mostly the Processor's responsibility).
  41.  *
  42.  * @author Costin Manolache
  43.  * @author Remy Maucherat
  44.  */
  45. public class OutputBuffer extends Writer {

  46.     private static final StringManager sm = StringManager.getManager(OutputBuffer.class);

  47.     public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;

  48.     /**
  49.      * Encoder cache.
  50.      */
  51.     private final Map<Charset,C2BConverter> encoders = new HashMap<>();


  52.     /**
  53.      * Default buffer size.
  54.      */
  55.     private final int defaultBufferSize;

  56.     // ----------------------------------------------------- Instance Variables

  57.     /**
  58.      * The byte buffer.
  59.      */
  60.     private ByteBuffer bb;


  61.     /**
  62.      * The char buffer.
  63.      */
  64.     private final CharBuffer cb;


  65.     /**
  66.      * State of the output buffer.
  67.      */
  68.     private boolean initial = true;


  69.     /**
  70.      * Number of bytes written.
  71.      */
  72.     private long bytesWritten = 0;


  73.     /**
  74.      * Number of chars written.
  75.      */
  76.     private long charsWritten = 0;


  77.     /**
  78.      * Flag which indicates if the output buffer is closed.
  79.      */
  80.     private volatile boolean closed = false;


  81.     /**
  82.      * Do a flush on the next operation.
  83.      */
  84.     private boolean doFlush = false;


  85.     /**
  86.      * Current char to byte converter.
  87.      */
  88.     protected C2BConverter conv;


  89.     /**
  90.      * Associated Coyote response.
  91.      */
  92.     private Response coyoteResponse;


  93.     /**
  94.      * Suspended flag. All output bytes will be swallowed if this is true.
  95.      */
  96.     private volatile boolean suspended = false;


  97.     // ----------------------------------------------------------- Constructors

  98.     /**
  99.      * Create the buffer with the specified initial size.
  100.      *
  101.      * @param size Buffer size to use
  102.      */
  103.     public OutputBuffer(int size) {
  104.         defaultBufferSize = size;
  105.         bb = ByteBuffer.allocate(size);
  106.         clear(bb);
  107.         cb = CharBuffer.allocate(size);
  108.         clear(cb);
  109.     }


  110.     // ------------------------------------------------------------- Properties

  111.     /**
  112.      * Associated Coyote response.
  113.      *
  114.      * @param coyoteResponse Associated Coyote response
  115.      */
  116.     public void setResponse(Response coyoteResponse) {
  117.         this.coyoteResponse = coyoteResponse;
  118.     }


  119.     /**
  120.      * Is the response output suspended ?
  121.      *
  122.      * @return suspended flag value
  123.      */
  124.     public boolean isSuspended() {
  125.         return this.suspended;
  126.     }


  127.     /**
  128.      * Set the suspended flag.
  129.      *
  130.      * @param suspended New suspended flag value
  131.      */
  132.     public void setSuspended(boolean suspended) {
  133.         this.suspended = suspended;
  134.     }


  135.     /**
  136.      * Is the response output closed ?
  137.      *
  138.      * @return closed flag value
  139.      */
  140.     public boolean isClosed() {
  141.         return this.closed;
  142.     }


  143.     // --------------------------------------------------------- Public Methods

  144.     /**
  145.      * Recycle the output buffer.
  146.      */
  147.     public void recycle() {

  148.         initial = true;
  149.         bytesWritten = 0;
  150.         charsWritten = 0;

  151.         if (bb.capacity() > 16 * defaultBufferSize) {
  152.             // Discard buffers which are too large
  153.             bb = ByteBuffer.allocate(defaultBufferSize);
  154.         }
  155.         clear(bb);
  156.         clear(cb);
  157.         closed = false;
  158.         suspended = false;
  159.         doFlush = false;

  160.         if (conv != null) {
  161.             conv.recycle();
  162.             conv = null;
  163.         }
  164.     }


  165.     /**
  166.      * Close the output buffer. This tries to calculate the response size if the response has not been committed yet.
  167.      *
  168.      * @throws IOException An underlying IOException occurred
  169.      */
  170.     @Override
  171.     public void close() throws IOException {

  172.         if (closed) {
  173.             return;
  174.         }
  175.         if (suspended) {
  176.             return;
  177.         }

  178.         // If there are chars, flush all of them to the byte buffer now as bytes are used to
  179.         // calculate the content-length (if everything fits into the byte buffer, of course).
  180.         if (cb.remaining() > 0) {
  181.             flushCharBuffer();
  182.         }

  183.         // Content length can be calculated if:
  184.         // - the response has not been committed
  185.         // AND
  186.         // - the content length has not been explicitly set
  187.         // AND
  188.         // - some content has been written OR this is NOT a HEAD request
  189.         if ((!coyoteResponse.isCommitted()) && (coyoteResponse.getContentLengthLong() == -1) &&
  190.                 ((bb.remaining() > 0 || !coyoteResponse.getRequest().method().equals("HEAD")))) {
  191.             coyoteResponse.setContentLength(bb.remaining());
  192.         }

  193.         if (coyoteResponse.getStatus() == HttpServletResponse.SC_SWITCHING_PROTOCOLS) {
  194.             doFlush(true);
  195.         } else {
  196.             doFlush(false);
  197.         }
  198.         closed = true;

  199.         // The request should have been completely read by the time the response
  200.         // is closed. Further reads of the input a) are pointless and b) really
  201.         // confuse AJP (bug 50189) so close the input buffer to prevent them.
  202.         Request req = (Request) coyoteResponse.getRequest().getNote(CoyoteAdapter.ADAPTER_NOTES);
  203.         req.inputBuffer.close();

  204.         coyoteResponse.action(ActionCode.CLOSE, null);
  205.     }


  206.     /**
  207.      * Flush bytes or chars contained in the buffer.
  208.      *
  209.      * @throws IOException An underlying IOException occurred
  210.      */
  211.     @Override
  212.     public void flush() throws IOException {
  213.         doFlush(true);
  214.     }


  215.     /**
  216.      * Flush bytes or chars contained in the buffer.
  217.      *
  218.      * @param realFlush <code>true</code> if this should also cause a real network flush
  219.      *
  220.      * @throws IOException An underlying IOException occurred
  221.      */
  222.     protected void doFlush(boolean realFlush) throws IOException {

  223.         if (suspended) {
  224.             return;
  225.         }

  226.         try {
  227.             doFlush = true;
  228.             if (initial) {
  229.                 coyoteResponse.commit();
  230.                 initial = false;
  231.             }
  232.             if (cb.remaining() > 0) {
  233.                 flushCharBuffer();
  234.             }
  235.             if (bb.remaining() > 0) {
  236.                 flushByteBuffer();
  237.             }
  238.         } finally {
  239.             doFlush = false;
  240.         }

  241.         if (realFlush) {
  242.             coyoteResponse.action(ActionCode.CLIENT_FLUSH, null);
  243.             // If some exception occurred earlier, or if some IOE occurred
  244.             // here, notify the servlet with an IOE
  245.             if (coyoteResponse.isExceptionPresent()) {
  246.                 throw new ClientAbortException(coyoteResponse.getErrorException());
  247.             }
  248.         }

  249.     }


  250.     // ------------------------------------------------- Bytes Handling Methods

  251.     /**
  252.      * Sends the buffer data to the client output, checking the state of Response and calling the right interceptors.
  253.      *
  254.      * @param buf the ByteBuffer to be written to the response
  255.      *
  256.      * @throws IOException An underlying IOException occurred
  257.      */
  258.     public void realWriteBytes(ByteBuffer buf) throws IOException {

  259.         if (closed) {
  260.             return;
  261.         }

  262.         // If we really have something to write
  263.         if (buf.remaining() > 0) {
  264.             // real write to the adapter
  265.             try {
  266.                 coyoteResponse.doWrite(buf);
  267.             } catch (CloseNowException e) {
  268.                 // Catch this sub-class as it requires specific handling.
  269.                 // Examples where this exception is thrown:
  270.                 // - HTTP/2 stream timeout
  271.                 // Prevent further output for this response
  272.                 closed = true;
  273.                 throw e;
  274.             } catch (IOException e) {
  275.                 // An IOException on a write is almost always due to
  276.                 // the remote client aborting the request. Wrap this
  277.                 // so that it can be handled better by the error dispatcher.
  278.                 throw new ClientAbortException(e);
  279.             }
  280.         }

  281.     }


  282.     public void write(byte b[], int off, int len) throws IOException {

  283.         if (suspended) {
  284.             return;
  285.         }

  286.         writeBytes(b, off, len);

  287.     }


  288.     public void write(ByteBuffer from) throws IOException {

  289.         if (suspended) {
  290.             return;
  291.         }

  292.         writeBytes(from);

  293.     }


  294.     private void writeBytes(byte b[], int off, int len) throws IOException {

  295.         if (closed) {
  296.             return;
  297.         }

  298.         append(b, off, len);
  299.         bytesWritten += len;

  300.         // if called from within flush(), then immediately flush
  301.         // remaining bytes
  302.         if (doFlush) {
  303.             flushByteBuffer();
  304.         }

  305.     }


  306.     private void writeBytes(ByteBuffer from) throws IOException {

  307.         if (closed) {
  308.             return;
  309.         }

  310.         int remaining = from.remaining();
  311.         append(from);
  312.         bytesWritten += remaining;

  313.         // if called from within flush(), then immediately flush
  314.         // remaining bytes
  315.         if (doFlush) {
  316.             flushByteBuffer();
  317.         }

  318.     }


  319.     public void writeByte(int b) throws IOException {

  320.         if (suspended) {
  321.             return;
  322.         }

  323.         if (isFull(bb)) {
  324.             flushByteBuffer();
  325.         }

  326.         transfer((byte) b, bb);
  327.         bytesWritten++;

  328.     }


  329.     // ------------------------------------------------- Chars Handling Methods


  330.     /**
  331.      * Convert the chars to bytes, then send the data to the client.
  332.      *
  333.      * @param from Char buffer to be written to the response
  334.      *
  335.      * @throws IOException An underlying IOException occurred
  336.      */
  337.     public void realWriteChars(CharBuffer from) throws IOException {

  338.         while (from.remaining() > 0) {
  339.             conv.convert(from, bb);
  340.             if (bb.remaining() == 0) {
  341.                 // Break out of the loop if more chars are needed to produce any output
  342.                 break;
  343.             }
  344.             if (from.remaining() > 0) {
  345.                 flushByteBuffer();
  346.             } else if (conv.isUndeflow() && bb.limit() > bb.capacity() - 4) {
  347.                 // Handle an edge case. There are no more chars to write at the
  348.                 // moment but there is a leftover character in the converter
  349.                 // which must be part of a surrogate pair. The byte buffer does
  350.                 // not have enough space left to output the bytes for this pair
  351.                 // once it is complete )it will require 4 bytes) so flush now to
  352.                 // prevent the bytes for the leftover char and the rest of the
  353.                 // surrogate pair yet to be written from being lost.
  354.                 // See TestOutputBuffer#testUtf8SurrogateBody()
  355.                 flushByteBuffer();
  356.             }
  357.         }

  358.     }

  359.     @Override
  360.     public void write(int c) throws IOException {

  361.         if (suspended) {
  362.             return;
  363.         }

  364.         if (isFull(cb)) {
  365.             flushCharBuffer();
  366.         }

  367.         transfer((char) c, cb);
  368.         charsWritten++;

  369.     }


  370.     @Override
  371.     public void write(char c[]) throws IOException {

  372.         if (suspended) {
  373.             return;
  374.         }

  375.         write(c, 0, c.length);

  376.     }


  377.     @Override
  378.     public void write(char c[], int off, int len) throws IOException {

  379.         if (suspended) {
  380.             return;
  381.         }

  382.         append(c, off, len);
  383.         charsWritten += len;

  384.     }


  385.     /**
  386.      * Append a string to the buffer
  387.      */
  388.     @Override
  389.     public void write(String s, int off, int len) throws IOException {

  390.         if (suspended) {
  391.             return;
  392.         }

  393.         if (s == null) {
  394.             throw new NullPointerException(sm.getString("outputBuffer.writeNull"));
  395.         }

  396.         int sOff = off;
  397.         int sEnd = off + len;
  398.         while (sOff < sEnd) {
  399.             int n = transfer(s, sOff, sEnd - sOff, cb);
  400.             sOff += n;
  401.             if (sOff < sEnd && isFull(cb)) {
  402.                 flushCharBuffer();
  403.             }
  404.         }

  405.         charsWritten += len;
  406.     }


  407.     @Override
  408.     public void write(String s) throws IOException {

  409.         if (suspended) {
  410.             return;
  411.         }

  412.         if (s == null) {
  413.             s = "null";
  414.         }
  415.         write(s, 0, s.length());
  416.     }


  417.     public void checkConverter() throws IOException {
  418.         if (conv != null) {
  419.             return;
  420.         }

  421.         Charset charset = coyoteResponse.getCharset();

  422.         if (charset == null) {
  423.             if (coyoteResponse.getCharacterEncoding() != null) {
  424.                 // setCharacterEncoding() was called with an invalid character set
  425.                 // Trigger an UnsupportedEncodingException
  426.                 charset = B2CConverter.getCharset(coyoteResponse.getCharacterEncoding());
  427.             }
  428.             charset = org.apache.coyote.Constants.DEFAULT_BODY_CHARSET;
  429.         }

  430.         conv = encoders.get(charset);

  431.         if (conv == null) {
  432.             conv = createConverter(charset);
  433.             encoders.put(charset, conv);
  434.         }
  435.     }


  436.     private static C2BConverter createConverter(final Charset charset) throws IOException {
  437.         if (Globals.IS_SECURITY_ENABLED) {
  438.             try {
  439.                 return AccessController.doPrivileged(new PrivilegedCreateConverter(charset));
  440.             } catch (PrivilegedActionException ex) {
  441.                 Exception e = ex.getException();
  442.                 if (e instanceof IOException) {
  443.                     throw (IOException) e;
  444.                 } else {
  445.                     throw new IOException(ex);
  446.                 }
  447.             }
  448.         } else {
  449.             return new C2BConverter(charset);
  450.         }
  451.     }


  452.     // -------------------- BufferedOutputStream compatibility

  453.     public long getContentWritten() {
  454.         return bytesWritten + charsWritten;
  455.     }

  456.     /**
  457.      * Has this buffer been used at all?
  458.      *
  459.      * @return true if no chars or bytes have been added to the buffer since the last call to {@link #recycle()}
  460.      */
  461.     public boolean isNew() {
  462.         return (bytesWritten == 0) && (charsWritten == 0);
  463.     }


  464.     public void setBufferSize(int size) {
  465.         if (size > bb.capacity()) {
  466.             bb = ByteBuffer.allocate(size);
  467.             clear(bb);
  468.         }
  469.     }


  470.     public void reset() {
  471.         reset(false);
  472.     }

  473.     public void reset(boolean resetWriterStreamFlags) {
  474.         clear(bb);
  475.         clear(cb);
  476.         bytesWritten = 0;
  477.         charsWritten = 0;
  478.         if (resetWriterStreamFlags) {
  479.             if (conv != null) {
  480.                 conv.recycle();
  481.             }
  482.             conv = null;
  483.         }
  484.         initial = true;
  485.     }


  486.     public int getBufferSize() {
  487.         return bb.capacity();
  488.     }


  489.     /*
  490.      * All the non-blocking write state information is held in the Response so it is visible / accessible to all the
  491.      * code that needs it.
  492.      */

  493.     public boolean isReady() {
  494.         return coyoteResponse.isReady();
  495.     }


  496.     public void setWriteListener(WriteListener listener) {
  497.         coyoteResponse.setWriteListener(listener);
  498.     }


  499.     public boolean isBlocking() {
  500.         return coyoteResponse.getWriteListener() == null;
  501.     }

  502.     public void checkRegisterForWrite() {
  503.         coyoteResponse.checkRegisterForWrite();
  504.     }

  505.     /**
  506.      * Add data to the buffer.
  507.      *
  508.      * @param src Bytes array
  509.      * @param off Offset
  510.      * @param len Length
  511.      *
  512.      * @throws IOException Writing overflow data to the output channel failed
  513.      */
  514.     public void append(byte src[], int off, int len) throws IOException {
  515.         if (bb.remaining() == 0) {
  516.             appendByteArray(src, off, len);
  517.         } else {
  518.             int n = transfer(src, off, len, bb);
  519.             len = len - n;
  520.             off = off + n;
  521.             if (len > 0 && isFull(bb)) {
  522.                 flushByteBuffer();
  523.                 appendByteArray(src, off, len);
  524.             }
  525.         }
  526.     }

  527.     /**
  528.      * Add data to the buffer.
  529.      *
  530.      * @param src Char array
  531.      * @param off Offset
  532.      * @param len Length
  533.      *
  534.      * @throws IOException Writing overflow data to the output channel failed
  535.      */
  536.     public void append(char src[], int off, int len) throws IOException {
  537.         // if we have limit and we're below
  538.         if (len <= cb.capacity() - cb.limit()) {
  539.             transfer(src, off, len, cb);
  540.             return;
  541.         }

  542.         // Optimization:
  543.         // If len-avail < length ( i.e. after we fill the buffer with
  544.         // what we can, the remaining will fit in the buffer ) we'll just
  545.         // copy the first part, flush, then copy the second part - 1 write
  546.         // and still have some space for more. We'll still have 2 writes, but
  547.         // we write more on the first.
  548.         if (len + cb.limit() < 2 * cb.capacity()) {
  549.             /*
  550.              * If the request length exceeds the size of the output buffer, flush the output buffer and then write the
  551.              * data directly. We can't avoid 2 writes, but we can write more on the second
  552.              */
  553.             int n = transfer(src, off, len, cb);

  554.             flushCharBuffer();

  555.             transfer(src, off + n, len - n, cb);
  556.         } else {
  557.             // long write - flush the buffer and write the rest
  558.             // directly from source
  559.             flushCharBuffer();

  560.             realWriteChars(CharBuffer.wrap(src, off, len));
  561.         }
  562.     }


  563.     public void append(ByteBuffer from) throws IOException {
  564.         if (bb.remaining() == 0) {
  565.             appendByteBuffer(from);
  566.         } else {
  567.             transfer(from, bb);
  568.             if (from.hasRemaining() && isFull(bb)) {
  569.                 flushByteBuffer();
  570.                 appendByteBuffer(from);
  571.             }
  572.         }
  573.     }


  574.     public void setErrorException(Exception e) {
  575.         coyoteResponse.setErrorException(e);
  576.     }


  577.     private void appendByteArray(byte src[], int off, int len) throws IOException {
  578.         if (len == 0) {
  579.             return;
  580.         }

  581.         int limit = bb.capacity();
  582.         while (len > limit) {
  583.             realWriteBytes(ByteBuffer.wrap(src, off, limit));
  584.             len = len - limit;
  585.             off = off + limit;
  586.         }

  587.         if (len > 0) {
  588.             transfer(src, off, len, bb);
  589.         }
  590.     }

  591.     private void appendByteBuffer(ByteBuffer from) throws IOException {
  592.         if (from.remaining() == 0) {
  593.             return;
  594.         }

  595.         int limit = bb.capacity();
  596.         int fromLimit = from.limit();
  597.         while (from.remaining() > limit) {
  598.             from.limit(from.position() + limit);
  599.             realWriteBytes(from.slice());
  600.             from.position(from.limit());
  601.             from.limit(fromLimit);
  602.         }

  603.         if (from.remaining() > 0) {
  604.             transfer(from, bb);
  605.         }
  606.     }

  607.     private void flushByteBuffer() throws IOException {
  608.         realWriteBytes(bb.slice());
  609.         clear(bb);
  610.     }

  611.     private void flushCharBuffer() throws IOException {
  612.         realWriteChars(cb.slice());
  613.         clear(cb);
  614.     }

  615.     private void transfer(byte b, ByteBuffer to) {
  616.         toWriteMode(to);
  617.         to.put(b);
  618.         toReadMode(to);
  619.     }

  620.     private void transfer(char b, CharBuffer to) {
  621.         toWriteMode(to);
  622.         to.put(b);
  623.         toReadMode(to);
  624.     }

  625.     private int transfer(byte[] buf, int off, int len, ByteBuffer to) {
  626.         toWriteMode(to);
  627.         int max = Math.min(len, to.remaining());
  628.         if (max > 0) {
  629.             to.put(buf, off, max);
  630.         }
  631.         toReadMode(to);
  632.         return max;
  633.     }

  634.     private int transfer(char[] buf, int off, int len, CharBuffer to) {
  635.         toWriteMode(to);
  636.         int max = Math.min(len, to.remaining());
  637.         if (max > 0) {
  638.             to.put(buf, off, max);
  639.         }
  640.         toReadMode(to);
  641.         return max;
  642.     }

  643.     private int transfer(String s, int off, int len, CharBuffer to) {
  644.         toWriteMode(to);
  645.         int max = Math.min(len, to.remaining());
  646.         if (max > 0) {
  647.             to.put(s, off, off + max);
  648.         }
  649.         toReadMode(to);
  650.         return max;
  651.     }

  652.     private void transfer(ByteBuffer from, ByteBuffer to) {
  653.         toWriteMode(to);
  654.         int max = Math.min(from.remaining(), to.remaining());
  655.         if (max > 0) {
  656.             int fromLimit = from.limit();
  657.             from.limit(from.position() + max);
  658.             to.put(from);
  659.             from.limit(fromLimit);
  660.         }
  661.         toReadMode(to);
  662.     }

  663.     private void clear(Buffer buffer) {
  664.         buffer.rewind().limit(0);
  665.     }

  666.     private boolean isFull(Buffer buffer) {
  667.         return buffer.limit() == buffer.capacity();
  668.     }

  669.     private void toReadMode(Buffer buffer) {
  670.         buffer.limit(buffer.position()).reset();
  671.     }

  672.     private void toWriteMode(Buffer buffer) {
  673.         buffer.mark().position(buffer.limit()).limit(buffer.capacity());
  674.     }


  675.     private static class PrivilegedCreateConverter implements PrivilegedExceptionAction<C2BConverter> {

  676.         private final Charset charset;

  677.         PrivilegedCreateConverter(Charset charset) {
  678.             this.charset = charset;
  679.         }

  680.         @Override
  681.         public C2BConverter run() throws IOException {
  682.             return new C2BConverter(charset);
  683.         }
  684.     }
  685. }