Skip to content

Commit

Permalink
fixed 1xx, 204 and 304 response support (closes #369)
Browse files Browse the repository at this point in the history
  • Loading branch information
kraih committed Aug 10, 2012
1 parent 48c16b5 commit 05a3735
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 142 deletions.
4 changes: 4 additions & 0 deletions Changes
@@ -1,7 +1,11 @@

3.28 2012-08-10
- Added no_body attribute to Mojo::Content.
- Added has_no_body method to Mojo::Message::Response.
- Updated jQuery to version 1.8.
- Improved message parser performance slightly.
- Improved tests.
- Fixed 1xx, 204 and 304 response support.

3.27 2012-08-09
- Improved documentation.
Expand Down
72 changes: 38 additions & 34 deletions lib/Mojo/Content.pm
Expand Up @@ -4,7 +4,7 @@ use Mojo::Base 'Mojo::EventEmitter';
use Carp 'croak';
use Mojo::Headers;

has [qw(auto_relax relaxed)] => 0;
has [qw(auto_relax no_body relaxed)];
has headers => sub { Mojo::Headers->new };
has max_leftover_size => sub { $ENV{MOJO_MAX_LEFTOVER_SIZE} || 262144 };

Expand Down Expand Up @@ -91,10 +91,16 @@ sub parse {
my $self = shift;

# Parse headers
$self->parse_until_body(@_);
$self->_parse_until_body(@_);
return $self if $self->{state} eq 'headers';
$self->_body;

# No content
if ($self->no_body) {
$self->{state} = 'finished';
return $self;
}

# Relaxed parsing
my $headers = $self->headers;
if ($self->auto_relax) {
Expand Down Expand Up @@ -148,29 +154,6 @@ sub parse_body {
return $self->parse(@_);
}

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

# Add chunk
$self->{raw_size} += length($chunk //= '');
$self->{pre_buffer} .= $chunk;

# Parser started
unless ($self->{state}) {

# Update size
$self->{header_size} = $self->{raw_size} - length $self->{pre_buffer};

# Headers
$self->{state} = 'headers';
}

# Parse headers
$self->_parse_headers if $self->{state} ~~ 'headers';

return $self;
}

sub progress {
my $self = shift;
return 0 unless $self->{state} ~~ [qw(body finished)];
Expand Down Expand Up @@ -326,6 +309,27 @@ sub _parse_headers {
$self->_body;
}

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

# Add chunk
$self->{raw_size} += length($chunk //= '');
$self->{pre_buffer} .= $chunk;

# Parser started
unless ($self->{state}) {

# Update size
$self->{header_size} = $self->{raw_size} - length $self->{pre_buffer};

# Headers
$self->{state} = 'headers';
}

# Parse headers
$self->_parse_headers if $self->{state} ~~ 'headers';
}

1;

=head1 NAME
Expand Down Expand Up @@ -402,7 +406,7 @@ L<Mojo::Content> implements the following attributes.
my $relax = $content->auto_relax;
$content = $content->auto_relax(1);
Try to detect broken web servers and turn on relaxed parsing automatically.
Try to detect when relaxed parsing is necessary.
=head2 C<headers>
Expand All @@ -419,6 +423,13 @@ Content headers, defaults to a L<Mojo::Headers> object.
Maximum size in bytes of buffer for pipelined HTTP requests, defaults to the
value of the C<MOJO_MAX_LEFTOVER_SIZE> environment variable or C<262144>.
=head2 C<no_body>
my $no_body = $content->no_body;
$content = $content->no_body(1);
Deactivate body parsing and finish after headers.
=head2 C<relaxed>
my $relaxed = $content->relaxed;
Expand Down Expand Up @@ -553,14 +564,7 @@ Parse content chunk.
$content = $content->parse_body('Hi!');
Parse body chunk.
=head2 C<parse_until_body>
$content
= $content->parse_until_body("Content-Length: 12\r\n\r\nHello World!");
Parse chunk and stop after headers.
Parse body chunk and skip headers.
=head2 C<progress>
Expand Down
2 changes: 1 addition & 1 deletion lib/Mojo/Content/Single.pm
Expand Up @@ -41,7 +41,7 @@ sub parse {
my $self = shift;

# Parse headers
$self->parse_until_body(@_);
$self->_parse_until_body(@_);

# Parse body
return $self->SUPER::parse
Expand Down
96 changes: 43 additions & 53 deletions lib/Mojo/Message.pm
Expand Up @@ -196,9 +196,49 @@ sub leftovers { shift->content->leftovers }

sub param { shift->body_params->param(@_) }

sub parse { shift->_parse(parse => @_) }
sub parse {
my ($self, $chunk) = @_;

sub parse_until_body { shift->_parse(parse_until_body => @_) }
# Add chunk
$self->{raw_size} += length($chunk //= '');
$self->{buffer} .= $chunk;

# Check message size
return $self->error('Maximum message size exceeded', 413)
if $self->{raw_size} > $self->max_message_size;

# Start line
unless ($self->{state}) {

# Check line size
my $len = index $self->{buffer}, "\x0a";
$len = length $self->{buffer} if $len < 0;
return $self->error('Maximum line size exceeded', 431)
if $len > $self->max_line_size;

# Extract
$self->{state} = 'content' if $self->extract_start_line(\$self->{buffer});
}

# Content
$self->content($self->content->parse(delete $self->{buffer}))
if $self->{state} ~~ [qw(content finished)];

# Check line size
return $self->error('Maximum line size exceeded', 431)
if $self->headers->is_limit_exceeded;

# Finished
$self->{state} = 'finished' if $self->content->is_finished;

# Progress
$self->emit('progress');

# Finished
$self->emit('finish') if $self->is_finished;

return $self;
}

sub start_line_size { length shift->build_start_line }

Expand Down Expand Up @@ -289,50 +329,6 @@ sub _nest {
return $hash;
}

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

# Add chunk
$self->{raw_size} += length($chunk //= '');
$self->{buffer} .= $chunk;

# Check message size
return $self->error('Maximum message size exceeded', 413)
if $self->{raw_size} > $self->max_message_size;

# Start line
unless ($self->{state}) {

# Check line size
my $len = index $self->{buffer}, "\x0a";
$len = length $self->{buffer} if $len < 0;
return $self->error('Maximum line size exceeded', 431)
if $len > $self->max_line_size;

# Extract
$self->{state} = 'content' if $self->extract_start_line(\$self->{buffer});
}

# Content
$self->content($self->content->$method(delete $self->{buffer}))
if $self->{state} ~~ [qw(content finished)];

# Check line size
return $self->error('Maximum line size exceeded', 431)
if $self->headers->is_limit_exceeded;

# Finished
$self->{state} = 'finished' if $self->content->is_finished;

# Progress
$self->emit('progress');

# Finished
$self->emit('finish') if $self->is_finished;

return $self;
}

sub _parse_formdata {
my $self = shift;

Expand Down Expand Up @@ -495,7 +491,7 @@ C<body_params>, C<dom> or C<json> methods.
my $version = $message->version;
$message = $message->version('1.1');
HTTP version of message.
HTTP version of message, defaults to C<1.1>.
=head1 METHODS
Expand Down Expand Up @@ -711,12 +707,6 @@ not be called before the entire message body has been received.
Parse message chunk.
=head2 C<parse_until_body>
$message = $message->parse_until_body('HTTP/1.1 200 OK...');
Parse message chunk and stop after headers.
=head2 C<start_line_size>
my $size = $message->start_line_size;
Expand Down
14 changes: 13 additions & 1 deletion lib/Mojo/Message/Response.pm
Expand Up @@ -100,7 +100,8 @@ sub extract_start_line {
return unless defined(my $line = get_line $bufferref);
$self->error('Bad response start line') and return
unless $line =~ m!^\s*HTTP/(\d\.\d)\s+(\d\d\d)\s*(.+)?$!;
return !!$self->version($1)->code($2)->message($3)->content->auto_relax(1);
$self->content->no_body(1) if $self->code($2)->has_no_body;
return !!$self->version($1)->message($3)->content->auto_relax(1);
}

sub fix_headers {
Expand Down Expand Up @@ -131,6 +132,11 @@ sub get_start_line_chunk {
return substr $self->{start_buffer}, $offset, 131072;
}

sub has_no_body {
my $self = shift;
return $self->is_status_class(100) || $self->code ~~ [qw(204 304)];
}

sub is_status_class {
my ($self, $class) = @_;
return unless my $code = $self->code;
Expand Down Expand Up @@ -229,6 +235,12 @@ Make sure response has all required headers for the current HTTP version.
Get a chunk of status line data starting from a specific position.
=head2 C<has_no_body>
my $success = $res->has_no_body;
Check if this type of response has no body.
=head2 C<is_status_class>
my $success = $res->is_status_class(200);
Expand Down

0 comments on commit 05a3735

Please sign in to comment.