Skip to content

ADR-010: Apache HttpClient Transport for JupnP UPnP Port Forwarding

Status: Accepted

Date: November 2025

Context: Issue #308, PR #309

Context

The Fukuii node was failing to start in certain environments due to a URLStreamHandlerFactory initialization error in the JupnP library:

ERROR [org.jupnp.transport.Router] - Unable to initialize network router: 
org.jupnp.transport.spi.InitializationException: Failed to set modified 
URLStreamHandlerFactory in this environment. Can't use bundled default 
client based on HTTPURLConnection, see manual.

Background

JupnP and UPnP Port Forwarding: - JupnP is used to automatically configure router port forwarding via UPnP (Universal Plug and Play) - Enables peer-to-peer connectivity without manual router configuration - Optional feature controlled by Config.Network.automaticPortForwarding setting

The URLStreamHandlerFactory Problem: - JupnP's default HTTP transport (HttpURLConnection-based) requires setting a global URLStreamHandlerFactory - The URLStreamHandlerFactory can only be set once per JVM - If another library has already set it, or security policies prevent it, JupnP initialization fails - The failure was fatal, preventing the entire node from starting

When This Occurs: - When running in containers with security restrictions - When other libraries have already claimed the URLStreamHandlerFactory - In certain JVM environments or application servers - With certain Java security managers enabled

Impact: - Node fails to start completely - Cannot sync blockchain or connect to peers - UPnP is optional, but its failure should not prevent node operation

Decision

We implemented a custom Apache HttpClient-based transport for JupnP that:

  1. Replaces the default HttpURLConnection-based transport with Apache HttpComponents Client 5
  2. Eliminates the URLStreamHandlerFactory requirement entirely
  3. Provides graceful degradation if UPnP initialization still fails for other reasons
  4. Maintains full UPnP functionality while being more robust

Implementation

New Dependency:

"org.apache.httpcomponents.client5" % "httpclient5" % "5.3.1"

New Component: ApacheHttpClientStreamClient - Implements JupnP's StreamClient interface - Uses Apache HttpClient 5 for all HTTP operations - Configures timeouts from StreamClientConfiguration - Properly handles response charset encoding - Includes error handling and logging

Updated Component: PortForwarder - Replaced JDKTransportConfiguration with ApacheHttpClientStreamClient - Added try-catch with graceful degradation - Logs warnings if UPnP fails, but allows node to continue

Rationale

Why Apache HttpClient?

  1. No URLStreamHandlerFactory Required
  2. Apache HttpClient manages HTTP connections without JVM-global state
  3. Works in restricted environments where factory cannot be set

  4. Mature, Well-Maintained Library

  5. Apache HttpComponents is industry-standard
  6. Actively maintained with security updates
  7. Extensive documentation and community support

  8. Modern Features

  9. HTTP/2 support (not needed now, but future-proof)
  10. Better connection pooling and timeout management
  11. Improved performance over HttpURLConnection

  12. Minimal Dependencies

  13. Single well-scoped dependency
  14. No transitive dependency conflicts in our stack

Why Not Alternative Solutions?

Option 1: Just Catch and Ignore the Error - Rejected: UPnP would never work, even in environments where it could - Loses functionality rather than fixing the root cause

Option 2: Use Different UPnP Library - Rejected: JupnP is well-established and maintained - Switching libraries is more risky than fixing the transport layer - JupnP's architecture allows custom transports, which is the right extension point

Option 3: System Property Workaround - Rejected: Undocumented, fragile, may not work in all cases - Doesn't actually solve the problem, just tries to bypass it

Option 4: Make UPnP Optional/Disable by Default - Partially Implemented: We added graceful degradation - But we want UPnP to work when possible, not disable it entirely

Consequences

Positive

  1. Node Starts Successfully
  2. Even in restricted environments, node initialization completes
  3. UPnP failure no longer blocks core functionality

  4. UPnP Works in More Environments

  5. Eliminates URLStreamHandlerFactory conflicts
  6. Broader compatibility with different deployment scenarios

  7. Better Error Handling

  8. Graceful degradation with informative logging
  9. Users know why UPnP failed and can take action if needed

  10. Modern HTTP Client

  11. Better performance and connection management
  12. Future-proof with HTTP/2 support
  13. Well-maintained dependency with security updates

  14. Minimal Code Changes

  15. Surgical fix targeting the specific problem
  16. No changes to UPnP logic or port mapping functionality
  17. Self-contained new module

Negative

  1. Additional Dependency
  2. Adds httpclient5 (~1.5MB) to the dependency tree
  3. Minimal impact, but increases artifact size slightly

  4. Maintenance Burden

  5. Custom implementation requires maintenance
  6. Must track Apache HttpClient API changes
  7. However, the API is stable and changes infrequently

  8. Testing Complexity

  9. UPnP testing requires specific network environment
  10. Cannot easily test in CI/CD without UPnP-enabled router
  11. Must rely on manual testing and user feedback

  12. Implementation Complexity

  13. ~200 lines of custom transport code
  14. More complex than using default transport
  15. However, well-documented and straightforward

Mitigations

  1. Dependency Size: 1.5MB is negligible for a full node implementation
  2. Maintenance: Apache HttpClient has stable API, updates are rare
  3. Testing: Implementation follows JupnP patterns, code review ensures correctness
  4. Complexity: Code is well-commented and follows standard patterns

Implementation Details

Key Components:

  1. ApacheHttpClientStreamClient
  2. Extends AbstractStreamClient[StreamClientConfiguration, HttpCallable]
  3. Configures HttpClient with timeouts from configuration
  4. Handles GET and POST requests for UPnP SOAP messages

  5. HttpCallable

  6. Implements Callable[StreamResponseMessage]
  7. Executes HTTP requests and converts responses to JupnP format
  8. Handles aborts and errors gracefully

  9. Request/Response Handling

  10. Preserves all headers from JupnP requests
  11. Extracts charset from Content-Type header
  12. Properly handles HTTP status codes and error responses

  13. Error Handling

  14. Try-catch in PortForwarder.startForwarding()
  15. Logs warnings for InitializationException and other errors
  16. Returns NoOpUpnpService to allow clean shutdown

Configuration: - Timeouts: Configured from StreamClientConfiguration.getTimeoutSeconds() - Connection timeout: Matches configured timeout - Response timeout: Matches configured timeout - User-Agent: "Fukuii/{version} UPnP/1.1"

Alternatives Considered

See "Why Not Alternative Solutions?" section above.

Testing

Compilation: ✅ Successfully compiles with no errors or warnings

Code Review: ✅ Addressed feedback on: - HttpClient timeout configuration - Code duplication reduction - Charset encoding handling

Security Analysis: ✅ CodeQL analysis passed with no vulnerabilities

Manual Testing: Requires UPnP-enabled router environment - Node should start successfully in restricted environments - UPnP port forwarding should work when router supports it - Graceful degradation when UPnP unavailable

Future Considerations

  1. Monitor Apache HttpClient Updates
  2. Track security advisories for httpclient5
  3. Update dependency regularly with patch releases

  4. Consider HTTP/2

  5. If UPnP protocol adds HTTP/2 support, we're ready
  6. Apache HttpClient 5 supports HTTP/2 natively

  7. Enhanced Error Reporting

  8. Could add more detailed diagnostics for UPnP failures
  9. Help users understand why UPnP isn't working

  10. Alternative Port Forwarding Methods

  11. Consider NAT-PMP/PCP as fallback if UPnP fails
  12. Could use similar Apache HttpClient approach

References

  • ADR-001: Migration to Scala 3 and JDK 21 (dependency compatibility)

Review and Update

This ADR should be reviewed when: - Apache HttpClient releases a major version (6.x) - JupnP library is upgraded to a new major version - UPnP port forwarding issues are reported - Alternative UPnP libraries emerge with better Java compatibility