Universeller Parser für beliebigen HTTP Content-Type im Request

Enctype application/x-www-form-urlencoded und multipart/form-data sind die Einzigen strukturierten Content-Types

Herkömmliche Parser unterstützen lediglich den Content-Type application/x-www-form-urlencoded oder multipart/form-data und unterliegen einer Logik, welche anhand der Request-Methode (POST,GET) über die Herkunft der Daten am Common Gateway entscheidet. So werden die Daten entweder aus STDIN oder aus dem QUERY_STRING gelesen. Es gibt jedoch Anforderungen wie Webservices, da werden die Daten anders verpackt, zum Beispiel als application/json oder application/soap+xml und ggf. Weitere

Diese Anforderungen begründen eine Eigenentwicklung, welche sich am vom Client gesendeten Content-Type orientiet und nicht an der HTTP-Request-Methode, ja von Letzterer gänzlich unabhängig ist.

Der für mein Framework entwickelte Parser unterstützt also beliebige Content-Types und ist in dieser Hinsicht leicht erweiterbar. Wesentlich ist, dass unabhängig vom gesendeten Content-Type nach dem Parsen stets diegleichen Datenstrukturen erzeugt werden. Mit:

ergeben sich also, außer Content-Type: application/octet-stream Schlüssel-Werte Paare, so wie das bisherige Parser auch handhaben. Damit ist ebenfalls die Abwärtskompatibilität meines universellen Parsers gesichert, Content-Type: application/x-www-form-urlencoded ist Default.

Mögliche Erweiterungen auf beliebige Content-Types hängen nur noch davon ab, ob serverseitig die entsprechenden Libraries verfügbar sind. Dem Parser folgender Programmcode bzw. Programmlogik ist dann unabhängig vom Content-Type im Request. Das setzt natürlich voraus, dass der Client im Request-Header-Content-Type einen solchen sendet, damit er serverseitig entsprechend geparst werden kann.

Der Vorteil dieser Abstraktion

Nur noch der Client bestimmt den Enctype für die zu übertragenden Daten. Beispielsweise werden bei einem Legacy Submit die Daten eines Formulars per Enctype= "application/x-www-form-urlencoded" übertragen, per Ajax hingegen wird dasselbe Formular als Enctype= "application/json" gesendet. Am serverseitigen Code ändert sich dadurch nichts, die Framework-Instanz greift wie immer mit $self->param('name') in die Parameter des Request.

Unabhängige Layer einbinden, Schichtenmodell

Zu Beachten: Der Client sendet dem passenden Content-Type im HTTP-Request Header. Somit wird der passende Layer automatisch geladen, siehe sub _parse_rawdata{}

# File Object Utility package xCGI::File; use strict; use warnings; use overload '""' => sub{ my $self = shift; $self->{iohandle}->seek(0,0); read( $self->{iohandle}, my $bin, $self->{content_length}); return $bin; }; sub new{ my $class = shift; my $ref = shift; bless $ref, $class; } # getter sub AUTOLOAD{ my $self = shift; return do{ our $AUTOLOAD =~ /(\w+)$/; $self->{$1} || ''; }; } sub DESTROY{} ########################################################################### package xCGI; # ersetzt das bisherige Perl-modul CGI.pm ################### use strict; use warnings; use bytes; require '/var/www/vhosts/rolfrost.de/files/fwlib/factory/dd.pm'; # Diagnostics binmode STDIN; sub new{ my $class = shift; my $self = bless{ CONTENT_LENGTH => $ENV{CONTENT_LENGTH} ? $ENV{CONTENT_LENGTH} : 0, CONTENT_TYPE => $ENV{CONTENT_TYPE} ? $ENV{CONTENT_TYPE} : 'application/x-www-form-urlencoded', QUERY_STRING => $ENV{QUERY_STRING} ? $ENV{QUERY_STRING} : '', STDIN => *STDIN, rawdata => '', eav => {}, param => {}, json => {} }, $class; $self->{CONTENT_TYPE_ORIGIN} = $self->{CONTENT_TYPE}; # XHR hängt charset=UTF-8 an Content-Type $self->{CONTENT_TYPE} = [split(";" ,$self->{CONTENT_TYPE})]->[0]; return $self; } # Public, lese Rohdaten aus dem # Common Gateway STDIN sub rawdata{ my $self = shift; return unless $ENV{CONTENT_LENGTH}; read(STDIN, my $rawdata, $ENV{CONTENT_LENGTH}); return $rawdata; } # Public, Haupt Methode zur Herausgabe # HTTP-Request-Parameter sub param{ my $self = shift; my $pname = shift; return '' if $self->{CONTENT_TYPE} eq 'application/octet-stream'; if(! keys %{$self->{param}} ){ $self->_parse_rawdata } # return options if( $pname ){ if( ref $self->{param}{$pname} eq 'ARRAY' && scalar @{$self->{param}{$pname}} == 1){ return $self->{param}{$pname}[0]; } elsif(ref $self->{param}{$pname} eq 'ARRAY' && scalar @{$self->{param}{$pname}} > 1){ return @{$self->{param}{$pname}}; } elsif(ref $self->{param}{$pname} eq 'ARRAY' && scalar @{$self->{param}{$pname}} == 0){ return '' } else{ return $self->{param}{$pname} ? $self->{param}{$pname} : (); } } else{ return keys %{$self->{param}} ? keys %{$self->{param}} : $ENV{QUERY_STRING} ? 1 : ''; } } # hier werden die jeweiligen Layer geladen sub _parse_rawdata{ my $self = shift; # Verschiedene Content-Types im Request # erweiterbare Kontrollstruktur if( $self->{CONTENT_TYPE} eq 'multipart/form-data' ){ require ParseMultipart; $self->{param} = ParseMultipart->parse_multipart( *STDIN ); } elsif( $self->{CONTENT_TYPE} eq 'application/json' ){ require JSON; $self->{json} = JSON->new->decode($self->rawdata); $self->{param} = $self->{json}{param}; } elsif( $self->{CONTENT_TYPE} eq 'application/body+query' ){ # QUERY_STRING mit Parametern + Message Body mit Binary $self->{param} = $self->qparse($self->{QUERY_STRING}); $self->{STDIN} = *STDIN; } else{ # Default Enctype # Parameter: Name => [Value], application/x-www-form-urlencoded $self->{param} = $ENV{CONTENT_LENGTH} ? do{ read(STDIN, my $buffer, $ENV{CONTENT_LENGTH}); $self->qparse($buffer); } : $self->qparse($ENV{QUERY_STRING}); # proprietäre Erweiterung für diesen Enctype # Feldnamen sind strukturiert so dass ein EAV # auf den namen abgebildet ist # z.B. person.firstname=, person.lastname= return unless $self->{tryeav}; foreach my $parametername( keys %{$self->{param}} ){ $self->_stacker([split /\./, $parametername], $self->{param}{$parametername}); } } } # Referenzen aufstocken sub _stacker{ my $self = shift; my $aref = shift; my $val = shift; my $hash = $self->{eav}; my $last = pop @$aref; foreach my $el(@$aref){ $hash = $hash->{$el} ||= {}; } $hash->{$last} = $val; } # Versuche eine EAV Struktur in den Parameternamen zu erkennen sub tryeav{ my $self = shift; $self->{tryeav} = 1; } # Public und unabhängig verwendbar # application/x-www-form-urlencoded sub qparse{ my $self = shift; my $rawdata = shift || ''; my $setparam = shift || 0; my %param = (); # Punkte in Parameternamen erlauben my @pie = split /[;&]/, $rawdata; foreach my $p(@pie){ my ($pname, $val) = split(/=/, $p, 2); next unless $pname; next unless defined $val; $val =~ s/\+/ /g; $val =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg; push @{$param{$pname}}, $val; } # overload attribute param if($setparam){ %{$self->{param}} = (%{$self->{param}}, %param); } return \%param; } # Getter via AUTOLOAD # Methodname => Attribute sub AUTOLOAD{ my $self = shift; my $name = do{ our $AUTOLOAD =~ /(\w+)$/; $1; }; return $self->{$name}; } sub DESTROY{} 1;######################################################################### __END__ application/x-www-form-urlencoded # handelsübliche Parameter, Default multipart/form-data # FileUpload multipart/c-eav # cEAV.js und cEAV.pm multipart/eav # Binär EAV, EAV.js und EAVHandle.pm application/octet-stream # Bytesequenzen application/json application/xml Wenn multipart/eav oder multipart/c-eav gibt es die entity 'param' und damit Parameter als Hash (Schlüssel => Wert) $query_string =~ s/%([\dA-Fa-f][\dA-Fa-f])/pack("C", hex($1))/eg;

Das Schichtenmodell zeigt die Unabhängigkeit und die universelle Verwendbarkeit. Die Source ist beliebig erweiterbar.


Datenschutzerklärung: Diese Seite dient rein privaten Zwecken. Auf den für diese Domäne installierten Seiten werden grundsätzlich keine personenbezogenen Daten erhoben. Das Loggen der Zugriffe mit Ihrer Remote Adresse erfolgt beim Provider soweit das technisch erforderlich ist. s​os­@rolf­rost.de.