Skip to content

Commit

Permalink
added multiple MIME type and quality support for Ajax content negotia…
Browse files Browse the repository at this point in the history
…tion with respond_to
  • Loading branch information
kraih committed Apr 4, 2012
1 parent 5abd60a commit b5697ec
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 28 deletions.
4 changes: 3 additions & 1 deletion Changes
@@ -1,6 +1,8 @@
This file documents the revision history for Perl extension Mojolicious.

2.75 2012-04-04
2.75 2012-04-05
- Added multiple MIME type and quality support for Ajax content
negotiation with respond_to.
- Improved documentation.

2.74 2012-04-04
Expand Down
4 changes: 2 additions & 2 deletions lib/Mojo/Headers.pm
Expand Up @@ -523,8 +523,8 @@ Shortcut for the C<Status> header.
=head2 C<to_hash>
my $hash = $headers->to_hash;
my $hash = $headers->to_hash(1);
my $single = $headers->to_hash;
my $multi = $headers->to_hash(1);
Turn headers into hash reference, nested array references to represent multi
line values are disabled by default.
Expand Down
21 changes: 10 additions & 11 deletions lib/Mojolicious/Controller.pm
Expand Up @@ -386,24 +386,21 @@ sub respond_to {
my $args = ref $_[0] ? $_[0] : {@_};

# Detect formats
my @formats;
my $app = $self->app;
push @formats, @{$app->types->detect($self->req->headers->accept)};
my @formats =
@{$app->types->detect($self->req->headers->accept, $self->req->is_xhr)};
my $stash = $self->stash;
unless (@formats) {
if (my $format = $stash->{format} || $self->req->param('format')) {
push @formats, $format;
}
else { push @formats, $app->renderer->default_format }
my $format = $stash->{format} || $self->req->param('format');
push @formats, $format ? $format : $app->renderer->default_format;
}

# Find target
my $target;
for my $format (@formats) {
if ($target = $args->{$format}) {
$stash->{format} = $format;
last;
}
next unless $target = $args->{$format};
$stash->{format} = $format;
last;
}

# Fallback
Expand Down Expand Up @@ -883,7 +880,9 @@ L<Mojo::Message::Response> object.
Automatically select best possible representation for resource from C<Accept>
request header, C<format> stash value or C<format> GET/POST parameter,
defaults to rendering an empty C<204> response.
defaults to rendering an empty C<204> response. Multiple MIME types are only
allowed for Ajax requests, which are determined by the presence of a
C<X-Requested-With> request header with the value C<XMLHttpRequest>.
$c->respond_to(
json => sub { $c->render_json({just => 'works'}) },
Expand Down
45 changes: 33 additions & 12 deletions lib/Mojolicious/Types.pm
Expand Up @@ -33,20 +33,23 @@ has types => sub {

# "Magic. Got it."
sub detect {
my ($self, $accept) = @_;
my ($self, $accept, $all) = @_;
$accept ||= '';

# First MIME type only
return [] unless $all || $accept =~ /^[^,]+?(?:\;[^,]*)*$/;

# Detect extensions from MIME type
return [] unless (($accept || '') =~ /^([^,]+?)(?:\;[^,]*)*$/);
my $pattern = $1;
my @exts;
my $types = $self->types;
for my $ext (sort keys %$types) {
my $type = quotemeta $types->{$ext};
$type =~ s/\\\;.*$//;
push @exts, $ext if $pattern =~ /^$type$/i;
my %results;
my $reverse = $self->_reverse;
for my $type (split /,/, $accept) {
next unless $type =~ /^\s*([^;]+?)\s*(?:\;.*)*?$/;
next unless my $exts = $reverse->{lc $1};
my $quality = $type =~ /\;\s*q=(\d+(?:\.\d+)?)/ ? $1 : 1;
$results{$_} = $quality for @$exts;
}

return \@exts;
return [sort { $results{$b} <=> $results{$a} } sort keys %results];
}

sub type {
Expand All @@ -56,6 +59,22 @@ sub type {
return $self;
}

sub _reverse {
my $self = shift;

# Index types
unless ($self->{reverse}) {
my $types = $self->types;
for my $ext (keys %$types) {
my $type = lc $types->{$ext};
$type =~ s/\;.*$//;
push @{$self->{reverse}->{$type}}, $ext;
}
}

return $self->{reverse};
}

1;
__END__
Expand Down Expand Up @@ -91,9 +110,11 @@ the following ones.
=head2 C<detect>
my $extensions = $types->detect('application/json;q=9');
my $first = $types->detect('application/json;q=9');
my $all = $types->detect('application/json;q=9', 1);
Detect file extensions from C<Accept> header value.
Detect file extensions from C<Accept> header value, detection of more than
one MIME type is disabled by default.
=head2 C<type>
Expand Down
19 changes: 18 additions & 1 deletion t/mojolicious/restful_lite_app.t
Expand Up @@ -6,7 +6,7 @@ BEGIN {
$ENV{MOJO_REACTOR} = 'Mojo::Reactor::Poll';
}

use Test::More tests => 333;
use Test::More tests => 345;

# "Woohoo, time to go clubbin'! Baby seals here I come!"
use Mojolicious::Lite;
Expand Down Expand Up @@ -426,3 +426,20 @@ $t->get_ok('/rest.html' => {Accept => $chrome})->status_is(200)
$t->get_ok('/rest?format=html' => {Accept => $chrome})->status_is(200)
->content_type_is('text/html;charset=UTF-8')
->text_is('html > body', 'works');

# GET /rest (not Ajax)
my $ajax = 'text/html,text/xml;q=1.1';
$t->get_ok('/rest' => {Accept => $ajax})->status_is(200)
->content_type_is('text/html;charset=UTF-8')
->content_is('<html><body>works');

# GET /rest (Ajax)
$t->get_ok(
'/rest' => {Accept => $ajax, 'X-Requested-With' => 'XMLHttpRequest'})
->status_is(200)->content_type_is('text/xml')
->content_is('<just>works</just>');

# GET /rest (Ajax with html format and query)
$t->get_ok('/rest.html?format=html' =>
{Accept => $ajax, 'X-Requested-With' => 'XMLHttpRequest'})->status_is(200)
->content_type_is('text/xml')->content_is('<just>works</just>');
13 changes: 12 additions & 1 deletion t/mojolicious/types.t
@@ -1,6 +1,6 @@
use Mojo::Base -strict;

use Test::More tests => 28;
use Test::More tests => 32;

# "Your mistletoe is no match for my *tow* missile."
use Mojolicious::Types;
Expand Down Expand Up @@ -40,3 +40,14 @@ is_deeply $t->detect('text/html,*/*'), [], 'no formats';
is_deeply $t->detect('text/html;q=0.9,*/*'), [], 'no formats';
is_deeply $t->detect('text/html,*/*;q=0.9'), [], 'no formats';
is_deeply $t->detect('text/html;q=0.8,*/*;q=0.9'), [], 'no formats';

# Multiple MIME types
is_deeply $t->detect('text/html;q=0.9,text/plain', 1), ['txt', 'htm', 'html'],
'right formats';
is_deeply $t->detect('application/json, text/javascript, */*; q=0.01', 1),
['json'], 'right formats';
is_deeply $t->detect('application/json , image/svg+xml ; q=10, image/png', 1),
['svg', 'json', 'png'], 'right formats';
is_deeply $t->detect('text/html;quality=0.9,text/plain', 1),
['htm', 'html', 'txt'],
'right formats';

0 comments on commit b5697ec

Please sign in to comment.