blob: 04f9dce8c9e30c3b7731f0eef84ff556c19ea095 [file] [log] [blame]
//
// ========================================================================
// 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);
buffer.flip();
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;
}
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);
}
}
}
}