ziguzagu.org

Writing Plack::Middleware modifying the response body

response body をいじる Plack::Middleware を作る場合、call で渡すコールバック関数の戻り値はサブルーチンにするのがよい(というかそうすべき?)。

sub call {
    my ($self, $env) = @_;
    my $res = $self->app->($env);
    $self->response_cb($res, sub {
        my $res = shift;
        ## こゆこと
        return sub {
            my $chunk = shift;
            $chunk .= 'test';
            return $chunk;
        };
    }
}

ただ $res をそのまんま書き換えてもちゃんと動いている風。

sub call {
    my ($self, $env) = @_;
    my $res = $self->app->($env);
    $self->response_cb($res, sub {
        my $res = shift;
        ## なまなましい感じで
        push @{ $res->[2] }, 'test';
    }
}

でも、後者をやってしまうと、Content-Length ヘッダがすでに設定されてしまっている場合、response body が変更されているにもかかわらず値が変更されない、という問題があったりする。

response_cb メソッドの実態は(いまのところ)Plack::Util::response_cb で、その中では、戻り値がサブルーチンだった場合にのみ設定済みの Content-Length を削除してくれるようになっている(ので、ContentLength Middleware も enable しておく必要がある)。以下、Plack::Util::response_cb (v0.9946) より抜粋。

my $body_filter = sub {
    my($cb, $res) = @_;
    my $filter_cb = $cb->($res);
    # If response_cb returns a callback, treat it as a $body filter
    if (defined $filter_cb && ref $filter_cb eq 'CODE') {
        Plack::Util::header_remove($res->[1], 'Content-Length');
        if (defined $res->[2]) {
            if (ref $res->[2] eq 'ARRAY') {
                for my $line (@{$res->[2]}) {
                    $line = $filter_cb->($line);
                }
                # Send EOF.
                my $eof = $filter_cb->( undef );
                push @{ $res->[2] }, $eof if defined $eof;
            } else {
                my $body    = $res->[2];
                my $getline = sub { $body->getline };
                $res->[2] = Plack::Util::inline_object
                    getline => sub { $filter_cb->($getline->()) },
                    close => sub { $body->close };
            }
        } else {
            return $filter_cb;
        }
    }
};

なんでサブルーチン返したときのみこうするようにしてるのかはよくわかっていないんだけど、、、そうなってるのでそうしましょう。。。(くぅ…、弱い)

そして、P::U::response_cb の doc を書くという issue が github にあることに今気づいた