diff --git a/pom.xml b/pom.xml
new file mode 100644
index 00000000000..9d6f12fdee1
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,100 @@
+
+
+ org.eclipse.jetty
+ jetty-project
+ 9.1.0-SNAPSHOT
+
+
+ 4.0.0
+ jetty-fcgi
+ Jetty :: FastCGI
+
+
+ ${project.groupId}.fcgi
+
+
+
+
+
+
+ org.apache.felix
+ maven-bundle-plugin
+ true
+
+
+
+ manifest
+
+
+
+ javax.net.*,*
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+ ${project.build.outputDirectory}/META-INF/MANIFEST.MF
+
+
+
+
+ org.codehaus.mojo
+ findbugs-maven-plugin
+
+ org.eclipse.jetty.fcgi.*
+
+
+
+
+
+
+
+ org.eclipse.jetty
+ jetty-util
+ ${project.version}
+
+
+ org.eclipse.jetty
+ jetty-io
+ ${project.version}
+
+
+ org.eclipse.jetty
+ jetty-http
+ ${project.version}
+
+
+
+ junit
+ junit
+ 4.11
+ test
+
+
+
+
diff --git a/src/main/java/org/eclipse/jetty/fcgi/FCGI.java b/src/main/java/org/eclipse/jetty/fcgi/FCGI.java
new file mode 100644
index 00000000000..e8f4a3f3376
--- /dev/null
+++ b/src/main/java/org/eclipse/jetty/fcgi/FCGI.java
@@ -0,0 +1,76 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 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;
+
+public class FCGI
+{
+ private FCGI()
+ {
+ }
+
+ public enum FrameType
+ {
+ BEGIN_REQUEST(1),
+ ABORT_REQUEST(2),
+ END_REQUEST(3),
+ PARAMS(4),
+ STDIN(5),
+ STDOUT(6),
+ STDERR(7),
+ DATA(8),
+ GET_VALUES(9),
+ GET_VALUES_RESULT(10);
+
+ public static FrameType from(int code)
+ {
+ switch (code)
+ {
+ case 1:
+ return BEGIN_REQUEST;
+ case 2:
+ return ABORT_REQUEST;
+ case 3:
+ return END_REQUEST;
+ case 4:
+ return PARAMS;
+ case 5:
+ return STDIN;
+ case 6:
+ return STDOUT;
+ case 7:
+ return STDERR;
+ case 8:
+ return DATA;
+ case 9:
+ return GET_VALUES;
+ case 10:
+ return GET_VALUES_RESULT;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ public final int code;
+
+ private FrameType(int code)
+ {
+ this.code = code;
+ }
+ }
+}
diff --git a/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java b/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java
new file mode 100644
index 00000000000..544adecc37a
--- /dev/null
+++ b/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java
@@ -0,0 +1,113 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 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.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Fields;
+
+public class ClientGenerator extends Generator
+{
+ private final ByteBufferPool byteBufferPool;
+
+ public ClientGenerator(ByteBufferPool byteBufferPool)
+ {
+ this.byteBufferPool = byteBufferPool;
+ }
+
+ public ByteBuffer generateRequestHeaders(int id, Fields fields)
+ {
+ id = id & 0xFFFF;
+
+ Charset utf8 = Charset.forName("UTF-8");
+ List bytes = new ArrayList<>(fields.size() * 2);
+ int fieldsLength = 0;
+ for (Fields.Field field : fields)
+ {
+ String name = field.name();
+ byte[] nameBytes = name.getBytes(utf8);
+ bytes.add(nameBytes);
+
+ String value = field.value();
+ byte[] valueBytes = value.getBytes(utf8);
+ bytes.add(valueBytes);
+
+ int nameLength = nameBytes.length;
+ ++fieldsLength;
+ if (nameLength > 127)
+ fieldsLength += 3;
+
+ int valueLength = valueBytes.length;
+ ++fieldsLength;
+ if (valueLength > 127)
+ fieldsLength += 3;
+
+ fieldsLength += nameLength;
+ fieldsLength += valueLength;
+ }
+
+ if (fieldsLength > 0x7F_FF)
+ throw new IllegalArgumentException(); // TODO: improve this ?
+
+ int capacity = 16 + 8 + fieldsLength + 8;
+ ByteBuffer buffer = byteBufferPool.acquire(capacity, true);
+ BufferUtil.clearToFill(buffer);
+
+ // Generate the FCGI_BEGIN_REQUEST frame
+ buffer.putInt(0x01_01_00_00 + id);
+ buffer.putInt(0x00_08_00_00);
+ buffer.putLong(0x00_01_01_00_00_00_00_00L);
+
+ // Generate the FCGI_PARAMS frame
+ buffer.putInt(0x01_04_00_00 + id);
+ buffer.putShort((short)fieldsLength);
+ buffer.putShort((short)0);
+
+ for (int i = 0; i < bytes.size(); i += 2)
+ {
+ byte[] nameBytes = bytes.get(i);
+ putParamLength(buffer, nameBytes.length);
+ byte[] valueBytes = bytes.get(i + 1);
+ putParamLength(buffer, valueBytes.length);
+ buffer.put(nameBytes);
+ buffer.put(valueBytes);
+ }
+
+ // Generate the last FCGI_PARAMS frame
+ buffer.putInt(0x01_04_00_00 + id);
+ buffer.putInt(0x00_00_00_00);
+
+ buffer.flip();
+
+ return buffer;
+ }
+
+ private void putParamLength(ByteBuffer buffer, int length)
+ {
+ if (length > 127)
+ buffer.putInt(length | 0x80_00_00_00);
+ else
+ buffer.put((byte)length);
+ }
+}
diff --git a/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java b/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java
new file mode 100644
index 00000000000..921630444f8
--- /dev/null
+++ b/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java
@@ -0,0 +1,31 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 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 org.eclipse.jetty.util.BufferUtil;
+
+public class Generator
+{
+ public ByteBuffer generateContent(ByteBuffer buffer)
+ {
+ return BufferUtil.EMPTY_BUFFER;
+ }
+}
diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java b/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java
new file mode 100644
index 00000000000..d434158d63d
--- /dev/null
+++ b/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java
@@ -0,0 +1,116 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 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.parser;
+
+import java.nio.ByteBuffer;
+
+public class BeginRequestContentParser extends ContentParser
+{
+ private State state = State.ROLE;
+ private int cursor;
+ private int role;
+ private int flags;
+
+ public BeginRequestContentParser(HeaderParser headerParser)
+ {
+ super(headerParser);
+ }
+
+ @Override
+ public boolean parse(ByteBuffer buffer)
+ {
+ while (buffer.hasRemaining())
+ {
+ switch (state)
+ {
+ case ROLE:
+ {
+ if (buffer.remaining() >= 2)
+ {
+ role = buffer.getShort();
+ state = State.FLAGS;
+ }
+ else
+ {
+ state = State.ROLE_BYTES;
+ cursor = 0;
+ }
+ break;
+ }
+ case ROLE_BYTES:
+ {
+ int halfShort = buffer.get() & 0xFF;
+ role = (role << (8 * cursor)) + halfShort;
+ if (++cursor == 2)
+ state = State.FLAGS;
+ break;
+ }
+ case FLAGS:
+ {
+ flags = buffer.get() & 0xFF;
+ state = State.RESERVED;
+ break;
+ }
+ case RESERVED:
+ {
+ if (buffer.remaining() >= 5)
+ {
+ buffer.position(buffer.position() + 5);
+ reset();
+ return true;
+ }
+ else
+ {
+ state = State.RESERVED_BYTES;
+ cursor = 0;
+ break;
+ }
+ }
+ case RESERVED_BYTES:
+ {
+ buffer.get();
+ if (++cursor == 5)
+ {
+ reset();
+ return true;
+ }
+ break;
+ }
+ default:
+ {
+ throw new IllegalStateException();
+ }
+ }
+ }
+ return false;
+ }
+
+ private void reset()
+ {
+ state = State.ROLE;
+ cursor = 0;
+ role = 0;
+ flags = 0;
+ }
+
+ private enum State
+ {
+ ROLE, ROLE_BYTES, FLAGS, RESERVED, RESERVED_BYTES
+ }
+}
diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java b/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java
new file mode 100644
index 00000000000..66f74a55739
--- /dev/null
+++ b/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java
@@ -0,0 +1,62 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 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.parser;
+
+import java.util.EnumMap;
+
+import org.eclipse.jetty.fcgi.FCGI;
+
+public class ClientParser extends Parser
+{
+ private final EnumMap contentParsers = new EnumMap<>(FCGI.FrameType.class);
+ private final Listener listener;
+
+ public ClientParser(Listener listener)
+ {
+ this.listener = listener;
+ contentParsers.put(FCGI.FrameType.BEGIN_REQUEST, new BeginRequestContentParser(headerParser));
+ contentParsers.put(FCGI.FrameType.PARAMS, new ParamsContentParser(headerParser, listener));
+ }
+
+ @Override
+ protected ContentParser findContentParser(FCGI.FrameType frameType)
+ {
+ return contentParsers.get(frameType);
+ }
+
+ public interface Listener
+ {
+ public void onParam(String name, String value);
+
+ public void onParams();
+
+ public static class Adapter implements Listener
+ {
+ @Override
+ public void onParam(String name, String value)
+ {
+ }
+
+ @Override
+ public void onParams()
+ {
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java b/src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java
new file mode 100644
index 00000000000..d3cfb6e0f70
--- /dev/null
+++ b/src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java
@@ -0,0 +1,43 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 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.parser;
+
+import java.nio.ByteBuffer;
+
+public abstract class ContentParser
+{
+ private final HeaderParser headerParser;
+
+ protected ContentParser(HeaderParser headerParser)
+ {
+ this.headerParser = headerParser;
+ }
+
+ public abstract boolean parse(ByteBuffer buffer);
+
+ public void noContent()
+ {
+ throw new IllegalStateException();
+ }
+
+ protected int getContentLength()
+ {
+ return headerParser.getContentLength();
+ }
+}
diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java b/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java
new file mode 100644
index 00000000000..85c7720ea21
--- /dev/null
+++ b/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java
@@ -0,0 +1,147 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 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.parser;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.fcgi.FCGI;
+
+public class HeaderParser
+{
+ private State state = State.VERSION;
+ private int cursor;
+ private int version;
+ private int type;
+ private int request;
+ private int length;
+ private int padding;
+
+ public boolean parse(ByteBuffer buffer)
+ {
+ while (buffer.hasRemaining())
+ {
+ switch (state)
+ {
+ case VERSION:
+ {
+ version = buffer.get() & 0xFF;
+ state = State.TYPE;
+ break;
+ }
+ case TYPE:
+ {
+ type = buffer.get() & 0xFF;
+ state = State.REQUEST;
+ break;
+ }
+ case REQUEST:
+ {
+ if (buffer.remaining() >= 2)
+ {
+ request = buffer.getShort() & 0xFFFF;
+ state = State.LENGTH;
+ }
+ else
+ {
+ state = State.REQUEST_BYTES;
+ cursor = 0;
+ }
+ break;
+ }
+ case REQUEST_BYTES:
+ {
+ int halfShort = buffer.get() & 0xFF;
+ request = (request << (8 * cursor)) + halfShort;
+ if (++cursor == 2)
+ state = State.LENGTH;
+ break;
+ }
+ case LENGTH:
+ {
+ if (buffer.remaining() >= 2)
+ {
+ length = buffer.getShort() & 0xFFFF;
+ state = State.PADDING;
+ }
+ else
+ {
+ state = State.LENGTH_BYTES;
+ cursor = 0;
+ }
+ break;
+ }
+ case LENGTH_BYTES:
+ {
+ int halfShort = buffer.get() & 0xFF;
+ length = (length << (8 * cursor)) + halfShort;
+ if (++cursor == 2)
+ state = State.PADDING;
+ break;
+ }
+ case PADDING:
+ {
+ padding = buffer.get() & 0xFF;
+ state = State.RESERVED;
+ break;
+ }
+ case RESERVED:
+ {
+ buffer.get();
+ return true;
+ }
+ default:
+ {
+ throw new IllegalStateException();
+ }
+ }
+ }
+ return false;
+ }
+
+ public FCGI.FrameType getFrameType()
+ {
+ return FCGI.FrameType.from(type);
+ }
+
+ public int getContentLength()
+ {
+ return length;
+ }
+
+ public int getPaddingLength()
+ {
+ return padding;
+ }
+
+ protected void reset()
+ {
+ state = State.VERSION;
+ cursor = 0;
+ version = 0;
+ type = 0;
+ request = 0;
+ length = 0;
+ padding = 0;
+ }
+
+ private enum State
+ {
+ VERSION, TYPE, REQUEST, REQUEST_BYTES, LENGTH, LENGTH_BYTES, PADDING, RESERVED
+ }
+}
diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java b/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java
new file mode 100644
index 00000000000..9b577671b35
--- /dev/null
+++ b/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java
@@ -0,0 +1,231 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 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.parser;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+
+public class ParamsContentParser extends ContentParser
+{
+ private final ClientParser.Listener listener;
+ private State state = State.LENGTH;
+ private int cursor;
+ private int length;
+ private int nameLength;
+ private int valueLength;
+ private byte[] nameBytes;
+ private byte[] valueBytes;
+
+ public ParamsContentParser(HeaderParser headerParser, ClientParser.Listener listener)
+ {
+ super(headerParser);
+ this.listener = listener;
+ }
+
+ @Override
+ public boolean parse(ByteBuffer buffer)
+ {
+ while (buffer.hasRemaining())
+ {
+ switch (state)
+ {
+ case LENGTH:
+ {
+ length = getContentLength();
+ state = State.NAME_LENGTH;
+ break;
+ }
+ case NAME_LENGTH:
+ {
+ if (isLargeLength(buffer))
+ {
+ if (buffer.remaining() >= 4)
+ {
+ nameLength = buffer.getInt() & 0x7F_FF;
+ state = State.VALUE_LENGTH;
+ length -= 4;
+ }
+ else
+ {
+ state = State.NAME_LENGTH_BYTES;
+ cursor = 0;
+ }
+ }
+ else
+ {
+ nameLength = buffer.get() & 0xFF;
+ state = State.VALUE_LENGTH;
+ --length;
+ }
+ break;
+ }
+ case NAME_LENGTH_BYTES:
+ {
+ int quarterInt = buffer.get() & 0xFF;
+ nameLength = (nameLength << (8 * cursor)) + quarterInt;
+ --length;
+ if (++cursor == 4)
+ state = State.VALUE_LENGTH;
+ break;
+ }
+ case VALUE_LENGTH:
+ {
+ if (isLargeLength(buffer))
+ {
+ if (buffer.remaining() >= 4)
+ {
+ valueLength = buffer.getInt() & 0x7F_FF;
+ state = State.NAME;
+ length -= 4;
+ }
+ else
+ {
+ state = State.VALUE_LENGTH_BYTES;
+ cursor = 0;
+ }
+ }
+ else
+ {
+ valueLength = buffer.get() & 0xFF;
+ state = State.NAME;
+ --length;
+ }
+ break;
+ }
+ case VALUE_LENGTH_BYTES:
+ {
+ int quarterInt = buffer.get() & 0xFF;
+ valueLength = (valueLength << (8 * cursor)) + quarterInt;
+ --length;
+ if (++cursor == 4)
+ state = State.NAME;
+ break;
+ }
+ case NAME:
+ {
+ nameBytes = new byte[nameLength];
+ if (buffer.remaining() >= nameLength)
+ {
+ buffer.get(nameBytes);
+ state = State.VALUE;
+ length -= nameLength;
+ }
+ else
+ {
+ state = State.NAME_BYTES;
+ cursor = 0;
+ }
+ break;
+ }
+ case NAME_BYTES:
+ {
+ nameBytes[cursor] = buffer.get();
+ --length;
+ if (++cursor == nameLength)
+ state = State.VALUE;
+ break;
+ }
+ case VALUE:
+ {
+ valueBytes = new byte[valueLength];
+ if (buffer.remaining() >= valueLength)
+ {
+ buffer.get(valueBytes);
+ state = State.PARAM;
+ length -= valueLength;
+ }
+ else
+ {
+ state = State.VALUE_BYTES;
+ cursor = 0;
+ }
+ break;
+ }
+ case VALUE_BYTES:
+ {
+ valueBytes[cursor] = buffer.get();
+ --length;
+ if (++cursor == valueLength)
+ state = State.PARAM;
+ break;
+ }
+ case PARAM:
+ {
+ Charset utf8 = Charset.forName("UTF-8");
+ onParam(new String(nameBytes, utf8), new String(valueBytes, utf8));
+ partialReset();
+ if (length == 0)
+ {
+ reset();
+ return true;
+ }
+ break;
+ }
+ default:
+ {
+ throw new IllegalStateException();
+ }
+ }
+ }
+ return false;
+ }
+
+ protected void onParam(String name, String value)
+ {
+ listener.onParam(name, value);
+ }
+
+ protected void onParams()
+ {
+ listener.onParams();
+ }
+
+ @Override
+ public void noContent()
+ {
+ onParams();
+ }
+
+ private boolean isLargeLength(ByteBuffer buffer)
+ {
+ return (buffer.get(buffer.position()) & 0x80) == 0x80;
+ }
+
+ private void partialReset()
+ {
+ state = State.NAME_LENGTH;
+ cursor = 0;
+ nameLength = 0;
+ valueLength = 0;
+ nameBytes = null;
+ valueBytes = null;
+ }
+
+ private void reset()
+ {
+ partialReset();
+ state = State.LENGTH;
+ length = 0;
+ }
+
+ private enum State
+ {
+ LENGTH, NAME_LENGTH, NAME_LENGTH_BYTES, VALUE_LENGTH, VALUE_LENGTH_BYTES, NAME, NAME_BYTES, VALUE, VALUE_BYTES, PARAM
+ }
+}
diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java b/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java
new file mode 100644
index 00000000000..ba67f2aeb07
--- /dev/null
+++ b/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java
@@ -0,0 +1,96 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 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.parser;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.fcgi.FCGI;
+
+public abstract class Parser
+{
+ protected final HeaderParser headerParser = new HeaderParser();
+ private State state = State.HEADER;
+ private int padding;
+
+ public void parse(ByteBuffer buffer)
+ {
+ while (true)
+ {
+ switch (state)
+ {
+ case HEADER:
+ {
+ if (!headerParser.parse(buffer))
+ return;
+ state = State.CONTENT;
+ break;
+ }
+ case CONTENT:
+ {
+ ContentParser contentParser = findContentParser(headerParser.getFrameType());
+ if (headerParser.getContentLength() == 0)
+ {
+ contentParser.noContent();
+ }
+ else
+ {
+ if (!contentParser.parse(buffer))
+ return;
+ }
+ padding = headerParser.getPaddingLength();
+ state = State.PADDING;
+ break;
+ }
+ case PADDING:
+ {
+ if (buffer.remaining() >= padding)
+ {
+ buffer.position(buffer.position() + padding);
+ reset();
+ break;
+ }
+ else
+ {
+ padding -= buffer.remaining();
+ buffer.position(buffer.limit());
+ return;
+ }
+ }
+ default:
+ {
+ throw new IllegalStateException();
+ }
+ }
+ }
+ }
+
+ protected abstract ContentParser findContentParser(FCGI.FrameType frameType);
+
+ private void reset()
+ {
+ headerParser.reset();
+ state = State.HEADER;
+ padding = 0;
+ }
+
+ private enum State
+ {
+ HEADER, CONTENT, PADDING
+ }
+}
diff --git a/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java b/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java
new file mode 100644
index 00000000000..b35c8d5ba1f
--- /dev/null
+++ b/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java
@@ -0,0 +1,115 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 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.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jetty.fcgi.parser.ClientParser;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.util.Fields;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ClientGeneratorTest
+{
+ @Test
+ public void testGenerateRequestWithoutContent() throws Exception
+ {
+ Fields fields = new Fields();
+ final String methodParamName = "REQUEST_METHOD";
+ final String methodParamValue = "GET";
+ fields.put(new Fields.Field(methodParamName, methodParamValue));
+ final String uriParamName = "REQUEST_URI";
+ // Be sure it's longer than 127 chars to test the large value
+ final String uriParamValue = "/api/0.6/map?bbox=-64.217736,-31.456810,-64.187736,-31.432322,filler=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
+ fields.put(new Fields.Field(uriParamName, uriParamValue));
+ final String protocolParamName = "SERVER_PROTOCOL";
+ final String protocolParamValue = "HTTP/1.1";
+ fields.put(new Fields.Field(protocolParamName, protocolParamValue));
+ // Be sure it's longer than 127 chars to test the large name
+ final String hostParamName = "FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210";
+ final String hostParamValue = "api.openstreetmap.org";
+ fields.put(new Fields.Field(hostParamName, hostParamValue));
+
+ ByteBufferPool byteBufferPool = new MappedByteBufferPool();
+ ClientGenerator generator = new ClientGenerator(byteBufferPool);
+ ByteBuffer buffer = generator.generateRequestHeaders(13, fields);
+
+ // Use the fundamental theorem of arithmetic to test the results.
+ // This way we know onParam() has been called the right number of
+ // times with the right arguments, and so onParams().
+ final int[] primes = new int[]{2, 3, 5, 7, 11};
+ int result = 1;
+ for (int prime : primes)
+ result *= prime;
+
+ final AtomicInteger params = new AtomicInteger(1);
+ ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter()
+ {
+ @Override
+ public void onParam(String name, String value)
+ {
+ switch (name)
+ {
+ case methodParamName:
+ Assert.assertEquals(methodParamValue, value);
+ params.set(params.get() * primes[0]);
+ break;
+ case uriParamName:
+ Assert.assertEquals(uriParamValue, value);
+ params.set(params.get() * primes[1]);
+ break;
+ case protocolParamName:
+ Assert.assertEquals(protocolParamValue, value);
+ params.set(params.get() * primes[2]);
+ break;
+ case hostParamName:
+ Assert.assertEquals(hostParamValue, value);
+ params.set(params.get() * primes[3]);
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ @Override
+ public void onParams()
+ {
+ params.set(params.get() * primes[4]);
+ }
+ });
+
+ parser.parse(buffer);
+
+ Assert.assertFalse(buffer.hasRemaining());
+ Assert.assertEquals(result, params.get());
+
+ // Parse again byte by byte
+ buffer.flip();
+ params.set(1);
+ while (buffer.hasRemaining())
+ parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()}));
+
+ Assert.assertFalse(buffer.hasRemaining());
+ Assert.assertEquals(result, params.get());
+
+ }
+}