Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## Summary

<!-- What does this PR do and why? -->

## Changes

<!-- List key changes, e.g.:
- Fixed X in `FCGIConnection.java`
- Added tests for Y
-->

## Test plan

- [ ] `mvn verify` passes locally
- [ ] New/updated tests cover the changes
50 changes: 50 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: CI

on:
pull_request:

jobs:
lint:
runs-on: ubuntu-latest
name: Checkstyle
steps:
- uses: actions/checkout@v4

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: temurin

- name: Checkstyle
run: mvn --batch-mode --no-transfer-progress checkstyle:check

test:
runs-on: ubuntu-latest

strategy:
matrix:
java: [ '8', '11', '17', '25' ]

name: Test (Java ${{ matrix.java }})

steps:
- uses: actions/checkout@v4

- name: Set up JDK ${{ matrix.java }}
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java }}
distribution: temurin

- name: Build and test
run: mvn --batch-mode --no-transfer-progress verify

- name: Coverage summary
if: success()
uses: cicirello/jacoco-badge-generator@v2
with:
jacoco-csv-file: target/site/jacoco/jacoco.csv
generate-summary: true
generate-branches-badge: false
generate-coverage-badge: false
2 changes: 0 additions & 2 deletions .travis.yml

This file was deleted.

40 changes: 32 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,28 @@ as expected.
FCGIConnection connection = FCGIConnection.open();
connection.connect(new InetSocketAddress("localhost", 5672));

connection.beginRequest("/var/www/foobar.php");
connection.setRequestMethod("POST");

byte[] postData = "hello=world".getBytes();

//set contentLength, it's important
connection.setContentLength(postData.length);
connection.write(ByteBuffer.wrap(postData));
String requestMethod = "GET"
String targetScript = "/var/www/foobar.php"

connection.beginRequest(targetScript);
connection.setRequestMethod(requestMethod);
connection.setQueryString("querystring=1");

connection.addParams("DOCUMENT_ROOT", "/var/www/");
connection.addParams("SCRIPT_FILENAME", targetScript);
connection.addParams("SCRIPT_NAME", targetScript);
connection.addParams("GATEWAY_INTERFACE", "FastCGI/1.0");
connection.addParams("SERVER_PROTOCOL", "HTTP/1.1");
connection.addParams("CONTENT_TYPE", "application/x-www-form-urlencoded");

//add post data only if request method is GET
if(requestMethod.equalsIgnoreCase("POST")){
byte[] postData = "hello=world".getBytes();

//set contentLength, it's important
connection.setContentLength(postData.length);
connection.write(ByteBuffer.wrap(postData));
}

//print response headers
Map<String, String> responseHeaders = connection.getResponseHeaders();
Expand All @@ -44,6 +58,16 @@ connection.close();

```

## Known bugs

The following bugs exist in `FCGIConnection` and have failing tests:

- **Scatter read doesn't handle STDERR.** `read(ByteBuffer[])` only checks for `FCGI_STDOUT` ([line 440](src/main/java/com/googlecode/fcgi4j/FCGIConnection.java#L440)). If an `FCGI_STDERR` frame arrives during a scatter read, its body is never consumed from the socket, corrupting the stream on the next `readHeader()` call. ([test](src/test/java/com/googlecode/fcgi4j/FCGIConnectionTest.java#L776))

- **STDERR-first breaks response header parsing.** `readyRead()` only reads one header ([lines 520-536](src/main/java/com/googlecode/fcgi4j/FCGIConnection.java#L520)). If the first frame is `FCGI_STDERR`, the subsequent `FCGI_STDOUT` frame containing HTTP response headers is never parsed — `getResponseHeaders()` returns an empty map and the stderr data leaks into the response body. ([test](src/test/java/com/googlecode/fcgi4j/FCGIConnectionTest.java#L832))

- **Scatter read drops non-STDOUT frames.** `read(ByteBuffer[])` hits `break outer` ([line 452](src/main/java/com/googlecode/fcgi4j/FCGIConnection.java#L452)) for any non-`FCGI_STDOUT` frame type without consuming the frame body. For `FCGI_END_REQUEST`, the 8-byte body is left on the socket and `isRequestEnded()` stays false. ([test](src/test/java/com/googlecode/fcgi4j/FCGIConnectionTest.java#L882))

## Specification
This library implements the [FastCGI Specification](http://www.fastcgi.com/devkit/doc/fcgi-spec.html).

Expand Down
57 changes: 57 additions & 0 deletions checkstyle.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">

<module name="Checker">
<property name="charset" value="UTF-8"/>
<property name="severity" value="warning"/>

<module name="TreeWalker">
<!-- Imports -->
<module name="AvoidStarImport"/>
<module name="UnusedImports"/>
<module name="RedundantImport"/>

<!-- Naming -->
<module name="TypeName"/>
<module name="MethodName"/>
<module name="ConstantName"/>
<module name="LocalVariableName"/>
<module name="MemberName"/>
<module name="PackageName"/>
<module name="ParameterName"/>
<module name="StaticVariableName"/>

<!-- Coding -->
<module name="EmptyStatement"/>
<module name="EqualsHashCode"/>
<module name="SimplifyBooleanExpression"/>
<module name="SimplifyBooleanReturn"/>
<module name="DefaultComesLast"/>
<module name="FallThrough"/>
<module name="MissingSwitchDefault"/>
<module name="OneStatementPerLine"/>

<!-- Block checks -->
<module name="LeftCurly"/>
<module name="NeedBraces">
<property name="allowSingleLineStatement" value="true"/>
</module>

<!-- Whitespace -->
<module name="GenericWhitespace"/>
<module name="MethodParamPad"/>
<module name="TypecastParenPad"/>

<!-- Miscellaneous -->
<module name="ArrayTypeStyle"/>
<module name="UpperEll"/>
<module name="OuterTypeFilename"/>
</module>

<!-- File-level checks -->
<module name="NewlineAtEndOfFile">
<property name="lineSeparator" value="lf_cr_crlf"/>
</module>
<module name="FileTabCharacter"/>
</module>
70 changes: 67 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,76 @@
<version>0.3.0-SNAPSHOT</version>
<name>fcgi4j</name>
<url>http://maven.apache.org</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>5.10.2</junit.version>
</properties>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.12.1</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.6.0</version>
<dependencies>
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>10.21.4</version>
</dependency>
</dependencies>
<configuration>
<configLocation>checkstyle.xml</configLocation>
<consoleOutput>true</consoleOutput>
<failOnViolation>true</failOnViolation>
<includeTestResources>true</includeTestResources>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.11</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
27 changes: 19 additions & 8 deletions src/main/java/com/googlecode/fcgi4j/FCGIConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -468,28 +468,39 @@ public long read(ByteBuffer[] dsts) throws IOException {
public int read(ByteBuffer dst) throws IOException {
readyRead();

int read = readBufferedData(dst);
int read = 0;

int available = 0, padding = 0;

outer:
if (available == 0) {
read += readBufferedData(dst);
} else {
read += readStdoutData(dst, available, padding);
}

while (dst.hasRemaining()) {
FCGIHeader header = readHeader();

if (header.getType() == FCGIHeaderType.FCGI_STDOUT && header.getLength() != 0) {
if ((header.getType() == FCGIHeaderType.FCGI_STDOUT || header.getType() == FCGIHeaderType.FCGI_STDERR) && header.getLength() != 0) {
int currentRead = readStdoutData(dst, header.getLength(), header.getPadding());

if (currentRead < header.getLength()) {
bufferStdoutData(header.getLength() - currentRead, header.getPadding());
}
available = header.getLength() - currentRead;
padding = header.getPadding();

read += currentRead;
} else {
if (header.getType() == FCGIHeaderType.FCGI_END_REQUEST) {
finishRequest(header);
break;
}

break;
}
}

if (available != 0) {
bufferStdoutData(available, padding);
}

return read;
}

Expand Down Expand Up @@ -599,4 +610,4 @@ private void finishRequest(FCGIHeader header) throws IOException {
endBuffer.rewind();
endRequest = FCGIEndRequest.parse(header, endBuffer);
}
}
}
Loading