Skip to content
Draft
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
249 changes: 249 additions & 0 deletions FORMS_AND_MULTIPART.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
# Forms and Multipart Support Evaluation

## Overview

This document evaluates the Java 11+ HTTP Client's support for HTML form submissions and multipart/form-data requests, including file uploads.

## Key Findings

### Forms (application/x-www-form-urlencoded)

**The Java HTTP Client does NOT provide a specific API for form data submission.**

Key limitations:
- No built-in `BodyPublishers.ofForm()` or similar convenience methods
- No automatic URL encoding of form parameters
- No form builder or helper API
- Developers must manually construct form data strings

### Multipart Requests (multipart/form-data)

**The Java HTTP Client does NOT provide a specific API for multipart requests.**

Key limitations:
- No built-in `BodyPublishers.ofMultipart()` or similar convenience methods
- No multipart builder or helper API
- No automatic boundary generation or formatting
- No file upload convenience methods
- Developers must manually construct the entire multipart body with proper boundaries and headers

## Detailed Analysis

### 1. Form Data Submission

#### What's Missing

The Java HTTP Client provides no convenience methods for form submissions. Developers must:

1. **Manually build the form data string** with URL encoding:
```java
String formData = "username=" + URLEncoder.encode("testuser", StandardCharsets.UTF_8) +
"&password=" + URLEncoder.encode("testpass", StandardCharsets.UTF_8);
```

2. **Set the correct Content-Type header**:
```java
HttpRequest request = HttpRequest.newBuilder()
.uri(uri)
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(formData))
.build();
```

#### Comparison with Other HTTP Clients

Other HTTP client libraries typically provide form convenience methods:

- **Apache HttpClient**: `UrlEncodedFormEntity` with `NameValuePair` list
- **OkHttp**: `FormBody.Builder()` with automatic encoding
- **Spring WebClient**: `FormInserter` API for reactive form submissions
- **Retrofit**: `@FormUrlEncoded` annotation with `@Field` parameters

#### Manual Implementation Required

Test: `JavaHttpClientFormsTest.testManualFormDataImplementation()`

Example helper method developers must implement:
```java
private String buildFormData(Map<String, String> data) {
return data.entrySet().stream()
.map(entry -> URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8) + "=" +
URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8))
.collect(Collectors.joining("&"));
}
```

This is straightforward but requires developers to:
- Remember to URL encode all values
- Handle special characters correctly (`&`, `=`, spaces, etc.)
- Join parameters with `&`
- Set the correct Content-Type header

### 2. Multipart/Form-Data Requests

#### What's Missing

Multipart requests are significantly more complex than form data, yet the Java HTTP Client provides no support:

1. **No boundary generation**
2. **No part construction helpers**
3. **No file upload convenience methods**
4. **No content-disposition header generation**
5. **No mixed content handling (text + files)**

#### Manual Implementation Required

Tests:
- `JavaHttpClientMultipartTest.testManualMultipartTextFields()`
- `JavaHttpClientMultipartTest.testManualFileUpload()`
- `JavaHttpClientMultipartTest.testManualMultipartMixedContent()`

Developers must manually construct the entire multipart body:

```java
String boundary = "----WebKitFormBoundary" + System.currentTimeMillis();

// For each text field:
"--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"fieldname\"\r\n" +
"\r\n" +
"field value\r\n"

// For each file:
"--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"file\"; filename=\"file.txt\"\r\n" +
"Content-Type: text/plain\r\n" +
"\r\n" +
[file bytes] + "\r\n"

// Final boundary:
"--" + boundary + "--\r\n"
```

And set the Content-Type header with the boundary:
```java
.header("Content-Type", "multipart/form-data; boundary=" + boundary)
```

#### Complexity Factors

1. **Boundary Selection**: Must not appear in content, typically random or timestamp-based
2. **CRLF Handling**: Must use `\r\n` (not just `\n`) for HTTP compliance
3. **Content-Disposition**: Different format for files vs. text fields
4. **Content-Type for Files**: Must determine and set correct MIME type for each file
5. **Binary Data**: Must handle file bytes correctly without corruption
6. **Final Boundary**: Must end with `--boundary--\r\n`

#### Comparison with Other HTTP Clients

Other HTTP client libraries provide multipart convenience APIs:

- **Apache HttpClient**: `MultipartEntityBuilder` with `addTextBody()` and `addBinaryBody()`
- **OkHttp**: `MultipartBody.Builder()` with `addFormDataPart()`
- **Spring WebClient**: `MultipartBodyBuilder` for reactive multipart requests
- **Retrofit**: `@Multipart` annotation with `@Part` parameters

### 3. Test Results

All tests demonstrate that while form and multipart submissions **work** with the Java HTTP Client, they require **complete manual implementation**.

#### Forms Tests (JavaHttpClientFormsTest)

✅ `testNoBuiltInFormAPI()` - Confirms no built-in API exists
✅ `testManualFormDataImplementation()` - Manual form submission works
✅ `testFormDataWithSpecialCharacters()` - URL encoding required for special chars
✅ `testCompleteFormHandlingRequired()` - Demonstrates full manual process

#### Multipart Tests (JavaHttpClientMultipartTest)

✅ `testNoBuiltInMultipartAPI()` - Confirms no built-in API exists
✅ `testManualMultipartTextFields()` - Manual multipart with text fields works
✅ `testManualFileUpload()` - Manual file upload works
✅ `testManualMultipartMixedContent()` - Mixed content (text + files) works
✅ `testBoundaryHandling()` - Demonstrates boundary complexity

## Impact Assessment

### For Simple Forms
**Impact: Medium**

- Manual implementation is straightforward for basic forms
- URL encoding is standard Java functionality
- Most developers can implement this correctly
- Main risk: forgetting to URL encode values with special characters

### For Multipart Requests
**Impact: High**

- Manual implementation is complex and error-prone
- Many opportunities for mistakes (CRLF, boundaries, headers)
- File uploads require careful binary data handling
- Mixed content increases complexity significantly
- Testing is essential to ensure correct formatting

## Recommendations

### For Application Developers

1. **Create utility classes** for form and multipart handling if you need these features frequently
2. **Consider using a third-party library** like Apache HttpClient or OkHttp if you have extensive form/multipart needs
3. **Thoroughly test** multipart implementations - the format is strict and errors are common
4. **Use existing libraries** rather than reimplementing multipart from scratch (see Helper Libraries below)

### For JDK Enhancement Consideration

Consider adding convenience APIs similar to other HTTP clients:

**For Forms:**
```java
HttpRequest.BodyPublishers.ofForm(Map<String, String> formData)
```

**For Multipart:**
```java
MultipartBodyBuilder builder = MultipartBodyBuilder.create()
.addField("name", "value")
.addFile("file", Path.of("file.txt"), MediaType.TEXT_PLAIN)
.build();

HttpRequest.BodyPublishers.ofMultipart(builder)
```

This would:
- Reduce boilerplate code
- Prevent common implementation errors
- Improve developer experience
- Match functionality of other modern HTTP clients
- Make Java HTTP Client more suitable for web application development

## Helper Libraries

Instead of manual implementation, developers can use:

1. **Apache HttpClient** - Mature library with excellent form and multipart support
2. **OkHttp** - Modern library with clean multipart API
3. **Third-party utilities** - Various open-source helpers for multipart construction

## Test Server Implementation

The test suite includes `NettyFormsServer` which demonstrates:
- Parsing `application/x-www-form-urlencoded` data
- Parsing `multipart/form-data` requests
- Handling file uploads
- Extracting form fields and file metadata

This server is used to validate that manually constructed requests are correctly formatted and can be parsed by a standard HTTP server.

## Conclusion

The Java HTTP Client can handle form submissions and multipart requests, but requires **complete manual implementation** for both:

- **Forms**: Moderate complexity, manageable for most developers
- **Multipart**: High complexity, error-prone, benefits greatly from helper utilities

This lack of convenience APIs is a notable gap compared to other modern HTTP client libraries. Applications with significant form or file upload requirements should consider:
1. Using a wrapper library or utility class
2. Using a different HTTP client library (Apache HttpClient, OkHttp)
3. Waiting for potential JDK enhancements

For occasional use, the manual implementation demonstrated in the test suite is workable, though it requires careful attention to details like URL encoding, boundary formatting, and CRLF handling.
37 changes: 34 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
# Java HTTP Client Testing

Check of Java HTTP Client Limitations with HTTP/2, GOAWAY frames, HTTP Compression, HTTP Caching, and HTTP Authentication
Check of Java HTTP Client Limitations with HTTP/2, GOAWAY frames, HTTP Compression, HTTP Caching, HTTP Authentication, Forms, and Multipart

## Overview

This project tests Java 11+ HTTP Client behavior with HTTP/2 protocol features, HTTP compression, HTTP caching, and HTTP authentication, specifically:
This project tests Java 11+ HTTP Client behavior with HTTP/2 protocol features, HTTP compression, HTTP caching, HTTP authentication, and form/multipart handling, specifically:
- HTTP/2 Upgrade from HTTP/1.1
- GOAWAY frame handling
- Connection management over HTTP and HTTPS
- ALPN negotiation and custom SSLParameters behavior
- HTTP compression support (or lack thereof)
- HTTP caching support (or lack thereof)
- HTTP authentication schemes support (Basic, Digest, NTLM, SPNEGO/Kerberos)
- HTML form submission support (application/x-www-form-urlencoded)
- Multipart/form-data support including file uploads

## Test Scenarios

Expand Down Expand Up @@ -112,6 +114,31 @@ The tests demonstrate that:

See [GITHUB_ISSUE_SUMMARY.md](GITHUB_ISSUE_SUMMARY.md) for a concise summary suitable for submitting as a JDK enhancement request.

### 10. HTML Forms and Multipart Support

Tests evaluating support for HTML form submissions and multipart/form-data requests. See [FORMS_AND_MULTIPART.md](FORMS_AND_MULTIPART.md) for detailed documentation.

Tests are in `JavaHttpClientFormsTest`:
- `testNoBuiltInFormAPI()` - Verifies client does NOT provide form data API
- `testManualFormDataImplementation()` - Demonstrates manual URL-encoded form submission
- `testFormDataWithSpecialCharacters()` - Tests URL encoding requirements
- `testCompleteFormHandlingRequired()` - Shows complete manual implementation needed

Tests are in `JavaHttpClientMultipartTest`:
- `testNoBuiltInMultipartAPI()` - Verifies client does NOT provide multipart API
- `testManualMultipartTextFields()` - Demonstrates manual multipart with text fields
- `testManualFileUpload()` - Tests file upload with multipart/form-data
- `testManualMultipartMixedContent()` - Tests mixed content (text fields + files)
- `testBoundaryHandling()` - Demonstrates boundary generation and formatting complexity

The tests demonstrate that:
- Java HTTP Client does NOT provide convenience APIs for form data submission
- Java HTTP Client does NOT provide convenience APIs for multipart/form-data
- Applications must manually build `application/x-www-form-urlencoded` strings with URL encoding
- Applications must manually construct multipart bodies with boundaries and headers
- File uploads require manual encoding and Content-Disposition header construction
- Both forms and multipart work but require complete manual implementation

## Known Limitations

### Custom SSLParameters Interfere with HTTP/2
Expand Down Expand Up @@ -178,7 +205,8 @@ mvn test -Dorg.slf4j.simpleLogger.defaultLogLevel=debug
├── src/main/java/io/github/laeubi/httpclient/
│ ├── NettyHttp2Server.java # Netty-based HTTP/2 test server with connection tracking
│ ├── NettyWebSocketServer.java # Netty-based WebSocket test server
│ └── NettyAuthenticationServer.java # Netty-based authentication test server
│ ├── NettyAuthenticationServer.java # Netty-based authentication test server
│ └── NettyFormsServer.java # Netty-based forms and multipart test server
├── src/test/java/io/github/laeubi/httpclient/
│ ├── JavaHttpClientUpgradeTest.java # HTTP/2 upgrade and ALPN tests
│ ├── JavaHttpClientGoawayTest.java # GOAWAY frame handling tests
Expand All @@ -187,6 +215,8 @@ mvn test -Dorg.slf4j.simpleLogger.defaultLogLevel=debug
│ ├── JavaHttpClientCompressionTest.java # HTTP compression tests
│ ├── JavaHttpClientCachingTest.java # HTTP caching tests
│ ├── JavaHttpClientAuthenticationTest.java # HTTP authentication tests
│ ├── JavaHttpClientFormsTest.java # HTML forms tests
│ ├── JavaHttpClientMultipartTest.java # Multipart/form-data tests
│ └── JavaHttpClientBase.java # Base test class with utilities
├── src/main/resources/
│ └── simplelogger.properties # Logging configuration
Expand All @@ -195,6 +225,7 @@ mvn test -Dorg.slf4j.simpleLogger.defaultLogLevel=debug
├── HTTP_COMPRESSION.md # HTTP compression documentation
├── HTTP_CACHING.md # HTTP caching documentation
├── HTTP_AUTHENTICATION.md # HTTP authentication documentation
├── FORMS_AND_MULTIPART.md # Forms and multipart/form-data documentation
├── GITHUB_ISSUE_SUMMARY.md # JDK enhancement request summary
└── pom.xml # Maven project configuration
```
Expand Down
Loading