Search code examples
perlobject-persistence

Can't figure out why Sereal encoder/decoder round-trip is not returning proper object


With all the hating on Storable -- I decided to check out Sereal for serialization needs. Plus I was having some issues with 32bit/64bit cross platform issues with Storable, so I figured this would be a good time.

After having some issues, I boiled the problem down to the following code. (i'm persisting an HTTP::Request object, hence the example code).

This is my encode test, i'm storing to a file:

use Sereal::Encoder;
use HTTP::Request;
use HTTP::Headers;
use URI;

my $encoder = Sereal::Encoder->new();

open(my $fh, ">", 'myfile.data') or die $!;
binmode($fh);
my $uri = URI->new('http://www.example.com');
my $headers = HTTP::Headers->new(
    Content_Type => 'application/json',
);
my $http_request = HTTP::Request->new(POST => $uri, $headers, 'bleh');
print $fh $encoder->encode( $http_request );
close($fh);

And on the same machine(same perl etc. on 5.18), I run the following:

use Sereal::Decoder;
use File::Slurp qw(read_file);
use URI;

my $data = read_file('myfile.data') or die $!;
my $dec = Sereal::Decoder->new();
my $decoded = $dec->decode($data);
print $decoded->{_uri}->scheme,"\n";

And the output of running the encoding program, and then the decoding program is:

Can't locate object method "scheme" via package "URI::http" at testd.pl line 8.

Anyhow, was really nagging me as to what the problem was. I ended up reverting back to Storable and using nfreeze to solve my arch issues with Storable but was wondering why my attempt to transition to Sereal crashed and burned.

Thanks!


Solution

  • Sereal, unlike Storable, won't automatically load a module when it encounters a serialized object. This is a security issue with Storable, so Sereal is working as intended.[1]

    At the point where scheme is called in the second test program, URI::http hasn't been loaded yet, so the method call results in an error. It seems that URI will load its subclasses when its constructor is used on a string that "looks like" one of them, e.g.

    URI->new('http://www.stackoverflow.com');
    

    loads the URI::http module. So one solution would be to add a dummy invocation of that constructor to ensure URI::http is loaded, or manually use URI::http instead. Either option causes the print $decoded->{_uri}->scheme line of the second script to work as expected, but I think the second is the lesser of two evils (importing an undocumented submodule from URI versus an arbitrary method call done specifically for its not-immediately-obvious side effect).