Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
added gzip support to Mojo::UserAgent
  • Loading branch information
kraih committed Nov 6, 2012
1 parent 4839160 commit 38e0679
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 5 deletions.
4 changes: 3 additions & 1 deletion Changes
@@ -1,5 +1,7 @@

3.55 2012-11-05
3.55 2012-11-06
- Added gzip support to Mojo::UserAgent.
- Added is_compressed method to Mojo::Content.
- Improved documentation.
- Improved tests.
- Fixed bug that prevented around_dispatch hooks from working correctly in
Expand Down
39 changes: 36 additions & 3 deletions lib/Mojo/Content.pm
Expand Up @@ -2,6 +2,7 @@ package Mojo::Content;
use Mojo::Base 'Mojo::EventEmitter';

use Carp 'croak';
use Compress::Raw::Zlib qw(WANT_GZIP Z_STREAM_END);
use Mojo::Headers;

has [qw(auto_relax relaxed skip_body)];
Expand Down Expand Up @@ -73,6 +74,8 @@ sub header_size { length shift->build_headers }

sub is_chunked { (shift->headers->transfer_encoding || '') =~ /chunked/i }

sub is_compressed { (shift->headers->content_encoding || '') eq 'gzip' }

sub is_dynamic {
my $self = shift;
return $self->{dynamic} && !defined $self->headers->content_length;
Expand Down Expand Up @@ -103,7 +106,7 @@ sub parse {
$self->{state} = 'finished' if ($self->{chunk_state} // '') eq 'finished';
}

# Not chunked, pass through to second buffer
# Not chunked, pass through to decompressor
else {
$self->{real_size} += length $self->{pre_buffer};
my $limit = $self->is_finished
Expand All @@ -130,7 +133,8 @@ sub parse {
# Chunked or relaxed content
if ($self->is_chunked || $self->relaxed) {
$self->{size} += length($self->{buffer} //= '');
$self->emit(read => $self->{buffer})->{buffer} = '';
$self->_uncompress($self->{buffer});
$self->{buffer} = '';
}

# Normal content
Expand All @@ -139,7 +143,8 @@ sub parse {
$self->{size} ||= 0;
if ((my $need = $len - $self->{size}) > 0) {
my $chunk = substr $self->{buffer}, 0, $need, '';
$self->emit(read => $chunk)->{size} += length $chunk;
$self->_uncompress($chunk);
$self->{size} += length $chunk;
}

# Finished
Expand Down Expand Up @@ -329,6 +334,28 @@ sub _parse_until_body {
$self->_parse_headers if ($self->{state} // '') eq 'headers';
}

sub _uncompress {
my ($self, $chunk) = @_;

# No compression
return $self->emit(read => $chunk) unless $self->is_compressed;

# Uncompress
$self->{post_buffer} .= $chunk;
my $gz = $self->{gz}
//= Compress::Raw::Zlib::Inflate->new(WindowBits => WANT_GZIP);
my $status = $gz->inflate(\$self->{post_buffer}, my $out);
$self->emit(read => $out) if defined $out;
# Replace Content-Encoding with Content-Length
$self->headers->content_length($gz->total_out)->remove('Transfer-Encoding')
if $status == Z_STREAM_END;
# Check buffer size
$self->{limit} = $self->{state} = 'finished'
if length($self->{post_buffer} // '') > $self->max_buffer_size;
}

1;

=head1 NAME
Expand Down Expand Up @@ -530,6 +557,12 @@ Size of headers in bytes.
Check if content is chunked.
=head2 C<is_compressed>
my $success = $content->is_compressed;
Check if content is C<gzip> compressed.
=head2 C<is_dynamic>
my $success = $content->is_dynamic;
Expand Down
3 changes: 2 additions & 1 deletion lib/Mojo/UserAgent.pm
Expand Up @@ -452,9 +452,10 @@ sub _start {
if $https && !defined $req->proxy && $scheme eq 'https';
}

# We identify ourselves
# We identify ourselves and accept gzip compression
my $headers = $req->headers;
$headers->user_agent($self->name) unless $headers->user_agent;
$headers->accept_encoding('gzip') unless $headers->accept_encoding;

# Inject cookies
if (my $jar = $self->cookie_jar) { $jar->inject($tx) }
Expand Down
20 changes: 20 additions & 0 deletions t/mojo/response.t
@@ -1,6 +1,7 @@
use Mojo::Base -strict;

use Test::More;
use IO::Compress::Gzip 'gzip';
use Mojo::Asset::File;
use Mojo::Content::Single;
use Mojo::Content::MultiPart;
Expand Down Expand Up @@ -413,6 +414,25 @@ ok $res->headers->content_type =~ m!multipart/form-data!,
isa_ok $res->content, 'Mojo::Content::Single', 'right content';
like $res->content->asset->slurp, qr/hallo welt/, 'right content';

# Parse HTTP 1.1 gzip compressed response
gzip \(my $uncompressed = 'abc' x 1000), \my $compressed;
$res = Mojo::Message::Response->new;
$res->parse("HTTP/1.1 200 OK\x0d\x0a");
$res->parse("Content-Type: text/plain\x0d\x0a");
$res->parse("Content-Length: @{[length $compressed]}\x0d\x0a");
$res->parse("Content-Encoding: gzip\x0d\x0a\x0d\x0a");
$res->parse(substr $compressed, 0, 1);
$res->parse(substr $compressed, 1, length($compressed));
ok $res->is_finished, 'response is finished';
ok !$res->error, 'no error';
is $res->code, 200, 'right status';
is $res->message, 'OK', 'right message';
is $res->version, '1.1', 'right version';
is $res->headers->content_type, 'text/plain', 'right "Content-Type" value';
is $res->headers->content_length, length($uncompressed),
'right "Content-Length" value';
is $res->body, $uncompressed, 'right content';

# Build HTTP 1.1 response start line with minimal headers
$res = Mojo::Message::Response->new;
$res->code(404);
Expand Down

0 comments on commit 38e0679

Please sign in to comment.