├── .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 | } --------------------------------------------------------------------------------