/** * Copyright (C) Zhang,Yuexiang (xfeep) * *For reuse some classes from tomcat8 we have to use this package */ package org.apache.coyote.http11; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.Selector; import nginx.clojure.NginxHttpServerChannel; import org.apache.coyote.OutputBuffer; import org.apache.coyote.Response; import org.apache.coyote.http11.AbstractOutputBuffer; import org.apache.coyote.http11.Constants; import org.apache.tomcat.util.buf.ByteChunk; import org.apache.tomcat.util.net.AbstractEndpoint; import org.apache.tomcat.util.net.NioEndpoint; import org.apache.tomcat.util.net.SocketWrapper; public class InternalNginxOutputBuffer extends AbstractOutputBuffer<NginxChannel> { protected NginxChannel socket; protected volatile boolean flipped = false; protected InternalNginxOutputBuffer(Response response, int headerBufferSize) { super(response, headerBufferSize); outputStreamOutputBuffer = new HttpChannelOutputBuffer(); } @Override public void init(SocketWrapper<NginxChannel> socketWrapper, AbstractEndpoint<NginxChannel> endpoint) throws IOException { socket = socketWrapper.getSocket(); } public void addActiveFilter(OutputFilter filter) { //by default socket.ignoreNginxFilter is true we will use nginx filter instead of tomcat filter if (socket.ignoreNginxFilter) { super.addActiveFilter(filter); } } @Override public void sendAck() throws IOException { if (!committed) { socket.getBufHandler().getWriteBuffer().put( Constants.ACK_BYTES, 0, Constants.ACK_BYTES.length); int result = writeToSocket(socket.getBufHandler().getWriteBuffer(), true, true); if (result < 0) { throw new IOException(sm.getString("iob.failedwrite.ack")); } } } protected synchronized int writeToSocket(byte[] buf, int pos, int len) throws IOException { try { socket.getIOChannel().send(buf, pos, len, true, false); }catch(RuntimeException e) { throw new IOException(e.getMessage(), e); } return len; } protected synchronized int writeToSocket(ByteBuffer bytebuffer, boolean block, boolean flip) throws IOException { if ( flip ) { bytebuffer.flip(); flipped = true; } int written = bytebuffer.remaining(); NginxEndpoint.KeyAttachment att = (NginxEndpoint.KeyAttachment)socket.getAttachment(); if ( att == null ) throw new IOException("Key must be cancelled"); socket.getIOChannel().send(bytebuffer, true, false); if ( block || bytebuffer.remaining()==0) { //blocking writes must empty the buffer //and if remaining==0 then we did empty it bytebuffer.clear(); flipped = false; } // If there is data left in the buffer the socket will be registered for // write further up the stack. This is to ensure the socket is only // registered for write once as both container and user code can trigger // write registration. return written; } @Override protected void commit() throws IOException { // The response is now committed committed = true; response.setCommitted(true); if (pos > 0 && (!socket.isSendFile() || socket.getIOChannel().isIgnoreFilter())) { socket.getIOChannel().sendHeader(headerBuffer, 0, pos, true, false); } } @Override public void endRequest() throws IOException { if (finished) { return; } super.endRequest(); if (!socket.isSendFile() || socket.getIOChannel().isIgnoreFilter()) { socket.close(); } } @Override protected boolean hasMoreDataToFlush() { return false; } @Override protected void registerWriteInterest() throws IOException { } @Override protected boolean flushBuffer(boolean block) throws IOException { try { socket.getIOChannel().flush(); }catch(RuntimeException e) { throw new IOException(e.getMessage(), e); } return false; } protected class HttpChannelOutputBuffer implements OutputBuffer { public HttpChannelOutputBuffer() { } @Override public long getBytesWritten() { return byteCount; } @Override public int doWrite(ByteChunk chunk, Response response) throws IOException { int c = writeToSocket(chunk.getBuffer(), chunk.getStart(), chunk.getLength()); byteCount += c; return c; } } }