All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.coyote.http11.Http11OutputBuffer Maven / Gradle / Ivy

There is a newer version: 11.0.1
Show newest version
/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.apache.coyote.http11;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;

import org.apache.coyote.ActionCode;
import org.apache.coyote.CloseNowException;
import org.apache.coyote.Response;
import org.apache.tomcat.util.buf.ByteChunk;
import org.apache.tomcat.util.buf.MessageBytes;
import org.apache.tomcat.util.net.SocketWrapperBase;
import org.apache.tomcat.util.res.StringManager;

/**
 * Provides buffering for the HTTP headers (allowing responses to be reset before they have been committed) and the link
 * to the Socket for writing the headers (once committed) and the response body. Note that buffering of the response
 * body happens at a higher level.
 */
public class Http11OutputBuffer implements HttpOutputBuffer {

    // -------------------------------------------------------------- Variables

    /**
     * The string manager for this package.
     */
    protected static final StringManager sm = StringManager.getManager(Http11OutputBuffer.class);


    // ----------------------------------------------------- Instance Variables

    /**
     * Associated Coyote response.
     */
    protected final Response response;


    private volatile boolean ackSent = false;


    /**
     * Finished flag.
     */
    protected boolean responseFinished;


    /**
     * The buffer used for header composition.
     */
    protected final ByteBuffer headerBuffer;


    /**
     * Filter library for processing the response body.
     */
    protected OutputFilter[] filterLibrary;


    /**
     * Active filters for the current request.
     */
    protected OutputFilter[] activeFilters;


    /**
     * Index of the last active filter.
     */
    protected int lastActiveFilter;


    /**
     * Underlying output buffer.
     */
    protected HttpOutputBuffer outputStreamOutputBuffer;


    /**
     * Wrapper for socket where data will be written to.
     */
    protected SocketWrapperBase socketWrapper;


    /**
     * Bytes written to client for the current request
     */
    protected long byteCount = 0;


    protected Http11OutputBuffer(Response response, int headerBufferSize) {

        this.response = response;

        headerBuffer = ByteBuffer.allocate(headerBufferSize);

        filterLibrary = new OutputFilter[0];
        activeFilters = new OutputFilter[0];
        lastActiveFilter = -1;

        responseFinished = false;

        outputStreamOutputBuffer = new SocketOutputBuffer();
    }


    // ------------------------------------------------------------- Properties

    /**
     * Add an output filter to the filter library. Note that calling this method resets the currently active filters to
     * none.
     *
     * @param filter The filter to add
     */
    public void addFilter(OutputFilter filter) {

        OutputFilter[] newFilterLibrary = Arrays.copyOf(filterLibrary, filterLibrary.length + 1);
        newFilterLibrary[filterLibrary.length] = filter;
        filterLibrary = newFilterLibrary;

        activeFilters = new OutputFilter[filterLibrary.length];
    }


    /**
     * Get filters.
     *
     * @return The current filter library containing all possible filters
     */
    public OutputFilter[] getFilters() {
        return filterLibrary;
    }


    /**
     * Add an output filter to the active filters for the current response.
     * 

* The filter does not have to be present in {@link #getFilters()}. *

* A filter can only be added to a response once. If the filter has already been added to this response then this * method will be a NO-OP. * * @param filter The filter to add */ public void addActiveFilter(OutputFilter filter) { if (lastActiveFilter == -1) { filter.setBuffer(outputStreamOutputBuffer); } else { for (int i = 0; i <= lastActiveFilter; i++) { if (activeFilters[i] == filter) { return; } } filter.setBuffer(activeFilters[lastActiveFilter]); } activeFilters[++lastActiveFilter] = filter; filter.setResponse(response); } // --------------------------------------------------- OutputBuffer Methods @Override public int doWrite(ByteBuffer chunk) throws IOException { if (!response.isCommitted()) { // Send the connector a request for commit. The connector should // then validate the headers, send them (using sendHeaders) and // set the filters accordingly. response.action(ActionCode.COMMIT, null); } if (lastActiveFilter == -1) { return outputStreamOutputBuffer.doWrite(chunk); } else { return activeFilters[lastActiveFilter].doWrite(chunk); } } @Override public long getBytesWritten() { if (lastActiveFilter == -1) { return outputStreamOutputBuffer.getBytesWritten(); } else { return activeFilters[lastActiveFilter].getBytesWritten(); } } // ----------------------------------------------- HttpOutputBuffer Methods @Override public void flush() throws IOException { if (lastActiveFilter == -1) { outputStreamOutputBuffer.flush(); } else { activeFilters[lastActiveFilter].flush(); } } @Override public void end() throws IOException { if (responseFinished) { return; } if (lastActiveFilter == -1) { outputStreamOutputBuffer.end(); } else { activeFilters[lastActiveFilter].end(); } responseFinished = true; } // --------------------------------------------------------- Public Methods /** * Reset the header buffer if an error occurs during the writing of the headers so the error response can be * written. */ void resetHeaderBuffer() { headerBuffer.position(0).limit(headerBuffer.capacity()); } /** * Recycle the output buffer. This should be called when closing the connection. */ public void recycle() { nextRequest(); socketWrapper = null; } /** * End processing of current HTTP request. Note: All bytes of the current request should have been already consumed. * This method only resets all the pointers so that we are ready to parse the next HTTP request. */ public void nextRequest() { // Recycle filters for (int i = 0; i <= lastActiveFilter; i++) { activeFilters[i].recycle(); } // Recycle response object response.recycle(); // Reset pointers headerBuffer.position(0).limit(headerBuffer.capacity()); lastActiveFilter = -1; ackSent = false; responseFinished = false; byteCount = 0; } public void init(SocketWrapperBase socketWrapper) { this.socketWrapper = socketWrapper; } public void sendAck() throws IOException { // It possible that the protocol configuration is changed between the // request being received and the first read of the body. That could led // to multiple calls to this method so ensure the ACK is only sent once. if (!response.isCommitted() && !ackSent) { ackSent = true; socketWrapper.write(isBlocking(), Constants.ACK_BYTES, 0, Constants.ACK_BYTES.length); if (flushBuffer(true)) { throw new IOException(sm.getString("iob.failedwrite.ack")); } } } /** * Commit the response. * * @throws IOException an underlying I/O error occurred */ protected void commit() throws IOException { response.setCommitted(true); writeHeaders(); } protected void writeHeaders() throws IOException { if (headerBuffer.position() > 0) { // Sending the response header buffer headerBuffer.flip(); try { SocketWrapperBase socketWrapper = this.socketWrapper; if (socketWrapper != null) { socketWrapper.write(isBlocking(), headerBuffer); } else { throw new CloseNowException(sm.getString("iob.failedwrite")); } } finally { headerBuffer.position(0).limit(headerBuffer.capacity()); } } } /** * Send the response status line. * * @deprecated Unused. Will be removed in Tomcat 11. Use {@link #sendStatus(int)}. */ @Deprecated public void sendStatus() { sendStatus(response.getStatus()); } /** * Send the response status line. * * @param status The HTTP status code to include in the status line */ public void sendStatus(int status) { // Write protocol name write(Constants.HTTP_11_BYTES); headerBuffer.put(Constants.SP); // Write status code switch (status) { case 200: write(Constants._200_BYTES); break; case 400: write(Constants._400_BYTES); break; case 404: write(Constants._404_BYTES); break; default: write(status); } headerBuffer.put(Constants.SP); // The reason phrase is optional but the space before it is not. Skip // sending the reason phrase. Clients should ignore it (RFC 7230) and it // just wastes bytes. headerBuffer.put(Constants.CR).put(Constants.LF); } /** * Send a header. * * @param name Header name * @param value Header value */ public void sendHeader(MessageBytes name, MessageBytes value) { write(name); headerBuffer.put(Constants.COLON).put(Constants.SP); write(value); headerBuffer.put(Constants.CR).put(Constants.LF); } /** * End the header block. */ public void endHeaders() { headerBuffer.put(Constants.CR).put(Constants.LF); } /** * This method will write the contents of the specified message bytes buffer to the output stream, without * filtering. This method is meant to be used to write the response header. * * @param mb data to be written */ private void write(MessageBytes mb) { if (mb.getType() != MessageBytes.T_BYTES) { mb.toBytes(); ByteChunk bc = mb.getByteChunk(); // Need to filter out CTLs excluding TAB. ISO-8859-1 and UTF-8 // values will be OK. Strings using other encodings may be // corrupted. byte[] buffer = bc.getBuffer(); for (int i = bc.getStart(); i < bc.getLength(); i++) { // byte values are signed i.e. -128 to 127 // The values are used unsigned. 0 to 31 are CTLs so they are // filtered (apart from TAB which is 9). 127 is a control (DEL). // The values 128 to 255 are all OK. Converting those to signed // gives -128 to -1. if ((buffer[i] > -1 && buffer[i] <= 31 && buffer[i] != 9) || buffer[i] == 127) { buffer[i] = ' '; } } } write(mb.getByteChunk()); } /** * This method will write the contents of the specified byte chunk to the output stream, without filtering. This * method is meant to be used to write the response header. * * @param bc data to be written */ private void write(ByteChunk bc) { // Writing the byte chunk to the output buffer int length = bc.getLength(); checkLengthBeforeWrite(length); headerBuffer.put(bc.getBytes(), bc.getStart(), length); } /** * This method will write the contents of the specified byte buffer to the output stream, without filtering. This * method is meant to be used to write the response header. * * @param b data to be written */ public void write(byte[] b) { checkLengthBeforeWrite(b.length); // Writing the byte chunk to the output buffer headerBuffer.put(b); } /** * This method will write the specified integer to the output stream. This method is meant to be used to write the * response header. * * @param value data to be written */ private void write(int value) { // From the Tomcat 3.3 HTTP/1.0 connector String s = Integer.toString(value); int len = s.length(); checkLengthBeforeWrite(len); for (int i = 0; i < len; i++) { char c = s.charAt(i); headerBuffer.put((byte) c); } } /** * Checks to see if there is enough space in the buffer to write the requested number of bytes. */ private void checkLengthBeforeWrite(int length) { // "+ 4": BZ 57509. Reserve space for CR/LF/COLON/SP characters that // are put directly into the buffer following this write operation. if (headerBuffer.position() + length + 4 > headerBuffer.capacity()) { throw new HeadersTooLargeException(sm.getString("iob.responseheadertoolarge.error")); } } // ------------------------------------------------------ Non-blocking writes /** * Writes any remaining buffered data. * * @param block Should this method block until the buffer is empty * * @return true if data remains in the buffer (which can only happen in non-blocking mode) else * false. * * @throws IOException Error writing data */ protected boolean flushBuffer(boolean block) throws IOException { return socketWrapper.flush(block); } /** * Is standard Servlet blocking IO being used for output? * * @return true if this is blocking IO */ protected final boolean isBlocking() { return response.getWriteListener() == null; } protected final boolean isReady() { boolean result = !hasDataToWrite(); if (!result) { socketWrapper.registerWriteInterest(); } return result; } public boolean hasDataToWrite() { return socketWrapper.hasDataToWrite(); } public void registerWriteInterest() { socketWrapper.registerWriteInterest(); } boolean isChunking() { for (int i = 0; i < lastActiveFilter; i++) { if (activeFilters[i] == filterLibrary[Constants.CHUNKED_FILTER]) { return true; } } return false; } // ------------------------------------------ SocketOutputBuffer Inner Class /** * This class is an output buffer which will write data to a socket. */ protected class SocketOutputBuffer implements HttpOutputBuffer { @Override public int doWrite(ByteBuffer chunk) throws IOException { try { int len = chunk.remaining(); SocketWrapperBase socketWrapper = Http11OutputBuffer.this.socketWrapper; if (socketWrapper != null) { socketWrapper.write(isBlocking(), chunk); } else { throw new CloseNowException(sm.getString("iob.failedwrite")); } len -= chunk.remaining(); byteCount += len; return len; } catch (IOException ioe) { response.action(ActionCode.CLOSE_NOW, ioe); // Re-throw throw ioe; } } @Override public long getBytesWritten() { return byteCount; } @Override public void end() throws IOException { socketWrapper.flush(true); } @Override public void flush() throws IOException { socketWrapper.flush(isBlocking()); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy