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()); + + } +}