Practical Perl Networking: Safe HTTP Requests, Forms and Mail
You will finish with small, testable Perl examples for fetching an HTTP resource, submitting a form, and constructing an email. The examples use the modules documented by the installed perlfaq9, rather than assuming a web framework or a particular mail server.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about 20 minutes. You need Perl and the modules you intend to use. On this machine, Perl is 5.38.2, HTTP::Tiny is 0.086, Net::Domain is 3.15, and MIME::Base64 is 3.16_01. The FAQ itself is version 5.20210520, shipped by the local perl-doc package. Check your own versions before relying on a version-specific default.
This guide makes network requests and can send mail if you run the mail example. Use a test endpoint and a test recipient first. Nothing here needs root privileges.
1. Check the local Perl modules
Start with a read-only compile check. It proves that the modules are available without contacting a server or sending mail:
$ perl -MHTTP::Tiny -MNet::Domain -MMIME::Base64 -e 'printf "perl %s\nHTTP::Tiny %s\nNet::Domain %s\nMIME::Base64 %s\n", $^V, $HTTP::Tiny::VERSION, $Net::Domain::VERSION, $MIME::Base64::VERSION'
perl v5.38.2
HTTP::Tiny 0.086
Net::Domain 3.15
MIME::Base64 3.16_01
Your version lines may differ. If a module is missing, stop here and install it through your normal package or Perl dependency process. Do not change the system Perl just to make one script run.
Checkpoint
The command exits with status 0 and prints the modules you will actually use.
2. Fetch a page and inspect the response
HTTP::Tiny returns a response hash. The useful fields are success, status, reason, headers and content. Do not treat a returned content string as proof of an HTTP success:
use strict;
use warnings;
use HTTP::Tiny;
my $url = 'https://example.com/';
my $http = HTTP::Tiny->new(timeout => 10);
my $response = $http->get($url);
die "GET $url failed: $response->{status} $response->{reason}\n"
unless $response->{success};
print $response->{content};
Run it from a file, not by pasting an untrusted URL into a shell command. A successful response has a 2xx status. DNS failures, TLS errors and other request exceptions are represented by a 599 response, so include both status and reason in diagnostics.
$ perl fetch.pl > example.html
$ test -s example.html && echo 'received a non-empty response'
received a non-empty response
The timeout is explicit because the module's default is 60 seconds. HTTPS certificate verification is enabled by default in the installed module family, and should remain enabled. Do not "fix" a certificate failure by setting verify_SSL => 0; investigate the hostname, clock, trust store or server certificate instead.
3. Encode a GET form without hand-building it
Query values need encoding. Use www_form_urlencode and append the result to a known HTTPS endpoint:
use strict;
use warnings;
use HTTP::Tiny;
my $http = HTTP::Tiny->new(timeout => 10);
my $query = $http->www_form_urlencode([
q => 'DB_File',
lucky => 1,
]);
my $url = "https://metacpan.org/search?$query";
my $response = $http->get($url);
die "search failed: $response->{status} $response->{reason}\n"
unless $response->{success};
print $response->{content};
Keep the URL and its components separate until the final request. Escaping an entire URL can change its delimiters, while leaving a value unescaped can change the query meaning. For a POST form, use post_form so the module selects the appropriate form encoding:
my $response = $http->post_form(
'https://example.test/search',
[ query => 'a phrase with spaces', page => 1 ],
);
die "POST failed: $response->{status} $response->{reason}\n"
unless $response->{success};
Replace example.test with an endpoint you control or have permission to test. A POST can create or alter remote state, so confirm the endpoint and payload before running it.
4. Treat web input as hostile
The FAQ's security boundary is simple: client-side checks do not make submitted data safe. Validate values on the server, use DBI placeholders for database values, and use list-form system or exec when invoking a program. Never interpolate a form value into SQL or a shell command.
For example, a value that should be a page number can be constrained before it reaches application logic:
my ($page) = $input =~ /\A([1-9][0-9]*)\z/;
die "invalid page\n" unless defined $page && $page <= 1000;
This is an example boundary, not a universal validator. Check length, allowed values and business rules for the actual field. If you need HTML extraction, use an HTML parser such as the modules named by the FAQ. Do not remove tags with a regular expression and then assume the result is safe HTML.
5. Decode mail content and read headers
For a complete RFC 2822 message, the FAQ recommends Email::MIME, which handles folded headers and encoded values. For a known Base64 body, MIME::Base64 is the narrow tool:
use strict;
use warnings;
use MIME::Base64 qw(decode_base64);
my $encoded = 'SGVsbG8sIFBlcmwh';
my $decoded = decode_base64($encoded);
print $decoded;
$ perl decode.pl
Hello, Perl!
Do not decode arbitrary mail as Base64 just because it contains text that looks encoded. Inspect the MIME part's declared transfer encoding first. For headers, parse the message with Email::MIME rather than splitting lines yourself.
6. Send mail only after a controlled test
The FAQ's Email::Stuffer example builds a message and calls send_or_die. That call can deliver real mail, and the default transport tries sendmail first when it is available. Check the recipient, sender and local mail configuration before running it:
use strict;
use warnings;
use Email::Stuffer;
Email::Stuffer->from('[email protected]')
->to('[email protected]')
->subject('Perl mail smoke test')
->text_body("Test message from a controlled run.\n")
->send_or_die;
Use a mailbox you control and a sender permitted by the configured transport. If you need a remote SMTP service, configure an explicit Email::Sender transport instead of assuming that local sendmail is present. The example changes external state, so there is no general undo: recall or deletion depends on the receiving system.
7. Diagnose failures without guessing
- A missing module is a dependency problem. Check the package or Perl environment, not the remote service.
- A 4xx or 5xx HTTP response is a server response. Log the status and reason, and do not retry a state-changing POST blindly.
- Status 599 indicates a request exception such as a timeout, connection or TLS failure. Check the endpoint and network path.
- An email send failure can be local transport, authentication, DNS or recipient policy. Keep the error and test with a controlled mailbox.
Done means
- The required modules compile on the installed Perl version.
- HTTP code checks
success, status and reason, with a finite timeout. - Form values are encoded by
HTTP::Tiny, not concatenated by hand. - Untrusted input stays outside SQL and shell interpolation.
- Mail decoding uses the declared format, and sending was tested only with a controlled recipient.