Started work on generation/parsing.
This commit is contained in:
parent
a2324d5adb
commit
c65847c89e
|
@ -0,0 +1,100 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-project</artifactId>
|
||||
<version>9.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>jetty-fcgi</artifactId>
|
||||
<name>Jetty :: FastCGI</name>
|
||||
|
||||
<properties>
|
||||
<bundle-symbolic-name>${project.groupId}.fcgi</bundle-symbolic-name>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!--
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<descriptorRefs>
|
||||
<descriptorRef>config</descriptorRef>
|
||||
</descriptorRefs>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
-->
|
||||
<plugin>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>maven-bundle-plugin</artifactId>
|
||||
<extensions>true</extensions>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>manifest</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<instructions>
|
||||
<Import-Package>javax.net.*,*</Import-Package>
|
||||
</instructions>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<!-- Required for OSGI -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>findbugs-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<onlyAnalyze>org.eclipse.jetty.fcgi.*</onlyAnalyze>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-util</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-io</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-http</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.11</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<byte[]> 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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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<FCGI.FrameType, ContentParser> 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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue