From a2324d5adbc6ea54a6a18f870a5d1d6c288176f1 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 30 Aug 2013 07:04:21 -0700 Subject: [PATCH 01/43] Initial commit --- README.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000000..c97b5d6dbd8 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +jetty-fcgi +========== + +Jetty FastCGI Support From c65847c89e32d8a587768d77bc35dfb423296a1a Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Sat, 31 Aug 2013 00:40:23 +0200 Subject: [PATCH 02/43] Started work on generation/parsing. --- pom.xml | 100 ++++++++ .../java/org/eclipse/jetty/fcgi/FCGI.java | 76 ++++++ .../jetty/fcgi/generator/ClientGenerator.java | 113 +++++++++ .../jetty/fcgi/generator/Generator.java | 31 +++ .../parser/BeginRequestContentParser.java | 116 +++++++++ .../jetty/fcgi/parser/ClientParser.java | 62 +++++ .../jetty/fcgi/parser/ContentParser.java | 43 ++++ .../jetty/fcgi/parser/HeaderParser.java | 147 +++++++++++ .../fcgi/parser/ParamsContentParser.java | 231 ++++++++++++++++++ .../org/eclipse/jetty/fcgi/parser/Parser.java | 96 ++++++++ .../fcgi/generator/ClientGeneratorTest.java | 115 +++++++++ 11 files changed, 1130 insertions(+) create mode 100644 pom.xml create mode 100644 src/main/java/org/eclipse/jetty/fcgi/FCGI.java create mode 100644 src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java create mode 100644 src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java create mode 100644 src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java create mode 100644 src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java create mode 100644 src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java create mode 100644 src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java create mode 100644 src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java create mode 100644 src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java create mode 100644 src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java 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()); + + } +} From a9b25418f27c7b281ca1cc9f462e55c1d406da02 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 2 Sep 2013 16:28:28 +0200 Subject: [PATCH 03/43] More work on generation/parsing. --- .../jetty/fcgi/generator/ClientGenerator.java | 124 +++++++++++++----- .../jetty/fcgi/generator/Generator.java | 58 +++++++- .../parser/BeginRequestContentParser.java | 2 +- .../jetty/fcgi/parser/HeaderParser.java | 8 +- .../fcgi/parser/ParamsContentParser.java | 12 +- .../fcgi/generator/ClientGeneratorTest.java | 82 +++++++----- 6 files changed, 209 insertions(+), 77 deletions(-) diff --git a/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java b/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java index 544adecc37a..55583ae4b13 100644 --- a/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java +++ b/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java @@ -23,12 +23,19 @@ import java.nio.charset.Charset; 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; import org.eclipse.jetty.util.Fields; public class ClientGenerator extends Generator { + // To keep the algorithm simple, and given that the max length of a + // frame is 0xFF_FF we allow the max length of a name (or value) to be + // 0x7F_FF - 4 (the 4 is to make room for the name (or value) length). + public static final int MAX_PARAM_LENGTH = 0x7F_FF - 4; + private final ByteBufferPool byteBufferPool; public ClientGenerator(ByteBufferPool byteBufferPool) @@ -36,9 +43,9 @@ public class ClientGenerator extends Generator this.byteBufferPool = byteBufferPool; } - public ByteBuffer generateRequestHeaders(int id, Fields fields) + public Result generateRequestHeaders(int id, Fields fields, Callback callback) { - id = id & 0xFFFF; + id = id & 0xFF_FF; Charset utf8 = Charset.forName("UTF-8"); List bytes = new ArrayList<>(fields.size() * 2); @@ -47,67 +54,114 @@ public class ClientGenerator extends Generator { String name = field.name(); byte[] nameBytes = name.getBytes(utf8); + if (nameBytes.length > MAX_PARAM_LENGTH) + throw new IllegalArgumentException("Field name " + name + " exceeds max length " + MAX_PARAM_LENGTH); bytes.add(nameBytes); String value = field.value(); byte[] valueBytes = value.getBytes(utf8); + if (valueBytes.length > MAX_PARAM_LENGTH) + throw new IllegalArgumentException("Field value " + value + " exceeds max length " + MAX_PARAM_LENGTH); bytes.add(valueBytes); int nameLength = nameBytes.length; - ++fieldsLength; - if (nameLength > 127) - fieldsLength += 3; + fieldsLength += bytesForLength(nameLength); int valueLength = valueBytes.length; - ++fieldsLength; - if (valueLength > 127) - fieldsLength += 3; + fieldsLength += bytesForLength(valueLength); fieldsLength += nameLength; fieldsLength += valueLength; } - if (fieldsLength > 0x7F_FF) - throw new IllegalArgumentException(); // TODO: improve this ? + // Worst case FCGI_PARAMS frame: long name + long value - both of MAX_PARAM_LENGTH + int maxCapacity = 4 + 4 + 2 * MAX_PARAM_LENGTH; - int capacity = 16 + 8 + fieldsLength + 8; - ByteBuffer buffer = byteBufferPool.acquire(capacity, true); - BufferUtil.clearToFill(buffer); + // One FCGI_BEGIN_REQUEST + N FCGI_PARAMS + one last FCGI_PARAMS + int numberOfFrames = 1 + (fieldsLength / maxCapacity + 1) + 1; + Result result = new Result(byteBufferPool, callback, numberOfFrames); + + ByteBuffer beginRequestBuffer = byteBufferPool.acquire(16, false); + BufferUtil.clearToFill(beginRequestBuffer); + result.add(beginRequestBuffer, true); // 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); + beginRequestBuffer.putInt(0x01_01_00_00 + id); + beginRequestBuffer.putInt(0x00_08_00_00); + beginRequestBuffer.putLong(0x00_01_01_00_00_00_00_00L); + beginRequestBuffer.flip(); - // 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) + int index = 0; + while (fieldsLength > 0) { - 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); + int capacity = 8 + Math.min(maxCapacity, fieldsLength); + ByteBuffer buffer = byteBufferPool.acquire(capacity, true); + BufferUtil.clearToFill(buffer); + result.add(buffer, true); + + // Generate the FCGI_PARAMS frame + buffer.putInt(0x01_04_00_00 + id); + buffer.putShort((short)0); + buffer.putShort((short)0); + capacity -= 8; + + int length = 0; + while (index < bytes.size()) + { + byte[] nameBytes = bytes.get(index); + int nameLength = nameBytes.length; + byte[] valueBytes = bytes.get(index + 1); + int valueLength = valueBytes.length; + + int required = bytesForLength(nameLength) + bytesForLength(valueLength) + nameLength + valueLength; + if (required > capacity) + break; + + putParamLength(buffer, nameLength); + putParamLength(buffer, valueLength); + buffer.put(nameBytes); + buffer.put(valueBytes); + + length += required; + fieldsLength -= required; + capacity -= required; + index += 2; + } + + buffer.putShort(4, (short)length); + buffer.flip(); } + + ByteBuffer lastParamsBuffer = byteBufferPool.acquire(8, false); + BufferUtil.clearToFill(lastParamsBuffer); + result.add(lastParamsBuffer, true); + // Generate the last FCGI_PARAMS frame - buffer.putInt(0x01_04_00_00 + id); - buffer.putInt(0x00_00_00_00); + lastParamsBuffer.putInt(0x01_04_00_00 + id); + lastParamsBuffer.putInt(0x00_00_00_00); + lastParamsBuffer.flip(); - buffer.flip(); - - return buffer; + return result; } - private void putParamLength(ByteBuffer buffer, int length) + private int putParamLength(ByteBuffer buffer, int length) { - if (length > 127) + int result = bytesForLength(length); + if (result == 4) buffer.putInt(length | 0x80_00_00_00); else buffer.put((byte)length); + return result; + } + + private int bytesForLength(int length) + { + return length > 127 ? 4 : 1; + } + + public ByteBuffer generateRequestContent(ByteBuffer content) + { + return content == null ? generateContent(FCGI.FrameType.STDIN, 0) : generateContent(FCGI.FrameType.STDIN, content.remaining()); } } diff --git a/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java b/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java index 921630444f8..dd861fbcc80 100644 --- a/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java +++ b/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java @@ -20,12 +20,68 @@ package org.eclipse.jetty.fcgi.generator; import java.nio.ByteBuffer; +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 ByteBuffer generateContent(ByteBuffer buffer) + protected ByteBuffer generateContent(FCGI.FrameType frameType, int length) { return BufferUtil.EMPTY_BUFFER; } + + public static class Result implements Callback + { + private final ByteBufferPool byteBufferPool; + private final Callback callback; + private final ByteBuffer[] buffers; + private final boolean[] recycles; + private int index; + + public Result(ByteBufferPool byteBufferPool, Callback callback, int frames) + { + this.byteBufferPool = byteBufferPool; + this.callback = callback; + this.buffers = new ByteBuffer[frames]; + this.recycles = new boolean[frames]; + } + + public void add(ByteBuffer buffer, boolean recycle) + { + buffers[index] = buffer; + recycles[index] = recycle; + ++index; + } + + public ByteBuffer[] getByteBuffers() + { + return buffers; + } + + @Override + public void succeeded() + { + recycle(); + callback.succeeded(); + } + + @Override + public void failed(Throwable x) + { + recycle(); + callback.failed(x); + } + + private void recycle() + { + for (int i = 0; i < buffers.length; ++i) + { + ByteBuffer buffer = buffers[i]; + if (recycles[i]) + byteBufferPool.release(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 index d434158d63d..a69d98c92c4 100644 --- a/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java +++ b/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java @@ -56,7 +56,7 @@ public class BeginRequestContentParser extends ContentParser case ROLE_BYTES: { int halfShort = buffer.get() & 0xFF; - role = (role << (8 * cursor)) + halfShort; + role = (role << 8) + halfShort; if (++cursor == 2) state = State.FLAGS; break; diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java b/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java index 85c7720ea21..4a3eeea149b 100644 --- a/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java +++ b/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java @@ -54,7 +54,7 @@ public class HeaderParser { if (buffer.remaining() >= 2) { - request = buffer.getShort() & 0xFFFF; + request = buffer.getShort() & 0xFF_FF; state = State.LENGTH; } else @@ -67,7 +67,7 @@ public class HeaderParser case REQUEST_BYTES: { int halfShort = buffer.get() & 0xFF; - request = (request << (8 * cursor)) + halfShort; + request = (request << 8) + halfShort; if (++cursor == 2) state = State.LENGTH; break; @@ -76,7 +76,7 @@ public class HeaderParser { if (buffer.remaining() >= 2) { - length = buffer.getShort() & 0xFFFF; + length = buffer.getShort() & 0xFF_FF; state = State.PADDING; } else @@ -89,7 +89,7 @@ public class HeaderParser case LENGTH_BYTES: { int halfShort = buffer.get() & 0xFF; - length = (length << (8 * cursor)) + halfShort; + length = (length << 8) + halfShort; if (++cursor == 2) state = State.PADDING; break; diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java b/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java index 9b577671b35..6639f39555d 100644 --- a/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java +++ b/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java @@ -41,7 +41,7 @@ public class ParamsContentParser extends ContentParser @Override public boolean parse(ByteBuffer buffer) { - while (buffer.hasRemaining()) + while (buffer.hasRemaining() || state == State.PARAM) { switch (state) { @@ -78,10 +78,13 @@ public class ParamsContentParser extends ContentParser case NAME_LENGTH_BYTES: { int quarterInt = buffer.get() & 0xFF; - nameLength = (nameLength << (8 * cursor)) + quarterInt; + nameLength = (nameLength << 8) + quarterInt; --length; if (++cursor == 4) + { + nameLength &= 0x7F_FF; state = State.VALUE_LENGTH; + } break; } case VALUE_LENGTH: @@ -111,10 +114,13 @@ public class ParamsContentParser extends ContentParser case VALUE_LENGTH_BYTES: { int quarterInt = buffer.get() & 0xFF; - valueLength = (valueLength << (8 * cursor)) + quarterInt; + valueLength = (valueLength << 8) + quarterInt; --length; if (++cursor == 4) + { + valueLength &= 0x7F_FF; state = State.NAME; + } break; } case NAME: diff --git a/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java b/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java index b35c8d5ba1f..f9a0efd6db5 100644 --- a/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java +++ b/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java @@ -19,6 +19,7 @@ package org.eclipse.jetty.fcgi.generator; import java.nio.ByteBuffer; +import java.util.Arrays; import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.jetty.fcgi.parser.ClientParser; @@ -34,32 +35,42 @@ public class ClientGeneratorTest 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"; + + // Short name, short value + final String shortShortName = "REQUEST_METHOD"; + final String shortShortValue = "GET"; + fields.put(new Fields.Field(shortShortName, shortShortValue)); + + // Short name, long value + final String shortLongName = "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)); + final String shortLongValue = "/api/0.6/map?bbox=-64.217736,-31.456810,-64.187736,-31.432322,filler=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; + fields.put(new Fields.Field(shortLongName, shortLongValue)); + + // Long name, short value // 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)); + final String longShortName = "FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210"; + final String longShortValue = "api.openstreetmap.org"; + fields.put(new Fields.Field(longShortName, longShortValue)); + + // Long name, long value + char[] chars = new char[ClientGenerator.MAX_PARAM_LENGTH]; + Arrays.fill(chars, 'z'); + final String longLongName = new String(chars); + final String longLongValue = longLongName; + fields.put(new Fields.Field(longLongName, longLongValue)); ByteBufferPool byteBufferPool = new MappedByteBufferPool(); ClientGenerator generator = new ClientGenerator(byteBufferPool); - ByteBuffer buffer = generator.generateRequestHeaders(13, fields); + Generator.Result result = generator.generateRequestHeaders(13, fields, null); // 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; + int value = 1; for (int prime : primes) - result *= prime; + value *= prime; final AtomicInteger params = new AtomicInteger(1); ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter() @@ -69,24 +80,23 @@ public class ClientGeneratorTest { switch (name) { - case methodParamName: - Assert.assertEquals(methodParamValue, value); + case shortShortName: + Assert.assertEquals(shortShortValue, value); params.set(params.get() * primes[0]); break; - case uriParamName: - Assert.assertEquals(uriParamValue, value); + case shortLongName: + Assert.assertEquals(shortLongValue, value); params.set(params.get() * primes[1]); break; - case protocolParamName: - Assert.assertEquals(protocolParamValue, value); + case longShortName: + Assert.assertEquals(longShortValue, value); params.set(params.get() * primes[2]); break; - case hostParamName: - Assert.assertEquals(hostParamValue, value); + default: + Assert.assertEquals(longLongName, name); + Assert.assertEquals(longLongValue, value); params.set(params.get() * primes[3]); break; - default: - throw new IllegalStateException(); } } @@ -97,19 +107,25 @@ public class ClientGeneratorTest } }); - parser.parse(buffer); + for (ByteBuffer buffer : result.getByteBuffers()) + { + parser.parse(buffer); + Assert.assertFalse(buffer.hasRemaining()); + } - Assert.assertFalse(buffer.hasRemaining()); - Assert.assertEquals(result, params.get()); + Assert.assertEquals(value, 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()); + for (ByteBuffer buffer : result.getByteBuffers()) + { + buffer.flip(); + while (buffer.hasRemaining()) + parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); + Assert.assertFalse(buffer.hasRemaining()); + } + Assert.assertEquals(value, params.get()); } } From b3eb881849b289f624515ec96bddc72e66817a29 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 2 Sep 2013 17:59:54 +0200 Subject: [PATCH 04/43] More work on generation/parsing. --- .../java/org/eclipse/jetty/fcgi/FCGI.java | 5 + .../jetty/fcgi/generator/ClientGenerator.java | 18 ++- .../jetty/fcgi/generator/Generator.java | 47 ++++++- .../jetty/fcgi/parser/ClientParser.java | 13 +- .../jetty/fcgi/parser/ContentParser.java | 5 + .../jetty/fcgi/parser/HeaderParser.java | 5 + .../fcgi/parser/ParamsContentParser.java | 39 ++++-- .../org/eclipse/jetty/fcgi/parser/Parser.java | 20 +++ .../fcgi/parser/StreamContentParser.java | 115 ++++++++++++++++++ .../fcgi/generator/ClientGeneratorTest.java | 70 ++++++++++- 10 files changed, 303 insertions(+), 34 deletions(-) create mode 100644 src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java diff --git a/src/main/java/org/eclipse/jetty/fcgi/FCGI.java b/src/main/java/org/eclipse/jetty/fcgi/FCGI.java index e8f4a3f3376..9bab54c966e 100644 --- a/src/main/java/org/eclipse/jetty/fcgi/FCGI.java +++ b/src/main/java/org/eclipse/jetty/fcgi/FCGI.java @@ -73,4 +73,9 @@ public class FCGI this.code = code; } } + + public enum StreamType + { + STD_IN, STD_OUT, STD_ERR + } } diff --git a/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java b/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java index 55583ae4b13..5db08e35560 100644 --- a/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java +++ b/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java @@ -36,16 +36,14 @@ public class ClientGenerator extends Generator // 0x7F_FF - 4 (the 4 is to make room for the name (or value) length). public static final int MAX_PARAM_LENGTH = 0x7F_FF - 4; - private final ByteBufferPool byteBufferPool; - public ClientGenerator(ByteBufferPool byteBufferPool) { - this.byteBufferPool = byteBufferPool; + super(byteBufferPool); } - public Result generateRequestHeaders(int id, Fields fields, Callback callback) + public Result generateRequestHeaders(int request, Fields fields, Callback callback) { - id = id & 0xFF_FF; + request &= 0xFF_FF; Charset utf8 = Charset.forName("UTF-8"); List bytes = new ArrayList<>(fields.size() * 2); @@ -86,7 +84,7 @@ public class ClientGenerator extends Generator result.add(beginRequestBuffer, true); // Generate the FCGI_BEGIN_REQUEST frame - beginRequestBuffer.putInt(0x01_01_00_00 + id); + beginRequestBuffer.putInt(0x01_01_00_00 + request); beginRequestBuffer.putInt(0x00_08_00_00); beginRequestBuffer.putLong(0x00_01_01_00_00_00_00_00L); beginRequestBuffer.flip(); @@ -100,7 +98,7 @@ public class ClientGenerator extends Generator result.add(buffer, true); // Generate the FCGI_PARAMS frame - buffer.putInt(0x01_04_00_00 + id); + buffer.putInt(0x01_04_00_00 + request); buffer.putShort((short)0); buffer.putShort((short)0); capacity -= 8; @@ -138,7 +136,7 @@ public class ClientGenerator extends Generator result.add(lastParamsBuffer, true); // Generate the last FCGI_PARAMS frame - lastParamsBuffer.putInt(0x01_04_00_00 + id); + lastParamsBuffer.putInt(0x01_04_00_00 + request); lastParamsBuffer.putInt(0x00_00_00_00); lastParamsBuffer.flip(); @@ -160,8 +158,8 @@ public class ClientGenerator extends Generator return length > 127 ? 4 : 1; } - public ByteBuffer generateRequestContent(ByteBuffer content) + public Result generateRequestContent(int request, ByteBuffer content, boolean lastContent, Callback callback) { - return content == null ? generateContent(FCGI.FrameType.STDIN, 0) : generateContent(FCGI.FrameType.STDIN, content.remaining()); + return generateContent(request, content, lastContent, callback, FCGI.FrameType.STDIN); } } diff --git a/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java b/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java index dd861fbcc80..d289e3fea7f 100644 --- a/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java +++ b/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java @@ -27,9 +27,52 @@ import org.eclipse.jetty.util.Callback; public class Generator { - protected ByteBuffer generateContent(FCGI.FrameType frameType, int length) + public static final int MAX_CONTENT_LENGTH = 0xFF_FF; + + protected final ByteBufferPool byteBufferPool; + + public Generator(ByteBufferPool byteBufferPool) { - return BufferUtil.EMPTY_BUFFER; + this.byteBufferPool = byteBufferPool; + } + + protected Result generateContent(int id, ByteBuffer content, boolean lastContent, Callback callback, FCGI.FrameType frameType) + { + id &= 0xFF_FF; + + int remaining = content == null ? 0 : content.remaining(); + int frames = 2 * (remaining / MAX_CONTENT_LENGTH + 1) + (lastContent ? 1 : 0); + Result result = new Result(byteBufferPool, callback, frames); + + while (remaining > 0 || lastContent) + { + ByteBuffer buffer = byteBufferPool.acquire(8, false); + BufferUtil.clearToFill(buffer); + result.add(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, remaining); + buffer.putShort((short)length); + buffer.putShort((short)0); + buffer.flip(); + + if (remaining == 0) + break; + + // Slice to content to avoid copying + int limit = content.limit(); + content.limit(content.position() + length); + ByteBuffer slice = content.slice(); + result.add(slice, false); + content.position(content.limit()); + content.limit(limit); + remaining -= length; + } + + return result; } public static class Result implements Callback diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java b/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java index 66f74a55739..6349dcee1aa 100644 --- a/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java +++ b/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java @@ -32,6 +32,7 @@ public class ClientParser extends Parser this.listener = listener; contentParsers.put(FCGI.FrameType.BEGIN_REQUEST, new BeginRequestContentParser(headerParser)); contentParsers.put(FCGI.FrameType.PARAMS, new ParamsContentParser(headerParser, listener)); + contentParsers.put(FCGI.FrameType.STDIN, new StreamContentParser(headerParser, FCGI.StreamType.STD_IN, listener)); } @Override @@ -40,21 +41,21 @@ public class ClientParser extends Parser return contentParsers.get(frameType); } - public interface Listener + public interface Listener extends Parser.Listener { - public void onParam(String name, String value); + public void onParam(int request, String name, String value); - public void onParams(); + public void onParams(int request); - public static class Adapter implements Listener + public static class Adapter extends Parser.Listener.Adapter implements Listener { @Override - public void onParam(String name, String value) + public void onParam(int request, String name, String value) { } @Override - public void onParams() + public void onParams(int request) { } } diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java b/src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java index d3cfb6e0f70..9775551d6f6 100644 --- a/src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java +++ b/src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java @@ -36,6 +36,11 @@ public abstract class ContentParser throw new IllegalStateException(); } + protected int getRequest() + { + return headerParser.getRequest(); + } + 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 index 4a3eeea149b..8176e777dca 100644 --- a/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java +++ b/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java @@ -119,6 +119,11 @@ public class HeaderParser return FCGI.FrameType.from(type); } + public int getRequest() + { + return request; + } + public int getContentLength() { return length; diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java b/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java index 6639f39555d..03032cbda70 100644 --- a/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java +++ b/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java @@ -21,8 +21,13 @@ package org.eclipse.jetty.fcgi.parser; import java.nio.ByteBuffer; import java.nio.charset.Charset; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + public class ParamsContentParser extends ContentParser { + private static final Logger logger = Log.getLogger(ParamsContentParser.class); + private final ClientParser.Listener listener; private State state = State.LENGTH; private int cursor; @@ -192,22 +197,36 @@ public class ParamsContentParser extends ContentParser return false; } - protected void onParam(String name, String value) - { - listener.onParam(name, value); - } - - protected void onParams() - { - listener.onParams(); - } - @Override public void noContent() { onParams(); } + protected void onParam(String name, String value) + { + try + { + listener.onParam(getRequest(), name, value); + } + catch (Throwable x) + { + logger.debug("Exception while invoking listener " + listener, x); + } + } + + protected void onParams() + { + try + { + listener.onParams(getRequest()); + } + catch (Throwable x) + { + logger.debug("Exception while invoking listener " + listener, x); + } + } + private boolean isLargeLength(ByteBuffer buffer) { return (buffer.get(buffer.position()) & 0x80) == 0x80; diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java b/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java index ba67f2aeb07..a9982559a77 100644 --- a/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java +++ b/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java @@ -89,6 +89,26 @@ public abstract class Parser padding = 0; } + public interface Listener + { + public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer); + + public void onEnd(int request); + + public static class Adapter implements Listener + { + @Override + public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) + { + } + + @Override + public void onEnd(int request) + { + } + } + } + private enum State { HEADER, CONTENT, PADDING diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java b/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java new file mode 100644 index 00000000000..419254544cd --- /dev/null +++ b/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.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.parser; + +import java.nio.ByteBuffer; + +import org.eclipse.jetty.fcgi.FCGI; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +public class StreamContentParser extends ContentParser +{ + private static final Logger logger = Log.getLogger(StreamContentParser.class); + + private final FCGI.StreamType streamType; + private final Parser.Listener listener; + private State state = State.LENGTH; + private int contentLength; + + public StreamContentParser(HeaderParser headerParser, FCGI.StreamType streamType, Parser.Listener listener) + { + super(headerParser); + this.streamType = streamType; + this.listener = listener; + } + + @Override + public boolean parse(ByteBuffer buffer) + { + while (buffer.hasRemaining()) + { + switch (state) + { + case LENGTH: + { + contentLength = getContentLength(); + state = State.CONTENT; + break; + } + case CONTENT: + { + int length = Math.min(contentLength, buffer.remaining()); + int limit = buffer.limit(); + buffer.limit(buffer.position() + length); + ByteBuffer slice = buffer.slice(); + onContent(slice); + buffer.position(buffer.limit()); + buffer.limit(limit); + contentLength -= length; + if (contentLength > 0) + break; + state = State.LENGTH; + return true; + } + default: + { + throw new IllegalStateException(); + } + } + } + return false; + } + + @Override + public void noContent() + { + if (streamType == FCGI.StreamType.STD_IN) + onEnd(); + } + + protected void onContent(ByteBuffer buffer) + { + try + { + listener.onContent(getRequest(), streamType, buffer); + } + catch (Throwable x) + { + logger.debug("Exception while invoking listener " + listener, x); + } + } + + protected void onEnd() + { + try + { + listener.onEnd(getRequest()); + } + catch (Throwable x) + { + logger.debug("Exception while invoking listener " + listener, x); + } + } + + private enum State + { + LENGTH, CONTENT + } +} diff --git a/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java b/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java index f9a0efd6db5..9c6bee81f35 100644 --- a/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java +++ b/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java @@ -22,6 +22,7 @@ import java.nio.ByteBuffer; import java.util.Arrays; import java.util.concurrent.atomic.AtomicInteger; +import org.eclipse.jetty.fcgi.FCGI; import org.eclipse.jetty.fcgi.parser.ClientParser; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.MappedByteBufferPool; @@ -32,7 +33,7 @@ import org.junit.Test; public class ClientGeneratorTest { @Test - public void testGenerateRequestWithoutContent() throws Exception + public void testGenerateRequestHeaders() throws Exception { Fields fields = new Fields(); @@ -57,12 +58,13 @@ public class ClientGeneratorTest char[] chars = new char[ClientGenerator.MAX_PARAM_LENGTH]; Arrays.fill(chars, 'z'); final String longLongName = new String(chars); - final String longLongValue = longLongName; + final String longLongValue = new String(chars); fields.put(new Fields.Field(longLongName, longLongValue)); ByteBufferPool byteBufferPool = new MappedByteBufferPool(); ClientGenerator generator = new ClientGenerator(byteBufferPool); - Generator.Result result = generator.generateRequestHeaders(13, fields, null); + final int id = 13; + Generator.Result result = generator.generateRequestHeaders(id, fields, null); // Use the fundamental theorem of arithmetic to test the results. // This way we know onParam() has been called the right number of @@ -76,8 +78,9 @@ public class ClientGeneratorTest ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter() { @Override - public void onParam(String name, String value) + public void onParam(int request, String name, String value) { + Assert.assertEquals(id, request); switch (name) { case shortShortName: @@ -101,8 +104,9 @@ public class ClientGeneratorTest } @Override - public void onParams() + public void onParams(int request) { + Assert.assertEquals(id, request); params.set(params.get() * primes[4]); } }); @@ -117,7 +121,6 @@ public class ClientGeneratorTest // Parse again byte by byte params.set(1); - for (ByteBuffer buffer : result.getByteBuffers()) { buffer.flip(); @@ -128,4 +131,59 @@ public class ClientGeneratorTest Assert.assertEquals(value, params.get()); } + + @Test + public void testGenerateSmallRequestContent() throws Exception + { + testGenerateRequestContent(1024); + } + + @Test + public void testGenerateLargeRequestContent() throws Exception + { + testGenerateRequestContent(128 * 1024); + } + + private void testGenerateRequestContent(final int contentLength) throws Exception + { + ByteBuffer content = ByteBuffer.allocate(contentLength); + + ByteBufferPool byteBufferPool = new MappedByteBufferPool(); + ClientGenerator generator = new ClientGenerator(byteBufferPool); + final int id = 13; + Generator.Result result = generator.generateRequestContent(id, content, true, null); + + final AtomicInteger length = new AtomicInteger(); + ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter() + { + @Override + public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) + { + Assert.assertEquals(id, request); + length.addAndGet(buffer.remaining()); + } + + @Override + public void onEnd(int request) + { + Assert.assertEquals(id, request); + Assert.assertEquals(contentLength, length.get()); + } + }); + + for (ByteBuffer buffer : result.getByteBuffers()) + { + parser.parse(buffer); + Assert.assertFalse(buffer.hasRemaining()); + } + + // Parse again one byte at a time + for (ByteBuffer buffer : result.getByteBuffers()) + { + buffer.flip(); + while (buffer.hasRemaining()) + parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); + Assert.assertFalse(buffer.hasRemaining()); + } + } } From 54accb36994ee1a3414c1d73bc4b3ce573a0adc2 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 3 Sep 2013 11:19:13 +0200 Subject: [PATCH 05/43] More work on generation/parsing. --- .../java/org/eclipse/jetty/fcgi/FCGI.java | 27 ++++ .../jetty/fcgi/generator/Generator.java | 17 ++- .../jetty/fcgi/generator/ServerGenerator.java | 97 ++++++++++++ .../parser/BeginRequestContentParser.java | 13 +- .../jetty/fcgi/parser/ClientParser.java | 28 +--- .../fcgi/parser/EndRequestContentParser.java | 126 ++++++++++++++++ .../fcgi/parser/ParamsContentParser.java | 8 +- .../org/eclipse/jetty/fcgi/parser/Parser.java | 14 ++ .../fcgi/parser/ResponseContentParser.java | 140 ++++++++++++++++++ .../jetty/fcgi/parser/ServerParser.java | 54 +++++++ .../fcgi/parser/StreamContentParser.java | 2 +- .../fcgi/generator/ClientGeneratorTest.java | 14 +- .../jetty/fcgi/parser/ClientParserTest.java | 94 ++++++++++++ 13 files changed, 593 insertions(+), 41 deletions(-) create mode 100644 src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java create mode 100644 src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java create mode 100644 src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java create mode 100644 src/main/java/org/eclipse/jetty/fcgi/parser/ServerParser.java create mode 100644 src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java diff --git a/src/main/java/org/eclipse/jetty/fcgi/FCGI.java b/src/main/java/org/eclipse/jetty/fcgi/FCGI.java index 9bab54c966e..10cf644db6c 100644 --- a/src/main/java/org/eclipse/jetty/fcgi/FCGI.java +++ b/src/main/java/org/eclipse/jetty/fcgi/FCGI.java @@ -24,6 +24,33 @@ public class FCGI { } + public enum Role + { + RESPONDER(1), AUTHORIZER(2), FILTER(3); + + public static Role from(int code) + { + switch (code) + { + case 1: + return RESPONDER; + case 2: + return AUTHORIZER; + case 3: + return FILTER; + default: + throw new IllegalArgumentException(); + } + } + + public final int code; + + private Role(int code) + { + this.code = code; + } + } + public enum FrameType { BEGIN_REQUEST(1), diff --git a/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java b/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java index d289e3fea7f..cfc03cbbc74 100644 --- a/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java +++ b/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java @@ -84,11 +84,22 @@ public class Generator private int index; public Result(ByteBufferPool byteBufferPool, Callback callback, int frames) + { + this(byteBufferPool, callback, new ByteBuffer[frames], new boolean[frames], 0); + } + + protected Result(Result that) + { + this(that.byteBufferPool, that.callback, that.buffers, that.recycles, that.index); + } + + private Result(ByteBufferPool byteBufferPool, Callback callback, ByteBuffer[] buffers, boolean[] recycles, int index) { this.byteBufferPool = byteBufferPool; this.callback = callback; - this.buffers = new ByteBuffer[frames]; - this.recycles = new boolean[frames]; + this.buffers = buffers; + this.recycles = recycles; + this.index = index; } public void add(ByteBuffer buffer, boolean recycle) @@ -117,7 +128,7 @@ public class Generator callback.failed(x); } - private void recycle() + protected void recycle() { for (int i = 0; i < buffers.length; ++i) { diff --git a/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java b/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java new file mode 100644 index 00000000000..668fe115066 --- /dev/null +++ b/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java @@ -0,0 +1,97 @@ +// +// ======================================================================== +// 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.fcgi.FCGI; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.Fields; + +public class ServerGenerator extends Generator +{ + private static final byte[] STATUS = new byte[]{'S', 't', 'a', 't', 'u', 's'}; + private static final byte[] COLON = new byte[]{':', ' '}; + private static final byte[] EOL = new byte[]{'\r', '\n'}; + + public ServerGenerator(ByteBufferPool byteBufferPool) + { + super(byteBufferPool); + } + + public Result generateResponseHeaders(int request, int code, String reason, Fields fields, Callback callback) + { + request &= 0xFF_FF; + + Charset utf8 = Charset.forName("UTF-8"); + List bytes = new ArrayList<>(fields.size() * 2); + int length = 0; + + // Special 'Status' header + bytes.add(STATUS); + length += STATUS.length + COLON.length; + if (reason == null) + reason = HttpStatus.getMessage(code); + byte[] responseBytes = (code + " " + reason).getBytes(utf8); + bytes.add(responseBytes); + length += responseBytes.length + EOL.length; + + // Other headers + 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); + + length += nameBytes.length + COLON.length; + length += valueBytes.length + EOL.length; + } + // End of headers + length += EOL.length; + + final ByteBuffer buffer = byteBufferPool.acquire(length, true); + BufferUtil.clearToFill(buffer); + + for (int i = 0; i < bytes.size(); i += 2) + buffer.put(bytes.get(i)).put(COLON).put(bytes.get(i + 1)).put(EOL); + buffer.put(EOL); + + buffer.flip(); + + return new Result(generateContent(request, buffer, false, callback, FCGI.FrameType.STDOUT)) + { + @Override + protected void recycle() + { + super.recycle(); + byteBufferPool.release(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 index a69d98c92c4..17e9efea921 100644 --- a/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java +++ b/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java @@ -20,16 +20,20 @@ package org.eclipse.jetty.fcgi.parser; import java.nio.ByteBuffer; +import org.eclipse.jetty.fcgi.FCGI; + public class BeginRequestContentParser extends ContentParser { + private final ServerParser.Listener listener; private State state = State.ROLE; private int cursor; private int role; private int flags; - public BeginRequestContentParser(HeaderParser headerParser) + public BeginRequestContentParser(HeaderParser headerParser, ServerParser.Listener listener) { super(headerParser); + this.listener = listener; } @Override @@ -72,6 +76,7 @@ public class BeginRequestContentParser extends ContentParser if (buffer.remaining() >= 5) { buffer.position(buffer.position() + 5); + onStart(getRequest(), role); reset(); return true; } @@ -87,6 +92,7 @@ public class BeginRequestContentParser extends ContentParser buffer.get(); if (++cursor == 5) { + onStart(getRequest(), role); reset(); return true; } @@ -101,6 +107,11 @@ public class BeginRequestContentParser extends ContentParser return false; } + private void onStart(int request, int role) + { + listener.onStart(request, FCGI.Role.from(role)); + } + private void reset() { state = State.ROLE; diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java b/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java index 6349dcee1aa..48932dfead3 100644 --- a/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java +++ b/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java @@ -25,14 +25,12 @@ 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)); - contentParsers.put(FCGI.FrameType.STDIN, new StreamContentParser(headerParser, FCGI.StreamType.STD_IN, listener)); + contentParsers.put(FCGI.FrameType.STDOUT, new ResponseContentParser(headerParser, FCGI.StreamType.STD_OUT, listener)); + contentParsers.put(FCGI.FrameType.STDERR, new StreamContentParser(headerParser, FCGI.StreamType.STD_ERR, listener)); + contentParsers.put(FCGI.FrameType.END_REQUEST, new EndRequestContentParser(headerParser, listener)); } @Override @@ -40,24 +38,4 @@ public class ClientParser extends Parser { return contentParsers.get(frameType); } - - public interface Listener extends Parser.Listener - { - public void onParam(int request, String name, String value); - - public void onParams(int request); - - public static class Adapter extends Parser.Listener.Adapter implements Listener - { - @Override - public void onParam(int request, String name, String value) - { - } - - @Override - public void onParams(int request) - { - } - } - } } diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java b/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java new file mode 100644 index 00000000000..b34e4ab0c93 --- /dev/null +++ b/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java @@ -0,0 +1,126 @@ +// +// ======================================================================== +// 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 EndRequestContentParser extends ContentParser +{ + private final Parser.Listener listener; + private State state = State.APPLICATION; + private int cursor; + private int application; + private int protocol; + + public EndRequestContentParser(HeaderParser headerParser, Parser.Listener listener) + { + super(headerParser); + this.listener = listener; + } + + @Override + public boolean parse(ByteBuffer buffer) + { + while (buffer.hasRemaining()) + { + switch (state) + { + case APPLICATION: + { + if (buffer.remaining() >= 4) + { + application = buffer.getInt(); + state = State.PROTOCOL; + } + else + { + state = State.APPLICATION_BYTES; + cursor = 0; + } + break; + } + case APPLICATION_BYTES: + { + int quarterInt = buffer.get() & 0xFF; + application = (application << 8) + quarterInt; + if (++cursor == 4) + state = State.PROTOCOL; + break; + } + case PROTOCOL: + { + protocol = buffer.get() & 0xFF; + state = State.RESERVED; + break; + } + case RESERVED: + { + if (buffer.remaining() >= 3) + { + buffer.position(buffer.position() + 3); + onEnd(); + reset(); + return true; + } + else + { + state = State.APPLICATION_BYTES; + cursor = 0; + break; + } + } + case RESERVED_BYTES: + { + buffer.get(); + if (++cursor == 0) + { + onEnd(); + reset(); + return true; + } + break; + } + default: + { + throw new IllegalStateException(); + } + } + } + return false; + } + + private void onEnd() + { + // TODO: if protocol != 0, invoke an error callback + listener.onEnd(getRequest()); + } + + private void reset() + { + state = State.APPLICATION; + cursor = 0; + application = 0; + protocol = 0; + } + + private enum State + { + APPLICATION, APPLICATION_BYTES, PROTOCOL, RESERVED, RESERVED_BYTES + } +} diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java b/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java index 03032cbda70..651e13f998f 100644 --- a/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java +++ b/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java @@ -28,7 +28,7 @@ public class ParamsContentParser extends ContentParser { private static final Logger logger = Log.getLogger(ParamsContentParser.class); - private final ClientParser.Listener listener; + private final ServerParser.Listener listener; private State state = State.LENGTH; private int cursor; private int length; @@ -37,7 +37,7 @@ public class ParamsContentParser extends ContentParser private byte[] nameBytes; private byte[] valueBytes; - public ParamsContentParser(HeaderParser headerParser, ClientParser.Listener listener) + public ParamsContentParser(HeaderParser headerParser, ServerParser.Listener listener) { super(headerParser); this.listener = listener; @@ -207,7 +207,7 @@ public class ParamsContentParser extends ContentParser { try { - listener.onParam(getRequest(), name, value); + listener.onHeader(getRequest(), name, value); } catch (Throwable x) { @@ -219,7 +219,7 @@ public class ParamsContentParser extends ContentParser { try { - listener.onParams(getRequest()); + listener.onHeaders(getRequest()); } catch (Throwable x) { diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java b/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java index a9982559a77..3ca650cdf31 100644 --- a/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java +++ b/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java @@ -91,12 +91,26 @@ public abstract class Parser public interface Listener { + public void onHeader(int request, String name, String value); + + public void onHeaders(int request); + public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer); public void onEnd(int request); public static class Adapter implements Listener { + @Override + public void onHeader(int request, String name, String value) + { + } + + @Override + public void onHeaders(int request) + { + } + @Override public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) { diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java b/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java new file mode 100644 index 00000000000..831046ec020 --- /dev/null +++ b/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java @@ -0,0 +1,140 @@ +// +// ======================================================================== +// 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; +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpParser; +import org.eclipse.jetty.http.HttpVersion; + +public class ResponseContentParser extends StreamContentParser +{ + public ResponseContentParser(HeaderParser headerParser, FCGI.StreamType streamType, Parser.Listener listener) + { + super(headerParser, streamType, new ResponseListener(headerParser, listener)); + } + + private static class ResponseListener extends Parser.Listener.Adapter implements HttpParser.ResponseHandler + { + private final HeaderParser headerParser; + private final Parser.Listener listener; + private final FCGIHttpParser httpParser; + + public ResponseListener(HeaderParser headerParser, Parser.Listener listener) + { + this.headerParser = headerParser; + this.listener = listener; + this.httpParser = new FCGIHttpParser(this); + } + + @Override + public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) + { + httpParser.parseHeaders(buffer); + } + + @Override + public void onEnd(int request) + { + // TODO + throw new UnsupportedOperationException(); + } + + @Override + public int getHeaderCacheSize() + { + // TODO: configure this + return 0; + } + + @Override + public boolean startResponse(HttpVersion version, int status, String reason) + { + throw new IllegalStateException(); + } + + @Override + public boolean parsedHeader(HttpField field) + { + try + { + listener.onHeader(headerParser.getRequest(), field.getName(), field.getValue()); + } + catch (Throwable x) + { + logger.debug("Exception while invoking listener " + listener, x); + } + return false; + } + + @Override + public boolean headerComplete() + { + try + { + listener.onHeaders(headerParser.getRequest()); + } + catch (Throwable x) + { + logger.debug("Exception while invoking listener " + listener, x); + } + return false; + } + + @Override + public boolean content(ByteBuffer item) + { + return false; + } + + @Override + public boolean messageComplete() + { + return false; + } + + @Override + public void earlyEOF() + { + } + + @Override + public void badMessage(int status, String reason) + { + } + } + + // Methods overridden to make them visible here + private static class FCGIHttpParser extends HttpParser + { + private FCGIHttpParser(ResponseHandler handler) + { + super(handler, 65 * 1024, true); + setState(State.HEADER); + } + + @Override + protected boolean parseHeaders(ByteBuffer buffer) + { + return super.parseHeaders(buffer); + } + } +} diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/ServerParser.java b/src/main/java/org/eclipse/jetty/fcgi/parser/ServerParser.java new file mode 100644 index 00000000000..1b049ca122f --- /dev/null +++ b/src/main/java/org/eclipse/jetty/fcgi/parser/ServerParser.java @@ -0,0 +1,54 @@ +// +// ======================================================================== +// 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 ServerParser extends Parser +{ + private final EnumMap contentParsers = new EnumMap<>(FCGI.FrameType.class); + + public ServerParser(Listener listener) + { + contentParsers.put(FCGI.FrameType.BEGIN_REQUEST, new BeginRequestContentParser(headerParser, listener)); + contentParsers.put(FCGI.FrameType.PARAMS, new ParamsContentParser(headerParser, listener)); + contentParsers.put(FCGI.FrameType.STDIN, new StreamContentParser(headerParser, FCGI.StreamType.STD_IN, listener)); + } + + @Override + protected ContentParser findContentParser(FCGI.FrameType frameType) + { + return contentParsers.get(frameType); + } + + public interface Listener extends Parser.Listener + { + public void onStart(int request, FCGI.Role role); + + public static class Adapter extends Parser.Listener.Adapter implements Listener + { + @Override + public void onStart(int request, FCGI.Role role) + { + } + } + } +} diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java b/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java index 419254544cd..79f0ed3fe7b 100644 --- a/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java +++ b/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java @@ -26,7 +26,7 @@ import org.eclipse.jetty.util.log.Logger; public class StreamContentParser extends ContentParser { - private static final Logger logger = Log.getLogger(StreamContentParser.class); + protected static final Logger logger = Log.getLogger(StreamContentParser.class); private final FCGI.StreamType streamType; private final Parser.Listener listener; diff --git a/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java b/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java index 9c6bee81f35..eedb32f96ef 100644 --- a/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java +++ b/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java @@ -23,7 +23,7 @@ import java.util.Arrays; import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.jetty.fcgi.FCGI; -import org.eclipse.jetty.fcgi.parser.ClientParser; +import org.eclipse.jetty.fcgi.parser.ServerParser; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.MappedByteBufferPool; import org.eclipse.jetty.util.Fields; @@ -67,18 +67,18 @@ public class ClientGeneratorTest Generator.Result result = generator.generateRequestHeaders(id, fields, null); // 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(). + // This way we know onHeader() has been called the right number of + // times with the right arguments, and so onHeaders(). final int[] primes = new int[]{2, 3, 5, 7, 11}; int value = 1; for (int prime : primes) value *= prime; final AtomicInteger params = new AtomicInteger(1); - ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter() + ServerParser parser = new ServerParser(new ServerParser.Listener.Adapter() { @Override - public void onParam(int request, String name, String value) + public void onHeader(int request, String name, String value) { Assert.assertEquals(id, request); switch (name) @@ -104,7 +104,7 @@ public class ClientGeneratorTest } @Override - public void onParams(int request) + public void onHeaders(int request) { Assert.assertEquals(id, request); params.set(params.get() * primes[4]); @@ -154,7 +154,7 @@ public class ClientGeneratorTest Generator.Result result = generator.generateRequestContent(id, content, true, null); final AtomicInteger length = new AtomicInteger(); - ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter() + ServerParser parser = new ServerParser(new ServerParser.Listener.Adapter() { @Override public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) diff --git a/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java b/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java new file mode 100644 index 00000000000..3abee92ee73 --- /dev/null +++ b/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java @@ -0,0 +1,94 @@ +// +// ======================================================================== +// 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.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.jetty.fcgi.generator.Generator; +import org.eclipse.jetty.fcgi.generator.ServerGenerator; +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 ClientParserTest +{ + @Test + public void testParseResponseHeaders() throws Exception + { + final int id = 13; + Fields fields = new Fields(); + + final String statusName = "Status"; + final int code = 200; + final String contentTypeName = "Content-Type"; + final String contentTypeValue = "text/html;charset=utf-8"; + fields.put(contentTypeName, contentTypeValue); + + ByteBufferPool byteBufferPool = new MappedByteBufferPool(); + ServerGenerator generator = new ServerGenerator(byteBufferPool); + Generator.Result result = generator.generateResponseHeaders(id, code, "OK", fields, null); + + // Use the fundamental theorem of arithmetic to test the results. + // This way we know onHeader() has been called the right number of + // times with the right arguments, and so onHeaders(). + final int[] primes = new int[]{2, 3, 5}; + int value = 1; + for (int prime : primes) + value *= prime; + + final AtomicInteger params = new AtomicInteger(1); + ClientParser parser = new ClientParser(new Parser.Listener.Adapter() + { + @Override + public void onHeader(int request, String name, String value) + { + Assert.assertEquals(id, request); + switch (name) + { + case statusName: + Assert.assertTrue(value.startsWith(String.valueOf(code))); + params.set(params.get() * primes[0]); + break; + case contentTypeName: + Assert.assertEquals(contentTypeValue, value); + params.set(params.get() * primes[1]); + break; + } + } + + @Override + public void onHeaders(int request) + { + Assert.assertEquals(id, request); + params.set(params.get() * primes[2]); + } + }); + + for (ByteBuffer buffer : result.getByteBuffers()) + { + parser.parse(buffer); + Assert.assertFalse(buffer.hasRemaining()); + } + + Assert.assertEquals(value, params.get()); + } +} From f546d59ea18ffabff6ff6a3d367d99735a6a86f8 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 3 Sep 2013 16:04:13 +0200 Subject: [PATCH 06/43] More work on generation/parsing. --- .../jetty/fcgi/generator/ClientGenerator.java | 3 +- .../jetty/fcgi/generator/Generator.java | 34 ++-- .../jetty/fcgi/generator/ServerGenerator.java | 18 ++ .../jetty/fcgi/parser/ClientParser.java | 2 +- .../fcgi/parser/ResponseContentParser.java | 114 ++++++++++--- .../jetty/fcgi/parser/ClientParserTest.java | 155 ++++++++++++++++++ 6 files changed, 284 insertions(+), 42 deletions(-) diff --git a/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java b/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java index 5db08e35560..e96aa5d30fe 100644 --- a/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java +++ b/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java @@ -76,8 +76,7 @@ public class ClientGenerator extends Generator int maxCapacity = 4 + 4 + 2 * MAX_PARAM_LENGTH; // One FCGI_BEGIN_REQUEST + N FCGI_PARAMS + one last FCGI_PARAMS - int numberOfFrames = 1 + (fieldsLength / maxCapacity + 1) + 1; - Result result = new Result(byteBufferPool, callback, numberOfFrames); + Result result = new Result(byteBufferPool, callback); ByteBuffer beginRequestBuffer = byteBufferPool.acquire(16, false); BufferUtil.clearToFill(beginRequestBuffer); diff --git a/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java b/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java index cfc03cbbc74..1a578f1d5ad 100644 --- a/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java +++ b/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java @@ -19,6 +19,8 @@ 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; @@ -41,8 +43,7 @@ public class Generator id &= 0xFF_FF; int remaining = content == null ? 0 : content.remaining(); - int frames = 2 * (remaining / MAX_CONTENT_LENGTH + 1) + (lastContent ? 1 : 0); - Result result = new Result(byteBufferPool, callback, frames); + Result result = new Result(byteBufferPool, callback); while (remaining > 0 || lastContent) { @@ -79,37 +80,34 @@ public class Generator { private final ByteBufferPool byteBufferPool; private final Callback callback; - private final ByteBuffer[] buffers; - private final boolean[] recycles; - private int index; + private final List buffers; + private final List recycles; - public Result(ByteBufferPool byteBufferPool, Callback callback, int frames) + public Result(ByteBufferPool byteBufferPool, Callback callback) { - this(byteBufferPool, callback, new ByteBuffer[frames], new boolean[frames], 0); + this(byteBufferPool, callback, new ArrayList(4), new ArrayList(4)); } - protected Result(Result that) + public Result(Result that) { - this(that.byteBufferPool, that.callback, that.buffers, that.recycles, that.index); + this(that.byteBufferPool, that.callback, that.buffers, that.recycles); } - private Result(ByteBufferPool byteBufferPool, Callback callback, ByteBuffer[] buffers, boolean[] recycles, int index) + private Result(ByteBufferPool byteBufferPool, Callback callback, List buffers, List recycles) { this.byteBufferPool = byteBufferPool; this.callback = callback; this.buffers = buffers; this.recycles = recycles; - this.index = index; } public void add(ByteBuffer buffer, boolean recycle) { - buffers[index] = buffer; - recycles[index] = recycle; - ++index; + buffers.add(buffer); + recycles.add(recycle); } - public ByteBuffer[] getByteBuffers() + public List getByteBuffers() { return buffers; } @@ -130,10 +128,10 @@ public class Generator protected void recycle() { - for (int i = 0; i < buffers.length; ++i) + for (int i = 0; i < buffers.size(); ++i) { - ByteBuffer buffer = buffers[i]; - if (recycles[i]) + ByteBuffer buffer = buffers.get(i); + if (recycles.get(i)) byteBufferPool.release(buffer); } } diff --git a/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java b/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java index 668fe115066..1ff841b7b75 100644 --- a/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java +++ b/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java @@ -94,4 +94,22 @@ public class ServerGenerator extends Generator } }; } + + public Result generateResponseContent(int request, ByteBuffer content, boolean lastContent, Callback callback) + { + Result result = generateContent(request, content, lastContent, callback, FCGI.FrameType.STDOUT); + if (lastContent) + { + // Generate the FCGI_END_REQUEST + request &= 0xFF_FF; + ByteBuffer endRequestBuffer = byteBufferPool.acquire(8, false); + BufferUtil.clearToFill(endRequestBuffer); + endRequestBuffer.putInt(0x01_03_00_00 + request); + endRequestBuffer.putInt(0x00_08_00_00); + endRequestBuffer.putLong(0x00L); + endRequestBuffer.flip(); + result.add(endRequestBuffer, true); + } + return result; + } } diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java b/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java index 48932dfead3..b5a4f3ae4c3 100644 --- a/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java +++ b/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java @@ -28,7 +28,7 @@ public class ClientParser extends Parser public ClientParser(Listener listener) { - contentParsers.put(FCGI.FrameType.STDOUT, new ResponseContentParser(headerParser, FCGI.StreamType.STD_OUT, listener)); + contentParsers.put(FCGI.FrameType.STDOUT, new ResponseContentParser(headerParser, listener)); contentParsers.put(FCGI.FrameType.STDERR, new StreamContentParser(headerParser, FCGI.StreamType.STD_ERR, listener)); contentParsers.put(FCGI.FrameType.END_REQUEST, new EndRequestContentParser(headerParser, listener)); } diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java b/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java index 831046ec020..5d03e61abe5 100644 --- a/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java +++ b/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java @@ -27,9 +27,9 @@ import org.eclipse.jetty.http.HttpVersion; public class ResponseContentParser extends StreamContentParser { - public ResponseContentParser(HeaderParser headerParser, FCGI.StreamType streamType, Parser.Listener listener) + public ResponseContentParser(HeaderParser headerParser, Parser.Listener listener) { - super(headerParser, streamType, new ResponseListener(headerParser, listener)); + super(headerParser, FCGI.StreamType.STD_OUT, new ResponseListener(headerParser, listener)); } private static class ResponseListener extends Parser.Listener.Adapter implements HttpParser.ResponseHandler @@ -37,6 +37,7 @@ public class ResponseContentParser extends StreamContentParser private final HeaderParser headerParser; private final Parser.Listener listener; private final FCGIHttpParser httpParser; + private State state = State.HEADERS; public ResponseListener(HeaderParser headerParser, Parser.Listener listener) { @@ -48,14 +49,41 @@ public class ResponseContentParser extends StreamContentParser @Override public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) { - httpParser.parseHeaders(buffer); + while (buffer.hasRemaining()) + { + switch (state) + { + case HEADERS: + { + if (httpParser.parseHeaders(buffer)) + state = State.CONTENT; + break; + } + case CONTENT: + { + if (httpParser.parseContent(buffer)) + reset(); + break; + } + default: + { + throw new IllegalStateException(); + } + } + } + } + + private void reset() + { + httpParser.reset(); + state = State.HEADERS; } @Override public void onEnd(int request) { - // TODO - throw new UnsupportedOperationException(); + // Never called for STD_OUT, since it relies on FCGI_END_REQUEST + throw new IllegalStateException(); } @Override @@ -68,6 +96,7 @@ public class ResponseContentParser extends StreamContentParser @Override public boolean startResponse(HttpVersion version, int status, String reason) { + // The HTTP request line does not exist in FCGI responses throw new IllegalStateException(); } @@ -76,6 +105,13 @@ public class ResponseContentParser extends StreamContentParser { try { + if ("Status".equalsIgnoreCase(field.getName())) + { + // Need to set the response status so the + // HttpParser can handle the content properly. + int code = Integer.parseInt(field.getValue().split(" ")[0]); + httpParser.setResponseStatus(code); + } listener.onHeader(headerParser.getRequest(), field.getName(), field.getValue()); } catch (Throwable x) @@ -96,45 +132,81 @@ public class ResponseContentParser extends StreamContentParser { logger.debug("Exception while invoking listener " + listener, x); } - return false; + // Return from parsing so that we can parse the content + return true; } @Override - public boolean content(ByteBuffer item) + public boolean content(ByteBuffer buffer) { + try + { + listener.onContent(headerParser.getRequest(), FCGI.StreamType.STD_OUT, buffer); + } + catch (Throwable x) + { + logger.debug("Exception while invoking listener " + listener, x); + } return false; } @Override public boolean messageComplete() { - return false; + // Return from parsing so that we can parse the next headers + return true; } @Override public void earlyEOF() { + // TODO } @Override public void badMessage(int status, String reason) { - } - } - - // Methods overridden to make them visible here - private static class FCGIHttpParser extends HttpParser - { - private FCGIHttpParser(ResponseHandler handler) - { - super(handler, 65 * 1024, true); - setState(State.HEADER); + // TODO } - @Override - protected boolean parseHeaders(ByteBuffer buffer) + // Methods overridden to make them visible here + private static class FCGIHttpParser extends HttpParser { - return super.parseHeaders(buffer); + private FCGIHttpParser(ResponseHandler handler) + { + super(handler, 65 * 1024, true); + reset(); + } + + @Override + public void reset() + { + super.reset(); + setState(State.HEADER); + } + + @Override + protected boolean parseHeaders(ByteBuffer buffer) + { + return super.parseHeaders(buffer); + } + + @Override + protected boolean parseContent(ByteBuffer buffer) + { + return super.parseContent(buffer); + } + + @Override + protected void setResponseStatus(int status) + { + super.setResponseStatus(status); + } + } + + private enum State + { + HEADERS, CONTENT } } } diff --git a/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java b/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java index 3abee92ee73..0ca63b312d0 100644 --- a/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java +++ b/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java @@ -19,8 +19,10 @@ package org.eclipse.jetty.fcgi.parser; import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import org.eclipse.jetty.fcgi.FCGI; import org.eclipse.jetty.fcgi.generator.Generator; import org.eclipse.jetty.fcgi.generator.ServerGenerator; import org.eclipse.jetty.io.ByteBufferPool; @@ -91,4 +93,157 @@ public class ClientParserTest Assert.assertEquals(value, params.get()); } + + @Test + public void testParseNoResponseContent() throws Exception + { + final int id = 13; + Fields fields = new Fields(); + + final int code = 200; + final String contentTypeName = "Content-Length"; + final String contentTypeValue = "0"; + fields.put(contentTypeName, contentTypeValue); + + ByteBufferPool byteBufferPool = new MappedByteBufferPool(); + ServerGenerator generator = new ServerGenerator(byteBufferPool); + Generator.Result result1 = generator.generateResponseHeaders(id, code, "OK", fields, null); + Generator.Result result2 = generator.generateResponseContent(id, null, true, null); + + final AtomicInteger verifier = new AtomicInteger(); + ClientParser parser = new ClientParser(new Parser.Listener.Adapter() + { + @Override + public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) + { + Assert.assertEquals(id, request); + verifier.addAndGet(2); + } + + @Override + public void onEnd(int request) + { + Assert.assertEquals(id, request); + verifier.addAndGet(3); + } + }); + + for (ByteBuffer buffer : result1.getByteBuffers()) + { + parser.parse(buffer); + Assert.assertFalse(buffer.hasRemaining()); + } + for (ByteBuffer buffer : result2.getByteBuffers()) + { + parser.parse(buffer); + Assert.assertFalse(buffer.hasRemaining()); + } + + Assert.assertEquals(3, verifier.get()); + } + + @Test + public void testParseSmallResponseContent() throws Exception + { + final int id = 13; + Fields fields = new Fields(); + + ByteBuffer content = ByteBuffer.wrap(new byte[1024]); + final int contentLength = content.remaining(); + + final int code = 200; + final String contentTypeName = "Content-Length"; + final String contentTypeValue = String.valueOf(contentLength); + fields.put(contentTypeName, contentTypeValue); + + ByteBufferPool byteBufferPool = new MappedByteBufferPool(); + ServerGenerator generator = new ServerGenerator(byteBufferPool); + Generator.Result result1 = generator.generateResponseHeaders(id, code, "OK", fields, null); + Generator.Result result2 = generator.generateResponseContent(id, content, true, null); + + final AtomicInteger verifier = new AtomicInteger(); + ClientParser parser = new ClientParser(new Parser.Listener.Adapter() + { + @Override + public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) + { + Assert.assertEquals(id, request); + Assert.assertEquals(contentLength, buffer.remaining()); + verifier.addAndGet(2); + } + + @Override + public void onEnd(int request) + { + Assert.assertEquals(id, request); + verifier.addAndGet(3); + } + }); + + for (ByteBuffer buffer : result1.getByteBuffers()) + { + parser.parse(buffer); + Assert.assertFalse(buffer.hasRemaining()); + } + for (ByteBuffer buffer : result2.getByteBuffers()) + { + parser.parse(buffer); + Assert.assertFalse(buffer.hasRemaining()); + } + + Assert.assertEquals(5, verifier.get()); + } + + @Test + public void testParseLargeResponseContent() throws Exception + { + final int id = 13; + Fields fields = new Fields(); + + ByteBuffer content = ByteBuffer.wrap(new byte[128 * 1024]); + final int contentLength = content.remaining(); + + final int code = 200; + final String contentTypeName = "Content-Length"; + final String contentTypeValue = String.valueOf(contentLength); + fields.put(contentTypeName, contentTypeValue); + + ByteBufferPool byteBufferPool = new MappedByteBufferPool(); + ServerGenerator generator = new ServerGenerator(byteBufferPool); + Generator.Result result1 = generator.generateResponseHeaders(id, code, "OK", fields, null); + Generator.Result result2 = generator.generateResponseContent(id, content, true, null); + + final AtomicInteger length = new AtomicInteger(); + final AtomicBoolean verifier = new AtomicBoolean(); + ClientParser parser = new ClientParser(new Parser.Listener.Adapter() + { + @Override + public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) + { + Assert.assertEquals(id, request); + length.addAndGet(buffer.remaining()); + } + + @Override + public void onEnd(int request) + { + Assert.assertEquals(id, request); + Assert.assertEquals(contentLength, length.get()); + verifier.set(true); + } + }); + + for (ByteBuffer buffer : result1.getByteBuffers()) + { + parser.parse(buffer); + Assert.assertFalse(buffer.hasRemaining()); + } + for (ByteBuffer buffer : result2.getByteBuffers()) + { + parser.parse(buffer); + Assert.assertFalse(buffer.hasRemaining()); + } + + Assert.assertTrue(verifier.get()); + } } From 9f3a7351fdcac40740155e684c0c5ab12face821 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Wed, 4 Sep 2013 16:23:38 +0200 Subject: [PATCH 07/43] Reorganized project structure to support multi modules. --- fcgi-core/pom.xml | 40 +++++++++++++ .../java/org/eclipse/jetty/fcgi/FCGI.java | 0 .../jetty/fcgi/generator/ClientGenerator.java | 0 .../jetty/fcgi/generator/Generator.java | 0 .../jetty/fcgi/generator/ServerGenerator.java | 0 .../parser/BeginRequestContentParser.java | 0 .../jetty/fcgi/parser/ClientParser.java | 0 .../jetty/fcgi/parser/ContentParser.java | 0 .../fcgi/parser/EndRequestContentParser.java | 0 .../jetty/fcgi/parser/HeaderParser.java | 0 .../fcgi/parser/ParamsContentParser.java | 0 .../org/eclipse/jetty/fcgi/parser/Parser.java | 0 .../fcgi/parser/ResponseContentParser.java | 0 .../jetty/fcgi/parser/ServerParser.java | 0 .../fcgi/parser/StreamContentParser.java | 0 .../fcgi/generator/ClientGeneratorTest.java | 0 .../jetty/fcgi/parser/ClientParserTest.java | 0 pom.xml | 56 +++++++------------ 18 files changed, 59 insertions(+), 37 deletions(-) create mode 100644 fcgi-core/pom.xml rename {src => fcgi-core/src}/main/java/org/eclipse/jetty/fcgi/FCGI.java (100%) rename {src => fcgi-core/src}/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java (100%) rename {src => fcgi-core/src}/main/java/org/eclipse/jetty/fcgi/generator/Generator.java (100%) rename {src => fcgi-core/src}/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java (100%) rename {src => fcgi-core/src}/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java (100%) rename {src => fcgi-core/src}/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java (100%) rename {src => fcgi-core/src}/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java (100%) rename {src => fcgi-core/src}/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java (100%) rename {src => fcgi-core/src}/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java (100%) rename {src => fcgi-core/src}/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java (100%) rename {src => fcgi-core/src}/main/java/org/eclipse/jetty/fcgi/parser/Parser.java (100%) rename {src => fcgi-core/src}/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java (100%) rename {src => fcgi-core/src}/main/java/org/eclipse/jetty/fcgi/parser/ServerParser.java (100%) rename {src => fcgi-core/src}/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java (100%) rename {src => fcgi-core/src}/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java (100%) rename {src => fcgi-core/src}/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java (100%) diff --git a/fcgi-core/pom.xml b/fcgi-core/pom.xml new file mode 100644 index 00000000000..cd17ca6e88b --- /dev/null +++ b/fcgi-core/pom.xml @@ -0,0 +1,40 @@ + + + org.eclipse.jetty.fcgi + fcgi-parent + 9.1.0-SNAPSHOT + + + 4.0.0 + fcgi-core + Jetty :: FastCGI :: Core + + + ${project.groupId}.core + + + + + org.eclipse.jetty + jetty-util + ${project.version} + + + org.eclipse.jetty + jetty-io + ${project.version} + + + org.eclipse.jetty + jetty-http + ${project.version} + + + + junit + junit + + + + diff --git a/src/main/java/org/eclipse/jetty/fcgi/FCGI.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/FCGI.java similarity index 100% rename from src/main/java/org/eclipse/jetty/fcgi/FCGI.java rename to fcgi-core/src/main/java/org/eclipse/jetty/fcgi/FCGI.java diff --git a/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java similarity index 100% rename from src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java rename to fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java diff --git a/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java similarity index 100% rename from src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java rename to fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java diff --git a/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java similarity index 100% rename from src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java rename to fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java similarity index 100% rename from src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java rename to fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java similarity index 100% rename from src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java rename to fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java similarity index 100% rename from src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java rename to fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java similarity index 100% rename from src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java rename to fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java similarity index 100% rename from src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java rename to fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java similarity index 100% rename from src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java rename to fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java similarity index 100% rename from src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java rename to fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java similarity index 100% rename from src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java rename to fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/ServerParser.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ServerParser.java similarity index 100% rename from src/main/java/org/eclipse/jetty/fcgi/parser/ServerParser.java rename to fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ServerParser.java diff --git a/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java similarity index 100% rename from src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java rename to fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java diff --git a/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java b/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java similarity index 100% rename from src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java rename to fcgi-core/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java diff --git a/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java b/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java similarity index 100% rename from src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java rename to fcgi-core/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java diff --git a/pom.xml b/pom.xml index 9d6f12fdee1..2ee5ba465ca 100644 --- a/pom.xml +++ b/pom.xml @@ -7,12 +7,14 @@ 4.0.0 - jetty-fcgi + org.eclipse.jetty.fcgi + fcgi-parent + pom Jetty :: FastCGI - - ${project.groupId}.fcgi - + + fcgi-core + @@ -46,13 +48,14 @@ - javax.net.*,* + ${bundle-symbolic-name}.*;version="9.1" + org.eclipse.jetty.*;version="[9.0,10.0)",* + <_nouses>true - org.apache.maven.plugins maven-jar-plugin @@ -62,39 +65,18 @@ - - 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 - - + + + + junit + junit + 4.11 + test + + + From 07acb25ce0903649b1dda6c4a4abd5da3f95b099 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 6 Sep 2013 23:14:53 +0200 Subject: [PATCH 08/43] Introduced fcgi-server and fcgi-http-client-transport modules. --- fcgi-core/pom.xml | 4 +- .../jetty/fcgi/generator/ClientGenerator.java | 11 +- .../jetty/fcgi/generator/ServerGenerator.java | 11 +- .../jetty/fcgi/parser/ClientParser.java | 13 + .../fcgi/parser/ParamsContentParser.java | 3 +- .../org/eclipse/jetty/fcgi/parser/Parser.java | 5 +- .../fcgi/parser/ResponseContentParser.java | 65 ++- .../fcgi/generator/ClientGeneratorTest.java | 27 +- .../jetty/fcgi/parser/ClientParserTest.java | 27 +- fcgi-http-client-transport/pom.xml | 43 ++ .../fcgi/client/http/HttpChannelOverFCGI.java | 85 ++++ .../http/HttpClientTransportOverFCGI.java | 57 +++ .../client/http/HttpConnectionOverFCGI.java | 318 +++++++++++++ .../client/http/HttpReceiverOverFCGI.java | 44 ++ .../fcgi/client/http/HttpSenderOverFCGI.java | 42 ++ .../MultiplexHttpDestinationOverFCGI.java | 37 ++ .../http/AbstractHttpClientServerTest.java | 70 +++ .../fcgi/client/http/EmptyServerHandler.java | 36 ++ .../fcgi/client/http/HttpClientTest.java | 422 ++++++++++++++++++ fcgi-server/pom.xml | 23 + .../server/FCGIServerConnectionFactory.java | 38 ++ .../fcgi/server/ServerFCGIConnection.java | 38 ++ pom.xml | 6 +- 23 files changed, 1373 insertions(+), 52 deletions(-) create mode 100644 fcgi-http-client-transport/pom.xml create mode 100644 fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java create mode 100644 fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java create mode 100644 fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java create mode 100644 fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpReceiverOverFCGI.java create mode 100644 fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java create mode 100644 fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/MultiplexHttpDestinationOverFCGI.java create mode 100644 fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/AbstractHttpClientServerTest.java create mode 100644 fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/EmptyServerHandler.java create mode 100644 fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/HttpClientTest.java create mode 100644 fcgi-server/pom.xml create mode 100644 fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/FCGIServerConnectionFactory.java create mode 100644 fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java diff --git a/fcgi-core/pom.xml b/fcgi-core/pom.xml index cd17ca6e88b..31a80e9e730 100644 --- a/fcgi-core/pom.xml +++ b/fcgi-core/pom.xml @@ -1,4 +1,6 @@ - + org.eclipse.jetty.fcgi diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java index e96aa5d30fe..715089d968f 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java @@ -24,10 +24,11 @@ import java.util.ArrayList; import java.util.List; import org.eclipse.jetty.fcgi.FCGI; +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.Fields; public class ClientGenerator extends Generator { @@ -41,22 +42,22 @@ public class ClientGenerator extends Generator super(byteBufferPool); } - public Result generateRequestHeaders(int request, Fields fields, Callback callback) + public Result generateRequestHeaders(int request, HttpFields fields, Callback callback) { request &= 0xFF_FF; Charset utf8 = Charset.forName("UTF-8"); List bytes = new ArrayList<>(fields.size() * 2); int fieldsLength = 0; - for (Fields.Field field : fields) + for (HttpField field : fields) { - String name = field.name(); + String name = field.getName(); byte[] nameBytes = name.getBytes(utf8); if (nameBytes.length > MAX_PARAM_LENGTH) throw new IllegalArgumentException("Field name " + name + " exceeds max length " + MAX_PARAM_LENGTH); bytes.add(nameBytes); - String value = field.value(); + String value = field.getValue(); byte[] valueBytes = value.getBytes(utf8); if (valueBytes.length > MAX_PARAM_LENGTH) throw new IllegalArgumentException("Field value " + value + " exceeds max length " + MAX_PARAM_LENGTH); diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java index 1ff841b7b75..2d4fdf12218 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java @@ -24,11 +24,12 @@ import java.util.ArrayList; import java.util.List; import org.eclipse.jetty.fcgi.FCGI; +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.Fields; public class ServerGenerator extends Generator { @@ -41,7 +42,7 @@ public class ServerGenerator extends Generator super(byteBufferPool); } - public Result generateResponseHeaders(int request, int code, String reason, Fields fields, Callback callback) + public Result generateResponseHeaders(int request, int code, String reason, HttpFields fields, Callback callback) { request &= 0xFF_FF; @@ -59,13 +60,13 @@ public class ServerGenerator extends Generator length += responseBytes.length + EOL.length; // Other headers - for (Fields.Field field : fields) + for (HttpField field : fields) { - String name = field.name(); + String name = field.getName(); byte[] nameBytes = name.getBytes(utf8); bytes.add(nameBytes); - String value = field.value(); + String value = field.getValue(); byte[] valueBytes = value.getBytes(utf8); bytes.add(valueBytes); diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java index b5a4f3ae4c3..d8b6640a362 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java @@ -38,4 +38,17 @@ public class ClientParser extends Parser { return contentParsers.get(frameType); } + + public interface Listener extends Parser.Listener + { + public void onBegin(int request, int code, String reason); + + public static class Adapter extends Parser.Listener.Adapter implements Listener + { + @Override + public void onBegin(int request, int code, String reason) + { + } + } + } } diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java index 651e13f998f..c2262a603de 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.fcgi.parser; import java.nio.ByteBuffer; import java.nio.charset.Charset; +import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -207,7 +208,7 @@ public class ParamsContentParser extends ContentParser { try { - listener.onHeader(getRequest(), name, value); + listener.onHeader(getRequest(), new HttpField(name, value)); } catch (Throwable x) { diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java index 3ca650cdf31..7cb9a0a1d15 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.fcgi.parser; import java.nio.ByteBuffer; import org.eclipse.jetty.fcgi.FCGI; +import org.eclipse.jetty.http.HttpField; public abstract class Parser { @@ -91,7 +92,7 @@ public abstract class Parser public interface Listener { - public void onHeader(int request, String name, String value); + public void onHeader(int request, HttpField field); public void onHeaders(int request); @@ -102,7 +103,7 @@ public abstract class Parser public static class Adapter implements Listener { @Override - public void onHeader(int request, String name, String value) + public void onHeader(int request, HttpField field) { } diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java index 5d03e61abe5..06ffe253a92 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java @@ -19,15 +19,18 @@ package org.eclipse.jetty.fcgi.parser; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; import org.eclipse.jetty.fcgi.FCGI; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpParser; +import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpVersion; public class ResponseContentParser extends StreamContentParser { - public ResponseContentParser(HeaderParser headerParser, Parser.Listener listener) + public ResponseContentParser(HeaderParser headerParser, ClientParser.Listener listener) { super(headerParser, FCGI.StreamType.STD_OUT, new ResponseListener(headerParser, listener)); } @@ -35,11 +38,13 @@ public class ResponseContentParser extends StreamContentParser private static class ResponseListener extends Parser.Listener.Adapter implements HttpParser.ResponseHandler { private final HeaderParser headerParser; - private final Parser.Listener listener; + private final ClientParser.Listener listener; private final FCGIHttpParser httpParser; private State state = State.HEADERS; + private boolean begun; + private List fields; - public ResponseListener(HeaderParser headerParser, Parser.Listener listener) + public ResponseListener(HeaderParser headerParser, ClientParser.Listener listener) { this.headerParser = headerParser; this.listener = listener; @@ -77,6 +82,8 @@ public class ResponseContentParser extends StreamContentParser { httpParser.reset(); state = State.HEADERS; + begun = false; + fields = null; } @Override @@ -101,18 +108,46 @@ public class ResponseContentParser extends StreamContentParser } @Override - public boolean parsedHeader(HttpField field) + public boolean parsedHeader(HttpField httpField) { try { - if ("Status".equalsIgnoreCase(field.getName())) + if ("Status".equalsIgnoreCase(httpField.getName())) { - // Need to set the response status so the - // HttpParser can handle the content properly. - int code = Integer.parseInt(field.getValue().split(" ")[0]); - httpParser.setResponseStatus(code); + if (!begun) + { + begun = true; + + // Need to set the response status so the + // HttpParser can handle the content properly. + String[] parts = httpField.getValue().split(" "); + int code = Integer.parseInt(parts[0]); + httpParser.setResponseStatus(code); + + String reason = parts.length > 1 ? parts[1] : HttpStatus.getMessage(code); + listener.onBegin(headerParser.getRequest(), code, reason); + + if (fields != null) + { + for (HttpField field : fields) + listener.onHeader(headerParser.getRequest(), field); + fields = null; + } + } + } + else + { + if (begun) + { + listener.onHeader(headerParser.getRequest(), httpField); + } + else + { + if (fields == null) + fields = new ArrayList<>(); + fields.add(httpField); + } } - listener.onHeader(headerParser.getRequest(), field.getName(), field.getValue()); } catch (Throwable x) { @@ -126,7 +161,15 @@ public class ResponseContentParser extends StreamContentParser { try { - listener.onHeaders(headerParser.getRequest()); + if (begun) + { + listener.onHeaders(headerParser.getRequest()); + } + else + { + fields = null; + // TODO: what here ? + } } catch (Throwable x) { diff --git a/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java b/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java index eedb32f96ef..7349500ecc2 100644 --- a/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java +++ b/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java @@ -24,9 +24,10 @@ import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.jetty.fcgi.FCGI; import org.eclipse.jetty.fcgi.parser.ServerParser; +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpFields; 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; @@ -35,31 +36,31 @@ public class ClientGeneratorTest @Test public void testGenerateRequestHeaders() throws Exception { - Fields fields = new Fields(); + HttpFields fields = new HttpFields(); // Short name, short value final String shortShortName = "REQUEST_METHOD"; final String shortShortValue = "GET"; - fields.put(new Fields.Field(shortShortName, shortShortValue)); + fields.put(new HttpField(shortShortName, shortShortValue)); // Short name, long value final String shortLongName = "REQUEST_URI"; // Be sure it's longer than 127 chars to test the large value final String shortLongValue = "/api/0.6/map?bbox=-64.217736,-31.456810,-64.187736,-31.432322,filler=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; - fields.put(new Fields.Field(shortLongName, shortLongValue)); + fields.put(new HttpField(shortLongName, shortLongValue)); // Long name, short value // Be sure it's longer than 127 chars to test the large name final String longShortName = "FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210"; final String longShortValue = "api.openstreetmap.org"; - fields.put(new Fields.Field(longShortName, longShortValue)); + fields.put(new HttpField(longShortName, longShortValue)); // Long name, long value char[] chars = new char[ClientGenerator.MAX_PARAM_LENGTH]; Arrays.fill(chars, 'z'); final String longLongName = new String(chars); final String longLongValue = new String(chars); - fields.put(new Fields.Field(longLongName, longLongValue)); + fields.put(new HttpField(longLongName, longLongValue)); ByteBufferPool byteBufferPool = new MappedByteBufferPool(); ClientGenerator generator = new ClientGenerator(byteBufferPool); @@ -78,26 +79,26 @@ public class ClientGeneratorTest ServerParser parser = new ServerParser(new ServerParser.Listener.Adapter() { @Override - public void onHeader(int request, String name, String value) + public void onHeader(int request, HttpField field) { Assert.assertEquals(id, request); - switch (name) + switch (field.getName()) { case shortShortName: - Assert.assertEquals(shortShortValue, value); + Assert.assertEquals(shortShortValue, field.getValue()); params.set(params.get() * primes[0]); break; case shortLongName: - Assert.assertEquals(shortLongValue, value); + Assert.assertEquals(shortLongValue, field.getValue()); params.set(params.get() * primes[1]); break; case longShortName: - Assert.assertEquals(longShortValue, value); + Assert.assertEquals(longShortValue, field.getValue()); params.set(params.get() * primes[2]); break; default: - Assert.assertEquals(longLongName, name); - Assert.assertEquals(longLongValue, value); + Assert.assertEquals(longLongName, field.getName()); + Assert.assertEquals(longLongValue, field.getValue()); params.set(params.get() * primes[3]); break; } diff --git a/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java b/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java index 0ca63b312d0..d5a43eee184 100644 --- a/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java +++ b/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java @@ -25,9 +25,10 @@ import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.jetty.fcgi.FCGI; import org.eclipse.jetty.fcgi.generator.Generator; import org.eclipse.jetty.fcgi.generator.ServerGenerator; +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpFields; 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; @@ -37,7 +38,7 @@ public class ClientParserTest public void testParseResponseHeaders() throws Exception { final int id = 13; - Fields fields = new Fields(); + HttpFields fields = new HttpFields(); final String statusName = "Status"; final int code = 200; @@ -58,20 +59,20 @@ public class ClientParserTest value *= prime; final AtomicInteger params = new AtomicInteger(1); - ClientParser parser = new ClientParser(new Parser.Listener.Adapter() + ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter() { @Override - public void onHeader(int request, String name, String value) + public void onHeader(int request, HttpField field) { Assert.assertEquals(id, request); - switch (name) + switch (field.getName()) { case statusName: - Assert.assertTrue(value.startsWith(String.valueOf(code))); + Assert.assertTrue(field.getValue().startsWith(String.valueOf(code))); params.set(params.get() * primes[0]); break; case contentTypeName: - Assert.assertEquals(contentTypeValue, value); + Assert.assertEquals(contentTypeValue, field.getValue()); params.set(params.get() * primes[1]); break; } @@ -98,7 +99,7 @@ public class ClientParserTest public void testParseNoResponseContent() throws Exception { final int id = 13; - Fields fields = new Fields(); + HttpFields fields = new HttpFields(); final int code = 200; final String contentTypeName = "Content-Length"; @@ -111,7 +112,7 @@ public class ClientParserTest Generator.Result result2 = generator.generateResponseContent(id, null, true, null); final AtomicInteger verifier = new AtomicInteger(); - ClientParser parser = new ClientParser(new Parser.Listener.Adapter() + ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter() { @Override public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) @@ -146,7 +147,7 @@ public class ClientParserTest public void testParseSmallResponseContent() throws Exception { final int id = 13; - Fields fields = new Fields(); + HttpFields fields = new HttpFields(); ByteBuffer content = ByteBuffer.wrap(new byte[1024]); final int contentLength = content.remaining(); @@ -162,7 +163,7 @@ public class ClientParserTest Generator.Result result2 = generator.generateResponseContent(id, content, true, null); final AtomicInteger verifier = new AtomicInteger(); - ClientParser parser = new ClientParser(new Parser.Listener.Adapter() + ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter() { @Override public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) @@ -198,7 +199,7 @@ public class ClientParserTest public void testParseLargeResponseContent() throws Exception { final int id = 13; - Fields fields = new Fields(); + HttpFields fields = new HttpFields(); ByteBuffer content = ByteBuffer.wrap(new byte[128 * 1024]); final int contentLength = content.remaining(); @@ -215,7 +216,7 @@ public class ClientParserTest final AtomicInteger length = new AtomicInteger(); final AtomicBoolean verifier = new AtomicBoolean(); - ClientParser parser = new ClientParser(new Parser.Listener.Adapter() + ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter() { @Override public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) diff --git a/fcgi-http-client-transport/pom.xml b/fcgi-http-client-transport/pom.xml new file mode 100644 index 00000000000..e8848e11501 --- /dev/null +++ b/fcgi-http-client-transport/pom.xml @@ -0,0 +1,43 @@ + + + + org.eclipse.jetty.fcgi + fcgi-parent + 9.1.0-SNAPSHOT + + + 4.0.0 + fcgi-http-client-transport + Jetty :: FastCGI :: HTTP Client Transport + + + + org.eclipse.jetty.fcgi + fcgi-core + ${project.version} + + + org.eclipse.jetty + jetty-client + ${project.version} + + + + org.eclipse.jetty.fcgi + fcgi-server + ${project.version} + test + + + org.eclipse.jetty.toolchain + jetty-test-helper + + + junit + junit + + + + diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java new file mode 100644 index 00000000000..7ca805d939f --- /dev/null +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java @@ -0,0 +1,85 @@ +// +// ======================================================================== +// 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.client.http; + +import org.eclipse.jetty.client.HttpChannel; +import org.eclipse.jetty.client.HttpDestination; +import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.fcgi.generator.Generator; +import org.eclipse.jetty.http.HttpField; + +public class HttpChannelOverFCGI extends HttpChannel +{ + private final HttpConnectionOverFCGI connection; + private final int id; + private final HttpSenderOverFCGI sender; + private final HttpReceiverOverFCGI receiver; + + public HttpChannelOverFCGI(HttpDestination destination, HttpConnectionOverFCGI connection, int id) + { + super(destination); + this.connection = connection; + this.id = id; + this.sender = new HttpSenderOverFCGI(this); + this.receiver = new HttpReceiverOverFCGI(this); + } + + protected int getId() + { + return id; + } + + @Override + public void send() + { + HttpExchange exchange = getHttpExchange(); + if (exchange != null) + sender.send(exchange); + } + + @Override + public void proceed(HttpExchange exchange, boolean proceed) + { + } + + @Override + public boolean abort(Throwable cause) + { + return false; + } + + protected void responseBegin() + { + HttpExchange exchange = getHttpExchange(); + if (exchange != null) + receiver.responseBegin(exchange); + } + + protected void responseHeader(HttpField field) + { + HttpExchange exchange = getHttpExchange(); + if (exchange != null) + receiver.responseHeader(exchange, field); + } + + protected void write(Generator.Result result) + { + connection.write(result); + } +} diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java new file mode 100644 index 00000000000..1a921dcef5d --- /dev/null +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java @@ -0,0 +1,57 @@ +// +// ======================================================================== +// 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.client.http; + +import org.eclipse.jetty.client.AbstractHttpClientTransport; +import org.eclipse.jetty.client.HttpDestination; +import org.eclipse.jetty.client.api.Connection; +import org.eclipse.jetty.io.EndPoint; + +// TODO: add parameter to tell whether use multiplex destinations or not +public class HttpClientTransportOverFCGI extends AbstractHttpClientTransport +{ + public HttpClientTransportOverFCGI() + { + this(1); + } + + public HttpClientTransportOverFCGI(int selectors) + { + super(selectors); + } + + @Override + public HttpDestination newHttpDestination(String scheme, String host, int port) + { + return new MultiplexHttpDestinationOverFCGI(getHttpClient(), scheme, host, port); + } + + @Override + protected org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, HttpDestination destination) + { + return new HttpConnectionOverFCGI(endPoint, destination); + } + + @Override + public Connection tunnel(Connection connection) + { + HttpConnectionOverFCGI httpConnection = (HttpConnectionOverFCGI)connection; + return tunnel(httpConnection.getEndPoint(), httpConnection.getHttpDestination(), connection); + } +} diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java new file mode 100644 index 00000000000..ebca5b0f27e --- /dev/null +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java @@ -0,0 +1,318 @@ +// +// ======================================================================== +// 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.client.http; + +import java.nio.ByteBuffer; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpConnection; +import org.eclipse.jetty.client.HttpDestination; +import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.client.api.Connection; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.api.Response; +import org.eclipse.jetty.fcgi.FCGI; +import org.eclipse.jetty.fcgi.generator.Generator; +import org.eclipse.jetty.fcgi.parser.ClientParser; +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.io.AbstractConnection; +import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.ConcurrentArrayQueue; +import org.eclipse.jetty.util.IteratingCallback; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +public class HttpConnectionOverFCGI extends AbstractConnection implements Connection +{ + private static final Logger LOG = Log.getLogger(HttpConnectionOverFCGI.class); + + private final LinkedList requests = new LinkedList<>(); + private final Map channels = new ConcurrentHashMap<>(); + private final Queue queue = new ConcurrentArrayQueue<>(); + private final Callback flushCallback = new FlushCallback(); + private final HttpDestination destination; + private final Delegate delegate; + private final ClientParser parser; + private boolean flushing; + + public HttpConnectionOverFCGI(EndPoint endPoint, HttpDestination destination) + { + super(endPoint, destination.getHttpClient().getExecutor(), destination.getHttpClient().isDispatchIO()); + this.destination = destination; + this.delegate = new Delegate(destination); + this.parser = new ClientParser(new ResponseListener()); + requests.addLast(0); + } + + public HttpDestination getHttpDestination() + { + return destination; + } + + @Override + public void send(Request request, Response.CompleteListener listener) + { + delegate.send(request, listener); + } + + protected void send(HttpExchange exchange) + { + delegate.send(exchange); + } + + @Override + public void onOpen() + { + super.onOpen(); + fillInterested(); + } + + @Override + public void onFillable() + { + EndPoint endPoint = getEndPoint(); + HttpClient client = destination.getHttpClient(); + ByteBufferPool bufferPool = client.getByteBufferPool(); + ByteBuffer buffer = bufferPool.acquire(client.getResponseBufferSize(), true); + try + { + while (true) + { + int read = endPoint.fill(buffer); + if (LOG.isDebugEnabled()) // Avoid boxing of variable 'read' + LOG.debug("Read {} bytes from {}", read, endPoint); + if (read > 0) + { + parse(buffer); + } + else if (read == 0) + { + fillInterested(); + break; + } + else + { + shutdown(); + break; + } + } + } + catch (Exception x) + { + LOG.debug(x); + // TODO: fail and close ? + } + finally + { + bufferPool.release(buffer); + } + } + + private void parse(ByteBuffer buffer) + { + while (buffer.hasRemaining()) + parser.parse(buffer); + } + + protected void write(Generator.Result result) + { + synchronized (queue) + { + queue.offer(result); + if (flushing) + return; + flushing = true; + } + getEndPoint().write(flushCallback); + } + + private void shutdown() + { + // TODO: we must signal to the HttpParser that we are at EOF + } + + @Override + public void close() + { + getEndPoint().shutdownOutput(); + LOG.debug("{} oshut", this); + getEndPoint().close(); + LOG.debug("{} closed", this); + } + + private int acquireRequest() + { + synchronized (requests) + { + int last = requests.getLast(); + int request = last + 1; + requests.addLast(request); + return request; + } + } + + private void releaseRequest(int request) + { + synchronized (requests) + { + requests.removeFirstOccurrence(request); + } + } + + @Override + public String toString() + { + return String.format("%s@%x(l:%s <-> r:%s)", + HttpConnection.class.getSimpleName(), + hashCode(), + getEndPoint().getLocalAddress(), + getEndPoint().getRemoteAddress()); + } + + private class Delegate extends HttpConnection + { + private Delegate(HttpDestination destination) + { + super(destination); + } + + @Override + protected void send(HttpExchange exchange) + { + Request request = exchange.getRequest(); + normalizeRequest(request); + + // FCGI may be multiplexed, so create one channel for each request. + int id = acquireRequest(); + HttpChannelOverFCGI channel = new HttpChannelOverFCGI(getHttpDestination(), HttpConnectionOverFCGI.this, id); + channels.put(id, channel); + channel.associate(exchange); + channel.send(); + } + + @Override + public void close() + { + HttpConnectionOverFCGI.this.close(); + } + } + + private class ResponseListener implements ClientParser.Listener + { + @Override + public void onBegin(int request, int code, String reason) + { + HttpChannelOverFCGI channel = channels.get(request); + if (channel != null) + channel.responseBegin(); + else + noChannel(request); + } + + @Override + public void onHeader(int request, HttpField field) + { + HttpChannelOverFCGI channel = channels.get(request); + if (channel != null) + channel.responseHeader(field); + else + noChannel(request); + } + + @Override + public void onHeaders(int request) + { + } + + @Override + public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) + { + } + + @Override + public void onEnd(int request) + { + // TODO: + + channels.remove(request); + releaseRequest(request); + } + + private void noChannel(int request) + { + // TODO: what here ? + } + } + + private class FlushCallback extends IteratingCallback + { + private Generator.Result active; + + @Override + protected boolean process() throws Exception + { + // We are flushing, we completed a write, notify + if (active != null) + active.succeeded(); + + // Look if other writes are needed. + Generator.Result result; + synchronized (queue) + { + if (queue.isEmpty()) + { + // No more writes to do, switch to non-flushing + flushing = false; + return true; + } + // TODO: here is where we want to gather more results to perform gathered writes + result = queue.poll(); + } + + active = result; + List buffers = result.getByteBuffers(); + getEndPoint().write(this, buffers.toArray(new ByteBuffer[buffers.size()])); + return false; + } + + @Override + protected void completed() + { + active = null; + } + + @Override + public void failed(Throwable x) + { + super.failed(x); + if (active != null) + { + active.failed(x); + active = null; + } + } + } +} diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpReceiverOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpReceiverOverFCGI.java new file mode 100644 index 00000000000..a5e3f5a1b23 --- /dev/null +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpReceiverOverFCGI.java @@ -0,0 +1,44 @@ +// +// ======================================================================== +// 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.client.http; + +import org.eclipse.jetty.client.HttpChannel; +import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.client.HttpReceiver; +import org.eclipse.jetty.http.HttpField; + +public class HttpReceiverOverFCGI extends HttpReceiver +{ + public HttpReceiverOverFCGI(HttpChannel channel) + { + super(channel); + } + + @Override + protected boolean responseBegin(HttpExchange exchange) + { + return super.responseBegin(exchange); + } + + @Override + protected boolean responseHeader(HttpExchange exchange, HttpField field) + { + return super.responseHeader(exchange, field); + } +} diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java new file mode 100644 index 00000000000..d37d6c13a45 --- /dev/null +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java @@ -0,0 +1,42 @@ +package org.eclipse.jetty.fcgi.client.http; + +import org.eclipse.jetty.client.HttpChannel; +import org.eclipse.jetty.client.HttpContent; +import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.client.HttpSender; +import org.eclipse.jetty.fcgi.generator.ClientGenerator; +import org.eclipse.jetty.fcgi.generator.Generator; +import org.eclipse.jetty.util.Callback; + +public class HttpSenderOverFCGI extends HttpSender +{ + private final ClientGenerator generator; + + public HttpSenderOverFCGI(HttpChannel channel) + { + super(channel); + this.generator = new ClientGenerator(channel.getHttpDestination().getHttpClient().getByteBufferPool()); + } + + @Override + protected HttpChannelOverFCGI getHttpChannel() + { + return (HttpChannelOverFCGI)super.getHttpChannel(); + } + + @Override + protected void sendHeaders(HttpExchange exchange, HttpContent content, Callback callback) + { + int request = getHttpChannel().getId(); + Generator.Result result = generator.generateRequestHeaders(request, exchange.getRequest().getHeaders(), callback); + getHttpChannel().write(result); + } + + @Override + protected void sendContent(HttpExchange exchange, HttpContent content, Callback callback) + { + int request = getHttpChannel().getId(); + Generator.Result result = generator.generateRequestContent(request, content.getByteBuffer(), content.isLast(), callback); + getHttpChannel().write(result); + } +} diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/MultiplexHttpDestinationOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/MultiplexHttpDestinationOverFCGI.java new file mode 100644 index 00000000000..171b06c5e43 --- /dev/null +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/MultiplexHttpDestinationOverFCGI.java @@ -0,0 +1,37 @@ +// +// ======================================================================== +// 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.client.http; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.client.MultiplexHttpDestination; + +public class MultiplexHttpDestinationOverFCGI extends MultiplexHttpDestination +{ + public MultiplexHttpDestinationOverFCGI(HttpClient client, String scheme, String host, int port) + { + super(client, scheme, host, port); + } + + @Override + protected void send(HttpConnectionOverFCGI connection, HttpExchange exchange) + { + connection.send(exchange); + } +} diff --git a/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/AbstractHttpClientServerTest.java b/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/AbstractHttpClientServerTest.java new file mode 100644 index 00000000000..63cfb66deba --- /dev/null +++ b/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/AbstractHttpClientServerTest.java @@ -0,0 +1,70 @@ +// +// ======================================================================== +// 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.client.http; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.fcgi.server.FCGIServerConnectionFactory; +import org.eclipse.jetty.http.HttpScheme; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.NetworkConnector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.toolchain.test.TestTracker; +import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.junit.After; +import org.junit.Rule; + +public abstract class AbstractHttpClientServerTest +{ + @Rule + public final TestTracker tracker = new TestTracker(); + + protected Server server; + protected NetworkConnector connector; + protected HttpClient client; + protected String scheme = HttpScheme.HTTP.asString(); + + public void start(Handler handler) throws Exception + { + server = new Server(); + + FCGIServerConnectionFactory fcgiConnectionFactory = new FCGIServerConnectionFactory(); + connector = new ServerConnector(server, fcgiConnectionFactory); + + server.addConnector(connector); + server.setHandler(handler); + server.start(); + + QueuedThreadPool executor = new QueuedThreadPool(); + executor.setName(executor.getName() + "-client"); + + client = new HttpClient(new HttpClientTransportOverFCGI(), null); + client.setExecutor(executor); + client.start(); + } + + @After + public void dispose() throws Exception + { + if (client != null) + client.stop(); + if (server != null) + server.stop(); + } +} diff --git a/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/EmptyServerHandler.java b/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/EmptyServerHandler.java new file mode 100644 index 00000000000..fc03a161307 --- /dev/null +++ b/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/EmptyServerHandler.java @@ -0,0 +1,36 @@ +// +// ======================================================================== +// 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.client.http; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; + +public class EmptyServerHandler extends AbstractHandler +{ + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + } +} diff --git a/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/HttpClientTest.java b/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/HttpClientTest.java new file mode 100644 index 00000000000..b7562e3de8b --- /dev/null +++ b/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/HttpClientTest.java @@ -0,0 +1,422 @@ +// +// ======================================================================== +// 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.client.http; + +import java.io.IOException; +import java.net.URI; +import java.net.URLEncoder; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.zip.GZIPOutputStream; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.api.Response; +import org.eclipse.jetty.client.util.BytesContentProvider; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.toolchain.test.annotation.Slow; +import org.junit.Assert; +import org.junit.Test; + +public class HttpClientTest extends AbstractHttpClientServerTest +{ + @Test + public void test_GET_ResponseWithoutContent() throws Exception + { + start(new EmptyServerHandler()); + + Response response = client.GET(scheme + "://localhost:" + connector.getLocalPort()); + + Assert.assertNotNull(response); + Assert.assertEquals(200, response.getStatus()); + } + + @Test + public void test_GET_ResponseWithContent() throws Exception + { + final byte[] data = new byte[]{0, 1, 2, 3, 4, 5, 6, 7}; + start(new AbstractHandler() + { + @Override + public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + response.getOutputStream().write(data); + baseRequest.setHandled(true); + } + }); + + ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort()); + + Assert.assertNotNull(response); + Assert.assertEquals(200, response.getStatus()); + byte[] content = response.getContent(); + Assert.assertArrayEquals(data, content); + } + + @Test + public void test_GET_WithParameters_ResponseWithContent() throws Exception + { + final String paramName1 = "a"; + final String paramName2 = "b"; + start(new AbstractHandler() + { + @Override + public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + response.setCharacterEncoding("UTF-8"); + ServletOutputStream output = response.getOutputStream(); + String paramValue1 = request.getParameter(paramName1); + output.write(paramValue1.getBytes("UTF-8")); + String paramValue2 = request.getParameter(paramName2); + Assert.assertEquals("", paramValue2); + output.write("empty".getBytes("UTF-8")); + baseRequest.setHandled(true); + } + }); + + String value1 = "\u20AC"; + String paramValue1 = URLEncoder.encode(value1, "UTF-8"); + String query = paramName1 + "=" + paramValue1 + "&" + paramName2; + ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort() + "/?" + query); + + Assert.assertNotNull(response); + Assert.assertEquals(200, response.getStatus()); + String content = new String(response.getContent(), "UTF-8"); + Assert.assertEquals(value1 + "empty", content); + } + + @Test + public void test_GET_WithParametersMultiValued_ResponseWithContent() throws Exception + { + final String paramName1 = "a"; + final String paramName2 = "b"; + start(new AbstractHandler() + { + @Override + public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + response.setCharacterEncoding("UTF-8"); + ServletOutputStream output = response.getOutputStream(); + String[] paramValues1 = request.getParameterValues(paramName1); + for (String paramValue : paramValues1) + output.write(paramValue.getBytes("UTF-8")); + String paramValue2 = request.getParameter(paramName2); + output.write(paramValue2.getBytes("UTF-8")); + baseRequest.setHandled(true); + } + }); + + String value11 = "\u20AC"; + String value12 = "\u20AA"; + String value2 = "&"; + String paramValue11 = URLEncoder.encode(value11, "UTF-8"); + String paramValue12 = URLEncoder.encode(value12, "UTF-8"); + String paramValue2 = URLEncoder.encode(value2, "UTF-8"); + String query = paramName1 + "=" + paramValue11 + "&" + paramName1 + "=" + paramValue12 + "&" + paramName2 + "=" + paramValue2; + ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort() + "/?" + query); + + Assert.assertNotNull(response); + Assert.assertEquals(200, response.getStatus()); + String content = new String(response.getContent(), "UTF-8"); + Assert.assertEquals(value11 + value12 + value2, content); + } + + @Test + public void test_POST_WithParameters() throws Exception + { + final String paramName = "a"; + final String paramValue = "\u20AC"; + start(new AbstractHandler() + { + @Override + public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + String value = request.getParameter(paramName); + if (paramValue.equals(value)) + { + response.setCharacterEncoding("UTF-8"); + response.setContentType("text/plain"); + response.getOutputStream().print(value); + } + } + }); + + ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort()) + .param(paramName, paramValue) + .timeout(5, TimeUnit.SECONDS) + .send(); + + Assert.assertNotNull(response); + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(paramValue, new String(response.getContent(), "UTF-8")); + } + + @Test + public void test_PUT_WithParameters() throws Exception + { + final String paramName = "a"; + final String paramValue = "\u20AC"; + start(new AbstractHandler() + { + @Override + public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + String value = request.getParameter(paramName); + if (paramValue.equals(value)) + { + response.setCharacterEncoding("UTF-8"); + response.setContentType("text/plain"); + response.getOutputStream().print(value); + } + } + }); + + URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort() + "/path?" + paramName + "=" + paramValue); + ContentResponse response = client.newRequest(uri) + .method(HttpMethod.PUT) + .timeout(5, TimeUnit.SECONDS) + .send(); + + Assert.assertNotNull(response); + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(paramValue, new String(response.getContent(), "UTF-8")); + } + + @Test + public void test_POST_WithParameters_WithContent() throws Exception + { + final byte[] content = {0, 1, 2, 3}; + final String paramName = "a"; + final String paramValue = "\u20AC"; + start(new AbstractHandler() + { + @Override + public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + String value = request.getParameter(paramName); + if (paramValue.equals(value)) + { + response.setCharacterEncoding("UTF-8"); + response.setContentType("application/octet-stream"); + response.getOutputStream().write(content); + } + } + }); + + ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort() + "/?b=1") + .param(paramName, paramValue) + .content(new BytesContentProvider(content)) + .timeout(555, TimeUnit.SECONDS) + .send(); + + Assert.assertNotNull(response); + Assert.assertEquals(200, response.getStatus()); + Assert.assertArrayEquals(content, response.getContent()); + } + + @Test + public void test_POST_WithContent_NotifiesRequestContentListener() throws Exception + { + final byte[] content = {0, 1, 2, 3}; + start(new EmptyServerHandler()); + + ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort()) + .onRequestContent(new Request.ContentListener() + { + @Override + public void onContent(Request request, ByteBuffer buffer) + { + byte[] bytes = new byte[buffer.remaining()]; + buffer.get(bytes); + if (!Arrays.equals(content, bytes)) + request.abort(new Exception()); + } + }) + .content(new BytesContentProvider(content)) + .timeout(5, TimeUnit.SECONDS) + .send(); + + Assert.assertNotNull(response); + Assert.assertEquals(200, response.getStatus()); + } + + @Test + public void test_POST_WithContent_TracksProgress() throws Exception + { + start(new EmptyServerHandler()); + + final AtomicInteger progress = new AtomicInteger(); + ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort()) + .onRequestContent(new Request.ContentListener() + { + @Override + public void onContent(Request request, ByteBuffer buffer) + { + byte[] bytes = new byte[buffer.remaining()]; + Assert.assertEquals(1, bytes.length); + buffer.get(bytes); + Assert.assertEquals(bytes[0], progress.getAndIncrement()); + } + }) + .content(new BytesContentProvider(new byte[]{0}, new byte[]{1}, new byte[]{2}, new byte[]{3}, new byte[]{4})) + .timeout(5, TimeUnit.SECONDS) + .send(); + + Assert.assertNotNull(response); + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(5, progress.get()); + } + + @Test + public void test_GZIP_ContentEncoding() throws Exception + { + final byte[] data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + start(new AbstractHandler() + { + @Override + public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + response.setHeader("Content-Encoding", "gzip"); + GZIPOutputStream gzipOutput = new GZIPOutputStream(response.getOutputStream()); + gzipOutput.write(data); + gzipOutput.finish(); + } + }); + + ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + .scheme(scheme) + .timeout(5, TimeUnit.SECONDS) + .send(); + + Assert.assertEquals(200, response.getStatus()); + Assert.assertArrayEquals(data, response.getContent()); + } + + @Slow + @Test + public void test_Request_IdleTimeout() throws Exception + { + final long idleTimeout = 1000; + start(new AbstractHandler() + { + @Override + public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + try + { + baseRequest.setHandled(true); + TimeUnit.MILLISECONDS.sleep(2 * idleTimeout); + } + catch (InterruptedException x) + { + throw new ServletException(x); + } + } + }); + + final String host = "localhost"; + final int port = connector.getLocalPort(); + try + { + client.newRequest(host, port) + .scheme(scheme) + .idleTimeout(idleTimeout, TimeUnit.MILLISECONDS) + .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS) + .send(); + Assert.fail(); + } + catch (ExecutionException expected) + { + Assert.assertTrue(expected.getCause() instanceof TimeoutException); + } + + // Make another request without specifying the idle timeout, should not fail + ContentResponse response = client.newRequest(host, port) + .scheme(scheme) + .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS) + .send(); + + Assert.assertNotNull(response); + Assert.assertEquals(200, response.getStatus()); + } + + @Test + public void testSendToIPv6Address() throws Exception + { + start(new EmptyServerHandler()); + + ContentResponse response = client.newRequest("[::1]", connector.getLocalPort()) + .scheme(scheme) + .timeout(5, TimeUnit.SECONDS) + .send(); + + Assert.assertNotNull(response); + Assert.assertEquals(200, response.getStatus()); + } + + @Test + public void test_HEAD_With_ResponseContentLength() throws Exception + { + final int length = 1024; + start(new AbstractHandler() + { + @Override + public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + response.getOutputStream().write(new byte[length]); + } + }); + + // HEAD requests receive a Content-Length header, but do not + // receive the content so they must handle this case properly + ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + .scheme(scheme) + .method(HttpMethod.HEAD) + .timeout(5, TimeUnit.SECONDS) + .send(); + + Assert.assertNotNull(response); + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(0, response.getContent().length); + + // Perform a normal GET request to be sure the content is now read + response = client.newRequest("localhost", connector.getLocalPort()) + .scheme(scheme) + .timeout(5, TimeUnit.SECONDS) + .send(); + + Assert.assertNotNull(response); + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(length, response.getContent().length); + } +} diff --git a/fcgi-server/pom.xml b/fcgi-server/pom.xml new file mode 100644 index 00000000000..e32a81f81e3 --- /dev/null +++ b/fcgi-server/pom.xml @@ -0,0 +1,23 @@ + + + + org.eclipse.jetty.fcgi + fcgi-parent + 9.1.0-SNAPSHOT + + + 4.0.0 + fcgi-server + Jetty :: FastCGI :: Server + + + + org.eclipse.jetty + jetty-server + ${project.version} + + + + diff --git a/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/FCGIServerConnectionFactory.java b/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/FCGIServerConnectionFactory.java new file mode 100644 index 00000000000..a78b37dacc8 --- /dev/null +++ b/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/FCGIServerConnectionFactory.java @@ -0,0 +1,38 @@ +// +// ======================================================================== +// 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.server; + +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.server.AbstractConnectionFactory; +import org.eclipse.jetty.server.Connector; + +public class FCGIServerConnectionFactory extends AbstractConnectionFactory +{ + public FCGIServerConnectionFactory() + { + super("fcgi/1.0"); + } + + @Override + public Connection newConnection(Connector connector, EndPoint endPoint) + { + return new ServerFCGIConnection(endPoint, connector.getExecutor()); + } +} diff --git a/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java b/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java new file mode 100644 index 00000000000..c81642055eb --- /dev/null +++ b/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java @@ -0,0 +1,38 @@ +// +// ======================================================================== +// 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.server; + +import java.util.concurrent.Executor; + +import org.eclipse.jetty.io.AbstractConnection; +import org.eclipse.jetty.io.EndPoint; + +public class ServerFCGIConnection extends AbstractConnection +{ + public ServerFCGIConnection(EndPoint endp, Executor executor) + { + super(endp, executor); + } + + @Override + public void onFillable() + { + // TODO: need to create a server-side HttpChannel and feed it with parser events + } +} diff --git a/pom.xml b/pom.xml index 2ee5ba465ca..7a21536967c 100644 --- a/pom.xml +++ b/pom.xml @@ -1,4 +1,6 @@ - + org.eclipse.jetty @@ -14,6 +16,8 @@ fcgi-core + fcgi-http-client-transport + fcgi-server From 5427a6d2dab5bc146085c177f9c3f3504de406fa Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Sun, 8 Sep 2013 00:12:42 +0200 Subject: [PATCH 09/43] Made first simple GET request working. --- .../eclipse/jetty/fcgi/generator/Flusher.java | 109 ++++++++++++++ .../fcgi/client/http/HttpChannelOverFCGI.java | 46 ++++-- .../client/http/HttpConnectionOverFCGI.java | 98 +++---------- .../client/http/HttpReceiverOverFCGI.java | 26 ++++ .../fcgi/client/http/HttpSenderOverFCGI.java | 26 +++- .../http/AbstractHttpClientServerTest.java | 9 +- .../test/resources/jetty-logging.properties | 3 + fcgi-server/pom.xml | 5 + .../fcgi/server/HttpChannelOverFCGI.java | 137 +++++++++++++++++ .../fcgi/server/HttpTransportOverFCGI.java | 64 ++++++++ .../fcgi/server/ServerFCGIConnection.java | 138 +++++++++++++++++- ....java => ServerFCGIConnectionFactory.java} | 10 +- 12 files changed, 567 insertions(+), 104 deletions(-) create mode 100644 fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java create mode 100644 fcgi-http-client-transport/src/test/resources/jetty-logging.properties create mode 100644 fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java create mode 100644 fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java rename fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/{FCGIServerConnectionFactory.java => ServerFCGIConnectionFactory.java} (76%) diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java new file mode 100644 index 00000000000..ccf868b3d54 --- /dev/null +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java @@ -0,0 +1,109 @@ +// +// ======================================================================== +// 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.List; +import java.util.Queue; + +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.ConcurrentArrayQueue; +import org.eclipse.jetty.util.IteratingCallback; + +public class Flusher +{ + private final Queue queue = new ConcurrentArrayQueue<>(); + private final Callback flushCallback = new FlushCallback(); + private final EndPoint endPoint; + private boolean flushing; + + public Flusher(EndPoint endPoint) + { + this.endPoint = endPoint; + } + + public void flush(Generator.Result... results) + { + synchronized (queue) + { + for (Generator.Result result : results) + queue.offer(result); + if (flushing) + return; + flushing = true; + } + endPoint.write(flushCallback); + } + + private class FlushCallback extends IteratingCallback + { + private Generator.Result active; + + @Override + protected boolean process() throws Exception + { + // We are flushing, we completed a write, notify + if (active != null) + { + active.succeeded(); + active = null; + } + + // Look if other writes are needed. + Generator.Result result; + synchronized (queue) + { + if (queue.isEmpty()) + { + // No more writes to do, switch to non-flushing + flushing = false; + return false; + } + // TODO: here is where we want to gather more results to perform gathered writes + result = queue.poll(); + } + + active = result; + List buffers = result.getByteBuffers(); + endPoint.write(this, buffers.toArray(new ByteBuffer[buffers.size()])); + return false; + } + + @Override + protected void completed() + { + // Nothing to do, we always return false from process(). + } + + @Override + public void failed(Throwable x) + { + super.failed(x); + + // TODO: set flushing=false ? + + if (active != null) + { + active.failed(x); + active = null; + } + } + } +} diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java index 7ca805d939f..83d8584e61c 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java @@ -21,28 +21,31 @@ package org.eclipse.jetty.fcgi.client.http; import org.eclipse.jetty.client.HttpChannel; import org.eclipse.jetty.client.HttpDestination; import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.fcgi.generator.Flusher; import org.eclipse.jetty.fcgi.generator.Generator; import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpVersion; public class HttpChannelOverFCGI extends HttpChannel { - private final HttpConnectionOverFCGI connection; - private final int id; + private final Flusher flusher; + private final int request; private final HttpSenderOverFCGI sender; private final HttpReceiverOverFCGI receiver; + private HttpVersion version; - public HttpChannelOverFCGI(HttpDestination destination, HttpConnectionOverFCGI connection, int id) + public HttpChannelOverFCGI(HttpDestination destination, Flusher flusher, int request) { super(destination); - this.connection = connection; - this.id = id; + this.flusher = flusher; + this.request = request; this.sender = new HttpSenderOverFCGI(this); this.receiver = new HttpReceiverOverFCGI(this); } - protected int getId() + protected int getRequest() { - return id; + return request; } @Override @@ -50,25 +53,32 @@ public class HttpChannelOverFCGI extends HttpChannel { HttpExchange exchange = getHttpExchange(); if (exchange != null) + { + version = exchange.getRequest().getVersion(); sender.send(exchange); + } } @Override public void proceed(HttpExchange exchange, boolean proceed) { + throw new UnsupportedOperationException(); } @Override public boolean abort(Throwable cause) { - return false; + throw new UnsupportedOperationException(); } - protected void responseBegin() + protected void responseBegin(int code, String reason) { HttpExchange exchange = getHttpExchange(); if (exchange != null) + { + exchange.getResponse().version(version).status(code).reason(reason); receiver.responseBegin(exchange); + } } protected void responseHeader(HttpField field) @@ -78,8 +88,22 @@ public class HttpChannelOverFCGI extends HttpChannel receiver.responseHeader(exchange, field); } - protected void write(Generator.Result result) + protected void responseHeaders() { - connection.write(result); + HttpExchange exchange = getHttpExchange(); + if (exchange != null) + receiver.responseHeaders(exchange); + } + + protected void responseSuccess() + { + HttpExchange exchange = getHttpExchange(); + if (exchange != null) + receiver.responseSuccess(exchange); + } + + protected void flush(Generator.Result result) + { + flusher.flush(result); } } diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java index ebca5b0f27e..108b2ba19a0 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java @@ -20,9 +20,7 @@ package org.eclipse.jetty.fcgi.client.http; import java.nio.ByteBuffer; import java.util.LinkedList; -import java.util.List; import java.util.Map; -import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import org.eclipse.jetty.client.HttpClient; @@ -33,15 +31,12 @@ import org.eclipse.jetty.client.api.Connection; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.fcgi.FCGI; -import org.eclipse.jetty.fcgi.generator.Generator; +import org.eclipse.jetty.fcgi.generator.Flusher; import org.eclipse.jetty.fcgi.parser.ClientParser; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.EndPoint; -import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.ConcurrentArrayQueue; -import org.eclipse.jetty.util.IteratingCallback; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -51,16 +46,15 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec private final LinkedList requests = new LinkedList<>(); private final Map channels = new ConcurrentHashMap<>(); - private final Queue queue = new ConcurrentArrayQueue<>(); - private final Callback flushCallback = new FlushCallback(); + private final Flusher flusher; private final HttpDestination destination; private final Delegate delegate; private final ClientParser parser; - private boolean flushing; public HttpConnectionOverFCGI(EndPoint endPoint, HttpDestination destination) { super(endPoint, destination.getHttpClient().getExecutor(), destination.getHttpClient().isDispatchIO()); + this.flusher = new Flusher(endPoint); this.destination = destination; this.delegate = new Delegate(destination); this.parser = new ClientParser(new ResponseListener()); @@ -137,18 +131,6 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec parser.parse(buffer); } - protected void write(Generator.Result result) - { - synchronized (queue) - { - queue.offer(result); - if (flushing) - return; - flushing = true; - } - getEndPoint().write(flushCallback); - } - private void shutdown() { // TODO: we must signal to the HttpParser that we are at EOF @@ -207,7 +189,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec // FCGI may be multiplexed, so create one channel for each request. int id = acquireRequest(); - HttpChannelOverFCGI channel = new HttpChannelOverFCGI(getHttpDestination(), HttpConnectionOverFCGI.this, id); + HttpChannelOverFCGI channel = new HttpChannelOverFCGI(getHttpDestination(), flusher, id); channels.put(id, channel); channel.associate(exchange); channel.send(); @@ -227,7 +209,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec { HttpChannelOverFCGI channel = channels.get(request); if (channel != null) - channel.responseBegin(); + channel.responseBegin(code, reason); else noChannel(request); } @@ -245,20 +227,33 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec @Override public void onHeaders(int request) { + HttpChannelOverFCGI channel = channels.get(request); + if (channel != null) + channel.responseHeaders(); + else + noChannel(request); } @Override public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) { + throw new UnsupportedOperationException(); } @Override public void onEnd(int request) { - // TODO: - - channels.remove(request); - releaseRequest(request); + HttpChannelOverFCGI channel = channels.get(request); + if (channel != null) + { + channel.responseSuccess(); + channels.remove(request); + releaseRequest(request); + } + else + { + noChannel(request); + } } private void noChannel(int request) @@ -266,53 +261,4 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec // TODO: what here ? } } - - private class FlushCallback extends IteratingCallback - { - private Generator.Result active; - - @Override - protected boolean process() throws Exception - { - // We are flushing, we completed a write, notify - if (active != null) - active.succeeded(); - - // Look if other writes are needed. - Generator.Result result; - synchronized (queue) - { - if (queue.isEmpty()) - { - // No more writes to do, switch to non-flushing - flushing = false; - return true; - } - // TODO: here is where we want to gather more results to perform gathered writes - result = queue.poll(); - } - - active = result; - List buffers = result.getByteBuffers(); - getEndPoint().write(this, buffers.toArray(new ByteBuffer[buffers.size()])); - return false; - } - - @Override - protected void completed() - { - active = null; - } - - @Override - public void failed(Throwable x) - { - super.failed(x); - if (active != null) - { - active.failed(x); - active = null; - } - } - } } diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpReceiverOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpReceiverOverFCGI.java index a5e3f5a1b23..494187e7ac5 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpReceiverOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpReceiverOverFCGI.java @@ -18,6 +18,8 @@ package org.eclipse.jetty.fcgi.client.http; +import java.nio.ByteBuffer; + import org.eclipse.jetty.client.HttpChannel; import org.eclipse.jetty.client.HttpExchange; import org.eclipse.jetty.client.HttpReceiver; @@ -41,4 +43,28 @@ public class HttpReceiverOverFCGI extends HttpReceiver { return super.responseHeader(exchange, field); } + + @Override + protected boolean responseHeaders(HttpExchange exchange) + { + return super.responseHeaders(exchange); + } + + @Override + protected boolean responseContent(HttpExchange exchange, ByteBuffer buffer) + { + return super.responseContent(exchange, buffer); + } + + @Override + protected boolean responseSuccess(HttpExchange exchange) + { + return super.responseSuccess(exchange); + } + + @Override + protected boolean responseFailure(Throwable failure) + { + return super.responseFailure(failure); + } } diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java index d37d6c13a45..1a8d853220c 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java @@ -27,16 +27,30 @@ public class HttpSenderOverFCGI extends HttpSender @Override protected void sendHeaders(HttpExchange exchange, HttpContent content, Callback callback) { - int request = getHttpChannel().getId(); - Generator.Result result = generator.generateRequestHeaders(request, exchange.getRequest().getHeaders(), callback); - getHttpChannel().write(result); + int request = getHttpChannel().getRequest(); + boolean noContent = !content.hasContent(); + Generator.Result result = generator.generateRequestHeaders(request, exchange.getRequest().getHeaders(), + noContent ? new Callback.Adapter() : callback); + getHttpChannel().flush(result); + if (noContent) + { + result = generator.generateRequestContent(request, null, true, callback); + getHttpChannel().flush(result); + } } @Override protected void sendContent(HttpExchange exchange, HttpContent content, Callback callback) { - int request = getHttpChannel().getId(); - Generator.Result result = generator.generateRequestContent(request, content.getByteBuffer(), content.isLast(), callback); - getHttpChannel().write(result); + if (content.isConsumed()) + { + callback.succeeded(); + } + else + { + int request = getHttpChannel().getRequest(); + Generator.Result result = generator.generateRequestContent(request, content.getByteBuffer(), content.isLast(), callback); + getHttpChannel().flush(result); + } } } diff --git a/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/AbstractHttpClientServerTest.java b/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/AbstractHttpClientServerTest.java index 63cfb66deba..c58ebcf7c87 100644 --- a/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/AbstractHttpClientServerTest.java +++ b/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/AbstractHttpClientServerTest.java @@ -19,10 +19,10 @@ package org.eclipse.jetty.fcgi.client.http; import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.fcgi.server.FCGIServerConnectionFactory; +import org.eclipse.jetty.fcgi.server.ServerFCGIConnectionFactory; import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.NetworkConnector; +import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.toolchain.test.TestTracker; @@ -36,7 +36,7 @@ public abstract class AbstractHttpClientServerTest public final TestTracker tracker = new TestTracker(); protected Server server; - protected NetworkConnector connector; + protected ServerConnector connector; protected HttpClient client; protected String scheme = HttpScheme.HTTP.asString(); @@ -44,8 +44,9 @@ public abstract class AbstractHttpClientServerTest { server = new Server(); - FCGIServerConnectionFactory fcgiConnectionFactory = new FCGIServerConnectionFactory(); + ServerFCGIConnectionFactory fcgiConnectionFactory = new ServerFCGIConnectionFactory(new HttpConfiguration()); connector = new ServerConnector(server, fcgiConnectionFactory); + connector.setPort(9000); server.addConnector(connector); server.setHandler(handler); diff --git a/fcgi-http-client-transport/src/test/resources/jetty-logging.properties b/fcgi-http-client-transport/src/test/resources/jetty-logging.properties new file mode 100644 index 00000000000..11c27cfd24f --- /dev/null +++ b/fcgi-http-client-transport/src/test/resources/jetty-logging.properties @@ -0,0 +1,3 @@ +org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog +org.eclipse.jetty.client.LEVEL=DEBUG +org.eclipse.jetty.fcgi.LEVEL=DEBUG diff --git a/fcgi-server/pom.xml b/fcgi-server/pom.xml index e32a81f81e3..8ea16c7a8b5 100644 --- a/fcgi-server/pom.xml +++ b/fcgi-server/pom.xml @@ -13,6 +13,11 @@ Jetty :: FastCGI :: Server + + org.eclipse.jetty.fcgi + fcgi-core + ${project.version} + org.eclipse.jetty jetty-server diff --git a/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java b/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java new file mode 100644 index 00000000000..bbb858b1b02 --- /dev/null +++ b/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java @@ -0,0 +1,137 @@ +// +// ======================================================================== +// 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.server; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.HttpChannel; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpInput; +import org.eclipse.jetty.server.HttpTransport; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +public class HttpChannelOverFCGI extends HttpChannel +{ + private static final Logger LOG = Log.getLogger(HttpChannelOverFCGI.class); + private static final String METHOD_HEADER = "REQUEST_METHOD"; + private static final String URI_HEADER = "REQUEST_URI"; + private static final String VERSION_HEADER = "SERVER_PROTOCOL"; + + private String method; + private String uri; + private String version; + private boolean started; + private List fields; + + public HttpChannelOverFCGI(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport, HttpInput input) + { + super(connector, configuration, endPoint, transport, input); + } + + public void header(HttpField field) + { + LOG.debug("FCGI header {}", field); + + if (METHOD_HEADER.equalsIgnoreCase(field.getName())) + { + method = field.getValue(); + if (uri != null && version != null) + startRequest(); + } + else if (URI_HEADER.equalsIgnoreCase(field.getName())) + { + uri = field.getValue(); + if (method != null && version != null) + startRequest(); + } + else if (VERSION_HEADER.equalsIgnoreCase(field.getName())) + { + version = field.getValue(); + if (method != null && uri != null) + startRequest(); + } + else + { + if (started) + { + resumeHeaders(); + convertHeader(field); + } + else + { + if (fields == null) + fields = new ArrayList<>(); + fields.add(field); + } + } + } + + private void startRequest() + { + started = true; + startRequest(null, method, ByteBuffer.wrap(uri.getBytes(Charset.forName("UTF-8"))), HttpVersion.fromString(version)); + resumeHeaders(); + } + + private void resumeHeaders() + { + if (fields != null) + { + for (HttpField field : fields) + convertHeader(field); + fields = null; + } + } + + private void convertHeader(HttpField field) + { + String name = field.getName(); + if (name.startsWith("HTTP_")) + { + // Converts e.g. "HTTP_ACCEPT_ENCODING" to "Accept-Encoding" + String[] parts = name.split("_"); + StringBuilder httpName = new StringBuilder(); + for (int i = 1; i < parts.length; ++i) + { + if (i > 1) + httpName.append("-"); + String part = parts[i]; + httpName.append(Character.toUpperCase(part.charAt(0))); + httpName.append(part.substring(1).toLowerCase(Locale.ENGLISH)); + } + field = new HttpField(httpName.toString(), field.getValue()); + } + LOG.debug("HTTP header {}", field); + parsedHeader(field); + } + + public void dispatch() + { + getConnector().getExecutor().execute(this); + } +} diff --git a/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java b/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java new file mode 100644 index 00000000000..26166fa8715 --- /dev/null +++ b/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java @@ -0,0 +1,64 @@ +// +// ======================================================================== +// 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.server; + +import java.nio.ByteBuffer; + +import org.eclipse.jetty.fcgi.generator.Flusher; +import org.eclipse.jetty.fcgi.generator.Generator; +import org.eclipse.jetty.fcgi.generator.ServerGenerator; +import org.eclipse.jetty.http.HttpGenerator; +import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.server.HttpTransport; +import org.eclipse.jetty.util.Callback; + +public class HttpTransportOverFCGI implements HttpTransport +{ + private final ServerGenerator generator; + private final Flusher flusher; + private final int request; + + public HttpTransportOverFCGI(ByteBufferPool byteBufferPool, Flusher flusher, int request) + { + this.generator = new ServerGenerator(byteBufferPool); + this.flusher = flusher; + this.request = request; + } + + @Override + public void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback) + { + Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(), + info.getHttpFields(), new Callback.Adapter()); + Generator.Result contentResult = generator.generateResponseContent(request, content, lastContent, callback); + flusher.flush(headersResult, contentResult); + } + + @Override + public void send(ByteBuffer content, boolean lastContent, Callback callback) + { + Generator.Result result = generator.generateResponseContent(request, content, lastContent, callback); + flusher.flush(result); + } + + @Override + public void completed() + { + } +} diff --git a/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java b/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java index c81642055eb..13783d253e5 100644 --- a/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java +++ b/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java @@ -18,21 +18,151 @@ package org.eclipse.jetty.fcgi.server; -import java.util.concurrent.Executor; +import java.nio.ByteBuffer; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import org.eclipse.jetty.fcgi.FCGI; +import org.eclipse.jetty.fcgi.generator.Flusher; +import org.eclipse.jetty.fcgi.parser.ServerParser; +import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.io.AbstractConnection; +import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.server.ByteBufferQueuedHttpInput; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; public class ServerFCGIConnection extends AbstractConnection { - public ServerFCGIConnection(EndPoint endp, Executor executor) + private static final Logger LOG = Log.getLogger(ServerFCGIConnection.class); + + private final ConcurrentMap channels = new ConcurrentHashMap<>(); + private final Connector connector; + private final Flusher flusher; + private final HttpConfiguration configuration; + private final ServerParser parser; + + public ServerFCGIConnection(Connector connector, EndPoint endPoint, HttpConfiguration configuration) { - super(endp, executor); + super(endPoint, connector.getExecutor()); + this.connector = connector; + this.flusher = new Flusher(endPoint); + this.configuration = configuration; + this.parser = new ServerParser(new ServerListener()); + } + + @Override + public void onOpen() + { + super.onOpen(); + fillInterested(); } @Override public void onFillable() { - // TODO: need to create a server-side HttpChannel and feed it with parser events + EndPoint endPoint = getEndPoint(); + ByteBufferPool bufferPool = connector.getByteBufferPool(); + ByteBuffer buffer = bufferPool.acquire(configuration.getResponseHeaderSize(), true); + try + { + while (true) + { + int read = endPoint.fill(buffer); + if (LOG.isDebugEnabled()) // Avoid boxing of variable 'read' + LOG.debug("Read {} bytes from {}", read, endPoint); + if (read > 0) + { + parse(buffer); + } + else if (read == 0) + { + fillInterested(); + break; + } + else + { + shutdown(); + break; + } + } + } + catch (Exception x) + { + LOG.debug(x); + // TODO: fail and close ? + } + finally + { + bufferPool.release(buffer); + } + } + + private void parse(ByteBuffer buffer) + { + while (buffer.hasRemaining()) + parser.parse(buffer); + } + + private void shutdown() + { + // TODO + } + + private class ServerListener implements ServerParser.Listener + { + @Override + public void onStart(int request, FCGI.Role role) + { + HttpChannelOverFCGI channel = new HttpChannelOverFCGI(connector, configuration, getEndPoint(), + new HttpTransportOverFCGI(connector.getByteBufferPool(), flusher, request), new ByteBufferQueuedHttpInput()); + HttpChannelOverFCGI existing = channels.putIfAbsent(request, channel); + if (existing != null) + throw new IllegalStateException(); + } + + @Override + public void onHeader(int request, HttpField field) + { + HttpChannelOverFCGI channel = channels.get(request); + if (channel != null) + channel.header(field); + else + noChannel(request); + } + + @Override + public void onHeaders(int request) + { + HttpChannelOverFCGI channel = channels.get(request); + if (channel != null) + channel.headerComplete(); + else + noChannel(request); + } + + @Override + public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) + { + } + + @Override + public void onEnd(int request) + { + HttpChannelOverFCGI channel = channels.get(request); + if (channel != null) + if (channel.messageComplete()) + channel.dispatch(); + else + noChannel(request); + } + + private void noChannel(int request) + { + // TODO + } } } diff --git a/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/FCGIServerConnectionFactory.java b/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnectionFactory.java similarity index 76% rename from fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/FCGIServerConnectionFactory.java rename to fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnectionFactory.java index a78b37dacc8..3d148222669 100644 --- a/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/FCGIServerConnectionFactory.java +++ b/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnectionFactory.java @@ -22,17 +22,21 @@ import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.server.AbstractConnectionFactory; import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.HttpConfiguration; -public class FCGIServerConnectionFactory extends AbstractConnectionFactory +public class ServerFCGIConnectionFactory extends AbstractConnectionFactory { - public FCGIServerConnectionFactory() + private final HttpConfiguration configuration; + + public ServerFCGIConnectionFactory(HttpConfiguration configuration) { super("fcgi/1.0"); + this.configuration = configuration; } @Override public Connection newConnection(Connector connector, EndPoint endPoint) { - return new ServerFCGIConnection(endPoint, connector.getExecutor()); + return new ServerFCGIConnection(connector, endPoint, configuration); } } From bfad01e0dc59cc7c74005e7b46e168cb44861225 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Sun, 8 Sep 2013 23:00:57 +0200 Subject: [PATCH 10/43] Handling request and response content. --- .../java/org/eclipse/jetty/fcgi/FCGI.java | 25 ++++ .../eclipse/jetty/fcgi/generator/Flusher.java | 35 ++++++ .../fcgi/parser/ResponseContentParser.java | 11 +- .../fcgi/parser/StreamContentParser.java | 3 +- .../fcgi/client/http/HttpChannelOverFCGI.java | 9 ++ .../client/http/HttpConnectionOverFCGI.java | 28 ++++- .../fcgi/client/http/HttpSenderOverFCGI.java | 44 ++++++- .../fcgi/client/http/HttpClientTest.java | 29 +++-- .../fcgi/server/HttpChannelOverFCGI.java | 116 ++++++++++++++++-- .../fcgi/server/ServerFCGIConnection.java | 38 +++--- 10 files changed, 291 insertions(+), 47 deletions(-) diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/FCGI.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/FCGI.java index 10cf644db6c..aae8d6c351c 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/FCGI.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/FCGI.java @@ -105,4 +105,29 @@ public class FCGI { STD_IN, STD_OUT, STD_ERR } + + public static class Headers + { + public static final String AUTH_TYPE = "AUTH_TYPE"; + public static final String CONTENT_LENGTH = "CONTENT_LENGTH"; + public static final String CONTENT_TYPE = "CONTENT_TYPE"; + public static final String GATEWAY_INTERFACE = "GATEWAY_INTERFACE"; + public static final String PATH_INFO = "PATH_INFO"; + public static final String PATH_TRANSLATED = "PATH_TRANSLATED"; + public static final String QUERY_STRING = "QUERY_STRING"; + public static final String REMOTE_ADDR = "REMOTE_ADDR"; + public static final String REMOTE_HOST = "REMOTE_HOST"; + public static final String REMOTE_USER = "REMOTE_USER"; + public static final String REQUEST_METHOD = "REQUEST_METHOD"; + public static final String REQUEST_URI = "REQUEST_URI"; + public static final String SCRIPT_NAME = "SCRIPT_NAME"; + public static final String SERVER_NAME = "SERVER_NAME"; + public static final String SERVER_PORT = "SERVER_PORT"; + public static final String SERVER_PROTOCOL = "SERVER_PROTOCOL"; + public static final String SERVER_SOFTWARE = "SERVER_SOFTWARE"; + + private Headers() + { + } + } } diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java index ccf868b3d54..8fd8b7252be 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java @@ -26,9 +26,13 @@ import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.ConcurrentArrayQueue; import org.eclipse.jetty.util.IteratingCallback; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; public class Flusher { + private static final Logger LOG = Log.getLogger(Flusher.class); + private final Queue queue = new ConcurrentArrayQueue<>(); private final Callback flushCallback = new FlushCallback(); private final EndPoint endPoint; @@ -52,6 +56,11 @@ public class Flusher endPoint.write(flushCallback); } + public void shutdown() + { + flush(new ShutdownResult()); + } + private class FlushCallback extends IteratingCallback { private Generator.Result active; @@ -106,4 +115,30 @@ public class Flusher } } } + + private class ShutdownResult extends Generator.Result + { + private ShutdownResult() + { + super(null, new Adapter()); + } + + @Override + public void succeeded() + { + shutdown(); + } + + @Override + public void failed(Throwable x) + { + shutdown(); + } + + private void shutdown() + { + LOG.debug("Shutting down {}", endPoint); + endPoint.shutdownOutput(); + } + } } diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java index 06ffe253a92..5a72638bfd5 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java @@ -27,9 +27,13 @@ import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpParser; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; public class ResponseContentParser extends StreamContentParser { + private static final Logger LOG = Log.getLogger(ResponseContentParser.class); + public ResponseContentParser(HeaderParser headerParser, ClientParser.Listener listener) { super(headerParser, FCGI.StreamType.STD_OUT, new ResponseListener(headerParser, listener)); @@ -54,6 +58,8 @@ public class ResponseContentParser extends StreamContentParser @Override public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) { + LOG.debug("Request {} {} content {} {}", request, stream, state, buffer); + while (buffer.hasRemaining()) { switch (state) @@ -89,8 +95,9 @@ public class ResponseContentParser extends StreamContentParser @Override public void onEnd(int request) { - // Never called for STD_OUT, since it relies on FCGI_END_REQUEST - throw new IllegalStateException(); + // We are a STD_OUT stream so the end of the request is + // signaled by a END_REQUEST. Here we just reset the state. + reset(); } @Override diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java index 79f0ed3fe7b..10c15be663e 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java @@ -80,8 +80,7 @@ public class StreamContentParser extends ContentParser @Override public void noContent() { - if (streamType == FCGI.StreamType.STD_IN) - onEnd(); + onEnd(); } protected void onContent(ByteBuffer buffer) diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java index 83d8584e61c..ad095fe17e7 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java @@ -18,6 +18,8 @@ package org.eclipse.jetty.fcgi.client.http; +import java.nio.ByteBuffer; + import org.eclipse.jetty.client.HttpChannel; import org.eclipse.jetty.client.HttpDestination; import org.eclipse.jetty.client.HttpExchange; @@ -95,6 +97,13 @@ public class HttpChannelOverFCGI extends HttpChannel receiver.responseHeaders(exchange); } + protected void content(ByteBuffer buffer) + { + HttpExchange exchange = getHttpExchange(); + if (exchange != null) + receiver.responseContent(exchange, buffer); + } + protected void responseSuccess() { HttpExchange exchange = getHttpExchange(); diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java index 108b2ba19a0..b0fed4dce00 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java @@ -37,6 +37,7 @@ import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -133,7 +134,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec private void shutdown() { - // TODO: we must signal to the HttpParser that we are at EOF + getEndPoint().shutdownOutput(); } @Override @@ -237,17 +238,36 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec @Override public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) { - throw new UnsupportedOperationException(); + switch (stream) + { + case STD_OUT: + { + HttpChannelOverFCGI channel = channels.get(request); + if (channel != null) + channel.content(buffer); + else + noChannel(request); + break; + } + case STD_ERR: + { + LOG.info(BufferUtil.toUTF8String(buffer)); + break; + } + default: + { + throw new IllegalArgumentException(); + } + } } @Override public void onEnd(int request) { - HttpChannelOverFCGI channel = channels.get(request); + HttpChannelOverFCGI channel = channels.remove(request); if (channel != null) { channel.responseSuccess(); - channels.remove(request); releaseRequest(request); } else diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java index 1a8d853220c..b1a715117c3 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java @@ -1,11 +1,18 @@ package org.eclipse.jetty.fcgi.client.http; +import java.net.URI; + import org.eclipse.jetty.client.HttpChannel; import org.eclipse.jetty.client.HttpContent; import org.eclipse.jetty.client.HttpExchange; import org.eclipse.jetty.client.HttpSender; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.fcgi.FCGI; import org.eclipse.jetty.fcgi.generator.ClientGenerator; import org.eclipse.jetty.fcgi.generator.Generator; +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.util.Callback; public class HttpSenderOverFCGI extends HttpSender @@ -27,9 +34,44 @@ public class HttpSenderOverFCGI extends HttpSender @Override protected void sendHeaders(HttpExchange exchange, HttpContent content, Callback callback) { + Request httpRequest = exchange.getRequest(); + URI uri = httpRequest.getURI(); + HttpFields headers = httpRequest.getHeaders(); + + HttpField field = headers.remove(HttpHeader.AUTHORIZATION); + if (field != null) + headers.put(FCGI.Headers.AUTH_TYPE, field.getValue()); + + field = headers.remove(HttpHeader.CONTENT_LENGTH); + if (field != null) + headers.put(FCGI.Headers.CONTENT_LENGTH, field.getValue()); + + field = headers.remove(HttpHeader.CONTENT_TYPE); + if (field != null) + headers.put(FCGI.Headers.CONTENT_TYPE, field.getValue()); + + headers.put(FCGI.Headers.GATEWAY_INTERFACE, "CGI/1.1"); + +// headers.put(Headers.PATH_INFO, ???); +// headers.put(Headers.PATH_TRANSLATED, ???); + + headers.put(FCGI.Headers.QUERY_STRING, uri.getQuery()); + +// headers.put(Headers.REMOTE_ADDR, ???); +// headers.put(Headers.REMOTE_HOST, ???); +// headers.put(Headers.REMOTE_USER, ???); + + headers.put(FCGI.Headers.REQUEST_METHOD, httpRequest.getMethod()); + + headers.put(FCGI.Headers.REQUEST_URI, uri.toString()); + + headers.put(FCGI.Headers.SERVER_PROTOCOL, httpRequest.getVersion().asString()); + + // TODO: translate remaining HTTP header into the HTTP_* format + int request = getHttpChannel().getRequest(); boolean noContent = !content.hasContent(); - Generator.Result result = generator.generateRequestHeaders(request, exchange.getRequest().getHeaders(), + Generator.Result result = generator.generateRequestHeaders(request, headers, noContent ? new Callback.Adapter() : callback); getHttpChannel().flush(result); if (noContent) diff --git a/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/HttpClientTest.java b/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/HttpClientTest.java index b7562e3de8b..fd5b0b1a76d 100644 --- a/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/HttpClientTest.java +++ b/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/HttpClientTest.java @@ -39,6 +39,7 @@ import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.util.BytesContentProvider; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.toolchain.test.IO; import org.eclipse.jetty.toolchain.test.annotation.Slow; import org.junit.Assert; import org.junit.Test; @@ -50,10 +51,12 @@ public class HttpClientTest extends AbstractHttpClientServerTest { start(new EmptyServerHandler()); - Response response = client.GET(scheme + "://localhost:" + connector.getLocalPort()); - - Assert.assertNotNull(response); - Assert.assertEquals(200, response.getStatus()); + for (int i = 0; i < 2; ++i) + { + Response response = client.GET(scheme + "://localhost:" + connector.getLocalPort()); + Assert.assertNotNull(response); + Assert.assertEquals(200, response.getStatus()); + } } @Test @@ -70,12 +73,14 @@ public class HttpClientTest extends AbstractHttpClientServerTest } }); - ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort()); - - Assert.assertNotNull(response); - Assert.assertEquals(200, response.getStatus()); - byte[] content = response.getContent(); - Assert.assertArrayEquals(data, content); + for (int i = 0; i < 2; ++i) + { + ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort()); + Assert.assertNotNull(response); + Assert.assertEquals(200, response.getStatus()); + byte[] content = response.getContent(); + Assert.assertArrayEquals(data, content); + } } @Test @@ -226,7 +231,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest { response.setCharacterEncoding("UTF-8"); response.setContentType("application/octet-stream"); - response.getOutputStream().write(content); + IO.copy(request.getInputStream(), response.getOutputStream()); } } }); @@ -234,7 +239,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort() + "/?b=1") .param(paramName, paramValue) .content(new BytesContentProvider(content)) - .timeout(555, TimeUnit.SECONDS) + .timeout(5, TimeUnit.SECONDS) .send(); Assert.assertNotNull(response); diff --git a/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java b/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java index bbb858b1b02..1f9c060ef7e 100644 --- a/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java +++ b/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java @@ -23,7 +23,10 @@ import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicReference; +import org.eclipse.jetty.fcgi.FCGI; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.io.EndPoint; @@ -38,10 +41,8 @@ import org.eclipse.jetty.util.log.Logger; public class HttpChannelOverFCGI extends HttpChannel { private static final Logger LOG = Log.getLogger(HttpChannelOverFCGI.class); - private static final String METHOD_HEADER = "REQUEST_METHOD"; - private static final String URI_HEADER = "REQUEST_URI"; - private static final String VERSION_HEADER = "SERVER_PROTOCOL"; + private final Dispatcher dispatcher; private String method; private String uri; private String version; @@ -51,25 +52,24 @@ public class HttpChannelOverFCGI extends HttpChannel public HttpChannelOverFCGI(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport, HttpInput input) { super(connector, configuration, endPoint, transport, input); + this.dispatcher = new Dispatcher(connector.getExecutor(), this); } - public void header(HttpField field) + protected void header(HttpField field) { - LOG.debug("FCGI header {}", field); - - if (METHOD_HEADER.equalsIgnoreCase(field.getName())) + if (FCGI.Headers.REQUEST_METHOD.equalsIgnoreCase(field.getName())) { method = field.getValue(); if (uri != null && version != null) startRequest(); } - else if (URI_HEADER.equalsIgnoreCase(field.getName())) + else if (FCGI.Headers.REQUEST_URI.equalsIgnoreCase(field.getName())) { uri = field.getValue(); if (method != null && version != null) startRequest(); } - else if (VERSION_HEADER.equalsIgnoreCase(field.getName())) + else if (FCGI.Headers.SERVER_PROTOCOL.equalsIgnoreCase(field.getName())) { version = field.getValue(); if (method != null && uri != null) @@ -126,12 +126,104 @@ public class HttpChannelOverFCGI extends HttpChannel } field = new HttpField(httpName.toString(), field.getValue()); } - LOG.debug("HTTP header {}", field); parsedHeader(field); } - public void dispatch() + @Override + public boolean headerComplete() { - getConnector().getExecutor().execute(this); + boolean result = super.headerComplete(); + started = false; + return result; + } + + protected void dispatch() + { + dispatcher.dispatch(); + } + + private static class Dispatcher implements Runnable + { + private final AtomicReference state = new AtomicReference<>(State.IDLE); + private final Executor executor; + private final Runnable runnable; + + private Dispatcher(Executor executor, Runnable runnable) + { + this.executor = executor; + this.runnable = runnable; + } + + public void dispatch() + { + while (true) + { + State current = state.get(); + switch (current) + { + case IDLE: + { + if (!state.compareAndSet(current, State.DISPATCH)) + continue; + executor.execute(this); + return; + } + case DISPATCH: + case EXECUTE: + { + if (state.compareAndSet(current, State.SCHEDULE)) + return; + continue; + } + case SCHEDULE: + { + return; + } + default: + { + throw new IllegalStateException(); + } + } + } + } + + @Override + public void run() + { + while (true) + { + State current = state.get(); + switch (current) + { + case DISPATCH: + { + if (state.compareAndSet(current, State.EXECUTE)) + runnable.run(); + continue; + } + case EXECUTE: + { + if (state.compareAndSet(current, State.IDLE)) + return; + continue; + } + case SCHEDULE: + { + if (state.compareAndSet(current, State.DISPATCH)) + continue; + throw new IllegalStateException(); + } + default: + { + throw new IllegalStateException(); + } + } + } + } + + private enum State + { + IDLE, DISPATCH, EXECUTE, SCHEDULE + } } } diff --git a/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java b/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java index 13783d253e5..4f1bba8ddfd 100644 --- a/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java +++ b/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java @@ -109,7 +109,7 @@ public class ServerFCGIConnection extends AbstractConnection private void shutdown() { - // TODO + flusher.shutdown(); } private class ServerListener implements ServerParser.Listener @@ -122,47 +122,57 @@ public class ServerFCGIConnection extends AbstractConnection HttpChannelOverFCGI existing = channels.putIfAbsent(request, channel); if (existing != null) throw new IllegalStateException(); + if (LOG.isDebugEnabled()) + LOG.debug("Request {} start on {}", request, channel); } @Override public void onHeader(int request, HttpField field) { HttpChannelOverFCGI channel = channels.get(request); + if (LOG.isDebugEnabled()) + LOG.debug("Request {} header {} on {}", request, field, channel); if (channel != null) channel.header(field); - else - noChannel(request); } @Override public void onHeaders(int request) { HttpChannelOverFCGI channel = channels.get(request); + if (LOG.isDebugEnabled()) + LOG.debug("Request {} headers on {}", request, channel); if (channel != null) - channel.headerComplete(); - else - noChannel(request); + { + if (channel.headerComplete()) + channel.dispatch(); + } } @Override public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) { + HttpChannelOverFCGI channel = channels.get(request); + if (LOG.isDebugEnabled()) + LOG.debug("Request {} {} content {} on {}", request, stream, buffer, channel); + if (channel != null) + { + if (channel.content(buffer)) + channel.dispatch(); + } } @Override public void onEnd(int request) { - HttpChannelOverFCGI channel = channels.get(request); + HttpChannelOverFCGI channel = channels.remove(request); + if (LOG.isDebugEnabled()) + LOG.debug("Request {} end on {}", request, channel); if (channel != null) + { if (channel.messageComplete()) channel.dispatch(); - else - noChannel(request); - } - - private void noChannel(int request) - { - // TODO + } } } } From 0c29968ee2ea43664f7157ef7cc4f3654192f411 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Thu, 3 Oct 2013 00:54:29 +0200 Subject: [PATCH 11/43] Improved the Flusher. --- .../eclipse/jetty/fcgi/generator/Flusher.java | 36 +++++++------------ 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java index 8fd8b7252be..ae262d7afaf 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java @@ -36,7 +36,6 @@ public class Flusher private final Queue queue = new ConcurrentArrayQueue<>(); private final Callback flushCallback = new FlushCallback(); private final EndPoint endPoint; - private boolean flushing; public Flusher(EndPoint endPoint) { @@ -48,10 +47,9 @@ public class Flusher synchronized (queue) { for (Generator.Result result : results) + { queue.offer(result); - if (flushing) - return; - flushing = true; + } } endPoint.write(flushCallback); } @@ -68,27 +66,15 @@ public class Flusher @Override protected boolean process() throws Exception { - // We are flushing, we completed a write, notify - if (active != null) - { - active.succeeded(); - active = null; - } - // Look if other writes are needed. Generator.Result result; synchronized (queue) { if (queue.isEmpty()) - { - // No more writes to do, switch to non-flushing - flushing = false; return false; - } // TODO: here is where we want to gather more results to perform gathered writes result = queue.poll(); } - active = result; List buffers = result.getByteBuffers(); endPoint.write(this, buffers.toArray(new ByteBuffer[buffers.size()])); @@ -101,18 +87,22 @@ public class Flusher // Nothing to do, we always return false from process(). } + @Override + public void succeeded() + { + if (active != null) + active.succeeded(); + active = null; + super.succeeded(); + } + @Override public void failed(Throwable x) { - super.failed(x); - - // TODO: set flushing=false ? - if (active != null) - { active.failed(x); - active = null; - } + active = null; + super.failed(x); } } From 1cb3649b7e1c50b4226db9a76feeb57a399d0d1f Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Thu, 3 Oct 2013 00:55:52 +0200 Subject: [PATCH 12/43] Added URI regexp functionality to be able to split the URIs into the CGI variables. --- .../http/HttpClientTransportOverFCGI.java | 16 +++++++++++++--- .../fcgi/client/http/HttpSenderOverFCGI.java | 17 ++++++++++++++--- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java index 1a921dcef5d..cbbbee4c506 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java @@ -18,6 +18,8 @@ package org.eclipse.jetty.fcgi.client.http; +import java.util.regex.Pattern; + import org.eclipse.jetty.client.AbstractHttpClientTransport; import org.eclipse.jetty.client.HttpDestination; import org.eclipse.jetty.client.api.Connection; @@ -26,14 +28,22 @@ import org.eclipse.jetty.io.EndPoint; // TODO: add parameter to tell whether use multiplex destinations or not public class HttpClientTransportOverFCGI extends AbstractHttpClientTransport { - public HttpClientTransportOverFCGI() + private final Pattern uriPattern; + + public HttpClientTransportOverFCGI(Pattern uriPattern) { - this(1); + this(1, uriPattern); } - public HttpClientTransportOverFCGI(int selectors) + public HttpClientTransportOverFCGI(int selectors, Pattern uriPattern) { super(selectors); + this.uriPattern = uriPattern; + } + + public Pattern getURIPattern() + { + return uriPattern; } @Override diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java index b1a715117c3..3e8fce96a29 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java @@ -1,6 +1,8 @@ package org.eclipse.jetty.fcgi.client.http; import java.net.URI; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.eclipse.jetty.client.HttpChannel; import org.eclipse.jetty.client.HttpContent; @@ -52,11 +54,20 @@ public class HttpSenderOverFCGI extends HttpSender headers.put(FCGI.Headers.GATEWAY_INTERFACE, "CGI/1.1"); + HttpClientTransportOverFCGI transport = (HttpClientTransportOverFCGI)getHttpChannel().getHttpDestination().getHttpClient().getTransport(); + Pattern uriPattern = transport.getURIPattern(); + Matcher matcher = uriPattern.matcher(uri.toString()); + + // TODO: what if the URI does not match ? Here is kinda too late to abort the request ? + // TODO: perhaps this works in conjuntion with the ProxyServlet, which is mapped to the same URI regexp + // TODO: so that if the call arrives here, we are sure it matches. + // headers.put(Headers.PATH_INFO, ???); // headers.put(Headers.PATH_TRANSLATED, ???); headers.put(FCGI.Headers.QUERY_STRING, uri.getQuery()); + // TODO: the fields below are probably provided by ProxyServlet as X-Forwarded-* // headers.put(Headers.REMOTE_ADDR, ???); // headers.put(Headers.REMOTE_HOST, ???); // headers.put(Headers.REMOTE_USER, ???); @@ -70,11 +81,11 @@ public class HttpSenderOverFCGI extends HttpSender // TODO: translate remaining HTTP header into the HTTP_* format int request = getHttpChannel().getRequest(); - boolean noContent = !content.hasContent(); + boolean hasContent = content.hasContent(); Generator.Result result = generator.generateRequestHeaders(request, headers, - noContent ? new Callback.Adapter() : callback); + hasContent ? callback : new Callback.Adapter()); getHttpChannel().flush(result); - if (noContent) + if (!hasContent) { result = generator.generateRequestContent(request, null, true, callback); getHttpChannel().flush(result); From 17497261b9624e28194102ca71bb0e840858d7b5 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Thu, 3 Oct 2013 09:25:10 +1000 Subject: [PATCH 13/43] LICENSE --- LICENSE | 323 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 323 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000000..aac5becc435 --- /dev/null +++ b/LICENSE @@ -0,0 +1,323 @@ + +CREATIVE COMMONS Attribution-NonCommercial-NoDerivs 3.0 Unported + +Summary + +Attribution — You must attribute the work in the manner specified +by the author or licensor (but not in any way that suggests that they +endorse you or your use of the work). + +Noncommercial — You may not use this work for commercial purposes. + +No Derivative Works — You may not alter, transform, or build upon +this work. + + +CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL +SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT +RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" +BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION +PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE. + +License + +THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE +COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY +COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN +AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. + +BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE +TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE +MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS +CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND +CONDITIONS. + +1. Definitions + +"Adaptation" means a work based upon the Work, or upon the Work and other +pre-existing works, such as a translation, adaptation, derivative work, +arrangement of music or other alterations of a literary or artistic work, +or phonogram or performance and includes cinematographic adaptations or +any other form in which the Work may be recast, transformed, or adapted +including in any form recognizably derived from the original, except that +a work that constitutes a Collection will not be considered an Adaptation +for the purpose of this License. For the avoidance of doubt, where the +Work is a musical work, performance or phonogram, the synchronization +of the Work in timed-relation with a moving image ("synching") will be +considered an Adaptation for the purpose of this License. + +"Collection" means a collection of literary or artistic works, such as +encyclopedias and anthologies, or performances, phonograms or broadcasts, +or other works or subject matter other than works listed in Section +1(f) below, which, by reason of the selection and arrangement of +their contents, constitute intellectual creations, in which the Work +is included in its entirety in unmodified form along with one or more +other contributions, each constituting separate and independent works +in themselves, which together are assembled into a collective whole. A +work that constitutes a Collection will not be considered an Adaptation +(as defined above) for the purposes of this License. + +"Distribute" means to make available to the public the original and +copies of the Work through sale or other transfer of ownership. + +"Licensor" means the individual, individuals, entity or entities that +offer(s) the Work under the terms of this License. + +"Original Author" means, in the case of a literary or artistic work, the +individual, individuals, entity or entities who created the Work or if +no individual or entity can be identified, the publisher; and in addition +(i) in the case of a performance the actors, singers, musicians, dancers, +and other persons who act, sing, deliver, declaim, play in, interpret or +otherwise perform literary or artistic works or expressions of folklore; +(ii) in the case of a phonogram the producer being the person or legal +entity who first fixes the sounds of a performance or other sounds; +and, (iii) in the case of broadcasts, the organization that transmits +the broadcast. + +"Work" means the literary and/or artistic work offered under the terms +of this License including without limitation any production in the +literary, scientific and artistic domain, whatever may be the mode or +form of its expression including digital form, such as a book, pamphlet +and other writing; a lecture, address, sermon or other work of the same +nature; a dramatic or dramatico-musical work; a choreographic work +or entertainment in dumb show; a musical composition with or without +words; a cinematographic work to which are assimilated works expressed +by a process analogous to cinematography; a work of drawing, painting, +architecture, sculpture, engraving or lithography; a photographic +work to which are assimilated works expressed by a process analogous +to photography; a work of applied art; an illustration, map, plan, +sketch or three-dimensional work relative to geography, topography, +architecture or science; a performance; a broadcast; a phonogram; +a compilation of data to the extent it is protected as a copyrightable +work; or a work performed by a variety or circus performer to the extent +it is not otherwise considered a literary or artistic work. + +"You" means an individual or entity exercising rights under this License +who has not previously violated the terms of this License with respect +to the Work, or who has received express permission from the Licensor +to exercise rights under this License despite a previous violation. + +"Publicly Perform" means to perform public recitations of the Work and +to communicate to the public those public recitations, by any means +or process, including by wire or wireless means or public digital +performances; to make available to the public Works in such a way that +members of the public may access these Works from a place and at a place +individually chosen by them; to perform the Work to the public by any +means or process and the communication to the public of the performances +of the Work, including by public digital performance; to broadcast and +rebroadcast the Work by any means including signs, sounds or images. + +"Reproduce" means to make copies of the Work by any means including +without limitation by sound or visual recordings and the right of fixation +and reproducing fixations of the Work, including storage of a protected +performance or phonogram in digital form or other electronic medium. + +2. Fair Dealing Rights. Nothing in this License is intended to reduce, +limit, or restrict any uses free from copyright or rights arising from +limitations or exceptions that are provided for in connection with the +copyright protection under copyright law or other applicable laws. + +3. License Grant. Subject to the terms and conditions of this License, +Licensor hereby grants You a worldwide, royalty-free, non-exclusive, +perpetual (for the duration of the applicable copyright) license to +exercise the rights in the Work as stated below: + +to Reproduce the Work, to incorporate the Work into one or more +Collections, and +to Reproduce the Work as incorporated in the Collections; +and, +to Distribute and Publicly Perform the Work including as incorporated +in Collections. + +The above rights may be exercised in all media and formats whether +now known or hereafter devised. The above rights include the right to +make such modifications as are technically necessary to exercise the +rights in other media and formats, but otherwise you have no rights to +make Adaptations. Subject to 8(f), all rights not expressly granted by +Licensor are hereby reserved, including but not limited to the rights +set forth in Section 4(d). + +4. Restrictions. The license granted in Section 3 above is expressly +made subject to and limited by the following restrictions: + +You may Distribute or Publicly Perform the Work only under the terms +of this License. You must include a copy of, or the Uniform Resource +Identifier (URI) for, this License with every copy of the Work You +Distribute or Publicly Perform. You may not offer or impose any terms on +the Work that restrict the terms of this License or the ability of the +recipient of the Work to exercise the rights granted to that recipient +under the terms of the License. You may not sublicense the Work. You +must keep intact all notices that refer to this License and to the +disclaimer of warranties with every copy of the Work You Distribute +or Publicly Perform. When You Distribute or Publicly Perform the Work, +You may not impose any effective technological measures on the Work that +restrict the ability of a recipient of the Work from You to exercise the +rights granted to that recipient under the terms of the License. This +Section 4(a) applies to the Work as incorporated in a Collection, but +this does not require the Collection apart from the Work itself to be +made subject to the terms of this License. If You create a Collection, +upon notice from any Licensor You must, to the extent practicable, remove +from the Collection any credit as required by Section 4(c), as requested. + +You may not exercise any of the rights granted to You in Section 3 +above in any manner that is primarily intended for or directed toward +commercial advantage or private monetary compensation. The exchange of +the Work for other copyrighted works by means of digital file-sharing or +otherwise shall not be considered to be intended for or directed toward +commercial advantage or private monetary compensation, provided there is +no payment of any monetary compensation in connection with the exchange +of copyrighted works. + +If You Distribute, or Publicly Perform the Work or Collections, You must, +unless a request has been made pursuant to Section 4(a), keep intact all +copyright notices for the Work and provide, reasonable to the medium +or means You are utilizing: (i) the name of the Original Author (or +pseudonym, if applicable) if supplied, and/or if the Original Author +and/or Licensor designate another party or parties (e.g., a sponsor +institute, publishing entity, journal) for attribution ("Attribution +Parties") in Licensor's copyright notice, terms of service or by other +reasonable means, the name of such party or parties; (ii) the title of +the Work if supplied; (iii) to the extent reasonably practicable, the +URI, if any, that Licensor specifies to be associated with the Work, +unless such URI does not refer to the copyright notice or licensing +information for the Work. The credit required by this Section 4(c) +may be implemented in any reasonable manner; provided, however, that +in the case of a Collection, at a minimum such credit will appear, +if a credit for all contributing authors of Collection appears, then +as part of these credits and in a manner at least as prominent as the +credits for the other contributing authors. For the avoidance of doubt, +You may only use the credit required by this Section for the purpose of +attribution in the manner set out above and, by exercising Your rights +under this License, You may not implicitly or explicitly assert or imply +any connection with, sponsorship or endorsement by the Original Author, +Licensor and/or Attribution Parties, as appropriate, of You or Your use +of the Work, without the separate, express prior written permission of +the Original Author, Licensor and/or Attribution Parties. + +For the avoidance of doubt: + + Non-waivable Compulsory License Schemes. In those jurisdictions + in which the right to collect royalties through any statutory or + compulsory licensing scheme cannot be waived, the Licensor reserves + the exclusive right to collect such royalties for any exercise by + You of the rights granted under this License; + + Waivable Compulsory License Schemes. In those jurisdictions in which + the right to collect royalties through any statutory or compulsory + licensing scheme can be waived, the Licensor reserves the exclusive + right to collect such royalties for any exercise by You of the rights + granted under this License if Your exercise of such rights is for + a purpose or use which is otherwise than noncommercial as permitted + under Section 4(b) and otherwise waives the right to collect royalties + through any statutory or compulsory licensing scheme; and, + + Voluntary License Schemes. The Licensor reserves the right to collect + royalties, whether individually or, in the event that the Licensor is + a member of a collecting society that administers voluntary licensing + schemes, via that society, from any exercise by You of the rights + granted under this License that is for a purpose or use which is + otherwise than noncommercial as permitted under Section 4(b). + +Except as otherwise agreed in writing by the Licensor or as may be +otherwise permitted by applicable law, if You Reproduce, Distribute or +Publicly Perform the Work either by itself or as part of any Collections, +You must not distort, mutilate, modify or take other derogatory action in +relation to the Work which would be prejudicial to the Original Author's +honor or reputation. + +5. Representations, Warranties and Disclaimer + +UNLESS OTHERWISE MUTUALLY AGREED BY THE PARTIES IN WRITING, LICENSOR +OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY +KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, +INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, +FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF +LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, +WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION +OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. + +6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE +LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR +ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES +ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR +HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. Termination + +This License and the rights granted hereunder will terminate automatically +upon any breach by You of the terms of this License. Individuals or +entities who have received Collections from You under this License, +however, will not have their licenses terminated provided such individuals +or entities remain in full compliance with those licenses. Sections 1, +2, 5, 6, 7, and 8 will survive any termination of this License. + +Subject to the above terms and conditions, the license granted here +is perpetual (for the duration of the applicable copyright in the +Work). Notwithstanding the above, Licensor reserves the right to release +the Work under different license terms or to stop distributing the Work +at any time; provided, however that any such election will not serve to +withdraw this License (or any other license that has been, or is required +to be, granted under the terms of this License), and this License will +continue in full force and effect unless terminated as stated above. + +8. Miscellaneous + +Each time You Distribute or Publicly Perform the Work or a Collection, +the Licensor offers to the recipient a license to the Work on the same +terms and conditions as the license granted to You under this License. + +If any provision of this License is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of +the remainder of the terms of this License, and without further action +by the parties to this agreement, such provision shall be reformed to +the minimum extent necessary to make such provision valid and enforceable. + +No term or provision of this License shall be deemed waived and no breach +consented to unless such waiver or consent shall be in writing and signed +by the party to be charged with such waiver or consent. + +This License constitutes the entire agreement between the parties +with respect to the Work licensed here. There are no understandings, +agreements or representations with respect to the Work not specified +here. Licensor shall not be bound by any additional provisions that may +appear in any communication from You. This License may not be modified +without the mutual written agreement of the Licensor and You. + +The rights granted under, and the subject matter referenced, in this +License were drafted utilizing the terminology of the Berne Convention for +the Protection of Literary and Artistic Works (as amended on September 28, +1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, +the WIPO Performances and Phonograms Treaty of 1996 and the Universal +Copyright Convention (as revised on July 24, 1971). These rights and +subject matter take effect in the relevant jurisdiction in which the +License terms are sought to be enforced according to the corresponding +provisions of the implementation of those treaty provisions in the +applicable national law. If the standard suite of rights granted under +applicable copyright law includes additional rights not granted under +this License, such additional rights are deemed to be included in the +License; this License is not intended to restrict the license of any +rights under applicable law. + +Creative Commons Notice + +Creative Commons is not a party to this License, and makes no warranty +whatsoever in connection with the Work. Creative Commons will not +be liable to You or any party on any legal theory for any damages +whatsoever, including without limitation any general, special, +incidental or consequential damages arising in connection to this +license. Notwithstanding the foregoing two (2) sentences, if Creative +Commons has expressly identified itself as the Licensor hereunder, +it shall have all rights and obligations of Licensor. + +Except for the limited purpose of indicating to the public that the Work +is licensed under the CCPL, Creative Commons does not authorize the use by +either party of the trademark "Creative Commons" or any related trademark +or logo of Creative Commons without the prior written consent of Creative +Commons. Any permitted use will be in compliance with Creative Commons' +then-current trademark usage guidelines, as may be published on its +website or otherwise made available upon request from time to time. For +the avoidance of doubt, this trademark restriction does not form part +of this License. + +Creative Commons may be contacted at http://creativecommons.org/. + From 67a287e5cf7c13cab8be8bebad7ddf31d88242b5 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 18 Oct 2013 12:13:56 +0200 Subject: [PATCH 14/43] Restored flushing field, it is necessary for concurrent writes. --- .../java/org/eclipse/jetty/fcgi/generator/Flusher.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java index ae262d7afaf..075617483ec 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java @@ -36,6 +36,7 @@ public class Flusher private final Queue queue = new ConcurrentArrayQueue<>(); private final Callback flushCallback = new FlushCallback(); private final EndPoint endPoint; + private boolean flushing; public Flusher(EndPoint endPoint) { @@ -47,9 +48,10 @@ public class Flusher synchronized (queue) { for (Generator.Result result : results) - { queue.offer(result); - } + if (flushing) + return; + flushing = true; } endPoint.write(flushCallback); } @@ -71,7 +73,11 @@ public class Flusher synchronized (queue) { if (queue.isEmpty()) + { + // No more writes to do, switch to non-flushing + flushing = false; return false; + } // TODO: here is where we want to gather more results to perform gathered writes result = queue.poll(); } From 642f8c1ee166e03c2f9c8ccbe66b8cfa88b7dc9b Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 18 Oct 2013 19:13:07 +0200 Subject: [PATCH 15/43] Implemented response content handling. Now the implementation can successfully make requests to PHP-FPM and receive the responses. --- .../java/org/eclipse/jetty/fcgi/FCGI.java | 7 +- .../jetty/fcgi/parser/ClientParser.java | 54 +++- .../fcgi/parser/ResponseContentParser.java | 263 +++++++++++------- .../fcgi/parser/StreamContentParser.java | 19 +- .../fcgi/generator/ClientGeneratorTest.java | 6 +- .../jetty/fcgi/parser/ClientParserTest.java | 6 +- .../fcgi/client/http/HttpChannelOverFCGI.java | 2 +- .../http/HttpClientTransportOverFCGI.java | 44 +-- .../fcgi/client/http/HttpSenderOverFCGI.java | 107 +++---- .../MultiplexHttpDestinationOverFCGI.java | 5 +- .../http/AbstractHttpClientServerTest.java | 2 +- .../http/ExternalFastCGIServerTest.java | 52 ++++ 12 files changed, 370 insertions(+), 197 deletions(-) create mode 100644 fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/ExternalFastCGIServerTest.java diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/FCGI.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/FCGI.java index aae8d6c351c..36ec33b5962 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/FCGI.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/FCGI.java @@ -111,16 +111,17 @@ public class FCGI public static final String AUTH_TYPE = "AUTH_TYPE"; public static final String CONTENT_LENGTH = "CONTENT_LENGTH"; public static final String CONTENT_TYPE = "CONTENT_TYPE"; + public static final String DOCUMENT_ROOT = "DOCUMENT_ROOT"; public static final String GATEWAY_INTERFACE = "GATEWAY_INTERFACE"; public static final String PATH_INFO = "PATH_INFO"; - public static final String PATH_TRANSLATED = "PATH_TRANSLATED"; public static final String QUERY_STRING = "QUERY_STRING"; public static final String REMOTE_ADDR = "REMOTE_ADDR"; - public static final String REMOTE_HOST = "REMOTE_HOST"; - public static final String REMOTE_USER = "REMOTE_USER"; + public static final String REMOTE_PORT = "REMOTE_PORT"; public static final String REQUEST_METHOD = "REQUEST_METHOD"; public static final String REQUEST_URI = "REQUEST_URI"; + public static final String SCRIPT_FILENAME = "SCRIPT_FILENAME"; public static final String SCRIPT_NAME = "SCRIPT_NAME"; + public static final String SERVER_ADDR = "SERVER_ADDR"; public static final String SERVER_NAME = "SERVER_NAME"; public static final String SERVER_PORT = "SERVER_PORT"; public static final String SERVER_PROTOCOL = "SERVER_PROTOCOL"; diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java index d8b6640a362..d9c0973a4ed 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java @@ -18,9 +18,11 @@ package org.eclipse.jetty.fcgi.parser; +import java.nio.ByteBuffer; import java.util.EnumMap; import org.eclipse.jetty.fcgi.FCGI; +import org.eclipse.jetty.http.HttpField; public class ClientParser extends Parser { @@ -28,9 +30,11 @@ public class ClientParser extends Parser public ClientParser(Listener listener) { - contentParsers.put(FCGI.FrameType.STDOUT, new ResponseContentParser(headerParser, listener)); - contentParsers.put(FCGI.FrameType.STDERR, new StreamContentParser(headerParser, FCGI.StreamType.STD_ERR, listener)); - contentParsers.put(FCGI.FrameType.END_REQUEST, new EndRequestContentParser(headerParser, listener)); + ResponseContentParser stdOutParser = new ResponseContentParser(headerParser, listener); + contentParsers.put(FCGI.FrameType.STDOUT, stdOutParser); + StreamContentParser stdErrParser = new StreamContentParser(headerParser, FCGI.StreamType.STD_ERR, listener); + contentParsers.put(FCGI.FrameType.STDERR, stdErrParser); + contentParsers.put(FCGI.FrameType.END_REQUEST, new EndRequestContentParser(headerParser, new EndRequestListener(listener, stdOutParser, stdErrParser))); } @Override @@ -51,4 +55,48 @@ public class ClientParser extends Parser } } } + + private class EndRequestListener implements Listener + { + private final Listener listener; + private final StreamContentParser[] streamParsers; + + private EndRequestListener(Listener listener, StreamContentParser... streamParsers) + { + this.listener = listener; + this.streamParsers = streamParsers; + } + + @Override + public void onBegin(int request, int code, String reason) + { + listener.onBegin(request, code, reason); + } + + @Override + public void onHeader(int request, HttpField field) + { + listener.onHeader(request, field); + } + + @Override + public void onHeaders(int request) + { + listener.onHeaders(request); + } + + @Override + public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) + { + listener.onContent(request, stream, buffer); + } + + @Override + public void onEnd(int request) + { + listener.onEnd(request); + for (StreamContentParser streamParser : streamParsers) + streamParser.end(request); + } + } } diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java index 5a72638bfd5..30e2427a3b9 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java @@ -19,11 +19,13 @@ package org.eclipse.jetty.fcgi.parser; import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.eclipse.jetty.fcgi.FCGI; import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpParser; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpVersion; @@ -34,46 +36,89 @@ public class ResponseContentParser extends StreamContentParser { private static final Logger LOG = Log.getLogger(ResponseContentParser.class); + private final Map parsers = new ConcurrentHashMap<>(); + private final ClientParser.Listener listener; + public ResponseContentParser(HeaderParser headerParser, ClientParser.Listener listener) { - super(headerParser, FCGI.StreamType.STD_OUT, new ResponseListener(headerParser, listener)); + super(headerParser, FCGI.StreamType.STD_OUT, listener); + this.listener = listener; } - private static class ResponseListener extends Parser.Listener.Adapter implements HttpParser.ResponseHandler + @Override + protected void onContent(ByteBuffer buffer) { - private final HeaderParser headerParser; - private final ClientParser.Listener listener; + int request = getRequest(); + ResponseParser parser = parsers.get(request); + if (parser == null) + { + parser = new ResponseParser(listener, request); + parsers.put(request, parser); + } + parser.parse(buffer); + } + + @Override + protected void end(int request) + { + super.end(request); + parsers.remove(request); + } + + private class ResponseParser implements HttpParser.ResponseHandler + { + private final HttpFields fields = new HttpFields(); + private ClientParser.Listener listener; + private final int request; private final FCGIHttpParser httpParser; private State state = State.HEADERS; - private boolean begun; - private List fields; + private boolean seenResponseCode; - public ResponseListener(HeaderParser headerParser, ClientParser.Listener listener) + private ResponseParser(ClientParser.Listener listener, int request) { - this.headerParser = headerParser; this.listener = listener; + this.request = request; this.httpParser = new FCGIHttpParser(this); } - @Override - public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) + public void parse(ByteBuffer buffer) { - LOG.debug("Request {} {} content {} {}", request, stream, state, buffer); + LOG.debug("Response {} {} content {} {}", request, FCGI.StreamType.STD_OUT, state, buffer); - while (buffer.hasRemaining()) + int remaining = buffer.remaining(); + while (remaining > 0) { switch (state) { case HEADERS: { if (httpParser.parseHeaders(buffer)) - state = State.CONTENT; + state = State.CONTENT_MODE; + remaining = buffer.remaining(); break; } - case CONTENT: + case CONTENT_MODE: { - if (httpParser.parseContent(buffer)) - reset(); + // If we have no indication of the content, then + // the HTTP parser will assume there is no content + // and will not parse it even if it is provided, + // so we have to parse it raw ourselves here. + boolean rawContent = fields.size() == 0 || + (fields.get(HttpHeader.CONTENT_LENGTH) == null && + fields.get(HttpHeader.TRANSFER_ENCODING) == null); + state = rawContent ? State.RAW_CONTENT : State.HTTP_CONTENT; + break; + } + case RAW_CONTENT: + { + notifyContent(buffer); + remaining = 0; + break; + } + case HTTP_CONTENT: + { + httpParser.parseContent(buffer); + remaining = buffer.remaining(); break; } default: @@ -84,22 +129,6 @@ public class ResponseContentParser extends StreamContentParser } } - private void reset() - { - httpParser.reset(); - state = State.HEADERS; - begun = false; - fields = null; - } - - @Override - public void onEnd(int request) - { - // We are a STD_OUT stream so the end of the request is - // signaled by a END_REQUEST. Here we just reset the state. - reset(); - } - @Override public int getHeaderCacheSize() { @@ -119,41 +148,30 @@ public class ResponseContentParser extends StreamContentParser { try { - if ("Status".equalsIgnoreCase(httpField.getName())) + String name = httpField.getName(); + if ("Status".equalsIgnoreCase(name)) { - if (!begun) + if (!seenResponseCode) { - begun = true; + seenResponseCode = true; // Need to set the response status so the // HttpParser can handle the content properly. String[] parts = httpField.getValue().split(" "); int code = Integer.parseInt(parts[0]); httpParser.setResponseStatus(code); - String reason = parts.length > 1 ? parts[1] : HttpStatus.getMessage(code); - listener.onBegin(headerParser.getRequest(), code, reason); - if (fields != null) - { - for (HttpField field : fields) - listener.onHeader(headerParser.getRequest(), field); - fields = null; - } + notifyBegin(code, reason); + notifyHeaders(fields); } } else { - if (begun) - { - listener.onHeader(headerParser.getRequest(), httpField); - } + if (seenResponseCode) + notifyHeader(httpField); else - { - if (fields == null) - fields = new ArrayList<>(); fields.add(httpField); - } } } catch (Throwable x) @@ -162,48 +180,89 @@ public class ResponseContentParser extends StreamContentParser } return false; } - - @Override - public boolean headerComplete() + private void notifyBegin(int code, String reason) { try { - if (begun) - { - listener.onHeaders(headerParser.getRequest()); - } - else - { - fields = null; - // TODO: what here ? - } + listener.onBegin(request, code, reason); } catch (Throwable x) { logger.debug("Exception while invoking listener " + listener, x); } + } + + private void notifyHeader(HttpField httpField) + { + try + { + listener.onHeader(request, httpField); + } + catch (Throwable x) + { + logger.debug("Exception while invoking listener " + listener, x); + } + } + + private void notifyHeaders(HttpFields fields) + { + if (fields != null) + { + for (HttpField field : fields) + notifyHeader(field); + } + } + + private void notifyHeaders() + { + try + { + listener.onHeaders(request); + } + catch (Throwable x) + { + logger.debug("Exception while invoking listener " + listener, x); + } + } + + @Override + public boolean headerComplete() + { + if (!seenResponseCode) + { + // No Status header but we have other headers, assume 200 OK + notifyBegin(200, "OK"); + notifyHeaders(fields); + } + notifyHeaders(); // Return from parsing so that we can parse the content return true; } @Override public boolean content(ByteBuffer buffer) + { + notifyContent(buffer); + return false; + } + + private void notifyContent(ByteBuffer buffer) { try { - listener.onContent(headerParser.getRequest(), FCGI.StreamType.STD_OUT, buffer); + listener.onContent(request, FCGI.StreamType.STD_OUT, buffer); } catch (Throwable x) { logger.debug("Exception while invoking listener " + listener, x); } - return false; } @Override public boolean messageComplete() { - // Return from parsing so that we can parse the next headers + // Return from parsing so that we can parse the next headers or the raw content. + // No need to notify the listener because it will be done by FCGI_END_REQUEST. return true; } @@ -218,45 +277,45 @@ public class ResponseContentParser extends StreamContentParser { // TODO } + } - // Methods overridden to make them visible here - private static class FCGIHttpParser extends HttpParser + // Methods overridden to make them visible here + private static class FCGIHttpParser extends HttpParser + { + private FCGIHttpParser(ResponseHandler handler) { - private FCGIHttpParser(ResponseHandler handler) - { - super(handler, 65 * 1024, true); - reset(); - } - - @Override - public void reset() - { - super.reset(); - setState(State.HEADER); - } - - @Override - protected boolean parseHeaders(ByteBuffer buffer) - { - return super.parseHeaders(buffer); - } - - @Override - protected boolean parseContent(ByteBuffer buffer) - { - return super.parseContent(buffer); - } - - @Override - protected void setResponseStatus(int status) - { - super.setResponseStatus(status); - } + super(handler, 65 * 1024, true); + reset(); } - private enum State + @Override + public void reset() { - HEADERS, CONTENT + super.reset(); + setState(State.HEADER); + } + + @Override + protected boolean parseHeaders(ByteBuffer buffer) + { + return super.parseHeaders(buffer); + } + + @Override + protected boolean parseContent(ByteBuffer buffer) + { + return super.parseContent(buffer); + } + + @Override + protected void setResponseStatus(int status) + { + super.setResponseStatus(status); } } + + private enum State + { + HEADERS, CONTENT_MODE, RAW_CONTENT, HTTP_CONTENT + } } diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java index 10c15be663e..950cd59f747 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java @@ -80,7 +80,14 @@ public class StreamContentParser extends ContentParser @Override public void noContent() { - onEnd(); + try + { + listener.onEnd(getRequest()); + } + catch (Throwable x) + { + logger.debug("Exception while invoking listener " + listener, x); + } } protected void onContent(ByteBuffer buffer) @@ -95,16 +102,8 @@ public class StreamContentParser extends ContentParser } } - protected void onEnd() + protected void end(int request) { - try - { - listener.onEnd(getRequest()); - } - catch (Throwable x) - { - logger.debug("Exception while invoking listener " + listener, x); - } } private enum State diff --git a/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java b/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java index 7349500ecc2..157893d5a6b 100644 --- a/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java +++ b/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java @@ -154,21 +154,21 @@ public class ClientGeneratorTest final int id = 13; Generator.Result result = generator.generateRequestContent(id, content, true, null); - final AtomicInteger length = new AtomicInteger(); + final AtomicInteger totalLength = new AtomicInteger(); ServerParser parser = new ServerParser(new ServerParser.Listener.Adapter() { @Override public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) { Assert.assertEquals(id, request); - length.addAndGet(buffer.remaining()); + totalLength.addAndGet(buffer.remaining()); } @Override public void onEnd(int request) { Assert.assertEquals(id, request); - Assert.assertEquals(contentLength, length.get()); + Assert.assertEquals(contentLength, totalLength.get()); } }); diff --git a/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java b/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java index d5a43eee184..bd2f5abf763 100644 --- a/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java +++ b/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java @@ -214,7 +214,7 @@ public class ClientParserTest Generator.Result result1 = generator.generateResponseHeaders(id, code, "OK", fields, null); Generator.Result result2 = generator.generateResponseContent(id, content, true, null); - final AtomicInteger length = new AtomicInteger(); + final AtomicInteger totalLength = new AtomicInteger(); final AtomicBoolean verifier = new AtomicBoolean(); ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter() { @@ -222,14 +222,14 @@ public class ClientParserTest public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) { Assert.assertEquals(id, request); - length.addAndGet(buffer.remaining()); + totalLength.addAndGet(buffer.remaining()); } @Override public void onEnd(int request) { Assert.assertEquals(id, request); - Assert.assertEquals(contentLength, length.get()); + Assert.assertEquals(contentLength, totalLength.get()); verifier.set(true); } }); diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java index ad095fe17e7..6146d0ff85f 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java @@ -111,7 +111,7 @@ public class HttpChannelOverFCGI extends HttpChannel receiver.responseSuccess(exchange); } - protected void flush(Generator.Result result) + protected void flush(Generator.Result... result) { flusher.flush(result); } diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java index cbbbee4c506..6f2b1aca812 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java @@ -18,50 +18,56 @@ package org.eclipse.jetty.fcgi.client.http; -import java.util.regex.Pattern; +import java.io.IOException; +import java.util.Map; import org.eclipse.jetty.client.AbstractHttpClientTransport; import org.eclipse.jetty.client.HttpDestination; +import org.eclipse.jetty.client.Origin; import org.eclipse.jetty.client.api.Connection; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.fcgi.FCGI; +import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.Promise; // TODO: add parameter to tell whether use multiplex destinations or not public class HttpClientTransportOverFCGI extends AbstractHttpClientTransport { - private final Pattern uriPattern; + private final String scriptRoot; - public HttpClientTransportOverFCGI(Pattern uriPattern) + public HttpClientTransportOverFCGI(String scriptRoot) { - this(1, uriPattern); + this(Runtime.getRuntime().availableProcessors() / 2 + 1, scriptRoot); } - public HttpClientTransportOverFCGI(int selectors, Pattern uriPattern) + public HttpClientTransportOverFCGI(int selectors, String scriptRoot) { super(selectors); - this.uriPattern = uriPattern; - } - - public Pattern getURIPattern() - { - return uriPattern; + this.scriptRoot = scriptRoot; } @Override - public HttpDestination newHttpDestination(String scheme, String host, int port) + public HttpDestination newHttpDestination(Origin origin) { - return new MultiplexHttpDestinationOverFCGI(getHttpClient(), scheme, host, port); + return new MultiplexHttpDestinationOverFCGI(getHttpClient(), origin); } @Override - protected org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, HttpDestination destination) + public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map context) throws IOException { - return new HttpConnectionOverFCGI(endPoint, destination); + HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY); + HttpConnectionOverFCGI connection = new HttpConnectionOverFCGI(endPoint, destination); + @SuppressWarnings("unchecked") + Promise promise = (Promise)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY); + promise.succeeded(connection); + return connection; } - @Override - public Connection tunnel(Connection connection) + protected void customize(Request request, HttpFields fastCGIHeaders) { - HttpConnectionOverFCGI httpConnection = (HttpConnectionOverFCGI)connection; - return tunnel(httpConnection.getEndPoint(), httpConnection.getHttpDestination(), connection); + String scriptName = fastCGIHeaders.get(FCGI.Headers.SCRIPT_NAME); + fastCGIHeaders.put(FCGI.Headers.SCRIPT_FILENAME, scriptRoot + scriptName); + fastCGIHeaders.put(FCGI.Headers.DOCUMENT_ROOT, scriptRoot); } } diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java index 3e8fce96a29..81dd8f4b9ba 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java @@ -1,8 +1,7 @@ package org.eclipse.jetty.fcgi.client.http; import java.net.URI; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.util.Locale; import org.eclipse.jetty.client.HttpChannel; import org.eclipse.jetty.client.HttpContent; @@ -16,6 +15,7 @@ import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.Jetty; public class HttpSenderOverFCGI extends HttpSender { @@ -36,59 +36,66 @@ public class HttpSenderOverFCGI extends HttpSender @Override protected void sendHeaders(HttpExchange exchange, HttpContent content, Callback callback) { - Request httpRequest = exchange.getRequest(); - URI uri = httpRequest.getURI(); - HttpFields headers = httpRequest.getHeaders(); + Request request = exchange.getRequest(); + // Copy the request headers to be able to convert them properly + HttpFields headers = new HttpFields(); + for (HttpField field : request.getHeaders()) + headers.put(field); + HttpFields fcgiHeaders = new HttpFields(); - HttpField field = headers.remove(HttpHeader.AUTHORIZATION); - if (field != null) - headers.put(FCGI.Headers.AUTH_TYPE, field.getValue()); + // FastCGI headers based on the URI + URI uri = request.getURI(); + String path = uri.getPath(); + fcgiHeaders.put(FCGI.Headers.REQUEST_URI, path); + String query = uri.getQuery(); + fcgiHeaders.put(FCGI.Headers.QUERY_STRING, query == null ? "" : query); + int lastSegment = path.lastIndexOf('/'); + String scriptName = lastSegment < 0 ? path : path.substring(lastSegment); + fcgiHeaders.put(FCGI.Headers.SCRIPT_NAME, scriptName); - field = headers.remove(HttpHeader.CONTENT_LENGTH); - if (field != null) - headers.put(FCGI.Headers.CONTENT_LENGTH, field.getValue()); + // FastCGI headers based on HTTP headers + HttpField httpField = headers.remove(HttpHeader.AUTHORIZATION); + if (httpField != null) + fcgiHeaders.put(FCGI.Headers.AUTH_TYPE, httpField.getValue()); + httpField = headers.remove(HttpHeader.CONTENT_LENGTH); + fcgiHeaders.put(FCGI.Headers.CONTENT_LENGTH, httpField == null ? "" : httpField.getValue()); + httpField = headers.remove(HttpHeader.CONTENT_TYPE); + fcgiHeaders.put(FCGI.Headers.CONTENT_TYPE, httpField == null ? "" : httpField.getValue()); - field = headers.remove(HttpHeader.CONTENT_TYPE); - if (field != null) - headers.put(FCGI.Headers.CONTENT_TYPE, field.getValue()); + // FastCGI headers that are not based on HTTP headers nor URI + fcgiHeaders.put(FCGI.Headers.REQUEST_METHOD, request.getMethod()); + fcgiHeaders.put(FCGI.Headers.SERVER_PROTOCOL, request.getVersion().asString()); + fcgiHeaders.put(FCGI.Headers.GATEWAY_INTERFACE, "CGI/1.1"); + fcgiHeaders.put(FCGI.Headers.SERVER_SOFTWARE, "Jetty/" + Jetty.VERSION); + // TODO: need to pass SERVER_NAME, SERVER_ADDR, SERVER_PORT, REMOTE_ADDR, REMOTE_PORT + // TODO: if the FCGI transport is used within a ProxyServlet, they must have certain values + // TODO: for example the remote address is taken from the ProxyServlet request + // TODO: if used standalone, they must have other values. - headers.put(FCGI.Headers.GATEWAY_INTERFACE, "CGI/1.1"); - - HttpClientTransportOverFCGI transport = (HttpClientTransportOverFCGI)getHttpChannel().getHttpDestination().getHttpClient().getTransport(); - Pattern uriPattern = transport.getURIPattern(); - Matcher matcher = uriPattern.matcher(uri.toString()); - - // TODO: what if the URI does not match ? Here is kinda too late to abort the request ? - // TODO: perhaps this works in conjuntion with the ProxyServlet, which is mapped to the same URI regexp - // TODO: so that if the call arrives here, we are sure it matches. - -// headers.put(Headers.PATH_INFO, ???); -// headers.put(Headers.PATH_TRANSLATED, ???); - - headers.put(FCGI.Headers.QUERY_STRING, uri.getQuery()); - - // TODO: the fields below are probably provided by ProxyServlet as X-Forwarded-* -// headers.put(Headers.REMOTE_ADDR, ???); -// headers.put(Headers.REMOTE_HOST, ???); -// headers.put(Headers.REMOTE_USER, ???); - - headers.put(FCGI.Headers.REQUEST_METHOD, httpRequest.getMethod()); - - headers.put(FCGI.Headers.REQUEST_URI, uri.toString()); - - headers.put(FCGI.Headers.SERVER_PROTOCOL, httpRequest.getVersion().asString()); - - // TODO: translate remaining HTTP header into the HTTP_* format - - int request = getHttpChannel().getRequest(); - boolean hasContent = content.hasContent(); - Generator.Result result = generator.generateRequestHeaders(request, headers, - hasContent ? callback : new Callback.Adapter()); - getHttpChannel().flush(result); - if (!hasContent) + // Translate remaining HTTP header into the HTTP_* format + for (HttpField field : headers) { - result = generator.generateRequestContent(request, null, true, callback); - getHttpChannel().flush(result); + String name = field.getName(); + String fcgiName = "HTTP_" + name.replaceAll("-", "_").toUpperCase(Locale.ENGLISH); + fcgiHeaders.add(fcgiName, field.getValue()); + } + + // Give a chance to the transport implementation to customize the FastCGI headers + HttpClientTransportOverFCGI transport = (HttpClientTransportOverFCGI)getHttpChannel().getHttpDestination().getHttpClient().getTransport(); + transport.customize(request, fcgiHeaders); + + int id = getHttpChannel().getRequest(); + boolean hasContent = content.hasContent(); + Generator.Result headersResult = generator.generateRequestHeaders(id, fcgiHeaders, + hasContent ? callback : new Callback.Adapter()); + if (hasContent) + { + getHttpChannel().flush(headersResult); + } + else + { + Generator.Result noContentResult = generator.generateRequestContent(id, null, true, callback); + getHttpChannel().flush(headersResult, noContentResult); } } diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/MultiplexHttpDestinationOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/MultiplexHttpDestinationOverFCGI.java index 171b06c5e43..3a823c4992b 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/MultiplexHttpDestinationOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/MultiplexHttpDestinationOverFCGI.java @@ -21,12 +21,13 @@ package org.eclipse.jetty.fcgi.client.http; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpExchange; import org.eclipse.jetty.client.MultiplexHttpDestination; +import org.eclipse.jetty.client.Origin; public class MultiplexHttpDestinationOverFCGI extends MultiplexHttpDestination { - public MultiplexHttpDestinationOverFCGI(HttpClient client, String scheme, String host, int port) + public MultiplexHttpDestinationOverFCGI(HttpClient client, Origin origin) { - super(client, scheme, host, port); + super(client, origin); } @Override diff --git a/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/AbstractHttpClientServerTest.java b/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/AbstractHttpClientServerTest.java index c58ebcf7c87..7dc0e497bda 100644 --- a/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/AbstractHttpClientServerTest.java +++ b/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/AbstractHttpClientServerTest.java @@ -55,7 +55,7 @@ public abstract class AbstractHttpClientServerTest QueuedThreadPool executor = new QueuedThreadPool(); executor.setName(executor.getName() + "-client"); - client = new HttpClient(new HttpClientTransportOverFCGI(), null); + client = new HttpClient(new HttpClientTransportOverFCGI(""), null); client.setExecutor(executor); client.start(); } diff --git a/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/ExternalFastCGIServerTest.java b/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/ExternalFastCGIServerTest.java new file mode 100644 index 00000000000..496134e71a5 --- /dev/null +++ b/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/ExternalFastCGIServerTest.java @@ -0,0 +1,52 @@ +// +// ======================================================================== +// 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.client.http; + +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +public class ExternalFastCGIServerTest +{ + @Test + @Ignore("Relies on an external server") + public void testExternalFastCGIServer() throws Exception + { + // Assume a FastCGI server is listening on port 9000 + + HttpClient client = new HttpClient(new HttpClientTransportOverFCGI("/var/www/php-fcgi"), null); + client.start(); + + ContentResponse response = client.newRequest("localhost", 9000) + .path("/index.php") + .timeout(5, TimeUnit.SECONDS) + .send(); + + Assert.assertEquals(200, response.getStatus()); + + Files.write(Paths.get(System.getProperty("java.io.tmpdir"), "fcgi_response.html"), response.getContent(), StandardOpenOption.CREATE, StandardOpenOption.WRITE); + } +} From b08e415ca0d2123e11638b5570476c1617ae6534 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 22 Oct 2013 19:20:40 +0200 Subject: [PATCH 16/43] Implemented FastCGIProxyServlet and handling of idle timeouts. --- fcgi-core/pom.xml | 8 +- .../eclipse/jetty/fcgi/generator/Flusher.java | 17 +++++ fcgi-http-client-transport/pom.xml | 4 +- .../fcgi/client/http/HttpChannelOverFCGI.java | 43 +++++++++-- .../http/HttpClientTransportOverFCGI.java | 1 + .../client/http/HttpConnectionOverFCGI.java | 30 ++++++-- .../fcgi/client/http/HttpSenderOverFCGI.java | 4 - .../http/AbstractHttpClientServerTest.java | 1 - .../fcgi/client/http/HttpClientTest.java | 75 +++++++++++++++--- fcgi-proxy/pom.xml | 47 ++++++++++++ .../jetty/fcgi/proxy/FastCGIProxyServlet.java | 76 +++++++++++++++++++ .../jetty/fcgi/proxy/FastCGIProxyServer.java | 44 +++++++++++ fcgi-server/pom.xml | 4 +- pom.xml | 8 +- 14 files changed, 324 insertions(+), 38 deletions(-) create mode 100644 fcgi-proxy/pom.xml create mode 100644 fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServlet.java create mode 100644 fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServer.java diff --git a/fcgi-core/pom.xml b/fcgi-core/pom.xml index 31a80e9e730..97c3964f8b8 100644 --- a/fcgi-core/pom.xml +++ b/fcgi-core/pom.xml @@ -5,7 +5,7 @@ org.eclipse.jetty.fcgi fcgi-parent - 9.1.0-SNAPSHOT + 1.0.0-SNAPSHOT 4.0.0 @@ -20,17 +20,17 @@ org.eclipse.jetty jetty-util - ${project.version} + ${jetty-version} org.eclipse.jetty jetty-io - ${project.version} + ${jetty-version} org.eclipse.jetty jetty-http - ${project.version} + ${jetty-version} diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java index 075617483ec..e4b9b65c5eb 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java @@ -19,6 +19,7 @@ package org.eclipse.jetty.fcgi.generator; import java.nio.ByteBuffer; +import java.util.ArrayList; import java.util.List; import java.util.Queue; @@ -108,6 +109,22 @@ public class Flusher if (active != null) active.failed(x); active = null; + + List pending = new ArrayList<>(); + synchronized (queue) + { + while (true) + { + Generator.Result result = queue.poll(); + if (result != null) + pending.add(result); + else + break; + } + } + for (Generator.Result result : pending) + result.failed(x); + super.failed(x); } } diff --git a/fcgi-http-client-transport/pom.xml b/fcgi-http-client-transport/pom.xml index e8848e11501..9cc7fec0111 100644 --- a/fcgi-http-client-transport/pom.xml +++ b/fcgi-http-client-transport/pom.xml @@ -5,7 +5,7 @@ org.eclipse.jetty.fcgi fcgi-parent - 9.1.0-SNAPSHOT + 1.0.0-SNAPSHOT 4.0.0 @@ -21,7 +21,7 @@ org.eclipse.jetty jetty-client - ${project.version} + ${jetty-version} diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java index 6146d0ff85f..0d40d6f6791 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java @@ -19,14 +19,15 @@ package org.eclipse.jetty.fcgi.client.http; import java.nio.ByteBuffer; +import java.util.concurrent.TimeoutException; import org.eclipse.jetty.client.HttpChannel; -import org.eclipse.jetty.client.HttpDestination; import org.eclipse.jetty.client.HttpExchange; import org.eclipse.jetty.fcgi.generator.Flusher; import org.eclipse.jetty.fcgi.generator.Generator; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.io.IdleTimeout; public class HttpChannelOverFCGI extends HttpChannel { @@ -36,13 +37,15 @@ public class HttpChannelOverFCGI extends HttpChannel private final HttpReceiverOverFCGI receiver; private HttpVersion version; - public HttpChannelOverFCGI(HttpDestination destination, Flusher flusher, int request) + public HttpChannelOverFCGI(final HttpConnectionOverFCGI connection, Flusher flusher, int request, long idleTimeout) { - super(destination); + super(connection.getHttpDestination()); this.flusher = flusher; this.request = request; this.sender = new HttpSenderOverFCGI(this); this.receiver = new HttpReceiverOverFCGI(this); + IdleTimeout idle = new FCGIIdleTimeout(connection); + idle.setIdleTimeout(idleTimeout); } protected int getRequest() @@ -64,13 +67,14 @@ public class HttpChannelOverFCGI extends HttpChannel @Override public void proceed(HttpExchange exchange, boolean proceed) { - throw new UnsupportedOperationException(); + sender.proceed(exchange, proceed); } @Override public boolean abort(Throwable cause) { - throw new UnsupportedOperationException(); + sender.abort(cause); + return receiver.abort(cause); } protected void responseBegin(int code, String reason) @@ -111,8 +115,33 @@ public class HttpChannelOverFCGI extends HttpChannel receiver.responseSuccess(exchange); } - protected void flush(Generator.Result... result) + protected void flush(Generator.Result... results) { - flusher.flush(result); + flusher.flush(results); + } + + private class FCGIIdleTimeout extends IdleTimeout + { + private final HttpConnectionOverFCGI connection; + + public FCGIIdleTimeout(HttpConnectionOverFCGI connection) + { + super(connection.getHttpDestination().getHttpClient().getScheduler()); + this.connection = connection; + } + + @Override + protected void onIdleExpired(TimeoutException timeout) + { + LOG.debug("Idle timeout for request {}", request); + abort(timeout); + close(); + } + + @Override + public boolean isOpen() + { + return connection.getEndPoint().isOpen(); + } } } diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java index 6f2b1aca812..f3983e5a03f 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java @@ -58,6 +58,7 @@ public class HttpClientTransportOverFCGI extends AbstractHttpClientTransport { HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY); HttpConnectionOverFCGI connection = new HttpConnectionOverFCGI(endPoint, destination); + LOG.debug("Created {}", connection); @SuppressWarnings("unchecked") Promise promise = (Promise)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY); promise.succeeded(connection); diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java index b0fed4dce00..75a9d3d3639 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java @@ -18,10 +18,12 @@ package org.eclipse.jetty.fcgi.client.http; +import java.io.EOFException; import java.nio.ByteBuffer; import java.util.LinkedList; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeoutException; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpConnection; @@ -134,12 +136,24 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec private void shutdown() { - getEndPoint().shutdownOutput(); + for (HttpChannelOverFCGI channel : channels.values()) + channel.abort(new EOFException()); + close(); + } + + @Override + protected boolean onReadTimeout() + { + for (HttpChannelOverFCGI channel : channels.values()) + channel.abort(new TimeoutException()); + close(); + return false; } @Override public void close() { + getHttpDestination().close(this); getEndPoint().shutdownOutput(); LOG.debug("{} oshut", this); getEndPoint().close(); @@ -168,9 +182,9 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec @Override public String toString() { - return String.format("%s@%x(l:%s <-> r:%s)", - HttpConnection.class.getSimpleName(), - hashCode(), + return String.format("%s@%h(l:%s <-> r:%s)", + getClass().getSimpleName(), + this, getEndPoint().getLocalAddress(), getEndPoint().getRemoteAddress()); } @@ -190,7 +204,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec // FCGI may be multiplexed, so create one channel for each request. int id = acquireRequest(); - HttpChannelOverFCGI channel = new HttpChannelOverFCGI(getHttpDestination(), flusher, id); + HttpChannelOverFCGI channel = new HttpChannelOverFCGI(HttpConnectionOverFCGI.this, flusher, id, request.getIdleTimeout()); channels.put(id, channel); channel.associate(exchange); channel.send(); @@ -201,6 +215,12 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec { HttpConnectionOverFCGI.this.close(); } + + @Override + public String toString() + { + return HttpConnectionOverFCGI.this.toString(); + } } private class ResponseListener implements ClientParser.Listener diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java index 81dd8f4b9ba..086a71b0825 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java @@ -67,10 +67,6 @@ public class HttpSenderOverFCGI extends HttpSender fcgiHeaders.put(FCGI.Headers.SERVER_PROTOCOL, request.getVersion().asString()); fcgiHeaders.put(FCGI.Headers.GATEWAY_INTERFACE, "CGI/1.1"); fcgiHeaders.put(FCGI.Headers.SERVER_SOFTWARE, "Jetty/" + Jetty.VERSION); - // TODO: need to pass SERVER_NAME, SERVER_ADDR, SERVER_PORT, REMOTE_ADDR, REMOTE_PORT - // TODO: if the FCGI transport is used within a ProxyServlet, they must have certain values - // TODO: for example the remote address is taken from the ProxyServlet request - // TODO: if used standalone, they must have other values. // Translate remaining HTTP header into the HTTP_* format for (HttpField field : headers) diff --git a/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/AbstractHttpClientServerTest.java b/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/AbstractHttpClientServerTest.java index 7dc0e497bda..ad6b465c4a6 100644 --- a/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/AbstractHttpClientServerTest.java +++ b/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/AbstractHttpClientServerTest.java @@ -46,7 +46,6 @@ public abstract class AbstractHttpClientServerTest ServerFCGIConnectionFactory fcgiConnectionFactory = new ServerFCGIConnectionFactory(new HttpConfiguration()); connector = new ServerConnector(server, fcgiConnectionFactory); - connector.setPort(9000); server.addConnector(connector); server.setHandler(handler); diff --git a/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/HttpClientTest.java b/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/HttpClientTest.java index fd5b0b1a76d..7ba69ff172c 100644 --- a/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/HttpClientTest.java +++ b/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/HttpClientTest.java @@ -18,6 +18,7 @@ package org.eclipse.jetty.fcgi.client.http; +import java.io.EOFException; import java.io.IOException; import java.net.URI; import java.net.URLEncoder; @@ -47,7 +48,7 @@ import org.junit.Test; public class HttpClientTest extends AbstractHttpClientServerTest { @Test - public void test_GET_ResponseWithoutContent() throws Exception + public void testGETResponseWithoutContent() throws Exception { start(new EmptyServerHandler()); @@ -60,7 +61,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest } @Test - public void test_GET_ResponseWithContent() throws Exception + public void testGETResponseWithContent() throws Exception { final byte[] data = new byte[]{0, 1, 2, 3, 4, 5, 6, 7}; start(new AbstractHandler() @@ -84,7 +85,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest } @Test - public void test_GET_WithParameters_ResponseWithContent() throws Exception + public void testGETWithParametersResponseWithContent() throws Exception { final String paramName1 = "a"; final String paramName2 = "b"; @@ -116,7 +117,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest } @Test - public void test_GET_WithParametersMultiValued_ResponseWithContent() throws Exception + public void testGETWithParametersMultiValuedResponseWithContent() throws Exception { final String paramName1 = "a"; final String paramName2 = "b"; @@ -152,7 +153,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest } @Test - public void test_POST_WithParameters() throws Exception + public void testPOSTWithParameters() throws Exception { final String paramName = "a"; final String paramValue = "\u20AC"; @@ -183,7 +184,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest } @Test - public void test_PUT_WithParameters() throws Exception + public void testPUTWithParameters() throws Exception { final String paramName = "a"; final String paramValue = "\u20AC"; @@ -215,7 +216,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest } @Test - public void test_POST_WithParameters_WithContent() throws Exception + public void testPOSTWithParametersWithContent() throws Exception { final byte[] content = {0, 1, 2, 3}; final String paramName = "a"; @@ -248,7 +249,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest } @Test - public void test_POST_WithContent_NotifiesRequestContentListener() throws Exception + public void testPOSTWithContentNotifiesRequestContentListener() throws Exception { final byte[] content = {0, 1, 2, 3}; start(new EmptyServerHandler()); @@ -274,7 +275,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest } @Test - public void test_POST_WithContent_TracksProgress() throws Exception + public void testPOSTWithContentTracksProgress() throws Exception { start(new EmptyServerHandler()); @@ -301,7 +302,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest } @Test - public void test_GZIP_ContentEncoding() throws Exception + public void testGZIPContentEncoding() throws Exception { final byte[] data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; start(new AbstractHandler() @@ -328,7 +329,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest @Slow @Test - public void test_Request_IdleTimeout() throws Exception + public void testRequestIdleTimeout() throws Exception { final long idleTimeout = 1000; start(new AbstractHandler() @@ -373,6 +374,56 @@ public class HttpClientTest extends AbstractHttpClientServerTest Assert.assertNotNull(response); Assert.assertEquals(200, response.getStatus()); } + + @Test + public void testConnectionIdleTimeout() throws Exception + { + final long idleTimeout = 1000; + start(new AbstractHandler() + { + @Override + public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + try + { + baseRequest.setHandled(true); + TimeUnit.MILLISECONDS.sleep(2 * idleTimeout); + } + catch (InterruptedException x) + { + throw new ServletException(x); + } + } + }); + + connector.setIdleTimeout(idleTimeout); + + try + { + client.newRequest("localhost", connector.getLocalPort()) + .scheme(scheme) + .idleTimeout(4 * idleTimeout, TimeUnit.MILLISECONDS) + .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS) + .send(); + Assert.fail(); + } + catch (ExecutionException x) + { + Assert.assertTrue(x.getCause() instanceof EOFException); + } + + connector.setIdleTimeout(5 * idleTimeout); + + // Make another request to be sure the connection is recreated + ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + .scheme(scheme) + .idleTimeout(4 * idleTimeout, TimeUnit.MILLISECONDS) + .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS) + .send(); + + Assert.assertNotNull(response); + Assert.assertEquals(200, response.getStatus()); + } @Test public void testSendToIPv6Address() throws Exception @@ -389,7 +440,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest } @Test - public void test_HEAD_With_ResponseContentLength() throws Exception + public void testHEADWithResponseContentLength() throws Exception { final int length = 1024; start(new AbstractHandler() diff --git a/fcgi-proxy/pom.xml b/fcgi-proxy/pom.xml new file mode 100644 index 00000000000..81f1cbd8c87 --- /dev/null +++ b/fcgi-proxy/pom.xml @@ -0,0 +1,47 @@ + + + + fcgi-parent + org.eclipse.jetty.fcgi + 1.0.0-SNAPSHOT + + + 4.0.0 + fcgi-proxy + Jetty :: FastCGI :: Proxy + + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + org.eclipse.jetty.fcgi + fcgi-http-client-transport + ${project.version} + + + org.eclipse.jetty + jetty-proxy + ${jetty-version} + + + + org.eclipse.jetty + jetty-server + ${jetty-version} + test + + + org.eclipse.jetty + jetty-servlet + ${jetty-version} + test + + + + diff --git a/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServlet.java b/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServlet.java new file mode 100644 index 00000000000..1a7bccbc167 --- /dev/null +++ b/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServlet.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.proxy; + +import javax.servlet.ServletConfig; +import javax.servlet.http.HttpServletRequest; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.fcgi.FCGI; +import org.eclipse.jetty.fcgi.client.http.HttpClientTransportOverFCGI; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.proxy.ProxyServlet; + +public class FastCGIProxyServlet extends ProxyServlet.Transparent +{ + public static final String SCRIPT_ROOT_INIT_PARAM = "scriptRoot"; + private static final String REMOTE_ADDR_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".remoteAddr"; + private static final String REMOTE_PORT_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".remotePort"; + private static final String SERVER_ADDR_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".serverAddr"; + private static final String SERVER_PORT_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".serverPort"; + + @Override + protected HttpClient newHttpClient() + { + ServletConfig config = getServletConfig(); + String scriptRoot = config.getInitParameter(SCRIPT_ROOT_INIT_PARAM); + if (scriptRoot == null) + throw new IllegalArgumentException("Mandatory parameter '" + SCRIPT_ROOT_INIT_PARAM + "' not configured"); + return new HttpClient(new ProxyHttpClientTransportOverFCGI(scriptRoot), null); + } + + @Override + protected void customizeProxyRequest(Request proxyRequest, HttpServletRequest request) + { + proxyRequest.attribute(REMOTE_ADDR_ATTRIBUTE, request.getRemoteAddr()); + proxyRequest.attribute(REMOTE_PORT_ATTRIBUTE, String.valueOf(request.getRemotePort())); + proxyRequest.attribute(SERVER_ADDR_ATTRIBUTE, request.getLocalAddr()); + proxyRequest.attribute(SERVER_PORT_ATTRIBUTE, String.valueOf(request.getLocalPort())); + super.customizeProxyRequest(proxyRequest, request); + } + + private static class ProxyHttpClientTransportOverFCGI extends HttpClientTransportOverFCGI + { + public ProxyHttpClientTransportOverFCGI(String scriptRoot) + { + super(scriptRoot); + } + + @Override + protected void customize(Request request, HttpFields fastCGIHeaders) + { + super.customize(request, fastCGIHeaders); + fastCGIHeaders.put(FCGI.Headers.REMOTE_ADDR, (String)request.getAttributes().get(REMOTE_ADDR_ATTRIBUTE)); + fastCGIHeaders.put(FCGI.Headers.REMOTE_PORT, (String)request.getAttributes().get(REMOTE_PORT_ATTRIBUTE)); + fastCGIHeaders.put(FCGI.Headers.SERVER_ADDR, (String)request.getAttributes().get(SERVER_ADDR_ATTRIBUTE)); + fastCGIHeaders.put(FCGI.Headers.SERVER_PORT, (String)request.getAttributes().get(SERVER_PORT_ATTRIBUTE)); + } + } +} diff --git a/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServer.java b/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServer.java new file mode 100644 index 00000000000..75d3e6c8f3e --- /dev/null +++ b/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServer.java @@ -0,0 +1,44 @@ +// +// ======================================================================== +// 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.proxy; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; + +public class FastCGIProxyServer +{ + public static void main(String[] args) throws Exception + { + Server server = new Server(); + ServerConnector connector = new ServerConnector(server); + connector.setPort(8080); + server.addConnector(connector); + + ServletContextHandler context = new ServletContextHandler(server, "/"); + ServletHolder servletHolder = new ServletHolder(FastCGIProxyServlet.class); + servletHolder.setInitParameter(FastCGIProxyServlet.SCRIPT_ROOT_INIT_PARAM, "/var/www/php-fcgi"); + servletHolder.setInitParameter("proxyTo", "http://localhost:9000/"); + servletHolder.setInitParameter("prefix", "/"); + context.addServlet(servletHolder, "/*"); + + server.start(); + } +} diff --git a/fcgi-server/pom.xml b/fcgi-server/pom.xml index 8ea16c7a8b5..ff3ad52e4fb 100644 --- a/fcgi-server/pom.xml +++ b/fcgi-server/pom.xml @@ -5,7 +5,7 @@ org.eclipse.jetty.fcgi fcgi-parent - 9.1.0-SNAPSHOT + 1.0.0-SNAPSHOT 4.0.0 @@ -21,7 +21,7 @@ org.eclipse.jetty jetty-server - ${project.version} + ${jetty-version} diff --git a/pom.xml b/pom.xml index 7a21536967c..b22488f07bb 100644 --- a/pom.xml +++ b/pom.xml @@ -11,13 +11,19 @@ 4.0.0 org.eclipse.jetty.fcgi fcgi-parent + 1.0.0-SNAPSHOT pom Jetty :: FastCGI + + 9.1.0-SNAPSHOT + + fcgi-core fcgi-http-client-transport fcgi-server + fcgi-proxy @@ -40,7 +46,6 @@ ---> org.apache.felix maven-bundle-plugin @@ -69,6 +74,7 @@ +--> From 24d4a971d24f6b429e385c38a446ea845bac2a92 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Wed, 23 Oct 2013 16:00:43 +0200 Subject: [PATCH 17/43] Fixed code and tests, first clean build. --- .../fcgi/parser/ResponseContentParser.java | 6 ++ .../jetty/fcgi/parser/ClientParserTest.java | 28 +++---- .../client/http/HttpConnectionOverFCGI.java | 5 +- .../fcgi/client/http/HttpSenderOverFCGI.java | 4 +- .../fcgi/server/HttpChannelOverFCGI.java | 77 ++++++------------- .../fcgi/server/HttpTransportOverFCGI.java | 50 ++++++++++-- 6 files changed, 96 insertions(+), 74 deletions(-) diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java index 30e2427a3b9..f25b7ba7603 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java @@ -45,6 +45,12 @@ public class ResponseContentParser extends StreamContentParser this.listener = listener; } + @Override + public void noContent() + { + // Does nothing, since for responses the end of content is signaled via a FCGI_END_REQUEST frame + } + @Override protected void onContent(ByteBuffer buffer) { diff --git a/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java b/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java index bd2f5abf763..6aae78333db 100644 --- a/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java +++ b/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java @@ -40,15 +40,15 @@ public class ClientParserTest final int id = 13; HttpFields fields = new HttpFields(); - final String statusName = "Status"; - final int code = 200; + final int statusCode = 200; + final String statusMessage = "OK"; final String contentTypeName = "Content-Type"; final String contentTypeValue = "text/html;charset=utf-8"; fields.put(contentTypeName, contentTypeValue); ByteBufferPool byteBufferPool = new MappedByteBufferPool(); ServerGenerator generator = new ServerGenerator(byteBufferPool); - Generator.Result result = generator.generateResponseHeaders(id, code, "OK", fields, null); + Generator.Result result = generator.generateResponseHeaders(id, statusCode, statusMessage, fields, null); // Use the fundamental theorem of arithmetic to test the results. // This way we know onHeader() has been called the right number of @@ -61,20 +61,26 @@ public class ClientParserTest final AtomicInteger params = new AtomicInteger(1); ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter() { + @Override + public void onBegin(int request, int code, String reason) + { + Assert.assertEquals(statusCode, code); + Assert.assertEquals(statusMessage, reason); + params.set(params.get() * primes[0]); + } + @Override public void onHeader(int request, HttpField field) { Assert.assertEquals(id, request); switch (field.getName()) { - case statusName: - Assert.assertTrue(field.getValue().startsWith(String.valueOf(code))); - params.set(params.get() * primes[0]); - break; case contentTypeName: Assert.assertEquals(contentTypeValue, field.getValue()); params.set(params.get() * primes[1]); break; + default: + break; } } @@ -100,15 +106,11 @@ public class ClientParserTest { final int id = 13; HttpFields fields = new HttpFields(); - - final int code = 200; - final String contentTypeName = "Content-Length"; - final String contentTypeValue = "0"; - fields.put(contentTypeName, contentTypeValue); + fields.put("Content-Length", "0"); ByteBufferPool byteBufferPool = new MappedByteBufferPool(); ServerGenerator generator = new ServerGenerator(byteBufferPool); - Generator.Result result1 = generator.generateResponseHeaders(id, code, "OK", fields, null); + Generator.Result result1 = generator.generateResponseHeaders(id, 200, "OK", fields, null); Generator.Result result2 = generator.generateResponseContent(id, null, true, null); final AtomicInteger verifier = new AtomicInteger(); diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java index 75a9d3d3639..f0f800ba548 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java @@ -136,9 +136,12 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec private void shutdown() { + // First close then abort, to be sure that the + // connection cannot be reused from an onFailure() + // handler or by blocking code waiting for completion. + close(); for (HttpChannelOverFCGI channel : channels.values()) channel.abort(new EOFException()); - close(); } @Override diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java index 086a71b0825..9d16d5c3eca 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java @@ -45,9 +45,9 @@ public class HttpSenderOverFCGI extends HttpSender // FastCGI headers based on the URI URI uri = request.getURI(); - String path = uri.getPath(); + String path = uri.getRawPath(); fcgiHeaders.put(FCGI.Headers.REQUEST_URI, path); - String query = uri.getQuery(); + String query = uri.getRawQuery(); fcgiHeaders.put(FCGI.Headers.QUERY_STRING, query == null ? "" : query); int lastSegment = path.lastIndexOf('/'); String scriptName = lastSegment < 0 ? path : path.substring(lastSegment); diff --git a/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java b/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java index 1f9c060ef7e..dfdcc75b72c 100644 --- a/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java +++ b/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java @@ -19,7 +19,7 @@ package org.eclipse.jetty.fcgi.server; import java.nio.ByteBuffer; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -28,6 +28,7 @@ import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jetty.fcgi.FCGI; import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.server.Connector; @@ -42,12 +43,12 @@ public class HttpChannelOverFCGI extends HttpChannel { private static final Logger LOG = Log.getLogger(HttpChannelOverFCGI.class); + private final List fields = new ArrayList<>(); private final Dispatcher dispatcher; private String method; - private String uri; + private String path; + private String query; private String version; - private boolean started; - private List fields; public HttpChannelOverFCGI(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport, HttpInput input) { @@ -58,57 +59,37 @@ public class HttpChannelOverFCGI extends HttpChannel protected void header(HttpField field) { if (FCGI.Headers.REQUEST_METHOD.equalsIgnoreCase(field.getName())) - { method = field.getValue(); - if (uri != null && version != null) - startRequest(); - } else if (FCGI.Headers.REQUEST_URI.equalsIgnoreCase(field.getName())) - { - uri = field.getValue(); - if (method != null && version != null) - startRequest(); - } + path = field.getValue(); + else if (FCGI.Headers.QUERY_STRING.equalsIgnoreCase(field.getName())) + query = field.getValue(); else if (FCGI.Headers.SERVER_PROTOCOL.equalsIgnoreCase(field.getName())) - { version = field.getValue(); - if (method != null && uri != null) - startRequest(); - } else - { - if (started) - { - resumeHeaders(); - convertHeader(field); - } - else - { - if (fields == null) - fields = new ArrayList<>(); - fields.add(field); - } - } + fields.add(field); } - private void startRequest() + @Override + public boolean headerComplete() { - started = true; - startRequest(null, method, ByteBuffer.wrap(uri.getBytes(Charset.forName("UTF-8"))), HttpVersion.fromString(version)); - resumeHeaders(); - } + String uri = path; + if (query != null && query.length() > 0) + uri += "?" + query; + startRequest(HttpMethod.fromString(method), method, ByteBuffer.wrap(uri.getBytes(StandardCharsets.UTF_8)), + HttpVersion.fromString(version)); - private void resumeHeaders() - { - if (fields != null) + for (HttpField fcgiField : fields) { - for (HttpField field : fields) - convertHeader(field); - fields = null; + HttpField httpField = convertHeader(fcgiField); + if (httpField != null) + parsedHeader(httpField); } + + return super.headerComplete(); } - private void convertHeader(HttpField field) + private HttpField convertHeader(HttpField field) { String name = field.getName(); if (name.startsWith("HTTP_")) @@ -124,17 +105,9 @@ public class HttpChannelOverFCGI extends HttpChannel httpName.append(Character.toUpperCase(part.charAt(0))); httpName.append(part.substring(1).toLowerCase(Locale.ENGLISH)); } - field = new HttpField(httpName.toString(), field.getValue()); + return new HttpField(httpName.toString(), field.getValue()); } - parsedHeader(field); - } - - @Override - public boolean headerComplete() - { - boolean result = super.headerComplete(); - started = false; - return result; + return null; } protected void dispatch() diff --git a/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java b/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java index 26166fa8715..c12d351e668 100644 --- a/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java +++ b/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java @@ -26,6 +26,7 @@ import org.eclipse.jetty.fcgi.generator.ServerGenerator; import org.eclipse.jetty.http.HttpGenerator; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.server.HttpTransport; +import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; public class HttpTransportOverFCGI implements HttpTransport @@ -33,6 +34,7 @@ public class HttpTransportOverFCGI implements HttpTransport private final ServerGenerator generator; private final Flusher flusher; private final int request; + private volatile boolean head; public HttpTransportOverFCGI(ByteBufferPool byteBufferPool, Flusher flusher, int request) { @@ -44,17 +46,53 @@ public class HttpTransportOverFCGI implements HttpTransport @Override public void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback) { - Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(), - info.getHttpFields(), new Callback.Adapter()); - Generator.Result contentResult = generator.generateResponseContent(request, content, lastContent, callback); - flusher.flush(headersResult, contentResult); + boolean head = this.head = info.isHead(); + if (head) + { + if (lastContent) + { + Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(), + info.getHttpFields(), new Callback.Adapter()); + Generator.Result contentResult = generator.generateResponseContent(request, BufferUtil.EMPTY_BUFFER, lastContent, callback); + flusher.flush(headersResult, contentResult); + } + else + { + Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(), + info.getHttpFields(), callback); + flusher.flush(headersResult); + } + } + else + { + Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(), + info.getHttpFields(), new Callback.Adapter()); + Generator.Result contentResult = generator.generateResponseContent(request, content, lastContent, callback); + flusher.flush(headersResult, contentResult); + } } @Override public void send(ByteBuffer content, boolean lastContent, Callback callback) { - Generator.Result result = generator.generateResponseContent(request, content, lastContent, callback); - flusher.flush(result); + if (head) + { + if (lastContent) + { + Generator.Result result = generator.generateResponseContent(request, BufferUtil.EMPTY_BUFFER, lastContent, callback); + flusher.flush(result); + } + else + { + // Skip content generation + callback.succeeded(); + } + } + else + { + Generator.Result result = generator.generateResponseContent(request, content, lastContent, callback); + flusher.flush(result); + } } @Override From 415fe749c58d35ba868373593b2f0ad6b1b50f01 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 25 Oct 2013 10:51:23 +0200 Subject: [PATCH 18/43] Fixed idle timeout, canceling the timeout task when the exchange completes. --- .../fcgi/client/http/HttpChannelOverFCGI.java | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java index 0d40d6f6791..dfee69eb232 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java @@ -23,6 +23,7 @@ import java.util.concurrent.TimeoutException; import org.eclipse.jetty.client.HttpChannel; import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.fcgi.generator.Flusher; import org.eclipse.jetty.fcgi.generator.Generator; import org.eclipse.jetty.http.HttpField; @@ -35,6 +36,7 @@ public class HttpChannelOverFCGI extends HttpChannel private final int request; private final HttpSenderOverFCGI sender; private final HttpReceiverOverFCGI receiver; + private final FCGIIdleTimeout idle; private HttpVersion version; public HttpChannelOverFCGI(final HttpConnectionOverFCGI connection, Flusher flusher, int request, long idleTimeout) @@ -44,8 +46,7 @@ public class HttpChannelOverFCGI extends HttpChannel this.request = request; this.sender = new HttpSenderOverFCGI(this); this.receiver = new HttpReceiverOverFCGI(this); - IdleTimeout idle = new FCGIIdleTimeout(connection); - idle.setIdleTimeout(idleTimeout); + this.idle = new FCGIIdleTimeout(connection, idleTimeout); } protected int getRequest() @@ -115,6 +116,13 @@ public class HttpChannelOverFCGI extends HttpChannel receiver.responseSuccess(exchange); } + @Override + public void exchangeTerminated(Result result) + { + super.exchangeTerminated(result); + idle.close(); + } + protected void flush(Generator.Result... results) { flusher.flush(results); @@ -124,10 +132,11 @@ public class HttpChannelOverFCGI extends HttpChannel { private final HttpConnectionOverFCGI connection; - public FCGIIdleTimeout(HttpConnectionOverFCGI connection) + public FCGIIdleTimeout(HttpConnectionOverFCGI connection, long idleTimeout) { super(connection.getHttpDestination().getHttpClient().getScheduler()); this.connection = connection; + setIdleTimeout(idleTimeout); } @Override @@ -135,7 +144,6 @@ public class HttpChannelOverFCGI extends HttpChannel { LOG.debug("Idle timeout for request {}", request); abort(timeout); - close(); } @Override @@ -143,5 +151,12 @@ public class HttpChannelOverFCGI extends HttpChannel { return connection.getEndPoint().isOpen(); } + + // Overridden for visibility + @Override + protected void close() + { + super.close(); + } } } From 7fe7c68e3b8ebee3a344b2d3fd6ebc2be7cf0f35 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 25 Oct 2013 10:53:37 +0200 Subject: [PATCH 19/43] Fixed values for REQUEST_URI, DOCUMENT_URI and SERVER_NAME FastCGI fields. Added Drupal and WordPress working examples. --- .../java/org/eclipse/jetty/fcgi/FCGI.java | 1 + .../http/HttpClientTransportOverFCGI.java | 2 - .../fcgi/client/http/HttpSenderOverFCGI.java | 6 +- .../fcgi/client/http/HttpClientTest.java | 32 ++++++ fcgi-proxy/pom.xml | 6 ++ .../jetty/fcgi/proxy/FastCGIProxyServlet.java | 41 ++++++- ...ver.java => DrupalFastCGIProxyServer.java} | 28 +++-- .../proxy/WordPressFastCGIProxyServer.java | 102 ++++++++++++++++++ .../test/resources/jetty-logging.properties | 3 + 9 files changed, 204 insertions(+), 17 deletions(-) rename fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/{FastCGIProxyServer.java => DrupalFastCGIProxyServer.java} (57%) create mode 100644 fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/WordPressFastCGIProxyServer.java create mode 100644 fcgi-proxy/src/test/resources/jetty-logging.properties diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/FCGI.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/FCGI.java index 36ec33b5962..14da7aab8f9 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/FCGI.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/FCGI.java @@ -112,6 +112,7 @@ public class FCGI public static final String CONTENT_LENGTH = "CONTENT_LENGTH"; public static final String CONTENT_TYPE = "CONTENT_TYPE"; public static final String DOCUMENT_ROOT = "DOCUMENT_ROOT"; + public static final String DOCUMENT_URI = "DOCUMENT_URI"; public static final String GATEWAY_INTERFACE = "GATEWAY_INTERFACE"; public static final String PATH_INFO = "PATH_INFO"; public static final String QUERY_STRING = "QUERY_STRING"; diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java index f3983e5a03f..87a719a9c87 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java @@ -67,8 +67,6 @@ public class HttpClientTransportOverFCGI extends AbstractHttpClientTransport protected void customize(Request request, HttpFields fastCGIHeaders) { - String scriptName = fastCGIHeaders.get(FCGI.Headers.SCRIPT_NAME); - fastCGIHeaders.put(FCGI.Headers.SCRIPT_FILENAME, scriptRoot + scriptName); fastCGIHeaders.put(FCGI.Headers.DOCUMENT_ROOT, scriptRoot); } } diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java index 9d16d5c3eca..6af5be81229 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java @@ -46,12 +46,10 @@ public class HttpSenderOverFCGI extends HttpSender // FastCGI headers based on the URI URI uri = request.getURI(); String path = uri.getRawPath(); - fcgiHeaders.put(FCGI.Headers.REQUEST_URI, path); + fcgiHeaders.put(FCGI.Headers.DOCUMENT_URI, path); String query = uri.getRawQuery(); fcgiHeaders.put(FCGI.Headers.QUERY_STRING, query == null ? "" : query); - int lastSegment = path.lastIndexOf('/'); - String scriptName = lastSegment < 0 ? path : path.substring(lastSegment); - fcgiHeaders.put(FCGI.Headers.SCRIPT_NAME, scriptName); + fcgiHeaders.put(FCGI.Headers.REQUEST_URI, query == null ? path : path + "?" + query); // FastCGI headers based on HTTP headers HttpField httpField = headers.remove(HttpHeader.AUTHORIZATION); diff --git a/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/HttpClientTest.java b/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/HttpClientTest.java index 7ba69ff172c..518e12cf830 100644 --- a/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/HttpClientTest.java +++ b/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/HttpClientTest.java @@ -183,6 +183,38 @@ public class HttpClientTest extends AbstractHttpClientServerTest Assert.assertEquals(paramValue, new String(response.getContent(), "UTF-8")); } + @Test + public void testPOSTWithQueryString() throws Exception + { + final String paramName = "a"; + final String paramValue = "\u20AC"; + start(new AbstractHandler() + { + @Override + public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + String value = request.getParameter(paramName); + if (paramValue.equals(value)) + { + response.setCharacterEncoding("UTF-8"); + response.setContentType("text/plain"); + response.getOutputStream().print(value); + } + } + }); + + String uri = scheme + "://localhost:" + connector.getLocalPort() + + "/?" + paramName + "=" + URLEncoder.encode(paramValue, "UTF-8"); + ContentResponse response = client.POST(uri) + .timeout(5, TimeUnit.SECONDS) + .send(); + + Assert.assertNotNull(response); + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(paramValue, new String(response.getContent(), "UTF-8")); + } + @Test public void testPUTWithParameters() throws Exception { diff --git a/fcgi-proxy/pom.xml b/fcgi-proxy/pom.xml index 81f1cbd8c87..0aee57ec7f3 100644 --- a/fcgi-proxy/pom.xml +++ b/fcgi-proxy/pom.xml @@ -42,6 +42,12 @@ ${jetty-version} test + + org.eclipse.jetty + jetty-rewrite + ${jetty-version} + test + diff --git a/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServlet.java b/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServlet.java index 1a7bccbc167..e19431e0daa 100644 --- a/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServlet.java +++ b/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServlet.java @@ -18,7 +18,10 @@ package org.eclipse.jetty.fcgi.proxy; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.servlet.ServletConfig; +import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import org.eclipse.jetty.client.HttpClient; @@ -31,11 +34,26 @@ import org.eclipse.jetty.proxy.ProxyServlet; public class FastCGIProxyServlet extends ProxyServlet.Transparent { public static final String SCRIPT_ROOT_INIT_PARAM = "scriptRoot"; + public static final String SCRIPT_PATTERN_INIT_PARAM = "scriptPattern"; private static final String REMOTE_ADDR_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".remoteAddr"; private static final String REMOTE_PORT_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".remotePort"; + private static final String SERVER_NAME_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".serverName"; private static final String SERVER_ADDR_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".serverAddr"; private static final String SERVER_PORT_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".serverPort"; + private Pattern scriptPattern; + + @Override + public void init() throws ServletException + { + super.init(); + + String value = getInitParameter(SCRIPT_PATTERN_INIT_PARAM); + if (value == null) + value = "(.+\\.php)"; + scriptPattern = Pattern.compile(value); + } + @Override protected HttpClient newHttpClient() { @@ -51,12 +69,28 @@ public class FastCGIProxyServlet extends ProxyServlet.Transparent { proxyRequest.attribute(REMOTE_ADDR_ATTRIBUTE, request.getRemoteAddr()); proxyRequest.attribute(REMOTE_PORT_ATTRIBUTE, String.valueOf(request.getRemotePort())); + proxyRequest.attribute(SERVER_NAME_ATTRIBUTE, request.getServerName()); proxyRequest.attribute(SERVER_ADDR_ATTRIBUTE, request.getLocalAddr()); proxyRequest.attribute(SERVER_PORT_ATTRIBUTE, String.valueOf(request.getLocalPort())); super.customizeProxyRequest(proxyRequest, request); } - private static class ProxyHttpClientTransportOverFCGI extends HttpClientTransportOverFCGI + protected void customizeFastCGIHeaders(Request proxyRequest, HttpFields fastCGIHeaders) + { + fastCGIHeaders.put(FCGI.Headers.REMOTE_ADDR, (String)proxyRequest.getAttributes().get(REMOTE_ADDR_ATTRIBUTE)); + fastCGIHeaders.put(FCGI.Headers.REMOTE_PORT, (String)proxyRequest.getAttributes().get(REMOTE_PORT_ATTRIBUTE)); + fastCGIHeaders.put(FCGI.Headers.SERVER_NAME, (String)proxyRequest.getAttributes().get(SERVER_NAME_ATTRIBUTE)); + fastCGIHeaders.put(FCGI.Headers.SERVER_ADDR, (String)proxyRequest.getAttributes().get(SERVER_ADDR_ATTRIBUTE)); + fastCGIHeaders.put(FCGI.Headers.SERVER_PORT, (String)proxyRequest.getAttributes().get(SERVER_PORT_ATTRIBUTE)); + String root = fastCGIHeaders.get(FCGI.Headers.DOCUMENT_ROOT); + String path = proxyRequest.getURI().getRawPath(); + Matcher matcher = scriptPattern.matcher(path); + String scriptName = matcher.matches() ? matcher.group(1) : path; + fastCGIHeaders.put(FCGI.Headers.SCRIPT_NAME, scriptName); + fastCGIHeaders.put(FCGI.Headers.SCRIPT_FILENAME, root + scriptName); + } + + private class ProxyHttpClientTransportOverFCGI extends HttpClientTransportOverFCGI { public ProxyHttpClientTransportOverFCGI(String scriptRoot) { @@ -67,10 +101,7 @@ public class FastCGIProxyServlet extends ProxyServlet.Transparent protected void customize(Request request, HttpFields fastCGIHeaders) { super.customize(request, fastCGIHeaders); - fastCGIHeaders.put(FCGI.Headers.REMOTE_ADDR, (String)request.getAttributes().get(REMOTE_ADDR_ATTRIBUTE)); - fastCGIHeaders.put(FCGI.Headers.REMOTE_PORT, (String)request.getAttributes().get(REMOTE_PORT_ATTRIBUTE)); - fastCGIHeaders.put(FCGI.Headers.SERVER_ADDR, (String)request.getAttributes().get(SERVER_ADDR_ATTRIBUTE)); - fastCGIHeaders.put(FCGI.Headers.SERVER_PORT, (String)request.getAttributes().get(SERVER_PORT_ATTRIBUTE)); + customizeFastCGIHeaders(request, fastCGIHeaders); } } } diff --git a/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServer.java b/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/DrupalFastCGIProxyServer.java similarity index 57% rename from fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServer.java rename to fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/DrupalFastCGIProxyServer.java index 75d3e6c8f3e..458a9921264 100644 --- a/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServer.java +++ b/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/DrupalFastCGIProxyServer.java @@ -20,10 +20,11 @@ package org.eclipse.jetty.fcgi.proxy; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.DefaultServlet; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; -public class FastCGIProxyServer +public class DrupalFastCGIProxyServer { public static void main(String[] args) throws Exception { @@ -32,12 +33,27 @@ public class FastCGIProxyServer connector.setPort(8080); server.addConnector(connector); + // Drupal seems to only work on the root context, + // at least out of the box without additional plugins + + String root = "/home/simon/programs/drupal-7.23"; + ServletContextHandler context = new ServletContextHandler(server, "/"); - ServletHolder servletHolder = new ServletHolder(FastCGIProxyServlet.class); - servletHolder.setInitParameter(FastCGIProxyServlet.SCRIPT_ROOT_INIT_PARAM, "/var/www/php-fcgi"); - servletHolder.setInitParameter("proxyTo", "http://localhost:9000/"); - servletHolder.setInitParameter("prefix", "/"); - context.addServlet(servletHolder, "/*"); + context.setResourceBase(root); + context.setWelcomeFiles(new String[]{"index.php"}); + + // Serve static resources + ServletHolder defaultServlet = new ServletHolder(DefaultServlet.class); + defaultServlet.setName("default"); + context.addServlet(defaultServlet, "/"); + + // FastCGI + ServletHolder fcgiServlet = new ServletHolder(FastCGIProxyServlet.class); + fcgiServlet.setInitParameter(FastCGIProxyServlet.SCRIPT_ROOT_INIT_PARAM, root); + fcgiServlet.setInitParameter("proxyTo", "http://localhost:9000"); + fcgiServlet.setInitParameter("prefix", "/"); + fcgiServlet.setInitParameter(FastCGIProxyServlet.SCRIPT_PATTERN_INIT_PARAM, "(.+\\.php)"); + context.addServlet(fcgiServlet, "*.php"); server.start(); } diff --git a/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/WordPressFastCGIProxyServer.java b/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/WordPressFastCGIProxyServer.java new file mode 100644 index 00000000000..353724144c0 --- /dev/null +++ b/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/WordPressFastCGIProxyServer.java @@ -0,0 +1,102 @@ +// +// ======================================================================== +// 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.proxy; + +import java.io.IOException; +import java.util.EnumSet; +import java.util.Locale; +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.DefaultServlet; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; + +public class WordPressFastCGIProxyServer +{ + public static void main(String[] args) throws Exception + { + Server server = new Server(); + ServerConnector connector = new ServerConnector(server); + connector.setPort(8080); + server.addConnector(connector); + + String root = "/home/simon/programs/wordpress-3.6.1"; + + ServletContextHandler context = new ServletContextHandler(server, "/"); + context.setResourceBase(root); + context.setWelcomeFiles(new String[]{"index.php"}); + + // Serve static resources + ServletHolder defaultServlet = new ServletHolder(DefaultServlet.class); + defaultServlet.setName("default"); + context.addServlet(defaultServlet, "/"); + + context.addFilter(WordPressFilter.class, "/index.php/*", EnumSet.of(DispatcherType.REQUEST)); + + // FastCGI + ServletHolder fcgiServlet = new ServletHolder(FastCGIProxyServlet.class); + fcgiServlet.setInitParameter(FastCGIProxyServlet.SCRIPT_ROOT_INIT_PARAM, root); + fcgiServlet.setInitParameter("proxyTo", "http://localhost:9000"); + fcgiServlet.setInitParameter("prefix", "/"); + fcgiServlet.setInitParameter(FastCGIProxyServlet.SCRIPT_PATTERN_INIT_PARAM, "/index\\.php(.+\\.php)"); + context.addServlet(fcgiServlet, "*.php"); + + server.start(); + } + + /** + * This filter is needed to get rid of the annoying "/index.php" prefix + * in WordPress URLs that prevents serving correctly static resources. + */ + public static class WordPressFilter implements Filter + { + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException + { + String path = ((HttpServletRequest)request).getRequestURI().toLowerCase(Locale.ENGLISH); + if (!path.endsWith(".php") && path.startsWith("/index.php/")) + { + request.getRequestDispatcher(path.substring("/index.php".length())).forward(request, response); + } + else + { + chain.doFilter(request, response); + } + } + + @Override + public void destroy() + { + } + } +} diff --git a/fcgi-proxy/src/test/resources/jetty-logging.properties b/fcgi-proxy/src/test/resources/jetty-logging.properties new file mode 100644 index 00000000000..11c27cfd24f --- /dev/null +++ b/fcgi-proxy/src/test/resources/jetty-logging.properties @@ -0,0 +1,3 @@ +org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog +org.eclipse.jetty.client.LEVEL=DEBUG +org.eclipse.jetty.fcgi.LEVEL=DEBUG From 67d8a9a30141d9b2118a75c844fd71336e53ba3c Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 25 Oct 2013 11:25:22 +0200 Subject: [PATCH 20/43] Add support for HTTPS FastCGI param. --- fcgi-core/src/main/java/org/eclipse/jetty/fcgi/FCGI.java | 1 + .../org/eclipse/jetty/fcgi/proxy/FastCGIProxyServlet.java | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/FCGI.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/FCGI.java index 14da7aab8f9..683ebd14ff1 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/FCGI.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/FCGI.java @@ -114,6 +114,7 @@ public class FCGI public static final String DOCUMENT_ROOT = "DOCUMENT_ROOT"; public static final String DOCUMENT_URI = "DOCUMENT_URI"; public static final String GATEWAY_INTERFACE = "GATEWAY_INTERFACE"; + public static final String HTTPS = "HTTPS"; public static final String PATH_INFO = "PATH_INFO"; public static final String QUERY_STRING = "QUERY_STRING"; public static final String REMOTE_ADDR = "REMOTE_ADDR"; diff --git a/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServlet.java b/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServlet.java index e19431e0daa..67b2726f07e 100644 --- a/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServlet.java +++ b/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServlet.java @@ -29,6 +29,7 @@ import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.fcgi.FCGI; import org.eclipse.jetty.fcgi.client.http.HttpClientTransportOverFCGI; import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.proxy.ProxyServlet; public class FastCGIProxyServlet extends ProxyServlet.Transparent @@ -40,6 +41,7 @@ public class FastCGIProxyServlet extends ProxyServlet.Transparent private static final String SERVER_NAME_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".serverName"; private static final String SERVER_ADDR_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".serverAddr"; private static final String SERVER_PORT_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".serverPort"; + private static final String SCHEME_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".scheme"; private Pattern scriptPattern; @@ -72,6 +74,7 @@ public class FastCGIProxyServlet extends ProxyServlet.Transparent proxyRequest.attribute(SERVER_NAME_ATTRIBUTE, request.getServerName()); proxyRequest.attribute(SERVER_ADDR_ATTRIBUTE, request.getLocalAddr()); proxyRequest.attribute(SERVER_PORT_ATTRIBUTE, String.valueOf(request.getLocalPort())); + proxyRequest.attribute(SCHEME_ATTRIBUTE, request.getScheme()); super.customizeProxyRequest(proxyRequest, request); } @@ -82,6 +85,8 @@ public class FastCGIProxyServlet extends ProxyServlet.Transparent fastCGIHeaders.put(FCGI.Headers.SERVER_NAME, (String)proxyRequest.getAttributes().get(SERVER_NAME_ATTRIBUTE)); fastCGIHeaders.put(FCGI.Headers.SERVER_ADDR, (String)proxyRequest.getAttributes().get(SERVER_ADDR_ATTRIBUTE)); fastCGIHeaders.put(FCGI.Headers.SERVER_PORT, (String)proxyRequest.getAttributes().get(SERVER_PORT_ATTRIBUTE)); + if (HttpScheme.HTTPS.is((String)proxyRequest.getAttributes().get(SCHEME_ATTRIBUTE))) + fastCGIHeaders.put(FCGI.Headers.HTTPS, "on"); String root = fastCGIHeaders.get(FCGI.Headers.DOCUMENT_ROOT); String path = proxyRequest.getURI().getRawPath(); Matcher matcher = scriptPattern.matcher(path); From 39600f4d8179daf8d9b23b5a8eb330a2fade0eb3 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 25 Oct 2013 12:54:25 +0200 Subject: [PATCH 21/43] Added parameter to create multiplexed and pooled destinations. --- .../http/HttpClientTransportOverFCGI.java | 10 ++++++---- .../client/http/HttpDestinationOverFCGI.java | 20 +++++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpDestinationOverFCGI.java diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java index 87a719a9c87..b8c198789b1 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java @@ -31,26 +31,28 @@ import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.util.Promise; -// TODO: add parameter to tell whether use multiplex destinations or not public class HttpClientTransportOverFCGI extends AbstractHttpClientTransport { + private final boolean multiplexed; private final String scriptRoot; public HttpClientTransportOverFCGI(String scriptRoot) { - this(Runtime.getRuntime().availableProcessors() / 2 + 1, scriptRoot); + this(Runtime.getRuntime().availableProcessors() / 2 + 1, false, scriptRoot); } - public HttpClientTransportOverFCGI(int selectors, String scriptRoot) + public HttpClientTransportOverFCGI(int selectors, boolean multiplexed, String scriptRoot) { super(selectors); + this.multiplexed = multiplexed; this.scriptRoot = scriptRoot; } @Override public HttpDestination newHttpDestination(Origin origin) { - return new MultiplexHttpDestinationOverFCGI(getHttpClient(), origin); + return multiplexed ? new MultiplexHttpDestinationOverFCGI(getHttpClient(), origin) + : new HttpDestinationOverFCGI(getHttpClient(), origin); } @Override diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpDestinationOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpDestinationOverFCGI.java new file mode 100644 index 00000000000..08459ca3af3 --- /dev/null +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpDestinationOverFCGI.java @@ -0,0 +1,20 @@ +package org.eclipse.jetty.fcgi.client.http; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.client.Origin; +import org.eclipse.jetty.client.PoolingHttpDestination; + +public class HttpDestinationOverFCGI extends PoolingHttpDestination +{ + public HttpDestinationOverFCGI(HttpClient client, Origin origin) + { + super(client, origin); + } + + @Override + protected void send(HttpConnectionOverFCGI connection, HttpExchange exchange) + { + connection.send(exchange); + } +} From 4706ed5d87cc10c75ce745bbd7a12b1364813458 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 25 Oct 2013 12:54:35 +0200 Subject: [PATCH 22/43] Added example for running Drupal with SPDY and SPDY Push. --- fcgi-proxy/pom.xml | 4 +- .../proxy/DrupalSPDYFastCGIProxyServer.java | 77 ++++++++++++++++++ fcgi-proxy/src/test/resources/keystore.jks | Bin 0 -> 2206 bytes fcgi-proxy/src/test/resources/truststore.jks | Bin 0 -> 916 bytes 4 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/DrupalSPDYFastCGIProxyServer.java create mode 100644 fcgi-proxy/src/test/resources/keystore.jks create mode 100644 fcgi-proxy/src/test/resources/truststore.jks diff --git a/fcgi-proxy/pom.xml b/fcgi-proxy/pom.xml index 0aee57ec7f3..4c5a998e053 100644 --- a/fcgi-proxy/pom.xml +++ b/fcgi-proxy/pom.xml @@ -43,8 +43,8 @@ test - org.eclipse.jetty - jetty-rewrite + org.eclipse.jetty.spdy + spdy-http-server ${jetty-version} test diff --git a/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/DrupalSPDYFastCGIProxyServer.java b/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/DrupalSPDYFastCGIProxyServer.java new file mode 100644 index 00000000000..a2f204665ba --- /dev/null +++ b/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/DrupalSPDYFastCGIProxyServer.java @@ -0,0 +1,77 @@ +// +// ======================================================================== +// 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.proxy; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.DefaultServlet; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.spdy.api.SPDY; +import org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnector; +import org.eclipse.jetty.spdy.server.http.PushStrategy; +import org.eclipse.jetty.spdy.server.http.ReferrerPushStrategy; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +public class DrupalSPDYFastCGIProxyServer +{ + public static void main(String[] args) throws Exception + { + SslContextFactory sslContextFactory = new SslContextFactory(); + sslContextFactory.setEndpointIdentificationAlgorithm(""); + sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks"); + sslContextFactory.setKeyStorePassword("storepwd"); + sslContextFactory.setTrustStorePath("src/test/resources/truststore.jks"); + sslContextFactory.setTrustStorePassword("storepwd"); + + Server server = new Server(); + + Map pushStrategies = new HashMap<>(); + pushStrategies.put(SPDY.V3, new ReferrerPushStrategy()); + HTTPSPDYServerConnector connector = new HTTPSPDYServerConnector(server, sslContextFactory, pushStrategies); + connector.setPort(8443); + server.addConnector(connector); + + // Drupal seems to only work on the root context, + // at least out of the box without additional plugins + + String root = "/home/simon/programs/drupal-7.23"; + + ServletContextHandler context = new ServletContextHandler(server, "/"); + context.setResourceBase(root); + context.setWelcomeFiles(new String[]{"index.php"}); + + // Serve static resources + ServletHolder defaultServlet = new ServletHolder(DefaultServlet.class); + defaultServlet.setName("default"); + context.addServlet(defaultServlet, "/"); + + // FastCGI + ServletHolder fcgiServlet = new ServletHolder(FastCGIProxyServlet.class); + fcgiServlet.setInitParameter(FastCGIProxyServlet.SCRIPT_ROOT_INIT_PARAM, root); + fcgiServlet.setInitParameter("proxyTo", "http://localhost:9000"); + fcgiServlet.setInitParameter("prefix", "/"); + fcgiServlet.setInitParameter(FastCGIProxyServlet.SCRIPT_PATTERN_INIT_PARAM, "(.+\\.php)"); + context.addServlet(fcgiServlet, "*.php"); + + server.start(); + } +} diff --git a/fcgi-proxy/src/test/resources/keystore.jks b/fcgi-proxy/src/test/resources/keystore.jks new file mode 100644 index 0000000000000000000000000000000000000000..428ba54776ede2fdcdeedd879edb927c2abd9953 GIT binary patch literal 2206 zcmcgt`9Bkm8{cNkoMUp6gmShKn!AQX*(l6Nj(i=TnQPOKYtv{*Wg>ItE=Q!pRYH8a z$Sp#S#2lYw#aw;$y9u4T}83H*%lp zAKZay0sy=q1Qoo85aAQh;$ zD(c2EIN#D7WwYDLKUg!CotQPD@dp;5FR#bgaace(^x$6g5frD~(_b(MI^J&*A2DRp zf5Q2onfE(zvUb9|9C`66)YFRNM6~xrz4;iVbU=P|*YT2eWHFJJtr+M@zt2qPm)K~rRcqcs=LM12)PX0TT%QO zlf*xkqD3}7l)1J`5W(>=9nR0e6j-<79<11v3ZuXXcQpoCsqY~n`$FN+S}hcVm5Y>G zXnD{@DYs1@{S0z(lW+?86LWKtku$$-(khsh>0qRUXn=84`GRn?77M^_JY`durnN;KE zW#OJ`h<6xcB{I))ekGpc*Ylt}0cx4|OMBDPQvx4`r`}4Ze5_ipdObGMTi3bZHd5PC zcY0;?uBWu$PSvjJeb87nY7ghNv?%M@SoDl6IWt`bQCosfSh$#D6$ea~QhKM^ud2Ut z+9PYJuVpoELmN-A`F$BicO{BSYg@#tS%avVfb}DxL)|NanJ)#zB!2~?#Ot%H7--9N zU$bs0fS5G!m5M4&WK3#a|H|Tgw*?X-;H+Lu@kwA>qSR~7UC7b)7MJXTn6PG>n@8jP zW+}F^X$$c;U~4ryqRF; z>`j!tbLMK4ZGyY643|~?%Mu#fm!l%wAKjBDmd+VYmp3S#$scD$~bxbf|z#)hShN0*AhRaPDcmqrftGlHq4^54MM$Xfy(2> zH8QYVMzmn_oHbvJCB`IN~E&{1*h&0gEM{e zKvWvzp(!BqMX8`t#)~0nq}Wa zr6>FRPyp;AAB&)1$5@;r$23J{K&~>TWjZf7V$wFzmGM95CXhFG1cJNVAXks}C+&2- zbf9Qn*D8N}Afd2kpwDxns3%1uaFhAqDV8ksWiWY|quuLGZ0)SqrJ!Y8yX}@}IyC$C zQ3rCUsn}#>F#D8%D?q~ySy4j&he%Bs{{7V%rl!ui`@KQP?NTi+_iN{cwom&9RaMRR zB~z!hz|0HAgB9_Ijvpe-zr#jLbckJsc>vmo{+im?t8lA;N#fD4?{lb&J0V8Gocq%; f1ihv=QIDh{M_<9V+45Z2{KE4_qW}V3B0uV%GgrOJ literal 0 HcmV?d00001 diff --git a/fcgi-proxy/src/test/resources/truststore.jks b/fcgi-proxy/src/test/resources/truststore.jks new file mode 100644 index 0000000000000000000000000000000000000000..839cb8c35151c2b7c64afca24b6b72caad070a05 GIT binary patch literal 916 zcmezO_TO6u1_mY|W(3o$xs} zE~X|%Muz1J{3AIFGbaABoD&*5saD@gH|APIn|qhRGl}gsUzm=o9G*UXZaLfkb^*)o zjA*-gTf)`m_MQJYE&gJ}p^PHkrj!4^W|XX5a=N7A{;n#yaON&k_bHloe-^*hm?Z91 zlB>xeD=<(C>yn{9D54u}krkl}HQ(Uscha(++qf!T9y+xaEfnXd1O zi0)T?voO%;QH9LK;*_O3mBblqm)!31vU@hm;^%>mh5U@y3R%l0gzi`2yxH!+?kPOi zt!Tnsz1x9B3U2~8STZp)GB6^C5HPs_Lx_=~O<3xi>MmQ;D_g$D<_pdct`+TyzWTQ= zW5Finm(sGEe;ty^>vg$!cV)t>;H#Mev23$*WWBpyJ}Ir;RW+Htrt6{Pk&qz&-XG2@ z8@{&Lu%DX7m47Uny+-3w`=4V611q#Ub(U`xZCtSK^2LO^3(s|HW&N14dV4@A&(kX% z*S_eUPs-bSWRp>avt;CP@7K+G&3=b&1eO-s3f`;Cf91p#$)FW&xME3L8sEBQQDVCvfG>mdwqnk+GXd2ihXqpv z;usF(WoYYmu8DZZa4%1z=+hI+*gpkUykAy5tj#grb*gH!M6TqIcifYBGVe^&T#-2O K*=+x>r_BKeJV|!| literal 0 HcmV?d00001 From 98de7500f93aa585b4ae381461c02e8e1d5a587e Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 28 Oct 2013 16:36:55 +0100 Subject: [PATCH 23/43] Made Generator.Result immutable. Now instead of holding a list and being mutable, it is a single-linked list that is traversed recursively. --- .../jetty/fcgi/generator/ClientGenerator.java | 9 ++- .../eclipse/jetty/fcgi/generator/Flusher.java | 6 +- .../jetty/fcgi/generator/Generator.java | 68 ++++++++++--------- .../jetty/fcgi/generator/ServerGenerator.java | 14 +--- .../http/HttpClientTransportOverFCGI.java | 2 +- .../test/resources/jetty-logging.properties | 4 +- .../test/resources/jetty-logging.properties | 4 +- .../fcgi/server/HttpChannelOverFCGI.java | 2 +- 8 files changed, 53 insertions(+), 56 deletions(-) diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java index 715089d968f..08871d26658 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java @@ -77,11 +77,10 @@ public class ClientGenerator extends Generator int maxCapacity = 4 + 4 + 2 * MAX_PARAM_LENGTH; // One FCGI_BEGIN_REQUEST + N FCGI_PARAMS + one last FCGI_PARAMS - Result result = new Result(byteBufferPool, callback); ByteBuffer beginRequestBuffer = byteBufferPool.acquire(16, false); BufferUtil.clearToFill(beginRequestBuffer); - result.add(beginRequestBuffer, true); + Result result = new Result(byteBufferPool, callback, beginRequestBuffer, true); // Generate the FCGI_BEGIN_REQUEST frame beginRequestBuffer.putInt(0x01_01_00_00 + request); @@ -95,7 +94,7 @@ public class ClientGenerator extends Generator int capacity = 8 + Math.min(maxCapacity, fieldsLength); ByteBuffer buffer = byteBufferPool.acquire(capacity, true); BufferUtil.clearToFill(buffer); - result.add(buffer, true); + result = result.append(buffer, true); // Generate the FCGI_PARAMS frame buffer.putInt(0x01_04_00_00 + request); @@ -133,7 +132,7 @@ public class ClientGenerator extends Generator ByteBuffer lastParamsBuffer = byteBufferPool.acquire(8, false); BufferUtil.clearToFill(lastParamsBuffer); - result.add(lastParamsBuffer, true); + result = result.append(lastParamsBuffer, true); // Generate the last FCGI_PARAMS frame lastParamsBuffer.putInt(0x01_04_00_00 + request); @@ -160,6 +159,6 @@ public class ClientGenerator extends Generator public Result generateRequestContent(int request, ByteBuffer content, boolean lastContent, Callback callback) { - return generateContent(request, content, lastContent, callback, FCGI.FrameType.STDIN); + return generateContent(request, content, false, lastContent, callback, FCGI.FrameType.STDIN); } } diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java index e4b9b65c5eb..506d91099f0 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java @@ -83,8 +83,8 @@ public class Flusher result = queue.poll(); } active = result; - List buffers = result.getByteBuffers(); - endPoint.write(this, buffers.toArray(new ByteBuffer[buffers.size()])); + ByteBuffer[] buffers = result.getByteBuffers(); + endPoint.write(this, buffers); return false; } @@ -133,7 +133,7 @@ public class Flusher { private ShutdownResult() { - super(null, new Adapter()); + super(null, null, null, false); } @Override diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java index 1a578f1d5ad..b0ddaf0463f 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java @@ -19,8 +19,6 @@ 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; @@ -38,18 +36,18 @@ public class Generator this.byteBufferPool = byteBufferPool; } - protected Result generateContent(int id, ByteBuffer content, boolean lastContent, Callback callback, FCGI.FrameType frameType) + protected Result generateContent(int id, ByteBuffer content, boolean recycle, boolean lastContent, Callback callback, FCGI.FrameType frameType) { id &= 0xFF_FF; int remaining = content == null ? 0 : content.remaining(); - Result result = new Result(byteBufferPool, callback); + Result result = new Result(byteBufferPool, callback, null, false); while (remaining > 0 || lastContent) { ByteBuffer buffer = byteBufferPool.acquire(8, false); BufferUtil.clearToFill(buffer); - result.add(buffer, true); + result = result.append(buffer, true); // Generate the frame header buffer.put((byte)0x01); @@ -67,7 +65,7 @@ public class Generator int limit = content.limit(); content.limit(content.position() + length); ByteBuffer slice = content.slice(); - result.add(slice, false); + result = result.append(slice, recycle); content.position(content.limit()); content.limit(limit); remaining -= length; @@ -80,60 +78,68 @@ public class Generator { private final ByteBufferPool byteBufferPool; private final Callback callback; - private final List buffers; - private final List recycles; + private final ByteBuffer buffer; + private final boolean recycle; + private final Result previous; - public Result(ByteBufferPool byteBufferPool, Callback callback) + public Result(ByteBufferPool byteBufferPool, Callback callback, ByteBuffer buffer, boolean recycle) { - this(byteBufferPool, callback, new ArrayList(4), new ArrayList(4)); + this(byteBufferPool, callback, buffer, recycle, null); } - public Result(Result that) - { - this(that.byteBufferPool, that.callback, that.buffers, that.recycles); - } - - private Result(ByteBufferPool byteBufferPool, Callback callback, List buffers, List recycles) + private Result(ByteBufferPool byteBufferPool, Callback callback, ByteBuffer buffer, boolean recycle, Result previous) { this.byteBufferPool = byteBufferPool; this.callback = callback; - this.buffers = buffers; - this.recycles = recycles; + this.buffer = buffer; + this.recycle = recycle; + this.previous = previous; } - public void add(ByteBuffer buffer, boolean recycle) + public Result append(ByteBuffer buffer, boolean recycle) { - buffers.add(buffer); - recycles.add(recycle); + return new Result(byteBufferPool, null, buffer, recycle, this); } - public List getByteBuffers() + public ByteBuffer[] getByteBuffers() { - return buffers; + return getByteBuffers(0); + } + + private ByteBuffer[] getByteBuffers(int length) + { + int newLength = length + (buffer == null ? 0 : 1); + ByteBuffer[] result; + result = previous != null ? previous.getByteBuffers(newLength) : new ByteBuffer[newLength]; + if (buffer != null) + result[result.length - newLength] = buffer; + return result; } @Override public void succeeded() { recycle(); - callback.succeeded(); + if (previous != null) + previous.succeeded(); + if (callback != null) + callback.succeeded(); } @Override public void failed(Throwable x) { recycle(); - callback.failed(x); + if (previous != null) + previous.failed(x); + 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); - } + if (recycle) + byteBufferPool.release(buffer); } } } diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java index 2d4fdf12218..d9f51e2d17b 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java @@ -85,20 +85,12 @@ public class ServerGenerator extends Generator buffer.flip(); - return new Result(generateContent(request, buffer, false, callback, FCGI.FrameType.STDOUT)) - { - @Override - protected void recycle() - { - super.recycle(); - byteBufferPool.release(buffer); - } - }; + return generateContent(request, buffer, true, false, callback, FCGI.FrameType.STDOUT); } public Result generateResponseContent(int request, ByteBuffer content, boolean lastContent, Callback callback) { - Result result = generateContent(request, content, lastContent, callback, FCGI.FrameType.STDOUT); + Result result = generateContent(request, content, false, lastContent, callback, FCGI.FrameType.STDOUT); if (lastContent) { // Generate the FCGI_END_REQUEST @@ -109,7 +101,7 @@ public class ServerGenerator extends Generator endRequestBuffer.putInt(0x00_08_00_00); endRequestBuffer.putLong(0x00L); endRequestBuffer.flip(); - result.add(endRequestBuffer, true); + result = result.append(endRequestBuffer, true); } return result; } diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java index b8c198789b1..c54bf0eef9a 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java @@ -38,7 +38,7 @@ public class HttpClientTransportOverFCGI extends AbstractHttpClientTransport public HttpClientTransportOverFCGI(String scriptRoot) { - this(Runtime.getRuntime().availableProcessors() / 2 + 1, false, scriptRoot); + this(Math.max(1, Runtime.getRuntime().availableProcessors() / 2), false, scriptRoot); } public HttpClientTransportOverFCGI(int selectors, boolean multiplexed, String scriptRoot) diff --git a/fcgi-http-client-transport/src/test/resources/jetty-logging.properties b/fcgi-http-client-transport/src/test/resources/jetty-logging.properties index 11c27cfd24f..b8df62d071d 100644 --- a/fcgi-http-client-transport/src/test/resources/jetty-logging.properties +++ b/fcgi-http-client-transport/src/test/resources/jetty-logging.properties @@ -1,3 +1,3 @@ org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog -org.eclipse.jetty.client.LEVEL=DEBUG -org.eclipse.jetty.fcgi.LEVEL=DEBUG +#org.eclipse.jetty.client.LEVEL=DEBUG +#org.eclipse.jetty.fcgi.LEVEL=DEBUG diff --git a/fcgi-proxy/src/test/resources/jetty-logging.properties b/fcgi-proxy/src/test/resources/jetty-logging.properties index 11c27cfd24f..b8df62d071d 100644 --- a/fcgi-proxy/src/test/resources/jetty-logging.properties +++ b/fcgi-proxy/src/test/resources/jetty-logging.properties @@ -1,3 +1,3 @@ org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog -org.eclipse.jetty.client.LEVEL=DEBUG -org.eclipse.jetty.fcgi.LEVEL=DEBUG +#org.eclipse.jetty.client.LEVEL=DEBUG +#org.eclipse.jetty.fcgi.LEVEL=DEBUG diff --git a/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java b/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java index dfdcc75b72c..24c0b5b04cb 100644 --- a/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java +++ b/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java @@ -60,7 +60,7 @@ public class HttpChannelOverFCGI extends HttpChannel { if (FCGI.Headers.REQUEST_METHOD.equalsIgnoreCase(field.getName())) method = field.getValue(); - else if (FCGI.Headers.REQUEST_URI.equalsIgnoreCase(field.getName())) + else if (FCGI.Headers.DOCUMENT_URI.equalsIgnoreCase(field.getName())) path = field.getValue(); else if (FCGI.Headers.QUERY_STRING.equalsIgnoreCase(field.getName())) query = field.getValue(); From 116062ce6ee14d9d4c237430501d48be06dc11b7 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 28 Oct 2013 21:34:37 +0100 Subject: [PATCH 24/43] Reverted Generator.Result to be mutable, so that Resulting gathering can be implemented efficiently. --- .../jetty/fcgi/generator/ClientGenerator.java | 3 +- .../eclipse/jetty/fcgi/generator/Flusher.java | 10 ++- .../jetty/fcgi/generator/Generator.java | 80 ++++++++++--------- 3 files changed, 53 insertions(+), 40 deletions(-) diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java index 08871d26658..ef30c7ad6d2 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java @@ -80,7 +80,8 @@ public class ClientGenerator extends Generator ByteBuffer beginRequestBuffer = byteBufferPool.acquire(16, false); BufferUtil.clearToFill(beginRequestBuffer); - Result result = new Result(byteBufferPool, callback, beginRequestBuffer, true); + Result result = new Result(byteBufferPool, callback); + result = result.append(beginRequestBuffer, true); // Generate the FCGI_BEGIN_REQUEST frame beginRequestBuffer.putInt(0x01_01_00_00 + request); diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java index 506d91099f0..365fe62e980 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java @@ -79,8 +79,14 @@ public class Flusher flushing = false; return false; } - // TODO: here is where we want to gather more results to perform gathered writes result = queue.poll(); + // Attempt to gather another result. + // Most often there is another result in the + // queue so this is a real optimization because + // it sends both results in just one TCP packet. + Generator.Result other = queue.poll(); + if (other != null) + result = result.join(other); } active = result; ByteBuffer[] buffers = result.getByteBuffers(); @@ -133,7 +139,7 @@ public class Flusher { private ShutdownResult() { - super(null, null, null, false); + super(null, null); } @Override diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java index b0ddaf0463f..0bbe28f7bd4 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java @@ -19,6 +19,8 @@ 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; @@ -41,7 +43,7 @@ public class Generator id &= 0xFF_FF; int remaining = content == null ? 0 : content.remaining(); - Result result = new Result(byteBufferPool, callback, null, false); + Result result = new Result(byteBufferPool, callback); while (remaining > 0 || lastContent) { @@ -76,70 +78,74 @@ public class Generator public static class Result implements Callback { + private final List callbacks = new ArrayList<>(2); + private final List buffers = new ArrayList<>(8); + private final List recycles = new ArrayList<>(8); private final ByteBufferPool byteBufferPool; - private final Callback callback; - private final ByteBuffer buffer; - private final boolean recycle; - private final Result previous; - public Result(ByteBufferPool byteBufferPool, Callback callback, ByteBuffer buffer, boolean recycle) - { - this(byteBufferPool, callback, buffer, recycle, null); - } - - private Result(ByteBufferPool byteBufferPool, Callback callback, ByteBuffer buffer, boolean recycle, Result previous) + public Result(ByteBufferPool byteBufferPool, Callback callback) { this.byteBufferPool = byteBufferPool; - this.callback = callback; - this.buffer = buffer; - this.recycle = recycle; - this.previous = previous; + this.callbacks.add(callback); } public Result append(ByteBuffer buffer, boolean recycle) { - return new Result(byteBufferPool, null, buffer, recycle, this); + 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 getByteBuffers(0); - } - - private ByteBuffer[] getByteBuffers(int length) - { - int newLength = length + (buffer == null ? 0 : 1); - ByteBuffer[] result; - result = previous != null ? previous.getByteBuffers(newLength) : new ByteBuffer[newLength]; - if (buffer != null) - result[result.length - newLength] = buffer; - return result; + return buffers.toArray(new ByteBuffer[buffers.size()]); } @Override + @SuppressWarnings("ForLoopReplaceableByForEach") public void succeeded() { recycle(); - if (previous != null) - previous.succeeded(); - if (callback != null) - callback.succeeded(); + 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(); - if (previous != null) - previous.failed(x); - if (callback != null) - callback.failed(x); + for (int i = 0; i < callbacks.size(); ++i) + { + Callback callback = callbacks.get(i); + if (callback != null) + callback.failed(x); + } } protected void recycle() { - if (recycle) - byteBufferPool.release(buffer); + for (int i = 0; i < buffers.size(); ++i) + { + ByteBuffer buffer = buffers.get(i); + if (recycles.get(i)) + byteBufferPool.release(buffer); + } } } } From f841b42fde32acd74375273700e4888fbe05e9ee Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Thu, 31 Oct 2013 19:47:57 +0100 Subject: [PATCH 25/43] Moved valorization of REQUEST_URI to the FastCGIProxyServlet, since it needs to refer to possibly forwarded URIs. --- .../fcgi/client/http/HttpSenderOverFCGI.java | 1 - .../jetty/fcgi/proxy/FastCGIProxyServlet.java | 34 ++++++++++++++++--- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java index 6af5be81229..bcce554fc81 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java @@ -49,7 +49,6 @@ public class HttpSenderOverFCGI extends HttpSender fcgiHeaders.put(FCGI.Headers.DOCUMENT_URI, path); String query = uri.getRawQuery(); fcgiHeaders.put(FCGI.Headers.QUERY_STRING, query == null ? "" : query); - fcgiHeaders.put(FCGI.Headers.REQUEST_URI, query == null ? path : path + "?" + query); // FastCGI headers based on HTTP headers HttpField httpField = headers.remove(HttpHeader.AUTHORIZATION); diff --git a/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServlet.java b/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServlet.java index 67b2726f07e..4ce0ea05ee7 100644 --- a/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServlet.java +++ b/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServlet.java @@ -20,6 +20,7 @@ package org.eclipse.jetty.fcgi.proxy; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.servlet.RequestDispatcher; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -42,6 +43,7 @@ public class FastCGIProxyServlet extends ProxyServlet.Transparent private static final String SERVER_ADDR_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".serverAddr"; private static final String SERVER_PORT_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".serverPort"; private static final String SCHEME_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".scheme"; + private static final String REQUEST_URI_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".requestURI"; private Pattern scriptPattern; @@ -52,7 +54,7 @@ public class FastCGIProxyServlet extends ProxyServlet.Transparent String value = getInitParameter(SCRIPT_PATTERN_INIT_PARAM); if (value == null) - value = "(.+\\.php)"; + value = "(.+?\\.php)"; scriptPattern = Pattern.compile(value); } @@ -74,7 +76,12 @@ public class FastCGIProxyServlet extends ProxyServlet.Transparent proxyRequest.attribute(SERVER_NAME_ATTRIBUTE, request.getServerName()); proxyRequest.attribute(SERVER_ADDR_ATTRIBUTE, request.getLocalAddr()); proxyRequest.attribute(SERVER_PORT_ATTRIBUTE, String.valueOf(request.getLocalPort())); + proxyRequest.attribute(SCHEME_ATTRIBUTE, request.getScheme()); + + // If we are forwarded, retain the original request URI. + proxyRequest.attribute(REQUEST_URI_ATTRIBUTE, request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI)); + super.customizeProxyRequest(proxyRequest, request); } @@ -85,13 +92,30 @@ public class FastCGIProxyServlet extends ProxyServlet.Transparent fastCGIHeaders.put(FCGI.Headers.SERVER_NAME, (String)proxyRequest.getAttributes().get(SERVER_NAME_ATTRIBUTE)); fastCGIHeaders.put(FCGI.Headers.SERVER_ADDR, (String)proxyRequest.getAttributes().get(SERVER_ADDR_ATTRIBUTE)); fastCGIHeaders.put(FCGI.Headers.SERVER_PORT, (String)proxyRequest.getAttributes().get(SERVER_PORT_ATTRIBUTE)); + if (HttpScheme.HTTPS.is((String)proxyRequest.getAttributes().get(SCHEME_ATTRIBUTE))) fastCGIHeaders.put(FCGI.Headers.HTTPS, "on"); - String root = fastCGIHeaders.get(FCGI.Headers.DOCUMENT_ROOT); - String path = proxyRequest.getURI().getRawPath(); - Matcher matcher = scriptPattern.matcher(path); - String scriptName = matcher.matches() ? matcher.group(1) : path; + + String rawPath = proxyRequest.getURI().getRawPath(); + String requestPath = (String)proxyRequest.getAttributes().get(REQUEST_URI_ATTRIBUTE); + if (requestPath == null) + requestPath = rawPath; + fastCGIHeaders.put(FCGI.Headers.REQUEST_URI, requestPath); + + String scriptName = rawPath; + Matcher matcher = scriptPattern.matcher(rawPath); + if (matcher.matches()) + { + // Expect at least one group in the regular expression. + scriptName = matcher.group(1); + + // If there is a second group, map it to PATH_INFO. + if (matcher.groupCount() > 1) + fastCGIHeaders.put(FCGI.Headers.PATH_INFO, matcher.group(2)); + } fastCGIHeaders.put(FCGI.Headers.SCRIPT_NAME, scriptName); + + String root = fastCGIHeaders.get(FCGI.Headers.DOCUMENT_ROOT); fastCGIHeaders.put(FCGI.Headers.SCRIPT_FILENAME, root + scriptName); } From 226c5224519a0dc715ef2189e14ce989fcc9fa2e Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Thu, 31 Oct 2013 19:54:59 +0100 Subject: [PATCH 26/43] Implemented a TryFilesFilter inspired by nginx's try_files directive. This is needed by - for example - WordPress because it allows for different permalinks schemes. For example, /foo could be a permalink that should be directed to PHP, while /favicon.ico is a file that must be served statically. URI /foo would be matched by the default servlet resulting in a 404. Therefore we need a filter mapped to /* that checks whether the request URI is a file that exists and likely to be served by the default servlet, otherwise it needs to rewrite the URL to something like /index.php?p=/foo. The rewrite filter does not have the functionality of "try the file, if missing then rewrite" that is now implemented by the TryFilesFilter. --- .../jetty/fcgi/proxy/TryFilesFilter.java | 109 ++++++++++++++++++ .../WordPressSPDYFastCGIProxyServer.java | 86 ++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/TryFilesFilter.java create mode 100644 fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/WordPressSPDYFastCGIProxyServer.java diff --git a/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/TryFilesFilter.java b/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/TryFilesFilter.java new file mode 100644 index 00000000000..1208889e7bb --- /dev/null +++ b/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/TryFilesFilter.java @@ -0,0 +1,109 @@ +// +// ======================================================================== +// 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.proxy; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Inspired by nginx's try_files functionality + */ +public class TryFilesFilter implements Filter +{ + public static final String ROOT_INIT_PARAM = "root"; + public static final String FILES_INIT_PARAM = "files"; + + private String root; + private String[] files; + + @Override + public void init(FilterConfig config) throws ServletException + { + root = config.getInitParameter(ROOT_INIT_PARAM); + if (root == null) + throw new ServletException(String.format("Missing mandatory parameter '%s'", ROOT_INIT_PARAM)); + String param = config.getInitParameter(FILES_INIT_PARAM); + if (param == null) + throw new ServletException(String.format("Missing mandatory parameter '%s'", FILES_INIT_PARAM)); + files = param.split(" "); + } + + public String getRoot() + { + return root; + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException + { + HttpServletRequest httpRequest = (HttpServletRequest)request; + HttpServletResponse httpResponse = (HttpServletResponse)response; + + for (int i = 0; i < files.length - 1; ++i) + { + String file = files[i]; + String resolved = resolve(httpRequest, file); + Path path = Paths.get(getRoot(), resolved); + if (Files.exists(path) && Files.isReadable(path)) + { + chain.doFilter(httpRequest, httpResponse); + return; + } + } + + // The last one is the fallback + fallback(httpRequest, httpResponse, chain, files[files.length - 1]); + } + + protected void fallback(HttpServletRequest request, HttpServletResponse response, FilterChain chain, String fallback) throws IOException, ServletException + { + String resolved = resolve(request, fallback); + request.getRequestDispatcher(resolved).forward(request, response); + } + + private String resolve(HttpServletRequest request, String value) + { + String path = request.getRequestURI(); + String query = request.getQueryString(); + + String result = value.replaceAll("\\$path", path); + result = result.replaceAll("\\$query", query == null ? "" : query); + + // Remove the "?" or "&" at the end if there is no query + if (query == null && (result.endsWith("?") || result.endsWith("&"))) + result = result.substring(0, result.length() - 1); + + return result; + } + + @Override + public void destroy() + { + } +} diff --git a/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/WordPressSPDYFastCGIProxyServer.java b/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/WordPressSPDYFastCGIProxyServer.java new file mode 100644 index 00000000000..3872536368f --- /dev/null +++ b/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/WordPressSPDYFastCGIProxyServer.java @@ -0,0 +1,86 @@ +// +// ======================================================================== +// 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.proxy; + +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import javax.servlet.DispatcherType; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.DefaultServlet; +import org.eclipse.jetty.servlet.FilterHolder; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.spdy.api.SPDY; +import org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnector; +import org.eclipse.jetty.spdy.server.http.PushStrategy; +import org.eclipse.jetty.spdy.server.http.ReferrerPushStrategy; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +public class WordPressSPDYFastCGIProxyServer +{ + public static void main(String[] args) throws Exception + { +// int port = 8080; + int port = 8443; + +// SslContextFactory sslContextFactory = null; + SslContextFactory sslContextFactory = new SslContextFactory(); + sslContextFactory.setEndpointIdentificationAlgorithm(""); + sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks"); + sslContextFactory.setKeyStorePassword("storepwd"); + sslContextFactory.setTrustStorePath("src/test/resources/truststore.jks"); + sslContextFactory.setTrustStorePassword("storepwd"); + + Server server = new Server(); + + Map pushStrategies = new HashMap<>(); + pushStrategies.put(SPDY.V3, new ReferrerPushStrategy()); + HTTPSPDYServerConnector connector = new HTTPSPDYServerConnector(server, sslContextFactory, pushStrategies); + connector.setPort(port); + server.addConnector(connector); + + String root = "/home/simon/programs/wordpress-3.7.1"; + + ServletContextHandler context = new ServletContextHandler(server, "/"); + context.setResourceBase(root); + context.setWelcomeFiles(new String[]{"index.php"}); + + // Serve static resources + ServletHolder defaultServlet = new ServletHolder(DefaultServlet.class); + defaultServlet.setName("default"); + context.addServlet(defaultServlet, "/"); + + FilterHolder tryFileFilter = context.addFilter(TryFilesFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); + tryFileFilter.setInitParameter(TryFilesFilter.ROOT_INIT_PARAM, root); +// tryFileFilter.setInitParameter(TryFilesFilter.FILES_INIT_PARAM, "$path $path/index.php?$query"); // Permalink /?p=123 + tryFileFilter.setInitParameter(TryFilesFilter.FILES_INIT_PARAM, "$path /index.php?p=$path&$query"); // Permalink /%year%/%monthnum%/%postname% + + // FastCGI + ServletHolder fcgiServlet = new ServletHolder(FastCGIProxyServlet.class); + fcgiServlet.setInitParameter(FastCGIProxyServlet.SCRIPT_ROOT_INIT_PARAM, root); + fcgiServlet.setInitParameter("proxyTo", "http://localhost:9000"); + fcgiServlet.setInitParameter("prefix", "/"); + fcgiServlet.setInitParameter(FastCGIProxyServlet.SCRIPT_PATTERN_INIT_PARAM, "(.+?\\.php)"); + context.addServlet(fcgiServlet, "*.php"); + + server.start(); + } +} From 2228b4e03b33629f97f23635ea5aebbc5bb187ce Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 5 Nov 2013 09:05:52 +0100 Subject: [PATCH 27/43] Improved TryFilesFilter to use built-in Servlet API methods to retrieve the files, in order to avoid filesystem vulnerabilities. --- .../jetty/fcgi/proxy/TryFilesFilter.java | 51 ++++++++++--------- .../WordPressSPDYFastCGIProxyServer.java | 4 +- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/TryFilesFilter.java b/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/TryFilesFilter.java index 1208889e7bb..a91a885f4d4 100644 --- a/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/TryFilesFilter.java +++ b/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/TryFilesFilter.java @@ -19,6 +19,8 @@ package org.eclipse.jetty.fcgi.proxy; import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -39,26 +41,17 @@ public class TryFilesFilter implements Filter public static final String ROOT_INIT_PARAM = "root"; public static final String FILES_INIT_PARAM = "files"; - private String root; private String[] files; @Override public void init(FilterConfig config) throws ServletException { - root = config.getInitParameter(ROOT_INIT_PARAM); - if (root == null) - throw new ServletException(String.format("Missing mandatory parameter '%s'", ROOT_INIT_PARAM)); String param = config.getInitParameter(FILES_INIT_PARAM); if (param == null) throw new ServletException(String.format("Missing mandatory parameter '%s'", FILES_INIT_PARAM)); files = param.split(" "); } - public String getRoot() - { - return root; - } - @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { @@ -69,8 +62,12 @@ public class TryFilesFilter implements Filter { String file = files[i]; String resolved = resolve(httpRequest, file); - Path path = Paths.get(getRoot(), resolved); - if (Files.exists(path) && Files.isReadable(path)) + + URL url = request.getServletContext().getResource(resolved); + if (url == null) + continue; + + if (Files.isReadable(toPath(url))) { chain.doFilter(httpRequest, httpResponse); return; @@ -81,25 +78,33 @@ public class TryFilesFilter implements Filter fallback(httpRequest, httpResponse, chain, files[files.length - 1]); } + private Path toPath(URL url) throws IOException + { + try + { + return Paths.get(url.toURI()); + } + catch (URISyntaxException x) + { + throw new IOException(x); + } + } + protected void fallback(HttpServletRequest request, HttpServletResponse response, FilterChain chain, String fallback) throws IOException, ServletException { String resolved = resolve(request, fallback); - request.getRequestDispatcher(resolved).forward(request, response); + request.getServletContext().getRequestDispatcher(resolved).forward(request, response); } private String resolve(HttpServletRequest request, String value) { - String path = request.getRequestURI(); - String query = request.getQueryString(); - - String result = value.replaceAll("\\$path", path); - result = result.replaceAll("\\$query", query == null ? "" : query); - - // Remove the "?" or "&" at the end if there is no query - if (query == null && (result.endsWith("?") || result.endsWith("&"))) - result = result.substring(0, result.length() - 1); - - return result; + String path = request.getServletPath(); + String info = request.getPathInfo(); + if (info != null) + path += info; + if (!path.startsWith("/")) + path = "/" + path; + return value.replaceAll("\\$path", path); } @Override diff --git a/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/WordPressSPDYFastCGIProxyServer.java b/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/WordPressSPDYFastCGIProxyServer.java index 3872536368f..16b9efab501 100644 --- a/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/WordPressSPDYFastCGIProxyServer.java +++ b/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/WordPressSPDYFastCGIProxyServer.java @@ -70,8 +70,8 @@ public class WordPressSPDYFastCGIProxyServer FilterHolder tryFileFilter = context.addFilter(TryFilesFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); tryFileFilter.setInitParameter(TryFilesFilter.ROOT_INIT_PARAM, root); -// tryFileFilter.setInitParameter(TryFilesFilter.FILES_INIT_PARAM, "$path $path/index.php?$query"); // Permalink /?p=123 - tryFileFilter.setInitParameter(TryFilesFilter.FILES_INIT_PARAM, "$path /index.php?p=$path&$query"); // Permalink /%year%/%monthnum%/%postname% +// tryFileFilter.setInitParameter(TryFilesFilter.FILES_INIT_PARAM, "$path $path/index.php"); // Permalink /?p=123 + tryFileFilter.setInitParameter(TryFilesFilter.FILES_INIT_PARAM, "$path /index.php?p=$path"); // Permalink /%year%/%monthnum%/%postname% // FastCGI ServletHolder fcgiServlet = new ServletHolder(FastCGIProxyServlet.class); From f5a4810bdac66562dd9cfb8da1379bbfcc7df724 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 5 Nov 2013 09:07:02 +0100 Subject: [PATCH 28/43] Fixed generation of REQUEST_URI parameter, taking into account forwarded or included URIs and the query string. --- .../jetty/fcgi/proxy/FastCGIProxyServlet.java | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServlet.java b/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServlet.java index 4ce0ea05ee7..2d33bd1e0e7 100644 --- a/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServlet.java +++ b/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServlet.java @@ -18,6 +18,7 @@ package org.eclipse.jetty.fcgi.proxy; +import java.net.URI; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.RequestDispatcher; @@ -79,8 +80,21 @@ public class FastCGIProxyServlet extends ProxyServlet.Transparent proxyRequest.attribute(SCHEME_ATTRIBUTE, request.getScheme()); - // If we are forwarded, retain the original request URI. - proxyRequest.attribute(REQUEST_URI_ATTRIBUTE, request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI)); + // If we are forwarded or included, retain the original request URI. + String originalPath = (String)request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI); + String originalQuery = (String)request.getAttribute(RequestDispatcher.FORWARD_QUERY_STRING); + if (originalPath == null) + { + originalPath = (String)request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI); + originalQuery = (String)request.getAttribute(RequestDispatcher.INCLUDE_QUERY_STRING); + } + if (originalPath != null) + { + String originalURI = originalPath; + if (originalQuery != null) + originalURI += "?" + originalQuery; + proxyRequest.attribute(REQUEST_URI_ATTRIBUTE, originalURI); + } super.customizeProxyRequest(proxyRequest, request); } @@ -96,11 +110,18 @@ public class FastCGIProxyServlet extends ProxyServlet.Transparent if (HttpScheme.HTTPS.is((String)proxyRequest.getAttributes().get(SCHEME_ATTRIBUTE))) fastCGIHeaders.put(FCGI.Headers.HTTPS, "on"); - String rawPath = proxyRequest.getURI().getRawPath(); - String requestPath = (String)proxyRequest.getAttributes().get(REQUEST_URI_ATTRIBUTE); - if (requestPath == null) - requestPath = rawPath; - fastCGIHeaders.put(FCGI.Headers.REQUEST_URI, requestPath); + URI proxyRequestURI = proxyRequest.getURI(); + String rawPath = proxyRequestURI.getRawPath(); + String rawQuery = proxyRequestURI.getRawQuery(); + + String requestURI = (String)proxyRequest.getAttributes().get(REQUEST_URI_ATTRIBUTE); + if (requestURI == null) + { + requestURI = rawPath; + if (rawQuery != null) + requestURI += "?" + rawQuery; + } + fastCGIHeaders.put(FCGI.Headers.REQUEST_URI, requestURI); String scriptName = rawPath; Matcher matcher = scriptPattern.matcher(rawPath); From 9548d0725a5de5ab47d2fae4af9fdf11513b3628 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Wed, 6 Nov 2013 11:17:49 +0100 Subject: [PATCH 29/43] Cosmetics. --- .../org/eclipse/jetty/fcgi/parser/ResponseContentParser.java | 1 + 1 file changed, 1 insertion(+) diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java index f25b7ba7603..11b4ebea14d 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java @@ -186,6 +186,7 @@ public class ResponseContentParser extends StreamContentParser } return false; } + private void notifyBegin(int code, String reason) { try From 2808f371913df31faa72bf788219a6f06a02e32b Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Wed, 6 Nov 2013 11:21:43 +0100 Subject: [PATCH 30/43] Removed unnecessary parameter "root". --- .../main/java/org/eclipse/jetty/fcgi/proxy/TryFilesFilter.java | 1 - 1 file changed, 1 deletion(-) diff --git a/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/TryFilesFilter.java b/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/TryFilesFilter.java index a91a885f4d4..a96a7566e1e 100644 --- a/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/TryFilesFilter.java +++ b/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/TryFilesFilter.java @@ -38,7 +38,6 @@ import javax.servlet.http.HttpServletResponse; */ public class TryFilesFilter implements Filter { - public static final String ROOT_INIT_PARAM = "root"; public static final String FILES_INIT_PARAM = "files"; private String[] files; From bba639b37116d3f93bf7236f5367e3a2d1af5281 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Wed, 6 Nov 2013 11:22:27 +0100 Subject: [PATCH 31/43] Removed unnecessary examples and improved existing ones. --- .../fcgi/proxy/DrupalFastCGIProxyServer.java | 60 ----------- .../proxy/WordPressFastCGIProxyServer.java | 102 ------------------ .../WordPressSPDYFastCGIProxyServer.java | 25 +++-- 3 files changed, 12 insertions(+), 175 deletions(-) delete mode 100644 fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/DrupalFastCGIProxyServer.java delete mode 100644 fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/WordPressFastCGIProxyServer.java diff --git a/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/DrupalFastCGIProxyServer.java b/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/DrupalFastCGIProxyServer.java deleted file mode 100644 index 458a9921264..00000000000 --- a/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/DrupalFastCGIProxyServer.java +++ /dev/null @@ -1,60 +0,0 @@ -// -// ======================================================================== -// 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.proxy; - -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.servlet.DefaultServlet; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; - -public class DrupalFastCGIProxyServer -{ - public static void main(String[] args) throws Exception - { - Server server = new Server(); - ServerConnector connector = new ServerConnector(server); - connector.setPort(8080); - server.addConnector(connector); - - // Drupal seems to only work on the root context, - // at least out of the box without additional plugins - - String root = "/home/simon/programs/drupal-7.23"; - - ServletContextHandler context = new ServletContextHandler(server, "/"); - context.setResourceBase(root); - context.setWelcomeFiles(new String[]{"index.php"}); - - // Serve static resources - ServletHolder defaultServlet = new ServletHolder(DefaultServlet.class); - defaultServlet.setName("default"); - context.addServlet(defaultServlet, "/"); - - // FastCGI - ServletHolder fcgiServlet = new ServletHolder(FastCGIProxyServlet.class); - fcgiServlet.setInitParameter(FastCGIProxyServlet.SCRIPT_ROOT_INIT_PARAM, root); - fcgiServlet.setInitParameter("proxyTo", "http://localhost:9000"); - fcgiServlet.setInitParameter("prefix", "/"); - fcgiServlet.setInitParameter(FastCGIProxyServlet.SCRIPT_PATTERN_INIT_PARAM, "(.+\\.php)"); - context.addServlet(fcgiServlet, "*.php"); - - server.start(); - } -} diff --git a/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/WordPressFastCGIProxyServer.java b/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/WordPressFastCGIProxyServer.java deleted file mode 100644 index 353724144c0..00000000000 --- a/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/WordPressFastCGIProxyServer.java +++ /dev/null @@ -1,102 +0,0 @@ -// -// ======================================================================== -// 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.proxy; - -import java.io.IOException; -import java.util.EnumSet; -import java.util.Locale; -import javax.servlet.DispatcherType; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; - -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.servlet.DefaultServlet; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; - -public class WordPressFastCGIProxyServer -{ - public static void main(String[] args) throws Exception - { - Server server = new Server(); - ServerConnector connector = new ServerConnector(server); - connector.setPort(8080); - server.addConnector(connector); - - String root = "/home/simon/programs/wordpress-3.6.1"; - - ServletContextHandler context = new ServletContextHandler(server, "/"); - context.setResourceBase(root); - context.setWelcomeFiles(new String[]{"index.php"}); - - // Serve static resources - ServletHolder defaultServlet = new ServletHolder(DefaultServlet.class); - defaultServlet.setName("default"); - context.addServlet(defaultServlet, "/"); - - context.addFilter(WordPressFilter.class, "/index.php/*", EnumSet.of(DispatcherType.REQUEST)); - - // FastCGI - ServletHolder fcgiServlet = new ServletHolder(FastCGIProxyServlet.class); - fcgiServlet.setInitParameter(FastCGIProxyServlet.SCRIPT_ROOT_INIT_PARAM, root); - fcgiServlet.setInitParameter("proxyTo", "http://localhost:9000"); - fcgiServlet.setInitParameter("prefix", "/"); - fcgiServlet.setInitParameter(FastCGIProxyServlet.SCRIPT_PATTERN_INIT_PARAM, "/index\\.php(.+\\.php)"); - context.addServlet(fcgiServlet, "*.php"); - - server.start(); - } - - /** - * This filter is needed to get rid of the annoying "/index.php" prefix - * in WordPress URLs that prevents serving correctly static resources. - */ - public static class WordPressFilter implements Filter - { - @Override - public void init(FilterConfig filterConfig) throws ServletException - { - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException - { - String path = ((HttpServletRequest)request).getRequestURI().toLowerCase(Locale.ENGLISH); - if (!path.endsWith(".php") && path.startsWith("/index.php/")) - { - request.getRequestDispatcher(path.substring("/index.php".length())).forward(request, response); - } - else - { - chain.doFilter(request, response); - } - } - - @Override - public void destroy() - { - } - } -} diff --git a/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/WordPressSPDYFastCGIProxyServer.java b/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/WordPressSPDYFastCGIProxyServer.java index 16b9efab501..2f6543aa266 100644 --- a/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/WordPressSPDYFastCGIProxyServer.java +++ b/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/WordPressSPDYFastCGIProxyServer.java @@ -38,10 +38,9 @@ public class WordPressSPDYFastCGIProxyServer { public static void main(String[] args) throws Exception { -// int port = 8080; - int port = 8443; + int port = 8080; + int tlsPort = 8443; -// SslContextFactory sslContextFactory = null; SslContextFactory sslContextFactory = new SslContextFactory(); sslContextFactory.setEndpointIdentificationAlgorithm(""); sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks"); @@ -53,33 +52,33 @@ public class WordPressSPDYFastCGIProxyServer Map pushStrategies = new HashMap<>(); pushStrategies.put(SPDY.V3, new ReferrerPushStrategy()); - HTTPSPDYServerConnector connector = new HTTPSPDYServerConnector(server, sslContextFactory, pushStrategies); + HTTPSPDYServerConnector tlsConnector = new HTTPSPDYServerConnector(server, sslContextFactory, pushStrategies); + tlsConnector.setPort(tlsPort); + server.addConnector(tlsConnector); + HTTPSPDYServerConnector connector = new HTTPSPDYServerConnector(server, null, pushStrategies); connector.setPort(port); server.addConnector(connector); String root = "/home/simon/programs/wordpress-3.7.1"; - ServletContextHandler context = new ServletContextHandler(server, "/"); + ServletContextHandler context = new ServletContextHandler(server, "/wp"); context.setResourceBase(root); context.setWelcomeFiles(new String[]{"index.php"}); // Serve static resources - ServletHolder defaultServlet = new ServletHolder(DefaultServlet.class); - defaultServlet.setName("default"); + ServletHolder defaultServlet = new ServletHolder("default", DefaultServlet.class); context.addServlet(defaultServlet, "/"); - FilterHolder tryFileFilter = context.addFilter(TryFilesFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); - tryFileFilter.setInitParameter(TryFilesFilter.ROOT_INIT_PARAM, root); -// tryFileFilter.setInitParameter(TryFilesFilter.FILES_INIT_PARAM, "$path $path/index.php"); // Permalink /?p=123 - tryFileFilter.setInitParameter(TryFilesFilter.FILES_INIT_PARAM, "$path /index.php?p=$path"); // Permalink /%year%/%monthnum%/%postname% + FilterHolder tryFilesFilter = context.addFilter(TryFilesFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); +// tryFilesFilter.setInitParameter(TryFilesFilter.FILES_INIT_PARAM, "$path $path/index.php"); // Permalink /?p=123 + tryFilesFilter.setInitParameter(TryFilesFilter.FILES_INIT_PARAM, "$path /index.php?p=$path"); // Permalink /%year%/%monthnum%/%postname% // FastCGI - ServletHolder fcgiServlet = new ServletHolder(FastCGIProxyServlet.class); + ServletHolder fcgiServlet = context.addServlet(FastCGIProxyServlet.class, "*.php"); fcgiServlet.setInitParameter(FastCGIProxyServlet.SCRIPT_ROOT_INIT_PARAM, root); fcgiServlet.setInitParameter("proxyTo", "http://localhost:9000"); fcgiServlet.setInitParameter("prefix", "/"); fcgiServlet.setInitParameter(FastCGIProxyServlet.SCRIPT_PATTERN_INIT_PARAM, "(.+?\\.php)"); - context.addServlet(fcgiServlet, "*.php"); server.start(); } From 5a382907501be8585f4dd1ecfc69b82f405532ca Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Wed, 6 Nov 2013 11:25:06 +0100 Subject: [PATCH 32/43] Added fcgi-distribution module to simplify deployment of FastCGI functionalities in a Jetty "base" directory. --- fcgi-distribution/pom.xml | 70 +++++++++++++++++++ .../src/main/assembly/distribution.xml | 31 ++++++++ .../src/main/config/modules/fcgi.mod | 16 +++++ .../main/config/webapps/wordpress-example.xml | 68 ++++++++++++++++++ pom.xml | 1 + 5 files changed, 186 insertions(+) create mode 100644 fcgi-distribution/pom.xml create mode 100644 fcgi-distribution/src/main/assembly/distribution.xml create mode 100644 fcgi-distribution/src/main/config/modules/fcgi.mod create mode 100644 fcgi-distribution/src/main/config/webapps/wordpress-example.xml diff --git a/fcgi-distribution/pom.xml b/fcgi-distribution/pom.xml new file mode 100644 index 00000000000..d693aa95d8e --- /dev/null +++ b/fcgi-distribution/pom.xml @@ -0,0 +1,70 @@ + + + + fcgi-parent + org.eclipse.jetty.fcgi + 1.0.0-SNAPSHOT + + + 4.0.0 + fcgi-distribution + pom + Jetty :: FastCGI :: Distribution + + + ${project.build.directory}/distribution + + + + + + maven-dependency-plugin + + + copy-jars + generate-resources + + copy-dependencies + + + org.eclipse.jetty.fcgi + fcgi-server + jar + ${distribution-directory}/lib/fcgi + + + + + + maven-assembly-plugin + + + assemble + package + + assembly + + + jetty-fcgi-${project.version} + + src/main/assembly/distribution.xml + + gnu + + + + + + + + + + org.eclipse.jetty.fcgi + fcgi-proxy + ${project.version} + + + + diff --git a/fcgi-distribution/src/main/assembly/distribution.xml b/fcgi-distribution/src/main/assembly/distribution.xml new file mode 100644 index 00000000000..df1f093682d --- /dev/null +++ b/fcgi-distribution/src/main/assembly/distribution.xml @@ -0,0 +1,31 @@ + + + + distribution + + + tar.gz + + + false + + + + ${project.basedir}/src/main/config/modules + /modules + + *.mod + + + + ${distribution-directory} + / + + lib/** + + + + + diff --git a/fcgi-distribution/src/main/config/modules/fcgi.mod b/fcgi-distribution/src/main/config/modules/fcgi.mod new file mode 100644 index 00000000000..00e44199be6 --- /dev/null +++ b/fcgi-distribution/src/main/config/modules/fcgi.mod @@ -0,0 +1,16 @@ +# +# FastCGI Module +# + +[depend] +servlet +client + +[lib] +lib/jetty-security-${jetty.version}.jar +lib/jetty-proxy-${jetty.version}.jar +lib/fcgi/*.jar + +[ini-template] +## For configuration of FastCGI contexts, see +## TODO: documentation url here diff --git a/fcgi-distribution/src/main/config/webapps/wordpress-example.xml b/fcgi-distribution/src/main/config/webapps/wordpress-example.xml new file mode 100644 index 00000000000..9ec42697473 --- /dev/null +++ b/fcgi-distribution/src/main/config/webapps/wordpress-example.xml @@ -0,0 +1,68 @@ + + + + + + /var/www/wordpress-3.7.1 + + + /wp + + + index.php + + + + org.eclipse.jetty.fcgi.proxy.TryFilesFilter + /* + + + + + + + files + $path /index.php?p=$path + + + + + + + default + + + org.eclipse.jetty.servlet.DefaultServlet + + + + dirAllowed + false + + + + / + + + + org.eclipse.jetty.fcgi.proxy.FastCGIProxyServlet + *.php + + proxyTo + http://localhost:9000 + + + prefix + / + + + scriptRoot + + + + scriptPattern + (.+?\\.php) + + + + diff --git a/pom.xml b/pom.xml index b22488f07bb..f57ce98d615 100644 --- a/pom.xml +++ b/pom.xml @@ -24,6 +24,7 @@ fcgi-http-client-transport fcgi-server fcgi-proxy + fcgi-distribution From 9a04f993e745c4b9c60fdc98b1461f5aa71397d5 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 19 Nov 2013 11:27:58 +0100 Subject: [PATCH 33/43] Updated version to 1.0.0, and Jetty to 9.1.0.v20131115. --- fcgi-core/pom.xml | 2 +- fcgi-distribution/pom.xml | 2 +- fcgi-http-client-transport/pom.xml | 2 +- fcgi-proxy/pom.xml | 2 +- fcgi-server/pom.xml | 2 +- pom.xml | 6 +++--- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/fcgi-core/pom.xml b/fcgi-core/pom.xml index 97c3964f8b8..96628305026 100644 --- a/fcgi-core/pom.xml +++ b/fcgi-core/pom.xml @@ -5,7 +5,7 @@ org.eclipse.jetty.fcgi fcgi-parent - 1.0.0-SNAPSHOT + 1.0.0 4.0.0 diff --git a/fcgi-distribution/pom.xml b/fcgi-distribution/pom.xml index d693aa95d8e..5b1d266f254 100644 --- a/fcgi-distribution/pom.xml +++ b/fcgi-distribution/pom.xml @@ -5,7 +5,7 @@ fcgi-parent org.eclipse.jetty.fcgi - 1.0.0-SNAPSHOT + 1.0.0 4.0.0 diff --git a/fcgi-http-client-transport/pom.xml b/fcgi-http-client-transport/pom.xml index 9cc7fec0111..c30276b5e96 100644 --- a/fcgi-http-client-transport/pom.xml +++ b/fcgi-http-client-transport/pom.xml @@ -5,7 +5,7 @@ org.eclipse.jetty.fcgi fcgi-parent - 1.0.0-SNAPSHOT + 1.0.0 4.0.0 diff --git a/fcgi-proxy/pom.xml b/fcgi-proxy/pom.xml index 4c5a998e053..9a39b448c22 100644 --- a/fcgi-proxy/pom.xml +++ b/fcgi-proxy/pom.xml @@ -5,7 +5,7 @@ fcgi-parent org.eclipse.jetty.fcgi - 1.0.0-SNAPSHOT + 1.0.0 4.0.0 diff --git a/fcgi-server/pom.xml b/fcgi-server/pom.xml index ff3ad52e4fb..d90eadb965f 100644 --- a/fcgi-server/pom.xml +++ b/fcgi-server/pom.xml @@ -5,7 +5,7 @@ org.eclipse.jetty.fcgi fcgi-parent - 1.0.0-SNAPSHOT + 1.0.0 4.0.0 diff --git a/pom.xml b/pom.xml index f57ce98d615..d0391899e21 100644 --- a/pom.xml +++ b/pom.xml @@ -5,18 +5,18 @@ org.eclipse.jetty jetty-project - 9.1.0-SNAPSHOT + 9.1.0.v20131115 4.0.0 org.eclipse.jetty.fcgi fcgi-parent - 1.0.0-SNAPSHOT + 1.0.0 pom Jetty :: FastCGI - 9.1.0-SNAPSHOT + 9.1.0.v20131115 From 8dddc2a3f63bcec8d746442c148bc3afa01becb6 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 19 Nov 2013 11:35:04 +0100 Subject: [PATCH 34/43] Updated version to 1.0.1-SNAPSHOT. --- fcgi-core/pom.xml | 2 +- fcgi-distribution/pom.xml | 2 +- fcgi-http-client-transport/pom.xml | 2 +- fcgi-proxy/pom.xml | 2 +- fcgi-server/pom.xml | 2 +- pom.xml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/fcgi-core/pom.xml b/fcgi-core/pom.xml index 96628305026..5ad278738a1 100644 --- a/fcgi-core/pom.xml +++ b/fcgi-core/pom.xml @@ -5,7 +5,7 @@ org.eclipse.jetty.fcgi fcgi-parent - 1.0.0 + 1.0.1-SNAPSHOT 4.0.0 diff --git a/fcgi-distribution/pom.xml b/fcgi-distribution/pom.xml index 5b1d266f254..b71ee83f039 100644 --- a/fcgi-distribution/pom.xml +++ b/fcgi-distribution/pom.xml @@ -5,7 +5,7 @@ fcgi-parent org.eclipse.jetty.fcgi - 1.0.0 + 1.0.1-SNAPSHOT 4.0.0 diff --git a/fcgi-http-client-transport/pom.xml b/fcgi-http-client-transport/pom.xml index c30276b5e96..8f3318c11dd 100644 --- a/fcgi-http-client-transport/pom.xml +++ b/fcgi-http-client-transport/pom.xml @@ -5,7 +5,7 @@ org.eclipse.jetty.fcgi fcgi-parent - 1.0.0 + 1.0.1-SNAPSHOT 4.0.0 diff --git a/fcgi-proxy/pom.xml b/fcgi-proxy/pom.xml index 9a39b448c22..fd9a1cc7d26 100644 --- a/fcgi-proxy/pom.xml +++ b/fcgi-proxy/pom.xml @@ -5,7 +5,7 @@ fcgi-parent org.eclipse.jetty.fcgi - 1.0.0 + 1.0.1-SNAPSHOT 4.0.0 diff --git a/fcgi-server/pom.xml b/fcgi-server/pom.xml index d90eadb965f..26829cf9a4d 100644 --- a/fcgi-server/pom.xml +++ b/fcgi-server/pom.xml @@ -5,7 +5,7 @@ org.eclipse.jetty.fcgi fcgi-parent - 1.0.0 + 1.0.1-SNAPSHOT 4.0.0 diff --git a/pom.xml b/pom.xml index d0391899e21..fd07fe8d888 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ 4.0.0 org.eclipse.jetty.fcgi fcgi-parent - 1.0.0 + 1.0.1-SNAPSHOT pom Jetty :: FastCGI From c7511dd2c74ac97d34bc63b753b469e3602d027a Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 16 Dec 2013 12:57:19 +0100 Subject: [PATCH 35/43] Updated to Jetty 9.1.1-SNAPSHOT. Updated Flusher to use the new IteratingCallback API, and HttpChannelOverFCGI to use the new IdleTimeout API. --- .../eclipse/jetty/fcgi/generator/Flusher.java | 71 +++++++------------ .../fcgi/client/http/HttpChannelOverFCGI.java | 9 +-- pom.xml | 4 +- 3 files changed, 29 insertions(+), 55 deletions(-) diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java index 365fe62e980..16a2cd75616 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java @@ -19,12 +19,9 @@ package org.eclipse.jetty.fcgi.generator; import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; import java.util.Queue; import org.eclipse.jetty.io.EndPoint; -import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.ConcurrentArrayQueue; import org.eclipse.jetty.util.IteratingCallback; import org.eclipse.jetty.util.log.Log; @@ -35,9 +32,8 @@ public class Flusher private static final Logger LOG = Log.getLogger(Flusher.class); private final Queue queue = new ConcurrentArrayQueue<>(); - private final Callback flushCallback = new FlushCallback(); + private final IteratingCallback flushCallback = new FlushCallback(); private final EndPoint endPoint; - private boolean flushing; public Flusher(EndPoint endPoint) { @@ -46,15 +42,9 @@ public class Flusher public void flush(Generator.Result... results) { - synchronized (queue) - { - for (Generator.Result result : results) - queue.offer(result); - if (flushing) - return; - flushing = true; - } - endPoint.write(flushCallback); + for (Generator.Result result : results) + queue.offer(result); + flushCallback.iterate(); } public void shutdown() @@ -67,37 +57,35 @@ public class Flusher private Generator.Result active; @Override - protected boolean process() throws Exception + protected Action process() throws Exception { // Look if other writes are needed. - Generator.Result result; - synchronized (queue) + Generator.Result result = queue.poll(); + if (result == null) { - if (queue.isEmpty()) - { - // No more writes to do, switch to non-flushing - flushing = false; - return false; - } - result = queue.poll(); - // Attempt to gather another result. - // Most often there is another result in the - // queue so this is a real optimization because - // it sends both results in just one TCP packet. - Generator.Result other = queue.poll(); - if (other != null) - result = result.join(other); + // No more writes to do, return. + return Action.IDLE; } + + // Attempt to gather another result. + // Most often there is another result in the + // queue so this is a real optimization because + // it sends both results in just one TCP packet. + Generator.Result other = queue.poll(); + if (other != null) + result = result.join(other); + active = result; ByteBuffer[] buffers = result.getByteBuffers(); endPoint.write(this, buffers); - return false; + return Action.SCHEDULED; } @Override protected void completed() { - // Nothing to do, we always return false from process(). + // We never return Action.SUCCEEDED, so this method is never called. + throw new IllegalStateException(); } @Override @@ -116,20 +104,13 @@ public class Flusher active.failed(x); active = null; - List pending = new ArrayList<>(); - synchronized (queue) + while (true) { - while (true) - { - Generator.Result result = queue.poll(); - if (result != null) - pending.add(result); - else - break; - } - } - for (Generator.Result result : pending) + Generator.Result result = queue.poll(); + if (result == null) + break; result.failed(x); + } super.failed(x); } diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java index dfee69eb232..6287238e37b 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java @@ -120,7 +120,7 @@ public class HttpChannelOverFCGI extends HttpChannel public void exchangeTerminated(Result result) { super.exchangeTerminated(result); - idle.close(); + idle.onClose(); } protected void flush(Generator.Result... results) @@ -151,12 +151,5 @@ public class HttpChannelOverFCGI extends HttpChannel { return connection.getEndPoint().isOpen(); } - - // Overridden for visibility - @Override - protected void close() - { - super.close(); - } } } diff --git a/pom.xml b/pom.xml index fd07fe8d888..51464b27a52 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.eclipse.jetty jetty-project - 9.1.0.v20131115 + 9.1.1-SNAPSHOT 4.0.0 @@ -16,7 +16,7 @@ Jetty :: FastCGI - 9.1.0.v20131115 + 9.1.1-SNAPSHOT From 63e8a462d4059005523c2814ca8253a25225a9a5 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 13 Jan 2014 18:16:03 +0100 Subject: [PATCH 36/43] Updated to Jetty 9.1.1.v20140108. --- .../eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java | 4 ++-- pom.xml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java index 6287238e37b..0befe6e9acd 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java @@ -66,9 +66,9 @@ public class HttpChannelOverFCGI extends HttpChannel } @Override - public void proceed(HttpExchange exchange, boolean proceed) + public void proceed(HttpExchange exchange, Throwable failure) { - sender.proceed(exchange, proceed); + sender.proceed(exchange, failure); } @Override diff --git a/pom.xml b/pom.xml index 51464b27a52..4499c238627 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.eclipse.jetty jetty-project - 9.1.1-SNAPSHOT + 9.1.1.v20140108 4.0.0 @@ -16,7 +16,7 @@ Jetty :: FastCGI - 9.1.1-SNAPSHOT + 9.1.1.v20140108 From 05f4367790ea55393c9005602ddb75bd03757ca6 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 14 Jan 2014 10:36:11 +0100 Subject: [PATCH 37/43] Improved handling of BEGIN_REQUEST flags. --- .../org/eclipse/jetty/fcgi/generator/ClientGenerator.java | 1 + .../jetty/fcgi/parser/BeginRequestContentParser.java | 8 ++++---- .../java/org/eclipse/jetty/fcgi/parser/ServerParser.java | 4 ++-- .../eclipse/jetty/fcgi/server/ServerFCGIConnection.java | 3 ++- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java index ef30c7ad6d2..ce4e869a924 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java @@ -86,6 +86,7 @@ public class ClientGenerator extends Generator // Generate the FCGI_BEGIN_REQUEST frame beginRequestBuffer.putInt(0x01_01_00_00 + request); beginRequestBuffer.putInt(0x00_08_00_00); + // Hardcode RESPONDER role and KEEP_ALIVE flag beginRequestBuffer.putLong(0x00_01_01_00_00_00_00_00L); beginRequestBuffer.flip(); diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java index 17e9efea921..210d3fdd0b5 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java @@ -76,7 +76,7 @@ public class BeginRequestContentParser extends ContentParser if (buffer.remaining() >= 5) { buffer.position(buffer.position() + 5); - onStart(getRequest(), role); + onStart(); reset(); return true; } @@ -92,7 +92,7 @@ public class BeginRequestContentParser extends ContentParser buffer.get(); if (++cursor == 5) { - onStart(getRequest(), role); + onStart(); reset(); return true; } @@ -107,9 +107,9 @@ public class BeginRequestContentParser extends ContentParser return false; } - private void onStart(int request, int role) + private void onStart() { - listener.onStart(request, FCGI.Role.from(role)); + listener.onStart(getRequest(), FCGI.Role.from(role), flags); } private void reset() diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ServerParser.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ServerParser.java index 1b049ca122f..124fcf0b61d 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ServerParser.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ServerParser.java @@ -41,12 +41,12 @@ public class ServerParser extends Parser public interface Listener extends Parser.Listener { - public void onStart(int request, FCGI.Role role); + public void onStart(int request, FCGI.Role role, int flags); public static class Adapter extends Parser.Listener.Adapter implements Listener { @Override - public void onStart(int request, FCGI.Role role) + public void onStart(int request, FCGI.Role role, int flags) { } } diff --git a/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java b/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java index 4f1bba8ddfd..296de57bda1 100644 --- a/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java +++ b/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java @@ -115,8 +115,9 @@ public class ServerFCGIConnection extends AbstractConnection private class ServerListener implements ServerParser.Listener { @Override - public void onStart(int request, FCGI.Role role) + public void onStart(int request, FCGI.Role role, int flags) { + // TODO: handle flags HttpChannelOverFCGI channel = new HttpChannelOverFCGI(connector, configuration, getEndPoint(), new HttpTransportOverFCGI(connector.getByteBufferPool(), flusher, request), new ByteBufferQueuedHttpInput()); HttpChannelOverFCGI existing = channels.putIfAbsent(request, channel); From df60fd5c2dfbf7e87116de719b959cfb7f872139 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 14 Jan 2014 10:37:49 +0100 Subject: [PATCH 38/43] Fixed release of connections when the exchange is terminated. --- .../fcgi/client/http/HttpChannelOverFCGI.java | 12 +++++++++ .../client/http/HttpConnectionOverFCGI.java | 27 +++++++++++++++---- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java index 0befe6e9acd..cda158c3c39 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java @@ -27,11 +27,15 @@ import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.fcgi.generator.Flusher; import org.eclipse.jetty.fcgi.generator.Generator; import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.io.IdleTimeout; public class HttpChannelOverFCGI extends HttpChannel { + private final HttpConnectionOverFCGI connection; private final Flusher flusher; private final int request; private final HttpSenderOverFCGI sender; @@ -42,6 +46,7 @@ public class HttpChannelOverFCGI extends HttpChannel public HttpChannelOverFCGI(final HttpConnectionOverFCGI connection, Flusher flusher, int request, long idleTimeout) { super(connection.getHttpDestination()); + this.connection = connection; this.flusher = flusher; this.request = request; this.sender = new HttpSenderOverFCGI(this); @@ -121,6 +126,13 @@ public class HttpChannelOverFCGI extends HttpChannel { super.exchangeTerminated(result); idle.onClose(); + boolean close = result.isFailed(); + HttpFields responseHeaders = result.getResponse().getHeaders(); + close |= responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()); + if (close) + connection.close(); + else + connection.release(); } protected void flush(Generator.Result... results) diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java index f0f800ba548..85fe70fab42 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java @@ -24,11 +24,13 @@ import java.util.LinkedList; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpConnection; import org.eclipse.jetty.client.HttpDestination; import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.client.PoolingHttpDestination; import org.eclipse.jetty.client.api.Connection; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; @@ -49,6 +51,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec private final LinkedList requests = new LinkedList<>(); private final Map channels = new ConcurrentHashMap<>(); + private final AtomicBoolean closed = new AtomicBoolean(); private final Flusher flusher; private final HttpDestination destination; private final Delegate delegate; @@ -153,14 +156,28 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec return false; } + public void release() + { + if (destination instanceof PoolingHttpDestination) + { + @SuppressWarnings("unchecked") + PoolingHttpDestination fcgiDestination = + (PoolingHttpDestination)destination; + fcgiDestination.release(this); + } + } + @Override public void close() { - getHttpDestination().close(this); - getEndPoint().shutdownOutput(); - LOG.debug("{} oshut", this); - getEndPoint().close(); - LOG.debug("{} closed", this); + if (closed.compareAndSet(false, true)) + { + getHttpDestination().close(this); + getEndPoint().shutdownOutput(); + LOG.debug("{} oshut", this); + getEndPoint().close(); + LOG.debug("{} closed", this); + } } private int acquireRequest() From 9dcde09cba8395e7b104674b3315eff48c025b0a Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 14 Jan 2014 12:43:37 +0100 Subject: [PATCH 39/43] Fixed buffer recycling. --- .../jetty/fcgi/generator/Generator.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java index 0bbe28f7bd4..c4329112aa1 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java @@ -42,10 +42,10 @@ public class Generator { id &= 0xFF_FF; - int remaining = content == null ? 0 : content.remaining(); + int contentLength = content == null ? 0 : content.remaining(); Result result = new Result(byteBufferPool, callback); - while (remaining > 0 || lastContent) + while (contentLength > 0 || lastContent) { ByteBuffer buffer = byteBufferPool.acquire(8, false); BufferUtil.clearToFill(buffer); @@ -55,22 +55,26 @@ public class Generator buffer.put((byte)0x01); buffer.put((byte)frameType.code); buffer.putShort((short)id); - int length = Math.min(MAX_CONTENT_LENGTH, remaining); + int length = Math.min(MAX_CONTENT_LENGTH, contentLength); buffer.putShort((short)length); buffer.putShort((short)0); buffer.flip(); - if (remaining == 0) + if (contentLength == 0) break; - // Slice to content to avoid copying + // Slice the content to avoid copying int limit = content.limit(); content.limit(content.position() + length); ByteBuffer slice = content.slice(); - result = result.append(slice, recycle); + // Don't recycle the slice + result = result.append(slice, false); content.position(content.limit()); content.limit(limit); - remaining -= length; + contentLength -= length; + // Recycle the content buffer if needed + if (recycle && contentLength == 0) + result = result.append(content, true); } return result; From d3126d8345f15c28a794c217ae51517e2b242c06 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 14 Jan 2014 12:44:14 +0100 Subject: [PATCH 40/43] Small refactorings. --- .../client/http/HttpClientTransportOverFCGI.java | 14 ++++++++++++-- .../jetty/fcgi/client/http/HttpSenderOverFCGI.java | 3 ++- .../client/http/ExternalFastCGIServerTest.java | 6 ++++-- .../jetty/fcgi/server/HttpChannelOverFCGI.java | 2 ++ 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java index c54bf0eef9a..8275ef7c408 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java @@ -48,10 +48,20 @@ public class HttpClientTransportOverFCGI extends AbstractHttpClientTransport this.scriptRoot = scriptRoot; } + public boolean isMultiplexed() + { + return multiplexed; + } + + public String getScriptRoot() + { + return scriptRoot; + } + @Override public HttpDestination newHttpDestination(Origin origin) { - return multiplexed ? new MultiplexHttpDestinationOverFCGI(getHttpClient(), origin) + return isMultiplexed() ? new MultiplexHttpDestinationOverFCGI(getHttpClient(), origin) : new HttpDestinationOverFCGI(getHttpClient(), origin); } @@ -69,6 +79,6 @@ public class HttpClientTransportOverFCGI extends AbstractHttpClientTransport protected void customize(Request request, HttpFields fastCGIHeaders) { - fastCGIHeaders.put(FCGI.Headers.DOCUMENT_ROOT, scriptRoot); + fastCGIHeaders.put(FCGI.Headers.DOCUMENT_ROOT, getScriptRoot()); } } diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java index bcce554fc81..f608824807a 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java @@ -14,6 +14,7 @@ import org.eclipse.jetty.fcgi.generator.Generator; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Jetty; @@ -87,7 +88,7 @@ public class HttpSenderOverFCGI extends HttpSender } else { - Generator.Result noContentResult = generator.generateRequestContent(id, null, true, callback); + Generator.Result noContentResult = generator.generateRequestContent(id, BufferUtil.EMPTY_BUFFER, true, callback); getHttpChannel().flush(headersResult, noContentResult); } } diff --git a/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/ExternalFastCGIServerTest.java b/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/ExternalFastCGIServerTest.java index 496134e71a5..87f4512b39d 100644 --- a/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/ExternalFastCGIServerTest.java +++ b/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/ExternalFastCGIServerTest.java @@ -19,6 +19,7 @@ package org.eclipse.jetty.fcgi.client.http; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.concurrent.TimeUnit; @@ -35,7 +36,7 @@ public class ExternalFastCGIServerTest @Ignore("Relies on an external server") public void testExternalFastCGIServer() throws Exception { - // Assume a FastCGI server is listening on port 9000 + // Assume a FastCGI server is listening on localhost:9000 HttpClient client = new HttpClient(new HttpClientTransportOverFCGI("/var/www/php-fcgi"), null); client.start(); @@ -47,6 +48,7 @@ public class ExternalFastCGIServerTest Assert.assertEquals(200, response.getStatus()); - Files.write(Paths.get(System.getProperty("java.io.tmpdir"), "fcgi_response.html"), response.getContent(), StandardOpenOption.CREATE, StandardOpenOption.WRITE); + Path responseFile = Paths.get(System.getProperty("java.io.tmpdir"), "fcgi_response.html"); + Files.write(responseFile, response.getContent(), StandardOpenOption.CREATE, StandardOpenOption.WRITE); } } diff --git a/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java b/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java index 24c0b5b04cb..834c181d734 100644 --- a/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java +++ b/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java @@ -132,6 +132,7 @@ public class HttpChannelOverFCGI extends HttpChannel while (true) { State current = state.get(); + LOG.debug("Dispatching, state={}", current); switch (current) { case IDLE: @@ -166,6 +167,7 @@ public class HttpChannelOverFCGI extends HttpChannel while (true) { State current = state.get(); + LOG.debug("Running, state={}", current); switch (current) { case DISPATCH: From 0492e34c9ee21433654e12bf884b8a5abd038db7 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 14 Jan 2014 12:44:55 +0100 Subject: [PATCH 41/43] Introduced leak tracking for buffers and connections in tests. --- .../http/AbstractHttpClientServerTest.java | 58 ++++++++++++++++++- .../fcgi/client/http/HttpClientTest.java | 24 +++++--- 2 files changed, 70 insertions(+), 12 deletions(-) diff --git a/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/AbstractHttpClientServerTest.java b/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/AbstractHttpClientServerTest.java index ad6b465c4a6..99bda9217fa 100644 --- a/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/AbstractHttpClientServerTest.java +++ b/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/AbstractHttpClientServerTest.java @@ -18,23 +18,34 @@ package org.eclipse.jetty.fcgi.client.http; +import java.util.concurrent.atomic.AtomicLong; + +import org.eclipse.jetty.client.ConnectionPool; import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpDestination; +import org.eclipse.jetty.client.LeakTrackingConnectionPool; +import org.eclipse.jetty.client.Origin; import org.eclipse.jetty.fcgi.server.ServerFCGIConnectionFactory; import org.eclipse.jetty.http.HttpScheme; +import org.eclipse.jetty.io.ArrayByteBufferPool; +import org.eclipse.jetty.io.LeakTrackingByteBufferPool; +import org.eclipse.jetty.io.MappedByteBufferPool; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.toolchain.test.TestTracker; +import org.eclipse.jetty.util.LeakDetector; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.junit.After; +import org.junit.Assert; import org.junit.Rule; public abstract class AbstractHttpClientServerTest { @Rule public final TestTracker tracker = new TestTracker(); - + private final AtomicLong leaks = new AtomicLong(); protected Server server; protected ServerConnector connector; protected HttpClient client; @@ -45,7 +56,16 @@ public abstract class AbstractHttpClientServerTest server = new Server(); ServerFCGIConnectionFactory fcgiConnectionFactory = new ServerFCGIConnectionFactory(new HttpConfiguration()); - connector = new ServerConnector(server, fcgiConnectionFactory); + connector = new ServerConnector(server, null, null, + new LeakTrackingByteBufferPool(new ArrayByteBufferPool()) + { + @Override + protected void leaked(LeakDetector.LeakInfo leakInfo) + { + leaks.incrementAndGet(); + } + }, 1, Math.max(1, Runtime.getRuntime().availableProcessors() / 2), fcgiConnectionFactory); +// connector.setPort(9000); server.addConnector(connector); server.setHandler(handler); @@ -54,14 +74,46 @@ public abstract class AbstractHttpClientServerTest QueuedThreadPool executor = new QueuedThreadPool(); executor.setName(executor.getName() + "-client"); - client = new HttpClient(new HttpClientTransportOverFCGI(""), null); + client = new HttpClient(new HttpClientTransportOverFCGI(1, false, "") + { + @Override + public HttpDestination newHttpDestination(Origin origin) + { + return new HttpDestinationOverFCGI(client, origin) + { + @Override + protected ConnectionPool newConnectionPool(HttpClient client) + { + return new LeakTrackingConnectionPool(this, client.getMaxConnectionsPerDestination(), this) + { + @Override + protected void leaked(LeakDetector.LeakInfo leakInfo) + { + leaks.incrementAndGet(); + } + }; + } + }; + } + }, null); client.setExecutor(executor); + client.setByteBufferPool(new LeakTrackingByteBufferPool(new MappedByteBufferPool()) + { + @Override + protected void leaked(LeakDetector.LeakInfo leakInfo) + { + leaks.incrementAndGet(); + } + }); client.start(); } @After public void dispose() throws Exception { + System.gc(); + Assert.assertEquals(0, leaks.get()); + if (client != null) client.stop(); if (server != null) diff --git a/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/HttpClientTest.java b/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/HttpClientTest.java index 518e12cf830..b08a0b517d3 100644 --- a/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/HttpClientTest.java +++ b/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/HttpClientTest.java @@ -74,7 +74,10 @@ public class HttpClientTest extends AbstractHttpClientServerTest } }); - for (int i = 0; i < 2; ++i) + int maxConnections = 256; + client.setMaxConnectionsPerDestination(maxConnections); + + for (int i = 0; i < maxConnections + 1; ++i) { ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort()); Assert.assertNotNull(response); @@ -269,15 +272,18 @@ public class HttpClientTest extends AbstractHttpClientServerTest } }); - ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort() + "/?b=1") - .param(paramName, paramValue) - .content(new BytesContentProvider(content)) - .timeout(5, TimeUnit.SECONDS) - .send(); + for (int i = 0; i < 256; ++i) + { + ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort() + "/?b=1") + .param(paramName, paramValue) + .content(new BytesContentProvider(content)) + .timeout(5, TimeUnit.SECONDS) + .send(); - Assert.assertNotNull(response); - Assert.assertEquals(200, response.getStatus()); - Assert.assertArrayEquals(content, response.getContent()); + Assert.assertNotNull(response); + Assert.assertEquals(200, response.getStatus()); + Assert.assertArrayEquals(content, response.getContent()); + } } @Test From 27a9970dc6406a22d81fa47b0704a53783a56239 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 17 Jan 2014 11:56:09 +0100 Subject: [PATCH 42/43] Updated version to 1.0.1. --- fcgi-core/pom.xml | 2 +- fcgi-distribution/pom.xml | 2 +- fcgi-http-client-transport/pom.xml | 2 +- fcgi-proxy/pom.xml | 2 +- fcgi-server/pom.xml | 2 +- pom.xml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/fcgi-core/pom.xml b/fcgi-core/pom.xml index 5ad278738a1..c86bec8a63a 100644 --- a/fcgi-core/pom.xml +++ b/fcgi-core/pom.xml @@ -5,7 +5,7 @@ org.eclipse.jetty.fcgi fcgi-parent - 1.0.1-SNAPSHOT + 1.0.1 4.0.0 diff --git a/fcgi-distribution/pom.xml b/fcgi-distribution/pom.xml index b71ee83f039..059b5e098f6 100644 --- a/fcgi-distribution/pom.xml +++ b/fcgi-distribution/pom.xml @@ -5,7 +5,7 @@ fcgi-parent org.eclipse.jetty.fcgi - 1.0.1-SNAPSHOT + 1.0.1 4.0.0 diff --git a/fcgi-http-client-transport/pom.xml b/fcgi-http-client-transport/pom.xml index 8f3318c11dd..f4e8513044b 100644 --- a/fcgi-http-client-transport/pom.xml +++ b/fcgi-http-client-transport/pom.xml @@ -5,7 +5,7 @@ org.eclipse.jetty.fcgi fcgi-parent - 1.0.1-SNAPSHOT + 1.0.1 4.0.0 diff --git a/fcgi-proxy/pom.xml b/fcgi-proxy/pom.xml index fd9a1cc7d26..6640785bf11 100644 --- a/fcgi-proxy/pom.xml +++ b/fcgi-proxy/pom.xml @@ -5,7 +5,7 @@ fcgi-parent org.eclipse.jetty.fcgi - 1.0.1-SNAPSHOT + 1.0.1 4.0.0 diff --git a/fcgi-server/pom.xml b/fcgi-server/pom.xml index 26829cf9a4d..d285e286804 100644 --- a/fcgi-server/pom.xml +++ b/fcgi-server/pom.xml @@ -5,7 +5,7 @@ org.eclipse.jetty.fcgi fcgi-parent - 1.0.1-SNAPSHOT + 1.0.1 4.0.0 diff --git a/pom.xml b/pom.xml index 4499c238627..86be47bb603 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ 4.0.0 org.eclipse.jetty.fcgi fcgi-parent - 1.0.1-SNAPSHOT + 1.0.1 pom Jetty :: FastCGI From fbbe2426e50f40a884d253f189df317494341c33 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Thu, 13 Mar 2014 15:35:19 +0100 Subject: [PATCH 43/43] Moved FastCGI project to a module directory for inclusion in the main Jetty repository. --- {fcgi-core => jetty-fcgi/fcgi-core}/pom.xml | 0 .../src/main/java/org/eclipse/jetty/fcgi/FCGI.java | 0 .../jetty/fcgi/generator/ClientGenerator.java | 0 .../org/eclipse/jetty/fcgi/generator/Flusher.java | 0 .../org/eclipse/jetty/fcgi/generator/Generator.java | 0 .../jetty/fcgi/generator/ServerGenerator.java | 0 .../fcgi/parser/BeginRequestContentParser.java | 0 .../org/eclipse/jetty/fcgi/parser/ClientParser.java | 0 .../eclipse/jetty/fcgi/parser/ContentParser.java | 0 .../jetty/fcgi/parser/EndRequestContentParser.java | 0 .../org/eclipse/jetty/fcgi/parser/HeaderParser.java | 0 .../jetty/fcgi/parser/ParamsContentParser.java | 0 .../java/org/eclipse/jetty/fcgi/parser/Parser.java | 0 .../jetty/fcgi/parser/ResponseContentParser.java | 0 .../org/eclipse/jetty/fcgi/parser/ServerParser.java | 0 .../jetty/fcgi/parser/StreamContentParser.java | 0 .../jetty/fcgi/generator/ClientGeneratorTest.java | 0 .../eclipse/jetty/fcgi/parser/ClientParserTest.java | 0 .../fcgi-distribution}/pom.xml | 0 .../src/main/assembly/distribution.xml | 0 .../src/main/config/modules/fcgi.mod | 0 .../src/main/config/webapps/wordpress-example.xml | 0 .../fcgi-http-client-transport}/pom.xml | 0 .../jetty/fcgi/client/http/HttpChannelOverFCGI.java | 0 .../client/http/HttpClientTransportOverFCGI.java | 0 .../fcgi/client/http/HttpConnectionOverFCGI.java | 0 .../fcgi/client/http/HttpDestinationOverFCGI.java | 0 .../fcgi/client/http/HttpReceiverOverFCGI.java | 0 .../jetty/fcgi/client/http/HttpSenderOverFCGI.java | 0 .../http/MultiplexHttpDestinationOverFCGI.java | 0 .../client/http/AbstractHttpClientServerTest.java | 0 .../jetty/fcgi/client/http/EmptyServerHandler.java | 0 .../fcgi/client/http/ExternalFastCGIServerTest.java | 0 .../jetty/fcgi/client/http/HttpClientTest.java | 0 .../src/test/resources/jetty-logging.properties | 0 {fcgi-proxy => jetty-fcgi/fcgi-proxy}/pom.xml | 0 .../jetty/fcgi/proxy/FastCGIProxyServlet.java | 0 .../eclipse/jetty/fcgi/proxy/TryFilesFilter.java | 0 .../fcgi/proxy/DrupalSPDYFastCGIProxyServer.java | 0 .../fcgi/proxy/WordPressSPDYFastCGIProxyServer.java | 0 .../src/test/resources/jetty-logging.properties | 0 .../fcgi-proxy}/src/test/resources/keystore.jks | Bin .../fcgi-proxy}/src/test/resources/truststore.jks | Bin {fcgi-server => jetty-fcgi/fcgi-server}/pom.xml | 0 .../jetty/fcgi/server/HttpChannelOverFCGI.java | 0 .../jetty/fcgi/server/HttpTransportOverFCGI.java | 0 .../jetty/fcgi/server/ServerFCGIConnection.java | 0 .../fcgi/server/ServerFCGIConnectionFactory.java | 0 pom.xml => jetty-fcgi/pom.xml | 0 49 files changed, 0 insertions(+), 0 deletions(-) rename {fcgi-core => jetty-fcgi/fcgi-core}/pom.xml (100%) rename {fcgi-core => jetty-fcgi/fcgi-core}/src/main/java/org/eclipse/jetty/fcgi/FCGI.java (100%) rename {fcgi-core => jetty-fcgi/fcgi-core}/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java (100%) rename {fcgi-core => jetty-fcgi/fcgi-core}/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java (100%) rename {fcgi-core => jetty-fcgi/fcgi-core}/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java (100%) rename {fcgi-core => jetty-fcgi/fcgi-core}/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java (100%) rename {fcgi-core => jetty-fcgi/fcgi-core}/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java (100%) rename {fcgi-core => jetty-fcgi/fcgi-core}/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java (100%) rename {fcgi-core => jetty-fcgi/fcgi-core}/src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java (100%) rename {fcgi-core => jetty-fcgi/fcgi-core}/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java (100%) rename {fcgi-core => jetty-fcgi/fcgi-core}/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java (100%) rename {fcgi-core => jetty-fcgi/fcgi-core}/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java (100%) rename {fcgi-core => jetty-fcgi/fcgi-core}/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java (100%) rename {fcgi-core => jetty-fcgi/fcgi-core}/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java (100%) rename {fcgi-core => jetty-fcgi/fcgi-core}/src/main/java/org/eclipse/jetty/fcgi/parser/ServerParser.java (100%) rename {fcgi-core => jetty-fcgi/fcgi-core}/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java (100%) rename {fcgi-core => jetty-fcgi/fcgi-core}/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java (100%) rename {fcgi-core => jetty-fcgi/fcgi-core}/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java (100%) rename {fcgi-distribution => jetty-fcgi/fcgi-distribution}/pom.xml (100%) rename {fcgi-distribution => jetty-fcgi/fcgi-distribution}/src/main/assembly/distribution.xml (100%) rename {fcgi-distribution => jetty-fcgi/fcgi-distribution}/src/main/config/modules/fcgi.mod (100%) rename {fcgi-distribution => jetty-fcgi/fcgi-distribution}/src/main/config/webapps/wordpress-example.xml (100%) rename {fcgi-http-client-transport => jetty-fcgi/fcgi-http-client-transport}/pom.xml (100%) rename {fcgi-http-client-transport => jetty-fcgi/fcgi-http-client-transport}/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java (100%) rename {fcgi-http-client-transport => jetty-fcgi/fcgi-http-client-transport}/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java (100%) rename {fcgi-http-client-transport => jetty-fcgi/fcgi-http-client-transport}/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java (100%) rename {fcgi-http-client-transport => jetty-fcgi/fcgi-http-client-transport}/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpDestinationOverFCGI.java (100%) rename {fcgi-http-client-transport => jetty-fcgi/fcgi-http-client-transport}/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpReceiverOverFCGI.java (100%) rename {fcgi-http-client-transport => jetty-fcgi/fcgi-http-client-transport}/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java (100%) rename {fcgi-http-client-transport => jetty-fcgi/fcgi-http-client-transport}/src/main/java/org/eclipse/jetty/fcgi/client/http/MultiplexHttpDestinationOverFCGI.java (100%) rename {fcgi-http-client-transport => jetty-fcgi/fcgi-http-client-transport}/src/test/java/org/eclipse/jetty/fcgi/client/http/AbstractHttpClientServerTest.java (100%) rename {fcgi-http-client-transport => jetty-fcgi/fcgi-http-client-transport}/src/test/java/org/eclipse/jetty/fcgi/client/http/EmptyServerHandler.java (100%) rename {fcgi-http-client-transport => jetty-fcgi/fcgi-http-client-transport}/src/test/java/org/eclipse/jetty/fcgi/client/http/ExternalFastCGIServerTest.java (100%) rename {fcgi-http-client-transport => jetty-fcgi/fcgi-http-client-transport}/src/test/java/org/eclipse/jetty/fcgi/client/http/HttpClientTest.java (100%) rename {fcgi-http-client-transport => jetty-fcgi/fcgi-http-client-transport}/src/test/resources/jetty-logging.properties (100%) rename {fcgi-proxy => jetty-fcgi/fcgi-proxy}/pom.xml (100%) rename {fcgi-proxy => jetty-fcgi/fcgi-proxy}/src/main/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServlet.java (100%) rename {fcgi-proxy => jetty-fcgi/fcgi-proxy}/src/main/java/org/eclipse/jetty/fcgi/proxy/TryFilesFilter.java (100%) rename {fcgi-proxy => jetty-fcgi/fcgi-proxy}/src/test/java/org/eclipse/jetty/fcgi/proxy/DrupalSPDYFastCGIProxyServer.java (100%) rename {fcgi-proxy => jetty-fcgi/fcgi-proxy}/src/test/java/org/eclipse/jetty/fcgi/proxy/WordPressSPDYFastCGIProxyServer.java (100%) rename {fcgi-proxy => jetty-fcgi/fcgi-proxy}/src/test/resources/jetty-logging.properties (100%) rename {fcgi-proxy => jetty-fcgi/fcgi-proxy}/src/test/resources/keystore.jks (100%) rename {fcgi-proxy => jetty-fcgi/fcgi-proxy}/src/test/resources/truststore.jks (100%) rename {fcgi-server => jetty-fcgi/fcgi-server}/pom.xml (100%) rename {fcgi-server => jetty-fcgi/fcgi-server}/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java (100%) rename {fcgi-server => jetty-fcgi/fcgi-server}/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java (100%) rename {fcgi-server => jetty-fcgi/fcgi-server}/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java (100%) rename {fcgi-server => jetty-fcgi/fcgi-server}/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnectionFactory.java (100%) rename pom.xml => jetty-fcgi/pom.xml (100%) diff --git a/fcgi-core/pom.xml b/jetty-fcgi/fcgi-core/pom.xml similarity index 100% rename from fcgi-core/pom.xml rename to jetty-fcgi/fcgi-core/pom.xml diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/FCGI.java b/jetty-fcgi/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/FCGI.java similarity index 100% rename from fcgi-core/src/main/java/org/eclipse/jetty/fcgi/FCGI.java rename to jetty-fcgi/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/FCGI.java diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java b/jetty-fcgi/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java similarity index 100% rename from fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java rename to jetty-fcgi/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java b/jetty-fcgi/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java similarity index 100% rename from fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java rename to jetty-fcgi/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java b/jetty-fcgi/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java similarity index 100% rename from fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java rename to jetty-fcgi/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java b/jetty-fcgi/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java similarity index 100% rename from fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java rename to jetty-fcgi/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java b/jetty-fcgi/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java similarity index 100% rename from fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java rename to jetty-fcgi/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java b/jetty-fcgi/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java similarity index 100% rename from fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java rename to jetty-fcgi/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java b/jetty-fcgi/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java similarity index 100% rename from fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java rename to jetty-fcgi/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java b/jetty-fcgi/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java similarity index 100% rename from fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java rename to jetty-fcgi/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java b/jetty-fcgi/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java similarity index 100% rename from fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java rename to jetty-fcgi/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java b/jetty-fcgi/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java similarity index 100% rename from fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java rename to jetty-fcgi/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java b/jetty-fcgi/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java similarity index 100% rename from fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java rename to jetty-fcgi/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java b/jetty-fcgi/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java similarity index 100% rename from fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java rename to jetty-fcgi/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ServerParser.java b/jetty-fcgi/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ServerParser.java similarity index 100% rename from fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ServerParser.java rename to jetty-fcgi/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ServerParser.java diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java b/jetty-fcgi/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java similarity index 100% rename from fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java rename to jetty-fcgi/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java diff --git a/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java b/jetty-fcgi/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java similarity index 100% rename from fcgi-core/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java rename to jetty-fcgi/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java diff --git a/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java b/jetty-fcgi/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java similarity index 100% rename from fcgi-core/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java rename to jetty-fcgi/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java diff --git a/fcgi-distribution/pom.xml b/jetty-fcgi/fcgi-distribution/pom.xml similarity index 100% rename from fcgi-distribution/pom.xml rename to jetty-fcgi/fcgi-distribution/pom.xml diff --git a/fcgi-distribution/src/main/assembly/distribution.xml b/jetty-fcgi/fcgi-distribution/src/main/assembly/distribution.xml similarity index 100% rename from fcgi-distribution/src/main/assembly/distribution.xml rename to jetty-fcgi/fcgi-distribution/src/main/assembly/distribution.xml diff --git a/fcgi-distribution/src/main/config/modules/fcgi.mod b/jetty-fcgi/fcgi-distribution/src/main/config/modules/fcgi.mod similarity index 100% rename from fcgi-distribution/src/main/config/modules/fcgi.mod rename to jetty-fcgi/fcgi-distribution/src/main/config/modules/fcgi.mod diff --git a/fcgi-distribution/src/main/config/webapps/wordpress-example.xml b/jetty-fcgi/fcgi-distribution/src/main/config/webapps/wordpress-example.xml similarity index 100% rename from fcgi-distribution/src/main/config/webapps/wordpress-example.xml rename to jetty-fcgi/fcgi-distribution/src/main/config/webapps/wordpress-example.xml diff --git a/fcgi-http-client-transport/pom.xml b/jetty-fcgi/fcgi-http-client-transport/pom.xml similarity index 100% rename from fcgi-http-client-transport/pom.xml rename to jetty-fcgi/fcgi-http-client-transport/pom.xml diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java b/jetty-fcgi/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java similarity index 100% rename from fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java rename to jetty-fcgi/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java b/jetty-fcgi/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java similarity index 100% rename from fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java rename to jetty-fcgi/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java b/jetty-fcgi/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java similarity index 100% rename from fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java rename to jetty-fcgi/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpDestinationOverFCGI.java b/jetty-fcgi/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpDestinationOverFCGI.java similarity index 100% rename from fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpDestinationOverFCGI.java rename to jetty-fcgi/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpDestinationOverFCGI.java diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpReceiverOverFCGI.java b/jetty-fcgi/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpReceiverOverFCGI.java similarity index 100% rename from fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpReceiverOverFCGI.java rename to jetty-fcgi/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpReceiverOverFCGI.java diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java b/jetty-fcgi/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java similarity index 100% rename from fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java rename to jetty-fcgi/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/MultiplexHttpDestinationOverFCGI.java b/jetty-fcgi/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/MultiplexHttpDestinationOverFCGI.java similarity index 100% rename from fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/MultiplexHttpDestinationOverFCGI.java rename to jetty-fcgi/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/MultiplexHttpDestinationOverFCGI.java diff --git a/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/AbstractHttpClientServerTest.java b/jetty-fcgi/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/AbstractHttpClientServerTest.java similarity index 100% rename from fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/AbstractHttpClientServerTest.java rename to jetty-fcgi/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/AbstractHttpClientServerTest.java diff --git a/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/EmptyServerHandler.java b/jetty-fcgi/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/EmptyServerHandler.java similarity index 100% rename from fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/EmptyServerHandler.java rename to jetty-fcgi/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/EmptyServerHandler.java diff --git a/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/ExternalFastCGIServerTest.java b/jetty-fcgi/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/ExternalFastCGIServerTest.java similarity index 100% rename from fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/ExternalFastCGIServerTest.java rename to jetty-fcgi/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/ExternalFastCGIServerTest.java diff --git a/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/HttpClientTest.java b/jetty-fcgi/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/HttpClientTest.java similarity index 100% rename from fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/HttpClientTest.java rename to jetty-fcgi/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/HttpClientTest.java diff --git a/fcgi-http-client-transport/src/test/resources/jetty-logging.properties b/jetty-fcgi/fcgi-http-client-transport/src/test/resources/jetty-logging.properties similarity index 100% rename from fcgi-http-client-transport/src/test/resources/jetty-logging.properties rename to jetty-fcgi/fcgi-http-client-transport/src/test/resources/jetty-logging.properties diff --git a/fcgi-proxy/pom.xml b/jetty-fcgi/fcgi-proxy/pom.xml similarity index 100% rename from fcgi-proxy/pom.xml rename to jetty-fcgi/fcgi-proxy/pom.xml diff --git a/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServlet.java b/jetty-fcgi/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServlet.java similarity index 100% rename from fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServlet.java rename to jetty-fcgi/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyServlet.java diff --git a/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/TryFilesFilter.java b/jetty-fcgi/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/TryFilesFilter.java similarity index 100% rename from fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/TryFilesFilter.java rename to jetty-fcgi/fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/TryFilesFilter.java diff --git a/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/DrupalSPDYFastCGIProxyServer.java b/jetty-fcgi/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/DrupalSPDYFastCGIProxyServer.java similarity index 100% rename from fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/DrupalSPDYFastCGIProxyServer.java rename to jetty-fcgi/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/DrupalSPDYFastCGIProxyServer.java diff --git a/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/WordPressSPDYFastCGIProxyServer.java b/jetty-fcgi/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/WordPressSPDYFastCGIProxyServer.java similarity index 100% rename from fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/WordPressSPDYFastCGIProxyServer.java rename to jetty-fcgi/fcgi-proxy/src/test/java/org/eclipse/jetty/fcgi/proxy/WordPressSPDYFastCGIProxyServer.java diff --git a/fcgi-proxy/src/test/resources/jetty-logging.properties b/jetty-fcgi/fcgi-proxy/src/test/resources/jetty-logging.properties similarity index 100% rename from fcgi-proxy/src/test/resources/jetty-logging.properties rename to jetty-fcgi/fcgi-proxy/src/test/resources/jetty-logging.properties diff --git a/fcgi-proxy/src/test/resources/keystore.jks b/jetty-fcgi/fcgi-proxy/src/test/resources/keystore.jks similarity index 100% rename from fcgi-proxy/src/test/resources/keystore.jks rename to jetty-fcgi/fcgi-proxy/src/test/resources/keystore.jks diff --git a/fcgi-proxy/src/test/resources/truststore.jks b/jetty-fcgi/fcgi-proxy/src/test/resources/truststore.jks similarity index 100% rename from fcgi-proxy/src/test/resources/truststore.jks rename to jetty-fcgi/fcgi-proxy/src/test/resources/truststore.jks diff --git a/fcgi-server/pom.xml b/jetty-fcgi/fcgi-server/pom.xml similarity index 100% rename from fcgi-server/pom.xml rename to jetty-fcgi/fcgi-server/pom.xml diff --git a/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java similarity index 100% rename from fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java rename to jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java diff --git a/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java similarity index 100% rename from fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java rename to jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java diff --git a/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java similarity index 100% rename from fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java rename to jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java diff --git a/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnectionFactory.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnectionFactory.java similarity index 100% rename from fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnectionFactory.java rename to jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnectionFactory.java diff --git a/pom.xml b/jetty-fcgi/pom.xml similarity index 100% rename from pom.xml rename to jetty-fcgi/pom.xml