Plack-Debugger
view release on metacpan or search on metacpan
lib/Plack/Middleware/Debugger/Injector.pm view on Meta::CPAN
# within the 2xx - Success range we have the 202 Accepted, which is basically HTTP for "meh", so we should ignore it
($resp->[0] == 202)
||
# within the 2xx - Success range we have the 204 No Content, which we should ignore
($resp->[0] == 204)
||
# within the 3xx - Redirection range we are unlikely to have a body, so we can ignore it
($resp->[0] < 400 && $resp->[0] >= 300)
||
# pretty much ignore anything within the 4xx & 5xx Error ranges ...
($resp->[0] >= 400)
}
sub has_parent_request_uid {
my ($self, $env, $resp) = @_;
# NOTE:
# if this is a request with a parent request id
# then it is a sub-request and therefore not
# something we probably need to inject into,
# however there could be cases where this is
# reasonable, so might make this optional later
# if the need arises.
# - SL
exists $env->{'HTTP_X_PLACK_DEBUGGER_PARENT_REQUEST_UID'}
||
exists $env->{'plack.debugger.parent_request_uid'}
}
# handlers to override
sub handle_no_content_type {
my ($self, $env, $resp) = @_;
# XXX - ... is this reasonable ???
die "No content type specified in the request, I cannot tell what to do!";
}
sub pass_through_content_type {
my ($self, $env, $resp) = @_;
# many responses really can't
# get injected into so we
# just ignore and pass them
# through
return $resp;
}
sub handle_json_content_type {
my ($self, $env, $resp) = @_;
# application/json responses really
# can't get injected into so we
# just ignore it for now, but we
# specifically handle this one
# just in case ...
return $resp;
}
sub handle_html_content_type {
my ($self, $env, $resp) = @_;
# content to be inserted ...
my $content = $self->get_content_to_insert( $env );
# if the response is not a streaming one ...
if ( (scalar @$resp) == 3 && ref $resp->[2] eq 'ARRAY' ) {
# adjust Content-Length if we have it ...
if ( my $content_length = Plack::Util::header_get( $resp->[1], 'Content-Length' ) ) {
Plack::Util::header_set( $resp->[1], 'Content-Length', $content_length + length($content) );
}
# now inject our content before the closing
# body tag, makes the most sense to process
# the body in reverse since it will most
# likely be at the end ...
foreach my $chunk ( reverse @{ $resp->[2] } ) {
# skip if we don't have it
next unless $chunk =~ m!(?=</body>)!i;
# if we do have it, substitute and ...
$chunk =~ s!(?=</body>)!$content!i;
# break out of the loop, we are done
last;
}
return $resp;
}
# if we have streaming response, just do what is sensible
else {
# NOTE:
# Plack will remove the Content-Length header
# if it has a streaming response, so there is
# no need to worry about that at all.
# - SL
return sub {
my $chunk = shift;
return unless defined $chunk;
$chunk =~ s!(?=</body>)!$content!i;
return $chunk;
};
}
}
sub handle_unknown_content_type {
my ($self, $env, $resp) = @_;
die "I have no idea what to do with this body type: " . Plack::Util::header_get( $resp->[1], 'Content-Type' );
}
# ...
sub call {
my ($self, $env) = @_;
$self->response_cb(
$self->app->( $env ),
sub {
my $resp = shift;
# check some basic predicates
return $resp if $self->should_ignore_status( $env, $resp );
return $resp if $self->has_parent_request_uid( $env, $resp );
# now check the content-type headers ...
my $content_type = Plack::Util::header_get( $resp->[1], 'Content-Type' );
if ( !$content_type ) {
return $self->handle_no_content_type( $env, $resp );
}
# be more specific
elsif ( $content_type =~ m!^(?:text/html|application/xhtml\+xml)! ) {
return $self->handle_html_content_type( $env, $resp );
}
elsif ( $content_type =~ m!^(?:application/json)! ) {
return $self->handle_json_content_type( $env, $resp );
}
# now be less specific
elsif ( $content_type =~ m!^(?:application/)! ) {
return $self->pass_through_content_type( $env, $resp );
}
elsif ( $content_type =~ m!^(?:text/)! ) {
return $self->pass_through_content_type( $env, $resp );
}
elsif ( $content_type =~ m!^(?:image/)! ) {
return $self->pass_through_content_type( $env, $resp );
}
# ... final wrapup
else {
return $self->handle_unknown_content_type( $env, $resp );
}
}
);
lib/Plack/Middleware/Debugger/Injector.pm view on Meta::CPAN
__END__
=pod
=head1 NAME
Plack::Middleware::Debugger::Injector - Middleware for injecting content into a web request
=head1 VERSION
version 0.03
=head1 DESCRIPTION
This middleware is used to inject some content into the body of a
given web request right before the closing C<<body>> tag. Its
primary use-case is to inject a C<<script>> tag which will then
create the L<Plack::Debugger> debugging UI.
Since this middleware will run on every request it has to decide
if injection is sensible or not. It does this by checking the
HTTP status code, some HTTP headers and finally the content-type
of the response.
=head2 Status codes
Most status codes not in the C<2xx> range are ignored since they
rarely contain a body that is of consequence to this module. The
only exception being C<204 - No Content>, which will not have a
body and so is ignored.
=head2 Headers
Currently we only handle one header, C<X-Plack-Debugger-Parent-Request-UID>,
which is a custom header that we add into AJAX requests that are
associated with a given request. If we see this header, we will
not bother injecting content since we can assume that it is meant
to be debugged via the parent page.
=head2 Content-Types
The following content-types are handled in the following order:
=over 4
=item No content-type specified
This currently throws an exception, perhaps this is not sensible, but
then again not specifying your content-type is not very sensible either.
=item C<text/html>, C<application/xhtml>, etc.
Given a content-type that looks like HTML, this will PSGI responses in
the most sensible way possible.
If we detect a PSGI response which is an array of strings, we will process
it (in reverse) looking for the closing C<<body>> tag and inject the
content accordingly.
All other PSGI responses will be handled as if they are streaming
responses, in which case we simple return a C<CODE> reference that will
process the stream and if a closing C<<body>> tag is found, inject
accordingly.
=item C<application/json>
While we have a specific handler for this content-type, we do not do
anything but just let it pass through. This is handled in this way
specifically in case someone decides it is sensible to inject some
kind of data into a JSON response.
It is left as an exercise to the reader to decide if this is sensible
or not.
=item C<application/*>, C<text/*>, C<image/*>
These three content types are fairly common, but there is no obvious way
to inject content into them. So instead of guessing, we just let them pass
through without modification. If there is any need to inject data into
these response types it is simply a matter of overriding the
C<pass_through_content_type> method in a subclass and then doing your own
content-type dispatching.
=item Unknown content-type
If none of these content-types match then we throw an exception and complain
that we are not use what to actually do. Again, as with the lack of a
content-type, this may not be sensible, if you disagree please give me a
use case.
=back
=head1 METHODS
=over 4
=item C<new (%args)>
This expects a C<content> key in C<%args> which is either a string or
a CODE reference that will accept a PSGI C<$env> as its only argument.
=item C<call ($env)>
This is just overriding the L<Plack::Middleware> C<call> method.
=item C<get_content_to_insert ($env)>
This will return the C<content> specified in C<new> and will just do
the appropriate thing depending on if the C<content> was a string or
a CODE reference.
=item HTTP Request predicates
The remaining methods deal with processing the HTTP request to
determine if we should inject the C<content> or not.
=over 4
=item C<should_ignore_status ($env, $resp)>
( run in 0.634 second using v1.01-cache-2.11-cpan-59e3e3084b8 )