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');
<?xml version="1.0"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>JSON::ForBrowsers</title>
<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=utf-8" />
<style type="text/css">
html, body {
padding: 0px;
margin: 0px;
}
pre {
background-color: #FAFAFA;
border: 1px solid #E9E9E9;
padding: 4px;
margin: 12px;
}
</style>
</head>
<body>
<pre><code>
EOHTML
(my $html_foot = <<'EOHTML') =~ s/^\s+//x;
</code></pre>
</body>
</html>
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<Plack::Middleware::Debug|Plack::Middleware::Debug>:
use Plack::Builder;
builder {
enable 'Debug';
enable 'JSON::ForBrowsers';
$app;
};
=head1 DESCRIPTION
Plack::Middleware::JSON::ForBrowsers turns C<application/json> responses
into HTML that can be displayed in the web browser. This is primarily intended
as a development tool, especially for use with
L<Plack::Middleware::Debug|Plack::Middleware::Debug>.
The middleware checks the request for the C<X-Requested-With> header - if it
does not exist or its value is not C<XMLHttpRequest> and the C<Accept> header
indicates that HTML is acceptable, it will wrap the JSON from an C<application/json>
response with HTML and adapt the content type accordingly.
This behaviour should not break clients which expect JSON, as they still I<do>
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<call> 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<Accept>
and C<X-Requested-With> headers for this decision.
=head3 Parameters
This method expects positional parameters.
=over
=item env
The L<PSGI|PSGI> 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<Plack::Middleware|Plack::Middleware>
=item *
L<Plack::Middleware::Debug|Plack::Middleware::Debug>
=back
=head1 AUTHOR
Manfred Stock <mstock@cpan.org>
=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