FU::Util: Add brotli_compress() and use it for FU output compression
Seems to compresses and perform better than libdeflate at level 6, so certainly worth using.
This commit is contained in:
parent
bc33fe53f0
commit
6159b33950
5 changed files with 99 additions and 14 deletions
19
FU.pm
19
FU.pm
|
|
@ -400,7 +400,7 @@ sub _do_req($c) {
|
||||||
($REQ->{trace_sqlexec}||0)*1000, ($REQ->{trace_sqlprep}||0)*1000,
|
($REQ->{trace_sqlexec}||0)*1000, ($REQ->{trace_sqlprep}||0)*1000,
|
||||||
$REQ->{trace_nsqldirect}||0, $REQ->{trace_nsqlprep}||0, $REQ->{trace_nsql} : '',
|
$REQ->{trace_nsqldirect}||0, $REQ->{trace_nsqlprep}||0, $REQ->{trace_nsql} : '',
|
||||||
$REQ->{status}, ($REQ->{reshdr}{'content-type'}//'-') =~ s/;.+$//r,
|
$REQ->{status}, ($REQ->{reshdr}{'content-type'}//'-') =~ s/;.+$//r,
|
||||||
length($REQ->{resbody}), substr($REQ->{reshdr}{'content-encoding'}//'bytes', 0, 1)
|
length($REQ->{resbody}), substr($REQ->{reshdr}{'content-encoding'}//'r', 0, 1)
|
||||||
) if FU::debug || $proc_ms > (FU::log_slow_reqs||1e10);
|
) if FU::debug || $proc_ms > (FU::log_slow_reqs||1e10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -850,6 +850,8 @@ sub _error_page($, $code, $title, $msg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
sub _finalize {
|
sub _finalize {
|
||||||
|
state $hasgzip = FU::Util::gzip_lib();
|
||||||
|
state $hasbrotli = eval { FU::Util::brotli_compress(6, ''); 1 };
|
||||||
my $r = $FU::REQ;
|
my $r = $FU::REQ;
|
||||||
|
|
||||||
fu->add_header('set-cookie', $_) for $r->{rescookie} ? sort values $r->{rescookie}->%* : ();
|
fu->add_header('set-cookie', $_) for $r->{rescookie} ? sort values $r->{rescookie}->%* : ();
|
||||||
|
|
@ -861,13 +863,19 @@ sub _finalize {
|
||||||
$r->{resbody} = '';
|
$r->{resbody} = '';
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (FU::Util::gzip_lib() && length($r->{resbody}) > 256
|
if (($hasgzip || $hasbrotli) && length($r->{resbody}) > 256
|
||||||
&& !defined $r->{reshdr}{'content-encoding'} && FU::compress_mimes->{$r->{reshdr}{'content-type'}}) {
|
&& !defined $r->{reshdr}{'content-encoding'}
|
||||||
|
&& FU::compress_mimes->{$r->{reshdr}{'content-type'}}
|
||||||
|
) {
|
||||||
|
|
||||||
$r->{reshdr}{'vary'} = ($r->{reshdr}{'vary'} ? $r->{reshdr}{'vary'}.', ' : '').'accept-encoding'
|
$r->{reshdr}{'vary'} = ($r->{reshdr}{'vary'} ? $r->{reshdr}{'vary'}.', ' : '').'accept-encoding'
|
||||||
if ($r->{reshdr}{'vary'}||'') !~ /accept-encoding/i;
|
if ($r->{reshdr}{'vary'}||'') !~ /accept-encoding/i;
|
||||||
|
|
||||||
if (($r->{hdr}{'accept-encoding'}||'') =~ /gzip/) {
|
if ($hasbrotli && ($r->{hdr}{'accept-encoding'}||'') =~ /\bbr\b/) {
|
||||||
|
$r->{resbody} = FU::Util::brotli_compress(6, $r->{resbody});
|
||||||
|
$r->{reshdr}{'content-encoding'} = 'br';
|
||||||
|
|
||||||
|
} elsif ($hasgzip && ($r->{hdr}{'accept-encoding'}||'') =~ /\bgzip\b/) {
|
||||||
$r->{resbody} = FU::Util::gzip_compress(6, $r->{resbody});
|
$r->{resbody} = FU::Util::gzip_compress(6, $r->{resbody});
|
||||||
$r->{reshdr}{'content-encoding'} = 'gzip';
|
$r->{reshdr}{'content-encoding'} = 'gzip';
|
||||||
}
|
}
|
||||||
|
|
@ -991,6 +999,9 @@ C<dlopen()>.
|
||||||
=item * C<libdeflate.so> or C<libz-ng.so> or C<libz.so> - required for
|
=item * C<libdeflate.so> or C<libz-ng.so> or C<libz.so> - required for
|
||||||
C<gzip_compress()> in L<FU::Util> and used for HTTP output compression.
|
C<gzip_compress()> in L<FU::Util> and used for HTTP output compression.
|
||||||
|
|
||||||
|
=item * C<libbrotlienc.so> - required for C<brotli_compress()> in L<FU::Util>
|
||||||
|
and used for HTTP output compression.
|
||||||
|
|
||||||
=back
|
=back
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
4
FU.xs
4
FU.xs
|
|
@ -126,6 +126,10 @@ void gzip_compress(IV level, SV *in)
|
||||||
CODE:
|
CODE:
|
||||||
ST(0) = fugz_compress(aTHX_ level, in);
|
ST(0) = fugz_compress(aTHX_ level, in);
|
||||||
|
|
||||||
|
void brotli_compress(IV level, SV *in)
|
||||||
|
CODE:
|
||||||
|
ST(0) = fubr_compress(aTHX_ level, in);
|
||||||
|
|
||||||
void fdpass_send(int socket, int fd, SV *data)
|
void fdpass_send(int socket, int fd, SV *data)
|
||||||
CODE:
|
CODE:
|
||||||
STRLEN buflen;
|
STRLEN buflen;
|
||||||
|
|
|
||||||
20
FU/Util.pm
20
FU/Util.pm
|
|
@ -13,7 +13,7 @@ our @EXPORT_OK = qw/
|
||||||
utf8_decode uri_escape uri_unescape
|
utf8_decode uri_escape uri_unescape
|
||||||
query_decode query_encode
|
query_decode query_encode
|
||||||
httpdate_format httpdate_parse
|
httpdate_format httpdate_parse
|
||||||
gzip_lib gzip_compress
|
gzip_lib gzip_compress brotli_compress
|
||||||
fdpass_send fdpass_recv
|
fdpass_send fdpass_recv
|
||||||
/;
|
/;
|
||||||
|
|
||||||
|
|
@ -401,6 +401,8 @@ I<zlib(-ng)> the level is capped at 9. 6 is typically used as a default.
|
||||||
|
|
||||||
Throws an error if no suitable library was found.
|
Throws an error if no suitable library was found.
|
||||||
|
|
||||||
|
This function is B<NOT> safe to use from multiple threads!
|
||||||
|
|
||||||
=back
|
=back
|
||||||
|
|
||||||
This module does not currently implement decompression. If you need that, or
|
This module does not currently implement decompression. If you need that, or
|
||||||
|
|
@ -409,6 +411,22 @@ L<Compress::Raw::Zlib> and L<Compress::Zlib> in the core Perl distribution and
|
||||||
L<Gzip::Deflate> on CPAN.
|
L<Gzip::Deflate> on CPAN.
|
||||||
|
|
||||||
|
|
||||||
|
=head2 Brotli Compression
|
||||||
|
|
||||||
|
Just a small wrapper around C<libbrotlienc.so>'s one-shot compression
|
||||||
|
interface.
|
||||||
|
|
||||||
|
=over
|
||||||
|
|
||||||
|
=item brotli_compress($level, $data)
|
||||||
|
|
||||||
|
Returns a byte string with the brotli-compressed version of C<$data> at the
|
||||||
|
given quality C<$level> (between 0 and 11).
|
||||||
|
|
||||||
|
Throws an error if C<libbrotlienc.so> could not be found or loaded.
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
|
|
||||||
=head2 File Descriptor Passing
|
=head2 File Descriptor Passing
|
||||||
|
|
||||||
|
|
|
||||||
35
c/compress.c
35
c/compress.c
|
|
@ -124,5 +124,38 @@ static SV *fugz_compress(pTHX_ IV level, SV *in) {
|
||||||
|
|
||||||
if (fugz_imp == 1) return fugz_compress_ld(aTHX_ level, bytes, inlen);
|
if (fugz_imp == 1) return fugz_compress_ld(aTHX_ level, bytes, inlen);
|
||||||
else return fugz_compress_zlib(aTHX_ level, bytes, inlen);
|
else return fugz_compress_zlib(aTHX_ level, bytes, inlen);
|
||||||
return &PL_sv_undef;
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Brotli */
|
||||||
|
|
||||||
|
typedef enum { BROTLI_MODE_GENERIC = 0, BROTLI_MODE_TEXT = 1, BROTLI_MODE_FONT = 2 } BrotliEncoderMode;
|
||||||
|
|
||||||
|
static size_t (*BrotliEncoderMaxCompressedSize)(size_t);
|
||||||
|
static int (*BrotliEncoderCompress)(int, int, BrotliEncoderMode, size_t, const char *, size_t *, char *);
|
||||||
|
|
||||||
|
static SV *fubr_compress(pTHX_ IV level, SV *in) {
|
||||||
|
if (!BrotliEncoderCompress) {
|
||||||
|
void *handle;
|
||||||
|
if (!(handle = dlopen("libbrotlienc.so", RTLD_LAZY))
|
||||||
|
|| !(BrotliEncoderMaxCompressedSize = dlsym(handle, "BrotliEncoderMaxCompressedSize"))
|
||||||
|
|| !(BrotliEncoderCompress = dlsym(handle, "BrotliEncoderCompress")))
|
||||||
|
fu_confess("Unable to load libbrotlienc.so: %s", dlerror());
|
||||||
|
}
|
||||||
|
if (level < 0 || level > 11) fu_confess("Invalid compression level: %"IVdf, level);
|
||||||
|
|
||||||
|
STRLEN inlen;
|
||||||
|
const char *bytes = SvPVbyte(in, inlen);
|
||||||
|
|
||||||
|
size_t outlen = BrotliEncoderMaxCompressedSize(inlen);
|
||||||
|
/* "Result is only valid if quality is at least 2", so let's use a (more conservative?) fallback */
|
||||||
|
if (level < 2 && outlen < inlen + 256) outlen = inlen + 256;
|
||||||
|
|
||||||
|
SV *out = sv_2mortal(newSV(outlen));
|
||||||
|
SvPOK_only(out);
|
||||||
|
if (!BrotliEncoderCompress(level, 22, BROTLI_MODE_GENERIC, inlen, bytes, &outlen, SvPVX(out)))
|
||||||
|
fu_confess("Brotli compression failed");
|
||||||
|
SvCUR_set(out, outlen);
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
31
t/compress.t
31
t/compress.t
|
|
@ -1,18 +1,34 @@
|
||||||
use v5.36;
|
use v5.36;
|
||||||
use Test::More;
|
use Test::More;
|
||||||
use FU::Util qw/gzip_lib gzip_compress/;
|
use FU::Util qw/gzip_lib gzip_compress brotli_compress/;
|
||||||
|
|
||||||
like gzip_lib, qr/^(|libdeflate|zlib-ng|zlib)$/, gzip_lib;
|
like gzip_lib, qr/^(|libdeflate|zlib-ng|zlib)$/, gzip_lib;
|
||||||
|
|
||||||
plan skip_all => 'No suitable gzip library found' if !gzip_lib;
|
my $incompressible;
|
||||||
plan skip_all => 'Compress::Zlib not found' if !eval { require Compress::Zlib };
|
|
||||||
|
|
||||||
my $incompressible = Compress::Zlib::memGzip(join '', map chr(rand 256), 0..93123);
|
subtest 'gzip_compress', sub {
|
||||||
|
plan skip_all => 'No suitable gzip library found' if !gzip_lib;
|
||||||
|
plan skip_all => 'Compress::Zlib not found' if !eval { require Compress::Zlib };
|
||||||
|
|
||||||
for my $str ('', 'Hello world!', 'x'x4096, $incompressible) {
|
$incompressible = Compress::Zlib::memGzip(join '', map chr(rand 256), 0..93123);
|
||||||
|
|
||||||
|
for my $str ('', 'Hello world!', 'x'x4096, $incompressible) {
|
||||||
is Compress::Zlib::memGunzip(gzip_compress(0, $str)), $str;
|
is Compress::Zlib::memGunzip(gzip_compress(0, $str)), $str;
|
||||||
is Compress::Zlib::memGunzip(gzip_compress(12, $str)), $str;
|
is Compress::Zlib::memGunzip(gzip_compress(12, $str)), $str;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
subtest 'brotli_compress', sub {
|
||||||
|
plan skip_all => 'libbrotlienc not available'
|
||||||
|
if !eval { brotli_compress 6, '' } && $@ =~ /Unable to load/;
|
||||||
|
|
||||||
|
ok length(brotli_compress 0, '') > 0;
|
||||||
|
ok length(brotli_compress 11, '') > 0;
|
||||||
|
# '0' does not disable compression...
|
||||||
|
ok length(brotli_compress 0, 'Hello world!'x100) < 200;
|
||||||
|
ok length(brotli_compress 11, 'Hello world!'x100) < 100;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
done_testing;
|
done_testing;
|
||||||
|
|
@ -29,6 +45,8 @@ for (0..1000) {
|
||||||
local $_ = gzip_lib;
|
local $_ = gzip_lib;
|
||||||
$_ = gzip_compress(0, $str);
|
$_ = gzip_compress(0, $str);
|
||||||
$_ = gzip_compress(12, $str);
|
$_ = gzip_compress(12, $str);
|
||||||
|
$_ = brotli_compress(0, $str);
|
||||||
|
$_ = brotli_compress(11, $str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
diag count_sv;
|
diag count_sv;
|
||||||
|
|
@ -44,4 +62,5 @@ my $data = <$F>;
|
||||||
cmpthese -3, {
|
cmpthese -3, {
|
||||||
memGzip => 'Compress::Zlib::memGzip($data)',
|
memGzip => 'Compress::Zlib::memGzip($data)',
|
||||||
gzip_compress => 'gzip_compress(6, $data)',
|
gzip_compress => 'gzip_compress(6, $data)',
|
||||||
|
brotli_compress => 'brotli_compress(6, $data)',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue