Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Snooping on http headers between different plack middlewares

Tags:

perl

plack

If I understand right, the PSGI application works as next:

  • got the request from a browser
  • the request is "bubbles" thru some middlewares in the order as them is defined in the builder
  • the request comes to my app
  • my app produces some respond
  • this respond again bubbles thru some middlewares
  • finally the respon is send to the browser

I can easily debug-print all headers (e.g. cookies) when the request landed in my $app.

The question is: How to debug-print the actual state of headers while the request coming thru many middlewares to my app and while the respond is going-out again thru middlewares.

So, Having an (simplyfied) app.psgi, like the next:

use strict;
use warnings;
use Plack::Builder;

my $app = sub { ... };

builder {
         # <- debug-print the first request headers
         #                and the last respond headers here
    enable "Debug";
         # <- debug-print the actual state of request/respond headers here
    enable "mid2";
         # <- and here
    enable "mid3";
         # <- and here

    $app; # <- and finally here - this is of course EASY
}

It is probably not as easy as something like,

print STDERR Dumper $dont_know_what->request->headers(); #HTTP::Headers ???
print STDERR Dumper $dont_know_what->respond->headers();

so adding a bounty :) ;)

like image 889
cajwine Avatar asked Mar 28 '14 10:03

cajwine


2 Answers

One basic approach is to create a middleware that dumps the headers before executing the wrapped application and then right afterward. Then you enable this middleware at each point where you want to see the headers as you have pointed out in your pseudocode.

The following code does this by building an in-line middleware each time you enable it.

use Plack::Builder;
use Plack::Request;
use Plack::Response;

sub headers_around {
  my $position = shift;

  # build and return the headers_around middleware as a closure
  return sub {
    my $app = shift;
    # gets called each request
    return sub {
      my $env = shift;

      my $req = Plack::Request->new($env);
      # display headers before next middleware
      print STDERR "req headers before $position:\n" . $req->headers->as_string . "\n=====\n";

      # execute the next app on the stack
      my $res = $app->($env);

      my $response = Plack::Response->new(@$res);

      # display headers after previous middleware
      print STDERR "res headers after $position:\n" . $response->headers->as_string . "\n=====\n";
      return $res;
    };
  };
};

builder {

  enable headers_around('Debug');
  enable 'Debug';

  enable headers_around('Lint');
  enable 'Lint';

  enable headers_around('StackTrace');
  enable 'StackTrace', force => 1;

  enable headers_around('App');
  mount '/' => builder { sub {
    return [ 200, [ 'Content-Type' => 'text/plain' ], [ 'Hello World' ] ];
  }}
};

# now build the application enabling regular middleware with our inline middleware
builder {

  enable headers_around('Debug');
  enable 'Debug';

  enable headers_around('Lint');
  enable 'Lint';

  enable headers_around('StackTrace');
  enable 'StackTrace', force => 1;

  enable headers_around('App');
  mount '/' => builder { sub {
        return [ 200, [ 'Content-Type' => 'text/plain' ], [ 'Hello World' ] ];
  }}
};

When I run it with plackup I get the following output:

$ plackup --app between_middleware.psgi
HTTP::Server::PSGI: Accepting connections at http://0:5000/
req headers before Debug:
Connection: Keep-Alive
Accept: */*
Host: 0:5000
User-Agent: Wget/1.12 (linux-gnu)

=====
req headers before Lint:
Connection: Keep-Alive
Accept: */*
Host: 0:5000
User-Agent: Wget/1.12 (linux-gnu)

=====
req headers before StackTrace:
Connection: Keep-Alive
Accept: */*
Host: 0:5000
User-Agent: Wget/1.12 (linux-gnu)

=====
req headers before App:
Connection: Keep-Alive
Accept: */*
Host: 0:5000
User-Agent: Wget/1.12 (linux-gnu)

=====
res headers after App:
Content-Type: text/plain

=====
res headers after StackTrace:
Content-Type: text/plain

=====
res headers after Lint:
Content-Type: text/plain

=====
res headers after Debug:
Content-Type: text/plain

=====
127.0.0.1 - - [02/Apr/2014:19:37:30 -0700] "GET / HTTP/1.0" 200 11 "-" "Wget/1.12 (linux-gnu)"

Obviously you could turn this into an actual middleware like Ashley's and you may have to tweak it to send log messages using whatever facility you have in place.

like image 140
benrifkah Avatar answered Nov 15 '22 05:11

benrifkah


Middleware

package ShowMeTheHeaders;
use parent "Plack::Middleware";
use Plack::Request;
use Plack::Response
require Text::Wrap;

my $_call_back = sub {
    my $response = Plack::Response->new(@{+shift});
    print "* Response Headers:\n",
        Text::Wrap::wrap("\t", "\t", $response->headers->as_string);
    return; # Explicit return suggested by docs.
};

sub call {
    my $self = shift;
    my $request = Plack::Request->new(shift);
    print "* Request Headers:\n",
        Text::Wrap::wrap("\t", "\t", $request->headers->as_string);
    my $response = $self->app->($request);
    Plack::Util::response_cb($response, $_call_back);
}

1;

You can do this without the objectification (Plack::Request and Plack::Response) but then you have to deal with raw attributes and keys for the header fields instead of the entirely more pleasant ->as_string. See also the “response callback” section of Plack::Middleware.

demo psgi

use warnings;
use strict;
use Plack::Builder;

my $app = sub {
    [ 200,
      [ "Content-Type" => "text/plain" ],
      [ "O HAI, PLAK!" ]
    ];
};

builder {
    enable "+ShowMeTheHeaders";
    mount "/" => $app;
};
like image 41
Ashley Avatar answered Nov 15 '22 03:11

Ashley