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

com.sun.grizzly.http.server.io.OutputBuffer Maven / Gradle / Ivy

The newest version!
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2010 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.sun.grizzly.http.server.io;

import com.sun.grizzly.Buffer;
import com.sun.grizzly.CompletionHandler;
import com.sun.grizzly.Connection;
import com.sun.grizzly.EmptyCompletionHandler;
import com.sun.grizzly.asyncqueue.AsyncQueueWriter;
import com.sun.grizzly.asyncqueue.TaskQueue;
import com.sun.grizzly.filterchain.FilterChainContext;
import com.sun.grizzly.http.Constants;
import com.sun.grizzly.http.HttpContent;
import com.sun.grizzly.http.HttpResponsePacket;
import com.sun.grizzly.http.util.Utils;
import com.sun.grizzly.memory.MemoryManager;
import com.sun.grizzly.memory.MemoryUtils;
import com.sun.grizzly.nio.AbstractNIOConnection;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Abstraction exposing both byte and character methods to write content
 * to the HTTP messaging system in Grizzly.
 */
public class OutputBuffer implements FileOutputBuffer, WritableByteChannel {

    private static final int DEFAULT_BUFFER_SIZE = 1024 * 8;

    private static final int CAPACITY_OK = -1;


    private HttpResponsePacket response;

    private FilterChainContext ctx;

    private Buffer buf;

    private boolean committed;

    private boolean finished;

    private boolean closed;

    private boolean processingChars;

    private String encoding = Constants.DEFAULT_CHARACTER_ENCODING; // should this be UTF-8?

    private CharsetEncoder encoder;

    private CharBuffer charBuf = CharBuffer.allocate(DEFAULT_BUFFER_SIZE);

    private MemoryManager memoryManager;

    private WriteHandler handler;

    private final AtomicReference asyncError = new AtomicReference();

    private TaskQueue.QueueMonitor monitor;

    private AsyncQueueWriter asyncWriter;


    // ---------------------------------------------------------- Public Methods


    public void initialize(HttpResponsePacket response,
                           FilterChainContext ctx) {

        this.response = response;
        this.ctx = ctx;
        memoryManager = ctx.getConnection().getTransport().getMemoryManager();
        buf = memoryManager.allocate(DEFAULT_BUFFER_SIZE);
        final Connection c = ctx.getConnection();
        asyncWriter = ((AsyncQueueWriter) c.getTransport().getWriter(c));

    }


    public void processingChars() {
        processingChars = true;
    }


    public void setEncoding(String encoding) {
        this.encoding = encoding;
    }


    public String getEncoding() {
        return encoding;
    }


    /**
     * Reset current response.
     *
     * @throws IllegalStateException if the response has already been committed
     */
    public void reset() {

        if (committed)
            throw new IllegalStateException(/*FIXME:Put an error message*/);

        buf.clear();
        charBuf.clear();

    }


    /**
     * Recycle the output buffer. This should be called when closing the
     * connection.
     */
    public void recycle() {

        response = null;

        buf.tryDispose();
        charBuf.clear();

        buf = null;
        encoder = null;
        ctx = null;
        memoryManager = null;
        handler = null;
        asyncError.set(null);

        committed = false;
        finished = false;
        closed = false;
        processingChars = false;

        encoding = Constants.DEFAULT_CHARACTER_ENCODING;

    }


    public void endRequest()
        throws IOException {

        handleAsyncErrors();

        if (finished) {
            return;
        }

        if (monitor != null) {
            final Connection c = ctx.getConnection();
            final TaskQueue tqueue = ((AbstractNIOConnection) c).getAsyncWriteQueue();
            tqueue.removeQueueMonitor(monitor);
            monitor = null;
        }

        close();

        finished = true;

    }


    /**
     * Commit the response.
     */
    public void commit() {

        if (committed) {
            return;
        }
        // The response is now committed
        committed = true;

    }


    /**
     * Acknowledge a HTTP Expect header.  The response status
     * code and reason phrase should be set before invoking this method.
     *
     * @throws IOException if an error occurs writing the acknowledgement.
     */
    public void acknowledge() throws IOException {

        ctx.write(response);
        
    }


    // ---------------------------------------------------- Writer-Based Methods


    public void write(char cbuf[], int off, int len) throws IOException {

        if (!processingChars) {
            throw new IllegalStateException();
        }

        handleAsyncErrors();

        if (closed) {
            return;
        }

        int total = len;
        do {
            int writeLen = requiresDrainChar(total);
            if (writeLen == CAPACITY_OK) {
                charBuf.put(cbuf, off, total);
                total = 0;
            } else if (writeLen == DEFAULT_BUFFER_SIZE) {
                charBuf.put(cbuf, off, writeLen);
                total -= writeLen;
                flushCharsToBuf();
            } else {
                charBuf.put(cbuf, off, writeLen);
                flushCharsToBuf();
                charBuf.put(cbuf, off + writeLen, total - writeLen);
                total -= (total + total - writeLen);
            }
            if (charBuf.remaining() == 0) {
                flushCharsToBuf();
            }
            off += DEFAULT_BUFFER_SIZE;
        } while (total > 0);

    }


    public void writeChar(int c) throws IOException {

        if (!processingChars) {
            throw new IllegalStateException();
        }

        handleAsyncErrors();

        if (closed) {
            return;
        }
        int writeLen = requiresDrainChar(1);
        if (writeLen == CAPACITY_OK) {
            charBuf.put((char) c);
        } else {
            flushCharsToBuf();
            charBuf.put((char) c);
        }
    }


    public void write(char cbuf[]) throws IOException {
        write(cbuf, 0, cbuf.length);
    }


    public void write(String str) throws IOException {
        write(str, 0, str.length());
    }


    public void write(String str, int off, int len) throws IOException {

        if (!processingChars) {
            throw new IllegalStateException();
        }

        handleAsyncErrors();

        if (closed) {
            return;
        }
        int total = len;
        do {
            int writeLen = requiresDrainChar(total);
            if (writeLen == CAPACITY_OK) {
                charBuf.put(str, off, total + off);
                total = 0;
            } else if (writeLen == DEFAULT_BUFFER_SIZE) {
                charBuf.put(str, off, writeLen + off);
                total -= (writeLen);
                flushCharsToBuf();
            } else {
                charBuf.put(str, off, off + writeLen);
                flushCharsToBuf();
                int rem = total - writeLen;
                charBuf.put(str, off + writeLen, off + writeLen + rem);
                total -= (writeLen + rem);
            }
            if (charBuf.remaining() == 0) {
                flushCharsToBuf();
            }
            off += DEFAULT_BUFFER_SIZE;

        } while (total > 0);
        
    }


    // ---------------------------------------------- OutputStream-Based Methods

    public void writeByte(int b) throws IOException {

        handleAsyncErrors();
        if (closed) {
            return;
        }
        int writeLen = requiresDrain(1);
        if (writeLen == CAPACITY_OK) {
            buf.put((byte) b);
        } else {
            flush();
            buf.put((byte) b);
        }

    }


    public void write(byte b[]) throws IOException {
        write(b, 0, b.length);
    }


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

        handleAsyncErrors();
        if (closed) {
            return;
        }
        int total = len;
        do {
            int writeLen = requiresDrain(total);
            if (writeLen == CAPACITY_OK) {
                buf.put(b, off, total);
                total = 0;
            } else if (writeLen == DEFAULT_BUFFER_SIZE) {
                buf.put(b, off, writeLen);
                total -= writeLen;
                flush();
            } else {
                buf.put(b, off, writeLen);
                flush();
                buf.put(b, off + writeLen, total - writeLen);
                total -= (total + total - writeLen);
            }
            if (!buf.hasRemaining()) {
                flush();
            }
            off += DEFAULT_BUFFER_SIZE;
        } while (total > 0);

    }


    // --------------------------------------------------- Common Output Methods


    @Override
    public void close() throws IOException {

        handleAsyncErrors();
        if (closed) {
            return;
        }
        closed = true;
        doCommit();
        if (processingChars) {
            flushCharsToBuf();
        }
        writeContentChunk(true);

    }




    /**
     * Flush the response.
     *
     * @throws java.io.IOException an undelying I/O error occured
     */
    public void flush() throws IOException {

        doCommit();
        if (processingChars) {
            flushCharsToBuf();
        }
        writeContentChunk(false);

    }


    /**
     * 

* Writes the contents of the specified {@link ByteBuffer} to the client. *

* * @param byteBuffer the {@link ByteBuffer} to write * @throws IOException if an error occurs during the write */ @SuppressWarnings({"unchecked"}) public void writeByteBuffer(ByteBuffer byteBuffer) throws IOException { Buffer w = MemoryUtils.wrap(memoryManager, byteBuffer); writeBuffer(w); } /** *

* Writes the contents of the specified {@link Buffer} to the client. *

* * @param buffer the {@link ByteBuffer} to write * @throws IOException if an error occurs during the write */ public void writeBuffer(Buffer buffer) throws IOException { int total = buffer.remaining(); int off = buffer.position(); do { int writeLen = requiresDrain(total); if (writeLen == CAPACITY_OK) { buf.put(buffer, off, total); total = 0; } else if (writeLen == DEFAULT_BUFFER_SIZE) { buf.put(buffer, off, writeLen); total -= writeLen; flush(); } else { buf.put(buffer, off, writeLen); flush(); buf.put(buffer, off + writeLen, total - writeLen); total -= (total + total - writeLen); } if (buf.remaining() == 0) { flush(); } off += DEFAULT_BUFFER_SIZE; } while (total > 0); } // ------------------------------------------- Methods from FileOutputBuffer /** *

* The use of {@link FileChannel#transferTo(long, long, java.nio.channels.WritableByteChannel)} * is supported. *

* * @return true */ @Override public boolean isSupportFileSend() { return true; } /** * @see FileOutputBuffer#sendFile(java.nio.channels.FileChannel, long, long) */ @Override public long sendFile(FileChannel fileChannel, long position, long length) throws IOException { return fileChannel.transferTo(position, length, this); } // ---------------------------------------- Methods from WritableByteChannel @Override public int write(ByteBuffer src) throws IOException { int len = src.remaining(); writeByteBuffer(src); return len; } @Override public boolean isOpen() { return !closed; } // -------------------------------------------------- General Public Methods public boolean canWriteChar(final int length) { if (length <= 0) { return true; } CharsetEncoder e = getEncoder(); final int len = Float.valueOf(length * e.averageBytesPerChar()).intValue(); return canWrite(len); } /** * @see AsyncQueueWriter#canWrite(com.sun.grizzly.Connection, int) */ public boolean canWrite(final int length) { if (length <= 0) { return true; } final Connection c = ctx.getConnection(); return asyncWriter.canWrite(c, length); } public boolean notifyCanWrite(final WriteHandler handler, final int length) { if (this.handler != null) { throw new IllegalStateException("Illegal attempt to set a new handler before the existing handler has been notified."); } if (canWrite(length)) { return false; } this.handler = handler; final Connection c = ctx.getConnection(); final TaskQueue taskQueue = ((AbstractNIOConnection) c).getAsyncWriteQueue(); final int maxBytes = asyncWriter.getMaxPendingBytesPerConnection(); if (length > maxBytes) { throw new IllegalArgumentException("Illegal request to write " + length + " bytes. Max allowable write is " + maxBytes + '.'); } monitor = new TaskQueue.QueueMonitor() { @Override public boolean shouldNotify() { return ((maxBytes - taskQueue.spaceInBytes()) >= length); } @Override public void onNotify() { handler.onWritePossible(); OutputBuffer.this.handler = null; } }; if (!taskQueue.addQueueMonitor(monitor)) { // monitor wasn't added because it was notified this.handler = null; monitor = null; } return true; } // --------------------------------------------------------- Private Methods private void handleAsyncErrors() throws IOException { final Throwable t = asyncError.get(); if (t != null) { if (t instanceof IOException) { throw (IOException) t; } else if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new IOException(t); } } } private void writeContentChunk(boolean includeTrailer) throws IOException { handleAsyncErrors(); if (buf.position() > 0) { buf.trim(); HttpContent.Builder builder = response.httpContentBuilder(); builder.content(buf); final CompletionHandler c = new EmptyCompletionHandler() { @Override public void failed(Throwable throwable) { if (handler != null) { handler.onError(throwable); } else { asyncError.compareAndSet(null, throwable); } } }; ctx.write(builder.build(), c); if (!includeTrailer) { buf = memoryManager.allocate(DEFAULT_BUFFER_SIZE); } } if (response.isChunked() && includeTrailer) { ctx.write(response.httpTrailerBuilder().build()); } } private void flushCharsToBuf() throws IOException { handleAsyncErrors(); // flush the buffer - need to take care of encoding at this point CharsetEncoder enc = getEncoder(); charBuf.flip(); CoderResult res = enc.encode(charBuf, (ByteBuffer) buf.underlying(), true); while (res == CoderResult.OVERFLOW) { commit(); writeContentChunk(false); res = enc.encode(charBuf, (ByteBuffer) buf.underlying(), true); } if (res != CoderResult.UNDERFLOW) { throw new IOException("Encoding error"); } charBuf.clear(); } private int requiresDrainChar(int len) { int rem = charBuf.remaining(); if (len < rem) { return CAPACITY_OK; } else { if (len == DEFAULT_BUFFER_SIZE) { return DEFAULT_BUFFER_SIZE; } if (len > DEFAULT_BUFFER_SIZE) { return DEFAULT_BUFFER_SIZE - charBuf.position(); } return (len - rem); } } private int requiresDrain(int len) { int rem = buf.remaining(); if (len < rem) { return CAPACITY_OK; } else { int ret = (len - rem); return ((ret > DEFAULT_BUFFER_SIZE) ? rem : ret); } } private CharsetEncoder getEncoder() { if (encoder == null) { Charset cs = Utils.lookupCharset(getEncoding()); encoder = cs.newEncoder(); encoder.onMalformedInput(CodingErrorAction.REPLACE); encoder.onUnmappableCharacter(CodingErrorAction.REPLACE); } return encoder; } private void doCommit() throws IOException { handleAsyncErrors(); if (!committed) { committed = true; // flush the message header to the client ctx.write(response); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy