├── .travis.yml
├── .classpath
├── .gitignore
├── src
├── main
│ └── java
│ │ └── com
│ │ └── googlecode
│ │ └── fcgi4j
│ │ ├── FCGIException.java
│ │ ├── exceptions
│ │ ├── FCGIUnKnownHeaderException.java
│ │ ├── FCGIException.java
│ │ └── FCGIInvalidHeaderException.java
│ │ ├── constant
│ │ ├── FCGIRole.java
│ │ ├── FCGIProtocolStatus.java
│ │ └── FCGIHeaderType.java
│ │ ├── util
│ │ └── IoUtils.java
│ │ ├── message
│ │ ├── FCGIBeginRequest.java
│ │ ├── FCGIEndRequest.java
│ │ ├── FCGIStdin.java
│ │ ├── FCGIHeader.java
│ │ └── FCGIParams.java
│ │ └── FCGIConnection.java
└── test
│ └── java
│ └── com
│ └── googlecode
│ └── fcgi4j
│ └── Test.java
├── fcgi4j.iml
├── pom.xml
└── README.md
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | sudo: false
3 |
--------------------------------------------------------------------------------
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 |
3 |
4 | # Package Files #
5 | *.jar
6 | *.war
7 | *.ear
8 |
9 | # Maven
10 | target/
11 | pom.xml.tag
12 | pom.xml.releaseBackup
13 | pom.xml.versionsBackup
14 | pom.xml.next
15 | release.properties
16 | dependency-reduced-pom.xml
17 | buildNumber.properties
18 | .mvn/timing.properties
--------------------------------------------------------------------------------
/src/main/java/com/googlecode/fcgi4j/FCGIException.java:
--------------------------------------------------------------------------------
1 | package com.googlecode.fcgi4j;
2 |
3 | /**
4 | * @author panzd
5 | */
6 | public class FCGIException extends RuntimeException {
7 | /**
8 | *
9 | */
10 | private static final long serialVersionUID = 1L;
11 |
12 | public FCGIException(String message) {
13 | super(message);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/googlecode/fcgi4j/exceptions/FCGIUnKnownHeaderException.java:
--------------------------------------------------------------------------------
1 | package com.googlecode.fcgi4j.exceptions;
2 |
3 | /**
4 | * com.googlecode.fcgi4j.exceptions
5 | *
6 | * @autor Tobias Nyholm
7 | */
8 | public class FCGIUnKnownHeaderException extends FCGIException {
9 |
10 | public FCGIUnKnownHeaderException(String message) {
11 | super(message);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/googlecode/fcgi4j/exceptions/FCGIException.java:
--------------------------------------------------------------------------------
1 | package com.googlecode.fcgi4j.exceptions;
2 |
3 | /**
4 | * @author panzd
5 | */
6 | public class FCGIException extends RuntimeException {
7 | /**
8 | *
9 | */
10 | private static final long serialVersionUID = 1L;
11 |
12 | public FCGIException(String message) {
13 | super(message);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/googlecode/fcgi4j/exceptions/FCGIInvalidHeaderException.java:
--------------------------------------------------------------------------------
1 | package com.googlecode.fcgi4j.exceptions;
2 |
3 | /**
4 | * com.googlecode.fcgi4j.exceptions
5 | *
6 | * @autor Tobias Nyholm
7 | */
8 | public class FCGIInvalidHeaderException extends FCGIException {
9 |
10 | public FCGIInvalidHeaderException() {
11 |
12 | super("The HTTP header is invalid.");
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/fcgi4j.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | com.googlecode.fcgi4j
5 | fcgi4j
6 | jar
7 | 0.3.0-SNAPSHOT
8 | fcgi4j
9 | http://maven.apache.org
10 |
11 |
12 | junit
13 | junit
14 | 3.8.1
15 | test
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/main/java/com/googlecode/fcgi4j/constant/FCGIRole.java:
--------------------------------------------------------------------------------
1 | package com.googlecode.fcgi4j.constant;
2 |
3 | /**
4 | * @author panzd
5 | */
6 | public enum FCGIRole {
7 | RESPONDER {
8 | @Override
9 | public int getId() {
10 | return 1;
11 | }
12 | },
13 | AUTHORIZER {
14 | @Override
15 | public int getId() {
16 | return 2;
17 | }
18 | },
19 | FILTER {
20 | @Override
21 | public int getId() {
22 | return 3;
23 | }
24 | };
25 | private static final FCGIRole[] roleMap;
26 |
27 | static {
28 | roleMap = new FCGIRole[4];
29 | for (FCGIRole role : values()) {
30 | roleMap[role.getId()] = role;
31 | }
32 | }
33 |
34 | public static FCGIRole valueOf(int id) {
35 | return roleMap[id];
36 | }
37 |
38 | abstract public int getId();
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/com/googlecode/fcgi4j/util/IoUtils.java:
--------------------------------------------------------------------------------
1 | package com.googlecode.fcgi4j.util;
2 |
3 | import java.io.IOException;
4 | import java.nio.ByteBuffer;
5 | import java.nio.channels.SocketChannel;
6 |
7 | /**
8 | * @author panzd
9 | */
10 | public class IoUtils {
11 |
12 | public static int safePut(ByteBuffer dst, ByteBuffer src) {
13 | int r = dst.remaining();
14 | int l = src.limit();
15 |
16 | if (src.remaining() > r) {
17 | src.limit(src.position() + r);
18 | }
19 |
20 | dst.put(src);
21 | src.limit(l);
22 |
23 | return r;
24 | }
25 |
26 | public static int socketRread(SocketChannel channel, ByteBuffer buffer) throws IOException {
27 | int total = 0;
28 | while (buffer.hasRemaining()) {
29 | int read = channel.read(buffer);
30 | if (read != -1) {
31 | total += read;
32 | } else {
33 | break;
34 | }
35 | }
36 |
37 | return total;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/com/googlecode/fcgi4j/message/FCGIBeginRequest.java:
--------------------------------------------------------------------------------
1 | package com.googlecode.fcgi4j.message;
2 |
3 | import com.googlecode.fcgi4j.constant.FCGIHeaderType;
4 | import com.googlecode.fcgi4j.constant.FCGIRole;
5 |
6 | import java.nio.ByteBuffer;
7 |
8 | /**
9 | * @author panzd
10 | */
11 | public class FCGIBeginRequest {
12 | public static final int FCGI_BEGIN_REQUEST_LEN = 8;
13 | public static final int FCGI_KEEP_CONN = 1;
14 | private FCGIHeader header;
15 | private FCGIRole role;
16 | private boolean keepAlive;
17 |
18 | public FCGIBeginRequest(FCGIRole role, boolean keepAlive) {
19 | header = new FCGIHeader(FCGIHeaderType.FCGI_BEGIN_REQUEST, FCGI_BEGIN_REQUEST_LEN);
20 |
21 | this.role = role;
22 | this.keepAlive = keepAlive;
23 | }
24 |
25 | public ByteBuffer[] getByteBuffers() {
26 | ByteBuffer[] buffers = new ByteBuffer[2];
27 | buffers[0] = header.getByteBuffer();
28 |
29 | ByteBuffer buffer = ByteBuffer.allocate(FCGI_BEGIN_REQUEST_LEN);
30 | buffer.putShort((short) role.getId());
31 | buffer.put((byte) (keepAlive ? FCGI_KEEP_CONN : 0));
32 | buffer.rewind();
33 |
34 | buffers[1] = buffer;
35 |
36 | return buffers;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/com/googlecode/fcgi4j/constant/FCGIProtocolStatus.java:
--------------------------------------------------------------------------------
1 | /*
2 | * To change this template, choose Tools | Templates
3 | * and open the template in the editor.
4 | */
5 | package com.googlecode.fcgi4j.constant;
6 |
7 | /**
8 | * @author panzd
9 | */
10 | public enum FCGIProtocolStatus {
11 | FCGI_REQUEST_COMPLETE {
12 | @Override
13 | public int getId() {
14 | return 0;
15 | }
16 | },
17 | FCGI_CANT_MPX_CONN {
18 | @Override
19 | public int getId() {
20 | return 1;
21 | }
22 | },
23 | FCGI_OVERLOADED {
24 | @Override
25 | public int getId() {
26 | return 2;
27 | }
28 | },
29 | FCGI_UNKNOWN_ROLE {
30 | @Override
31 | public int getId() {
32 | return 3;
33 | }
34 | };
35 | private static final FCGIProtocolStatus[] statusMap;
36 |
37 | static {
38 | statusMap = new FCGIProtocolStatus[4];
39 | for (FCGIProtocolStatus status : values()) {
40 | statusMap[status.getId()] = status;
41 | }
42 | }
43 |
44 | public static FCGIProtocolStatus valueOf(int id) {
45 | return statusMap[id];
46 | }
47 |
48 | public abstract int getId();
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/googlecode/fcgi4j/message/FCGIEndRequest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * To change this template, choose Tools | Templates
3 | * and open the template in the editor.
4 | */
5 | package com.googlecode.fcgi4j.message;
6 |
7 | import com.googlecode.fcgi4j.constant.FCGIProtocolStatus;
8 |
9 | import java.nio.ByteBuffer;
10 |
11 | /**
12 | * @author panzd
13 | */
14 | public class FCGIEndRequest {
15 | private FCGIHeader header;
16 | private long appStatus;
17 | private FCGIProtocolStatus protocolStatus;
18 |
19 |
20 | private FCGIEndRequest() {
21 | }
22 |
23 | public static FCGIEndRequest parse(FCGIHeader header, ByteBuffer buffer) {
24 | FCGIEndRequest endRequest = new FCGIEndRequest();
25 | endRequest.header = header;
26 | endRequest.appStatus = buffer.getInt() & 0xffffffff;
27 | endRequest.protocolStatus = FCGIProtocolStatus.valueOf(buffer.get());
28 |
29 | return endRequest;
30 | }
31 |
32 | /**
33 | * @return the header
34 | */
35 | public FCGIHeader getHeader() {
36 | return header;
37 | }
38 |
39 | /**
40 | * @return the appStatus
41 | */
42 | public long getAppStatus() {
43 | return appStatus;
44 | }
45 |
46 | /**
47 | * @return the protocolStatus
48 | */
49 | public FCGIProtocolStatus getProtocolStatus() {
50 | return protocolStatus;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/com/googlecode/fcgi4j/message/FCGIStdin.java:
--------------------------------------------------------------------------------
1 | package com.googlecode.fcgi4j.message;
2 |
3 | import com.googlecode.fcgi4j.constant.FCGIHeaderType;
4 | import com.googlecode.fcgi4j.exceptions.FCGIException;
5 |
6 | import java.nio.ByteBuffer;
7 |
8 | /**
9 | * @author panzd
10 | */
11 | public class FCGIStdin {
12 | public static final FCGIStdin NULL = new FCGIStdin();
13 |
14 | private FCGIHeader header;
15 | private byte[] data;
16 |
17 | public FCGIStdin(byte[] data) {
18 | if (data == null) {
19 | throw new FCGIException("FCGI_STDIN's data can't be null");
20 | }
21 |
22 | this.data = data;
23 | header = new FCGIHeader(FCGIHeaderType.FCGI_STDIN, data.length);
24 | }
25 |
26 | public FCGIStdin(String str) {
27 | this(str != null ? str.getBytes() : null);
28 | }
29 |
30 | private FCGIStdin() {
31 | header = new FCGIHeader(FCGIHeaderType.FCGI_STDIN, 0);
32 | }
33 |
34 | public int getLength() {
35 | return data == null ? 0 : data.length;
36 | }
37 |
38 | public ByteBuffer[] getByteBuffers() {
39 | if (data == null) {
40 | return new ByteBuffer[]{header.getByteBuffer()};
41 | } else {
42 | ByteBuffer[] buffers = new ByteBuffer[2];
43 |
44 | buffers[0] = header.getByteBuffer();
45 | buffers[1] = ByteBuffer.wrap(data);
46 | return buffers;
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/test/java/com/googlecode/fcgi4j/Test.java:
--------------------------------------------------------------------------------
1 | package com.googlecode.fcgi4j;
2 |
3 | import com.googlecode.fcgi4j.FCGIConnection;
4 |
5 | import java.io.IOException;
6 | import java.net.InetSocketAddress;
7 | import java.nio.ByteBuffer;
8 | import java.util.Map;
9 |
10 | /**
11 | * @author panzd
12 | */
13 | public class Test {
14 |
15 | /**
16 | * @param args the command line arguments
17 | */
18 | public static void main(String[] args) throws IOException {
19 | FCGIConnection connection = FCGIConnection.open();
20 | connection.connect(new InetSocketAddress("127.0.0.1", 9000));
21 |
22 | connection.beginRequest("/var/www/stderr.php");
23 | connection.setRequestMethod("POST");
24 | connection.setQueryString("text=hello");
25 | connection.addParams("DOCUMENT_ROOT", "/var/www");
26 |
27 | byte[] postData = "hello=world".getBytes();
28 |
29 | connection.setContentLength(postData.length);
30 | connection.write(ByteBuffer.wrap(postData));
31 |
32 | Map responseHeaders = connection.getResponseHeaders();
33 | for (String key : responseHeaders.keySet()) {
34 | System.out.println("HTTP HEADER: " + key + "->" + responseHeaders.get(key));
35 | }
36 |
37 | ByteBuffer buffer = ByteBuffer.allocate(10240);
38 | connection.read(buffer);
39 | buffer.flip();
40 |
41 | byte[] data = new byte[buffer.remaining()];
42 | buffer.get(data);
43 |
44 | System.out.println(new String(data));
45 |
46 | System.out.println(connection.getEndRequest().getProtocolStatus());
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FCGI4j
2 |
3 | Connect to Fast CGI from your java application. With this library you may start PHP scripts with PHP-FPM.
4 |
5 | This is a fork from the [subversion repo][google] at revision r17. The project was not maintained any more and I could not get in
6 | touch with the author. I decided to maintain this library myself. r17 was the last revision where the library worked
7 | as expected.
8 |
9 | ## Usage example
10 |
11 | ```java
12 |
13 | //create FastCGI connection
14 | FCGIConnection connection = FCGIConnection.open();
15 | connection.connect(new InetSocketAddress("localhost", 5672));
16 |
17 | connection.beginRequest("/var/www/foobar.php");
18 | connection.setRequestMethod("POST");
19 |
20 | byte[] postData = "hello=world".getBytes();
21 |
22 | //set contentLength, it's important
23 | connection.setContentLength(postData.length);
24 | connection.write(ByteBuffer.wrap(postData));
25 |
26 | //print response headers
27 | Map responseHeaders = connection.getResponseHeaders();
28 | for (String key : responseHeaders.keySet()) {
29 | System.out.println("HTTP HEADER: " + key + "->" + responseHeaders.get(key));
30 | }
31 |
32 | //read response data
33 | ByteBuffer buffer = ByteBuffer.allocate(10240);
34 | connection.read(buffer);
35 | buffer.flip();
36 |
37 | byte[] data = new byte[buffer.remaining()];
38 | buffer.get(data);
39 |
40 | System.out.println(new String(data));
41 |
42 | //close the connection
43 | connection.close();
44 |
45 | ```
46 |
47 | ## Specification
48 | This library implements the [FastCGI Specification](http://www.fastcgi.com/devkit/doc/fcgi-spec.html).
49 |
50 | [google]:https://code.google.com/p/fcgi4j/
51 |
--------------------------------------------------------------------------------
/src/main/java/com/googlecode/fcgi4j/constant/FCGIHeaderType.java:
--------------------------------------------------------------------------------
1 | package com.googlecode.fcgi4j.constant;
2 |
3 | import com.googlecode.fcgi4j.exceptions.FCGIUnKnownHeaderException;
4 |
5 | /**
6 | * @author panzd
7 | */
8 | public enum FCGIHeaderType {
9 | FCGI_BEGIN_REQUEST {
10 | @Override
11 | public int getId() {
12 | return 1;
13 | }
14 | },
15 | FCGI_ABORT_REQUEST {
16 | @Override
17 | public int getId() {
18 | return 2;
19 | }
20 | },
21 | FCGI_END_REQUEST {
22 | @Override
23 | public int getId() {
24 | return 3;
25 | }
26 | },
27 | FCGI_PARAMS {
28 | @Override
29 | public int getId() {
30 | return 4;
31 | }
32 | },
33 | FCGI_STDIN {
34 | @Override
35 | public int getId() {
36 | return 5;
37 | }
38 | },
39 | FCGI_STDOUT {
40 | @Override
41 | public int getId() {
42 | return 6;
43 | }
44 | },
45 | FCGI_STDERR {
46 | @Override
47 | public int getId() {
48 | return 7;
49 | }
50 | },
51 | FCGI_DATA {
52 | @Override
53 | public int getId() {
54 | return 8;
55 | }
56 | },
57 | FCGI_GET_VALUES {
58 | @Override
59 | public int getId() {
60 | return 9;
61 | }
62 | },
63 | FCGI_GET_VALUES_RESULT {
64 | @Override
65 | public int getId() {
66 | return 10;
67 | }
68 | },
69 | FCGI_UNKNOWN_TYPE {
70 | @Override
71 | public int getId() {
72 | return 11;
73 | }
74 | };
75 | private static final FCGIHeaderType[] typeMap;
76 |
77 | static {
78 | typeMap = new FCGIHeaderType[12];
79 | for (FCGIHeaderType type : values()) {
80 | typeMap[type.getId()] = type;
81 | }
82 | }
83 |
84 | public static FCGIHeaderType valueOf(int id) {
85 | try {
86 | return typeMap[id];
87 | } catch (ArrayIndexOutOfBoundsException e) {
88 | throw new FCGIUnKnownHeaderException("Header: " + id);
89 | }
90 | }
91 |
92 | public abstract int getId();
93 | }
94 |
--------------------------------------------------------------------------------
/src/main/java/com/googlecode/fcgi4j/message/FCGIHeader.java:
--------------------------------------------------------------------------------
1 | package com.googlecode.fcgi4j.message;
2 |
3 | import com.googlecode.fcgi4j.constant.FCGIHeaderType;
4 |
5 | import java.nio.ByteBuffer;
6 |
7 | /**
8 | * @author panzd
9 | */
10 | public class FCGIHeader {
11 |
12 | public static final int FCGI_HEADER_LEN = 8;
13 | public static final int FCGI_VERSION_1 = 1;
14 | public static final int ID = 1;
15 | private int version;
16 | private FCGIHeaderType type;
17 | private int id;
18 | private int length;
19 | private int padding;
20 |
21 | public FCGIHeader(FCGIHeaderType type, int length) {
22 | this.version = FCGI_VERSION_1;
23 | this.type = type;
24 | this.id = ID;
25 | this.length = length;
26 | }
27 |
28 | private FCGIHeader() {
29 | }
30 |
31 | public static FCGIHeader parse(ByteBuffer buffer) {
32 | FCGIHeader header = new FCGIHeader();
33 | header.version = buffer.get();
34 | header.type = FCGIHeaderType.valueOf(buffer.get());
35 | header.id = buffer.getShort() & 0xffff;
36 | header.length = buffer.getShort() & 0xffff;
37 | header.padding = buffer.get();
38 |
39 | return header;
40 | }
41 |
42 | /**
43 | * @return the version
44 | */
45 | public int getVersion() {
46 | return version;
47 | }
48 |
49 | /**
50 | * @return the type
51 | */
52 | public FCGIHeaderType getType() {
53 | return type;
54 | }
55 |
56 | /**
57 | * @return the id
58 | */
59 | public int getId() {
60 | return id;
61 | }
62 |
63 | /**
64 | * @return the length
65 | */
66 | public int getLength() {
67 | return length;
68 | }
69 |
70 | /**
71 | * @return the padding
72 | */
73 | public int getPadding() {
74 | return padding;
75 | }
76 |
77 | public ByteBuffer getByteBuffer() {
78 | ByteBuffer buffer = ByteBuffer.allocate(FCGI_HEADER_LEN);
79 |
80 | buffer.put((byte) getVersion());
81 | buffer.put((byte) getType().getId());
82 | buffer.putShort((short) (id & 0xffff));
83 | buffer.putShort((short) (length & 0xffff));
84 | buffer.put((byte) getPadding());
85 |
86 | buffer.rewind();
87 | return buffer;
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/main/java/com/googlecode/fcgi4j/message/FCGIParams.java:
--------------------------------------------------------------------------------
1 | package com.googlecode.fcgi4j.message;
2 |
3 | import com.googlecode.fcgi4j.constant.FCGIHeaderType;
4 | import com.googlecode.fcgi4j.exceptions.FCGIException;
5 |
6 | import java.io.UnsupportedEncodingException;
7 | import java.nio.ByteBuffer;
8 |
9 | /**
10 | * @author panzd
11 | */
12 | public class FCGIParams {
13 | public static final FCGIParams NULL = new FCGIParams();
14 | private FCGIHeader header;
15 | private String key;
16 | private String value;
17 |
18 | public FCGIParams(String key, String value) {
19 | if (key == null || value == null) {
20 | throw new FCGIException("FCGI_RARAMS's key and value can't be null");
21 | }
22 |
23 | int length = countLength(key) + countLength(value);
24 | header = new FCGIHeader(FCGIHeaderType.FCGI_PARAMS, length);
25 |
26 | this.key = key;
27 | this.value = value;
28 | }
29 |
30 | private FCGIParams() {
31 | header = new FCGIHeader(FCGIHeaderType.FCGI_PARAMS, 0);
32 | }
33 |
34 | public ByteBuffer[] getByteBuffers() {
35 | if (key == null || value == null) {
36 | return new ByteBuffer[]{header.getByteBuffer()};
37 | }
38 |
39 | ByteBuffer[] buffers = new ByteBuffer[2];
40 | buffers[0] = header.getByteBuffer();
41 |
42 | ByteBuffer byteBuffer = ByteBuffer.allocate(header.getLength());
43 |
44 | bufferLength(byteBuffer, key);
45 | bufferLength(byteBuffer, value);
46 | byteBuffer.put(key.getBytes());
47 | byteBuffer.put(value.getBytes());
48 | byteBuffer.rewind();
49 |
50 | buffers[1] = byteBuffer;
51 |
52 | return buffers;
53 | }
54 |
55 | private int countLength(String str) {
56 | int length = utf8StringLength(str);
57 | if (length < 0x80) {
58 | length += 1;
59 | } else {
60 | length += 4;
61 | }
62 |
63 | return length;
64 | }
65 |
66 | private void bufferLength(ByteBuffer byteBuffer, String str) {
67 | int length = utf8StringLength(str);
68 |
69 | if (length < 0x80) {
70 | byteBuffer.put((byte) length);
71 | } else {
72 | // make sure that the first bit in the first byte is 1
73 | byteBuffer.put((byte) ((length >> 24) | 0x80));
74 | byteBuffer.put((byte) (length >> 16));
75 | byteBuffer.put((byte) (length >> 8));
76 | byteBuffer.put((byte) (length));
77 | }
78 | }
79 |
80 | private int utf8StringLength(String str) {
81 | try {
82 | return str.getBytes("UTF-8").length;
83 | } catch (UnsupportedEncodingException e) {
84 | e.printStackTrace();
85 | }
86 |
87 | return 0;
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/main/java/com/googlecode/fcgi4j/FCGIConnection.java:
--------------------------------------------------------------------------------
1 | package com.googlecode.fcgi4j;
2 |
3 | import com.googlecode.fcgi4j.constant.FCGIHeaderType;
4 | import com.googlecode.fcgi4j.constant.FCGIRole;
5 | import com.googlecode.fcgi4j.exceptions.FCGIException;
6 | import com.googlecode.fcgi4j.exceptions.FCGIInvalidHeaderException;
7 | import com.googlecode.fcgi4j.exceptions.FCGIUnKnownHeaderException;
8 | import com.googlecode.fcgi4j.message.*;
9 | import com.googlecode.fcgi4j.util.IoUtils;
10 |
11 | import java.io.IOException;
12 | import java.net.SocketAddress;
13 | import java.nio.BufferUnderflowException;
14 | import java.nio.ByteBuffer;
15 | import java.nio.channels.ByteChannel;
16 | import java.nio.channels.GatheringByteChannel;
17 | import java.nio.channels.ScatteringByteChannel;
18 | import java.nio.channels.SocketChannel;
19 | import java.util.ArrayList;
20 | import java.util.Collections;
21 | import java.util.HashMap;
22 | import java.util.Map;
23 |
24 | /**
25 | * @author panzd
26 | */
27 | public class FCGIConnection implements GatheringByteChannel, ScatteringByteChannel, ByteChannel {
28 |
29 | private static final String DEFAULT_CONTENT_TYPE = "application/x-www-form-urlencoded";
30 | private String contentType = DEFAULT_CONTENT_TYPE;
31 | private static final String KEY_SCRIPT_FILENAME = "SCRIPT_FILENAME";
32 | private static final String KEY_QUERY_STRING = "QUERY_STRING";
33 | private static final String KEY_CONTENT_TYPE = "CONTENT_TYPE";
34 | private static final String KEY_CONTENT_LENGTH = "CONTENT_LENGTH";
35 | private static final String KEY_REQUEST_METHOD = "REQUEST_METHOD";
36 | private String scriptFilename;
37 | private String queryString;
38 | private int contentLength;
39 | private String requestMethod = "GET";
40 | private Map paramsMap;
41 | private Map responseHeaders;
42 | private ByteBuffer headerBuffer;
43 | private ByteBuffer dataBuffer;
44 | private boolean open;
45 | private boolean paramsFlushed;
46 | private boolean stdinsWritten;
47 | private boolean stdinsFlushed;
48 | private FCGIEndRequest endRequest;
49 | private boolean readStarted;
50 | private boolean bufferEmpty;
51 | private boolean cleanStdErr;
52 | private SocketChannel socketChannel;
53 |
54 | private FCGIConnection() {
55 | paramsMap = new HashMap();
56 | responseHeaders = new HashMap();
57 | headerBuffer = ByteBuffer.allocateDirect(FCGIHeader.FCGI_HEADER_LEN);
58 | dataBuffer = ByteBuffer.allocateDirect(128 * 1024);
59 | }
60 |
61 | public static FCGIConnection open() throws IOException {
62 | FCGIConnection connection = new FCGIConnection();
63 | connection.socketChannel = SocketChannel.open();
64 | connection.open = true;
65 |
66 | return connection;
67 | }
68 |
69 | /**
70 | * @return the queryString
71 | */
72 | public String getQueryString() {
73 | return queryString;
74 | }
75 |
76 | /**
77 | * @param queryString the queryString to set
78 | */
79 | public void setQueryString(String queryString) {
80 | this.queryString = queryString;
81 | }
82 |
83 | /**
84 | * @return the contentType
85 | */
86 | public String getContentType() {
87 | return contentType;
88 | }
89 |
90 | /**
91 | * @param contentType the contentType to set
92 | */
93 | public void setContentType(String contentType) {
94 | this.contentType = contentType;
95 | }
96 |
97 | /**
98 | * @return the requestMethod
99 | */
100 | public String getRequestMethod() {
101 | return requestMethod;
102 | }
103 |
104 | /**
105 | * @param requestMethod the requestMethod to set
106 | */
107 | public void setRequestMethod(String requestMethod) {
108 | this.requestMethod = requestMethod;
109 | }
110 |
111 | /**
112 | * @return the scriptFilename
113 | */
114 | public String getScriptFilename() {
115 | return scriptFilename;
116 | }
117 |
118 | /**
119 | * @return the contentLength
120 | */
121 | public int getContentLength() {
122 | return contentLength;
123 | }
124 |
125 | /**
126 | * @param contentLength the contentLength to set
127 | */
128 | public void setContentLength(int contentLength) {
129 | this.contentLength = contentLength;
130 | }
131 |
132 | /**
133 | * @return the responseHeaders
134 | */
135 | public Map getResponseHeaders() throws IOException {
136 | readyRead();
137 | return Collections.unmodifiableMap(responseHeaders);
138 | }
139 |
140 | /**
141 | * @return the endRequest
142 | */
143 | public boolean isRequestEnded() {
144 | return endRequest != null;
145 | }
146 |
147 | public FCGIEndRequest getEndRequest() {
148 | return endRequest;
149 | }
150 |
151 | public void connect(SocketAddress fcgiRemote) throws IOException {
152 | socketChannel.connect(fcgiRemote);
153 | }
154 |
155 | public void beginRequest(String scriptFilename, boolean keepAlive) throws IOException {
156 | beginRequest(scriptFilename, null, keepAlive);
157 | }
158 |
159 | public void beginRequest(String scriptFilename, String queryString, boolean keepAlive) throws IOException {
160 | socketChannel.write(new FCGIBeginRequest(FCGIRole.RESPONDER, keepAlive).getByteBuffers());
161 |
162 | this.scriptFilename = scriptFilename;
163 | this.queryString = queryString;
164 |
165 | endRequest = null;
166 | paramsFlushed = false;
167 | stdinsWritten = false;
168 | stdinsFlushed = false;
169 | readStarted = false;
170 | cleanStdErr = true;
171 | bufferEmpty = true;
172 |
173 | dataBuffer.clear();
174 | responseHeaders.clear();
175 | paramsMap.clear();
176 |
177 | setContentLength(0);
178 | setContentType(DEFAULT_CONTENT_TYPE);
179 | setRequestMethod("GET");
180 | }
181 |
182 | public void beginRequest(String scriptFilename) throws IOException {
183 | beginRequest(scriptFilename, true);
184 | }
185 |
186 | public void beginRequest(String scriptFilename, String queryString) throws IOException {
187 | beginRequest(scriptFilename, queryString, true);
188 | }
189 |
190 | public void addParams(String key, String value) {
191 | if (scriptFilename == null) {
192 | throw new FCGIException("please invoke method beginRequest() first");
193 | }
194 |
195 | if (paramsFlushed) {
196 | throw new FCGIException("params has flushed and lock");
197 | }
198 |
199 | paramsMap.put(key, new FCGIParams(key, value));
200 | }
201 |
202 | public void abortRequest() throws IOException {
203 | socketChannel.write(new FCGIHeader(FCGIHeaderType.FCGI_ABORT_REQUEST, 0).getByteBuffer());
204 |
205 | ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
206 |
207 | while (!isRequestEnded()) {
208 | read(byteBuffer);
209 | byteBuffer.clear();
210 | }
211 | }
212 |
213 | private void readResponseHeaders() {
214 | byte ch = dataBuffer.get();
215 |
216 | for (; ; ) {
217 | StringBuilder key = new StringBuilder();
218 | StringBuilder value = new StringBuilder();
219 |
220 | while (ch != ' ' && ch != '\r' && ch != '\n' && ch != ':') {
221 | key.append((char) ch);
222 | ch = dataBuffer.get();
223 | }
224 |
225 | while (ch == ' ' || ch == ':') {
226 | ch = dataBuffer.get();
227 | }
228 |
229 | while (ch != '\r' && ch != '\n') {
230 | value.append((char) ch);
231 | ch = dataBuffer.get();
232 | }
233 |
234 | responseHeaders.put(key.toString(), value.toString());
235 |
236 | int lnCount = 0;
237 |
238 | while ((ch == '\r' || ch == '\n') && lnCount < 3) {
239 | ch = dataBuffer.get();
240 | lnCount++;
241 |
242 | }
243 |
244 | if (lnCount == 3) {
245 | return;
246 | }
247 | }
248 | }
249 |
250 | /**
251 | * Read IP packet headers
252 | *
253 | * @return
254 | * @throws IOException
255 | */
256 | private FCGIHeader readHeader() throws IOException {
257 | IoUtils.socketRread(socketChannel, headerBuffer);
258 |
259 | headerBuffer.flip();
260 | FCGIHeader header = FCGIHeader.parse(headerBuffer);
261 | headerBuffer.clear();
262 | return header;
263 | }
264 |
265 | private void flushParams() throws IOException {
266 | if (paramsFlushed) {
267 | return;
268 | }
269 |
270 | readyInternalParams();
271 |
272 | int buffersSize[] = new int[1];
273 | ArrayList bufferArrays = new ArrayList();
274 |
275 | for (FCGIParams params : paramsMap.values()) {
276 | collectBuffers(params.getByteBuffers(), bufferArrays, buffersSize);
277 | }
278 |
279 | collectBuffers(FCGIParams.NULL.getByteBuffers(), bufferArrays, buffersSize);
280 |
281 | ByteBuffer[] buffers = new ByteBuffer[buffersSize[0]];
282 | int pos = 0;
283 | for (ByteBuffer[] bufferArray : bufferArrays) {
284 | System.arraycopy(bufferArray, 0, buffers, pos, bufferArray.length);
285 | pos += bufferArray.length;
286 | }
287 |
288 | socketChannel.write(buffers);
289 | paramsFlushed = true;
290 | }
291 |
292 | private void flushStdins() throws IOException {
293 | if (!stdinsWritten || stdinsFlushed) {
294 | return;
295 | }
296 |
297 | socketChannel.write(FCGIStdin.NULL.getByteBuffers());
298 | stdinsFlushed = true;
299 | }
300 |
301 | private void readyInternalParams() {
302 | addParams(KEY_SCRIPT_FILENAME, getScriptFilename());
303 |
304 | String myQueryString = getQueryString();
305 | if (myQueryString != null) {
306 | addParams(KEY_QUERY_STRING, myQueryString);
307 | }
308 |
309 | String myRequestMethod = getRequestMethod().toUpperCase();
310 |
311 | addParams(KEY_REQUEST_METHOD, myRequestMethod);
312 |
313 | if (myRequestMethod.equals("POST")) {
314 | addParams(KEY_CONTENT_TYPE, getContentType());
315 | addParams(KEY_CONTENT_LENGTH, String.valueOf(getContentLength()));
316 | }
317 | }
318 |
319 | private void collectBuffers(ByteBuffer[] buffers, ArrayList bufferArrays, int[] buffersSize) {
320 | bufferArrays.add(buffers);
321 | buffersSize[0] += buffers.length;
322 | }
323 |
324 | public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
325 | readyWrite();
326 |
327 | long written = 0;
328 |
329 | for (int i = offset; i < offset + length; i++) {
330 | ByteBuffer src = srcs[i];
331 | while (src.hasRemaining()) {
332 | IoUtils.safePut(dataBuffer, src);
333 |
334 | if (!dataBuffer.hasRemaining()) {
335 | dataBuffer.flip();
336 | written += writeStdin(createStdin(dataBuffer));
337 | dataBuffer.clear();
338 | }
339 | }
340 | }
341 |
342 | if (dataBuffer.hasRemaining()) {
343 | dataBuffer.flip();
344 | written += writeStdin(createStdin(dataBuffer));
345 | dataBuffer.clear();
346 | }
347 |
348 | return written;
349 | }
350 |
351 | public long write(ByteBuffer[] srcs) throws IOException {
352 | return write(srcs, 0, srcs.length);
353 | }
354 |
355 | public int write(ByteBuffer src) throws IOException {
356 | readyWrite();
357 |
358 | int written = 0;
359 |
360 | while (src.hasRemaining()) {
361 | IoUtils.safePut(dataBuffer, src);
362 |
363 | dataBuffer.flip();
364 | written += writeStdin(createStdin(dataBuffer));
365 | dataBuffer.clear();
366 | }
367 |
368 | return written;
369 | }
370 |
371 | private void readyWrite() throws IOException {
372 | if (scriptFilename == null) {
373 | throw new FCGIException("please invoke method beginRequest() first");
374 | }
375 |
376 | flushParams();
377 |
378 | if (stdinsFlushed) {
379 | throw new FCGIException("stdin has flushed and lock, can't be writen");
380 | }
381 |
382 | checkRequestFinished();
383 |
384 | }
385 |
386 | private FCGIStdin createStdin(ByteBuffer buffer) {
387 | if (buffer.limit() == 0) {
388 | return null;
389 | }
390 |
391 | byte[] data = new byte[buffer.limit()];
392 | buffer.get(data);
393 |
394 | return new FCGIStdin(data);
395 | }
396 |
397 | private long writeStdin(FCGIStdin stdin) throws IOException {
398 | if (stdin == null) {
399 | return 0;
400 | }
401 |
402 | ByteBuffer[] buffers = stdin.getByteBuffers();
403 | socketChannel.write(buffers);
404 | stdinsWritten = true;
405 |
406 | return stdin.getLength();
407 | }
408 |
409 | public boolean isOpen() {
410 | return open;
411 | }
412 |
413 | public void close() throws IOException {
414 | socketChannel.close();
415 | open = false;
416 | }
417 |
418 | public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
419 | readyRead();
420 |
421 | long read = 0;
422 |
423 | int available = 0, padding = 0;
424 |
425 | int maxLength = Math.min(offset + length, dsts.length);
426 |
427 | outer:
428 | for (int i = offset; i < maxLength; i++) {
429 | ByteBuffer dst = dsts[i];
430 |
431 | if (available == 0) {
432 | read += readBufferedData(dst);
433 | } else {
434 | read += readStdoutData(dst, available, padding);
435 | }
436 |
437 | while (dst.hasRemaining()) {
438 | FCGIHeader header = readHeader();
439 |
440 | if (header.getType() == FCGIHeaderType.FCGI_STDOUT && header.getLength() != 0) {
441 | int currentRead = readStdoutData(dst, header.getLength(), header.getPadding());
442 |
443 | available = header.getLength() - currentRead;
444 | padding = header.getPadding();
445 |
446 | read += currentRead;
447 | } else {
448 | if (header.getType() == FCGIHeaderType.FCGI_END_REQUEST) {
449 | finishRequest(header);
450 | }
451 |
452 | break outer;
453 | }
454 | }
455 | }
456 |
457 | if (available != 0) {
458 | bufferStdoutData(available, padding);
459 | }
460 |
461 | return read;
462 | }
463 |
464 | public long read(ByteBuffer[] dsts) throws IOException {
465 | return read(dsts, 0, dsts.length);
466 | }
467 |
468 | public int read(ByteBuffer dst) throws IOException {
469 | readyRead();
470 |
471 | int read = readBufferedData(dst);
472 |
473 | while (dst.hasRemaining()) {
474 | FCGIHeader header = readHeader();
475 |
476 | if (header.getType() == FCGIHeaderType.FCGI_STDOUT && header.getLength() != 0) {
477 | int currentRead = readStdoutData(dst, header.getLength(), header.getPadding());
478 |
479 | if (currentRead < header.getLength()) {
480 | bufferStdoutData(header.getLength() - currentRead, header.getPadding());
481 | }
482 |
483 | read += currentRead;
484 | } else {
485 | if (header.getType() == FCGIHeaderType.FCGI_END_REQUEST) {
486 | finishRequest(header);
487 | }
488 |
489 | break;
490 | }
491 | }
492 |
493 | return read;
494 | }
495 |
496 | private void readyRead() throws IOException {
497 | if (!readStarted) {
498 | flushParams();
499 | flushStdins();
500 |
501 | FCGIHeader firstHeader;
502 | try {
503 | firstHeader = readHeader();
504 |
505 | } catch (FCGIUnKnownHeaderException e) {
506 | throw e;
507 | }
508 |
509 | if (firstHeader.getType() == FCGIHeaderType.FCGI_STDOUT) {
510 | bufferStdoutData(firstHeader.getLength(), firstHeader.getPadding());
511 |
512 | try {
513 | readResponseHeaders();
514 | } catch (BufferUnderflowException e) {
515 | throw new FCGIInvalidHeaderException();
516 | }
517 | }
518 |
519 | //if error
520 | if (firstHeader.getType() == FCGIHeaderType.FCGI_STDERR) {
521 | bufferStdoutData(firstHeader.getLength(), firstHeader.getPadding());
522 |
523 | //Make a note that this request has errors
524 | cleanStdErr = false;
525 | }
526 |
527 | readStarted = true;
528 | }
529 |
530 | checkRequestFinished();
531 | }
532 |
533 | private void checkRequestFinished() {
534 | if (endRequest != null) {
535 | throw new FCGIException("the request has Finished");
536 | }
537 | }
538 |
539 | /**
540 | * Has this request posted anything on stderr?
541 | *
542 | * @return boolean
543 | */
544 | public boolean hasOutputOnStdErr() {
545 | return !cleanStdErr;
546 | }
547 |
548 | private int bufferStdoutData(int available, int padding) throws IOException {
549 | int read = readStdoutData(dataBuffer, available, padding);
550 |
551 | dataBuffer.flip();
552 |
553 | bufferEmpty = false;
554 | return read;
555 | }
556 |
557 | private int readBufferedData(ByteBuffer dst) {
558 | if (bufferEmpty) {
559 | return 0;
560 | }
561 |
562 | int read = IoUtils.safePut(dst, dataBuffer);
563 |
564 | if (!dataBuffer.hasRemaining()) {
565 | dataBuffer.clear();
566 | bufferEmpty = true;
567 | }
568 |
569 | return read;
570 | }
571 |
572 | private int readStdoutData(ByteBuffer buffer, int available, int padding) throws IOException {
573 | if (!buffer.hasRemaining()) {
574 | return 0;
575 | }
576 |
577 | int read;
578 | if (buffer.remaining() > available) {
579 | int limit = buffer.limit();
580 |
581 | buffer.limit(buffer.position() + available);
582 | read = IoUtils.socketRread(socketChannel, buffer);
583 | buffer.limit(limit);
584 |
585 | if (padding != 0) {
586 | ByteBuffer paddingBuffer = ByteBuffer.allocate(padding);
587 | IoUtils.socketRread(socketChannel, paddingBuffer);
588 | }
589 | } else {
590 | read = IoUtils.socketRread(socketChannel, buffer);
591 | }
592 |
593 | return read;
594 | }
595 |
596 | private void finishRequest(FCGIHeader header) throws IOException {
597 | ByteBuffer endBuffer = ByteBuffer.allocate(8);
598 | IoUtils.socketRread(socketChannel, endBuffer);
599 | endBuffer.rewind();
600 | endRequest = FCGIEndRequest.parse(header, endBuffer);
601 | }
602 | }
--------------------------------------------------------------------------------