package Plack::Middleware::JSON::ForBrowsers; { $Plack::Middleware::JSON::ForBrowsers::VERSION = '0.001000'; } use parent qw(Plack::Middleware); # ABSTRACT: Plack middleware which turns application/json responses into HTML use strict; use warnings; use Carp; use JSON; use MRO::Compat; use Plack::Util::Accessor qw(json); use List::MoreUtils qw(any); use Encode; use HTML::Entities qw(encode_entities_numeric); chomp(my $html_head = <<'EOHTML'); JSON::ForBrowsers

EOHTML

(my $html_foot = <<'EOHTML') =~ s/^\s+//x;
		
EOHTML my @json_types = qw(application/json); my @html_types = qw(text/html application/xhtml+xml); sub new { my ($class, $arg_ref) = @_; my $self = $class->next::method($arg_ref); $self->json(JSON->new()->utf8()->pretty()); return $self; } sub call { my($self, $env) = @_; my $res = $self->app->($env); unless ($self->looks_like_browser_request($env)) { return $res; } return $self->response_cb($res, sub { my ($cb_res) = @_; my $h = Plack::Util::headers($cb_res->[1]); # Ignore stuff like '; charset=utf-8' for now, just assume UTF-8 input if (any { index($h->get('Content-Type'), $_) >= 0 } @json_types) { $h->set('Content-Type' => 'text/html; charset=utf-8'); my $json = ''; my $seen_last = 0; return sub { if (defined $_[0]) { $json .= $_[0]; return ''; } else { if ($seen_last) { return; } else { $seen_last = 1; return $self->json_to_html($json); } } }; } return; }); } sub looks_like_browser_request { my ($self, $env) = @_; if (defined $env->{HTTP_X_REQUESTED_WITH} && $env->{HTTP_X_REQUESTED_WITH} eq 'XMLHttpRequest') { return 0; } if (defined $env->{HTTP_ACCEPT} && any { index($env->{HTTP_ACCEPT}, $_) >= 0 } @html_types) { return 1; } return 0; } sub json_to_html { my ($self, $json) = @_; my $pretty_json_string = decode( 'UTF-8', $self->json()->encode( $self->json()->decode($json) ) ); return encode( 'UTF-8', $html_head.encode_entities_numeric($pretty_json_string).$html_foot ); } 1; __END__ =pod =head1 NAME Plack::Middleware::JSON::ForBrowsers - Plack middleware which turns application/json responses into HTML =head1 VERSION version 0.001000 =head1 SYNOPSIS Basic Usage: use Plack::Builder; builder { enable 'JSON::ForBrowsers'; $app; }; Combined with L: use Plack::Builder; builder { enable 'Debug'; enable 'JSON::ForBrowsers'; $app; }; =head1 DESCRIPTION Plack::Middleware::JSON::ForBrowsers turns C responses into HTML that can be displayed in the web browser. This is primarily intended as a development tool, especially for use with L. The middleware checks the request for the C header - if it does not exist or its value is not C and the C header indicates that HTML is acceptable, it will wrap the JSON from an C response with HTML and adapt the content type accordingly. This behaviour should not break clients which expect JSON, as they still I get JSON. But when the same URI is requested with a web browser, HTML-wrapped and pretty-printed JSON will be returned, which can be displayed without external programs or special extensions. =head1 METHODS =head2 new Constructor, creates a new instance of the middleware. =head2 call Specialized C method. Expects the response body to contain a UTF-8 encoded byte string. =head2 looks_like_browser_request Tries to decide if a request is coming from a web browser. Uses the C and C headers for this decision. =head3 Parameters This method expects positional parameters. =over =item env The L environment. =back =head3 Result C<1> if it looks like the request came from a browser, C<0> otherwise. =head2 json_to_html Takes a UTF-8 encoded JSON byte string as input and turns it into a UTF-8 encoded HTML byte string, with HTML entity encoded characters to avoid XSS. =head3 Parameters This method expects positional parameters. =over =item json The JSON byte string. =back =head3 Result The JSON wrapped in HTML. =head1 SEE ALSO =over =item * L =item * L =back =head1 AUTHOR Manfred Stock =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2012 by Manfred Stock. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut