No description
This repository has been archived on 2026-08-22. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
  • Perl 83.8%
  • Raku 16.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2014-08-12 18:15:32 +02:00
lib/Mojolicious/Plugin Started on Mojolicious::Plugin::SOAP 2014-08-12 18:15:32 +02:00
t Initialized 2014-08-12 18:02:19 +02:00
.gitignore Initialized 2014-08-12 18:02:19 +02:00
.ship.conf Initialized 2014-08-12 18:02:19 +02:00
Changes Initialized 2014-08-12 18:02:19 +02:00
cpanfile Initialized 2014-08-12 18:02:19 +02:00
MANIFEST.SKIP Initialized 2014-08-12 18:02:19 +02:00
README.pod Initialized 2014-08-12 18:02:19 +02:00

package Mojolicious::Plugin::SOAP;

=head1 NAME

Mojolicious::Plugin::SOAP - Parse SOAP messages and dispatch to a route

=head1 DESCRIPTION

This plugin register a L<around_dispatch|Mojolicious/around_dispatch> hook,
which parse the body of the incoming message on POST and tries to find a
SOAP method, if the data looks like XML.

The method is found by looking at the first tag name inside the SOAP "Body"
tag - with prefixes stripped away.

=head1 SYNOPSIS

  use Mojolicious::Lite;
  plugin "SOAP";

  post "/soap/someMethodInsideBody" => sub {
    my $c = shift;
    $c->render("someMethodInsideBodyResponse");
  };

  app->start;

See L</register> for C<%config> options.

=cut

use Mojo::Base 'Mojolicious::Plugin';
use Mojo::DOM;
use constant DEBUG => $ENV{WAPS_SOAP_DEBUG} ? 1 : 0;

=head1 ATTRIBUTES

=head2 content_type

  $str = $self->content_type;

=cut

has content_type => 'text/xml; charset=ISO-8859-1';

=head1 METHODS

=head2 register

  $self->register($app, \%config);

C<%config> can contain:

=over 4

=item * allow_empty => 1

This enabling a custom route called "/soap/empty" to be fired on a POST
without a body.

=item * base => $str,

Used to change the "/soap" part to something else.

=back

=cut

sub register {
  my ($self, $app, $config) = @_;
  my $allow_empty = $config->{allow_empty} || 0;
  my $base = $config->{base} || '/soap';
  my $e;

  $app->routes->add_condition(chunked => sub {
    my ($route, $c, $captures) = @_;
    $c->stash('transfer.encoding.chunked' => 1);
    $c->res->code(200);
    $c->render_later;
  });

  $app->hook(around_dispatch => sub {
    my ($next, $c) = @_;
    my $req = $c->tx->req;
    my($xml, $action);

    return $next->() unless $req->method eq 'POST';

    if ($req->body =~ /^\s*$/s) {
      if ($allow_empty) {
        $self->_got_soap_message($c, {}, $base, 'empty');
        $c->app->log->warn("Server <<< Client ()") if DEBUG;
      }
      $next->();
      $self->_render_response($c);
      return;
    }
    elsif($req->body !~ /^\s*</s) { # not xml
        return $next->();
    }

    $c->app->log->warn("Server <<< Client (@{[$req->body]})") if DEBUG;
    $xml = XML::Bare->new(text => $req->body)->parse or return $next->();
    $self->_remove_prefix($xml);
    $self->_got_soap_message($c, $xml, $base, grep { not /^(?:_|value|encodingStyle)/ } keys %{ $xml->{Envelope}{Body} });
    $next->();
    $self->_render_response($c);
});
}

sub _remove_prefix {
    my($self, $tree) = @_;

    if(ref $tree eq 'ARRAY') {
        $self->_remove_prefix($_) for @$tree;
    }
    elsif(ref $tree eq 'HASH') {
        for my $key (keys %$tree) {
            if($key =~ /^xmlns/) {
                next;
            }
            elsif($key =~ /^[\w-]+:(\w+)$/) {
                $tree->{$1} = delete $tree->{$key};
                $self->_remove_prefix($tree->{$1});
            }
        }
    }
}

sub _render_response {
    my($self, $c) = @_;
    my $stash = $c->stash;
    my($output, $format);

    if(!$stash->{'transfer.encoding.chunked'}) {
        $c->render unless $c->stash('mojo.rendered');
        $c->app->log->warn("Client <<< Server (@{[$c->res->body]})") if DEBUG and $c->stash('soap_action') and $c->stash('mojo.rendered');
        return;
    }
    if($c->res->code ne '200') {
        return;
    }

    local $stash->{template}
        = $stash->{template}
       || join '/', grep { $_ } @$stash{qw/ controller action /};

    if(!$stash->{template}) {
        $c->logf(error => 'SOAP: Internal error. No template.');
        $c->render(text => 'SOAP: Internal error. No template.', status => 500);
        return;
    }

    ($output, $format) = $c->app->renderer->render($c, $stash);

    $c->app->log->warn("Client <<< Server ($output)") if DEBUG;
    $c->write_chunk($output, sub {
        my $c = shift;
        $c->write_chunk('');
        $c->finish;
    });
}

sub _got_soap_message {
  my ($self, $c, $base, $action) = @_;
  my $headers = $c->res->headers;

  $headers->connection('keep-alive');
  $headers->content_type($self->content_type) if $self->content_type;
  $headers->header('SOAPAction' => '');
  $c->stash(format => 'xml', 'soap.message' => $soap);
  $c->req->url->path("$base/$action");
}

=head1 AUTHOR

Jan Henning Thorsen - C<jhthorsen@cpan.org>

=cut

1;