// // ======================================================================== // Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. // // The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php // // You may elect to redistribute this code under either of these licenses. // ======================================================================== // package org.eclipse.jetty.fcgi.generator; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import org.eclipse.jetty.fcgi.FCGI; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; public class Generator { public static final int MAX_CONTENT_LENGTH = 0xFF_FF; protected final ByteBufferPool byteBufferPool; public Generator(ByteBufferPool byteBufferPool) { this.byteBufferPool = byteBufferPool; } protected Result generateContent(int id, ByteBuffer content, boolean recycle, boolean lastContent, Callback callback, FCGI.FrameType frameType) { id &= 0xFF_FF; int contentLength = content == null ? 0 : content.remaining(); Result result = new Result(byteBufferPool, callback); while (contentLength > 0 || lastContent) { ByteBuffer buffer = byteBufferPool.acquire(8, false); BufferUtil.clearToFill(buffer); result = result.append(buffer, true); // Generate the frame header buffer.put((byte)0x01); buffer.put((byte)frameType.code); buffer.putShort((short)id); int length = Math.min(MAX_CONTENT_LENGTH, contentLength); buffer.putShort((short)length); buffer.putShort((short)0); BufferUtil.flipToFlush(buffer, 0); if (contentLength == 0) break; // Slice the content to avoid copying int limit = content.limit(); content.limit(content.position() + length); ByteBuffer slice = content.slice(); // Don't recycle the slice result = result.append(slice, false); content.position(content.limit()); content.limit(limit); contentLength -= length; // Recycle the content buffer if needed if (recycle && contentLength == 0) result = result.append(content, true); } return result; } // TODO: rewrite this class in light of ByteBufferPool.Lease. public static class Result implements Callback { private final List<Callback> callbacks = new ArrayList<>(2); private final List<ByteBuffer> buffers = new ArrayList<>(8); private final List<Boolean> recycles = new ArrayList<>(8); private final ByteBufferPool byteBufferPool; public Result(ByteBufferPool byteBufferPool, Callback callback) { this.byteBufferPool = byteBufferPool; this.callbacks.add(callback); } public Result append(ByteBuffer buffer, boolean recycle) { if (buffer != null) { buffers.add(buffer); recycles.add(recycle); } return this; } public Result join(Result that) { callbacks.addAll(that.callbacks); buffers.addAll(that.buffers); recycles.addAll(that.recycles); return this; } public ByteBuffer[] getByteBuffers() { return buffers.toArray(new ByteBuffer[buffers.size()]); } @Override @SuppressWarnings("ForLoopReplaceableByForEach") public void succeeded() { recycle(); for (int i = 0; i < callbacks.size(); ++i) { Callback callback = callbacks.get(i); if (callback != null) callback.succeeded(); } } @Override @SuppressWarnings("ForLoopReplaceableByForEach") public void failed(Throwable x) { recycle(); for (int i = 0; i < callbacks.size(); ++i) { Callback callback = callbacks.get(i); if (callback != null) callback.failed(x); } } protected void recycle() { for (int i = 0; i < buffers.size(); ++i) { ByteBuffer buffer = buffers.get(i); if (recycles.get(i)) byteBufferPool.release(buffer); } } } }