A complete working example demonstrating how to run PSGI applications on PerlOnJava using Netty as the HTTP server backend.
Plack::Handler::Netty is a PSGI server handler that bridges PSGI apps to Java's Netty HTTP server. It provides:
- General PSGI support - Any PSGI-compatible app can be adapted
- High-performance async I/O - Netty handles 10k+ concurrent connections on a single thread
- Single-threaded model - Compatible with PerlOnJava's no-threads/no-fork constraints
- Standard PSGI 1.1 - Full compliance with streaming and delayed response support
Client → Netty (async I/O, single thread) → PlackHandlerNetty.java
↓
Plack::Handler::Netty.pm (Perl facade)
↓
PSGI Application ($app->(\%env))
↓
Response ← [status, headers, body] ← Netty
Implementation Location:
- Java Backend:
src/main/java/org/perlonjava/runtime/perlmodule/PlackHandlerNetty.java - Perl Module:
src/main/perl/lib/Plack/Handler/Netty.pm - Bundled in: PerlOnJava JAR (
target/perlonjava-*.jar)
From the project root:
./gradlew shadowJar # or: mvn package./jperl examples/http_server_plack/test.plThe server will start on http://localhost:5000.
In another terminal:
# Homepage
curl http://localhost:5000/
# Route with parameter
curl http://localhost:5000/hello/World
# JSON API
curl http://localhost:5000/json
# View PSGI environment
curl http://localhost:5000/env
# POST request (echo)
curl -X POST http://localhost:5000/echo -d 'test data'
# 404 error
curl http://localhost:5000/notfoundThe test.pl script contains a complete PSGI application with:
/- Homepage with HTML response/hello/{name}- Route with path parameter extraction/json- JSON API endpoint/env- PSGI environment hash dump (debugging)/echo(POST) - Echoes back POST body- 404 handling - Returns 404 for unknown routes
use Plack::Handler::Netty;
my $app = sub {
my ($env) = @_;
return [
200,
['Content-Type' => 'text/plain'],
['Hello from Netty!']
];
};
my $handler = Plack::Handler::Netty->new(
host => '0.0.0.0',
port => 5000,
);
$handler->run($app);The handler provides all standard PSGI v1.1 environment keys:
REQUEST_METHOD,PATH_INFO,QUERY_STRINGSERVER_NAME,SERVER_PORT,SERVER_PROTOCOLCONTENT_LENGTH,CONTENT_TYPEHTTP_*headers (normalized to uppercase with underscores)psgi.version,psgi.url_scheme,psgi.input,psgi.errorspsgi.multithread(false),psgi.multiprocess(false)psgi.run_once(false),psgi.nonblocking(true),psgi.streaming(true)
Plack::Handler::Netty->new(%options) accepts:
| Option | Default | Description |
|---|---|---|
host |
0.0.0.0 | Bind address |
port |
5000 | Listen port |
backlog |
128 | TCP backlog queue size |
keepalive |
30 | HTTP keep-alive timeout (seconds) |
max_request_size |
10485760 | Max request body size (bytes, ~10MB) |
ssl |
0 | Enable HTTPS/TLS |
ssl_cert |
- | SSL certificate path (PEM format) |
ssl_key |
- | SSL private key path (PEM format) |
ssl_ca |
- | CA certificate for client verification (optional) |
ssl_protocols |
TLSv1.2, TLSv1.3 | Allowed TLS versions (arrayref, optional) |
ssl_ciphers |
- | Cipher suites (colon-separated, optional) |
The handler supports SSL/TLS via Netty's SslHandler:
my $handler = Plack::Handler::Netty->new(
port => 443,
ssl => 1,
ssl_cert => '/path/to/cert.pem',
ssl_key => '/path/to/key.pem',
);
$handler->run($app);For testing (self-signed certificate):
cd examples/http_server_plack/certs
./generate_test_cert.shThen run the HTTPS test server:
./jperl examples/http_server_plack/test_https.pl
curl -k https://localhost:8443/ # -k skips cert verificationFor production, use Let's Encrypt:
certbot certonly --standalone -d yourdomain.com
# Certificates will be at:
# /etc/letsencrypt/live/yourdomain.com/fullchain.pem
# /etc/letsencrypt/live/yourdomain.com/privkey.pemDo NOT use self-signed certificates in production.
Use your own PSGI app coderef with Plack::Handler::Netty->run($app) as shown in the generic example above.
Framework-specific experiments are kept in dev/sandbox/http_server/ until they are stable.
Single-threaded async I/O - Uses Netty's event loop (NioEventLoopGroup(1)) to handle concurrent connections without threads/fork:
✅ Good for: I/O-bound apps (databases, APIs, file serving)
✅ Handles: Thousands of concurrent connections efficiently
This design avoids PerlOnJava's thread-safety constraints while still providing excellent performance for typical web applications.
- Single-threaded - CPU-intensive handlers block other requests
Expected performance for "Hello World" apps: 30,000+ requests/sec
Benchmark results (Apple Silicon):
- Hello World: 32,980 req/sec @ 100 concurrent connections
- JSON API: 22,461 req/sec with dynamic content
- Streaming: 16,312 req/sec for streaming responses
See PERFORMANCE.md for detailed benchmarks.
test.pl- Complete working PSGI example with multiple test endpointstest_streaming.pl- PSGI streaming response examples
README.md- This file (main documentation)PERFORMANCE.md- Detailed performance benchmarks
certs/- Test SSL certificates and generation scripts
Real implementation is bundled in the JAR:
- Java:
src/main/java/org/perlonjava/runtime/perlmodule/PlackHandlerNetty.java - Perl:
src/main/perl/lib/Plack/Handler/Netty.pm
Make sure you're running with the built JAR:
./jperl your_app.pl # Good - uses bundled version
java -cp ... your_app.pl # May not find moduleChange the port in your code:
my $handler = Plack::Handler::Netty->new(port => 9000);
$handler->run($app);Found a bug or want to improve this? Please submit an issue or PR to PerlOnJava.
Same as PerlOnJava.