stas 2003/06/06 02:21:57
Modified: src/docs/2.0/user/porting porting.pod
Log:
How Apache::MP3 was ported to mod_perl 2.0
Revision Changes Path
1.5 +793 -11 modperl-docs/src/docs/2.0/user/porting/porting.pod
Index: porting.pod
===================================================================
RCS file: /home/cvs/modperl-docs/src/docs/2.0/user/porting/porting.pod,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -r1.4 -r1.5
--- porting.pod 17 Apr 2003 02:36:08 -0000 1.4
+++ porting.pod 6 Jun 2003 09:21:57 -0000 1.5
@@ -107,7 +107,7 @@
your module, or if you distribute your module on CPAN and want to
maintain and release just one codebase.
-This is a relatively simple ehancement of option (2) above. The module
+This is a relatively simple enhancement of option (2) above. The module
tests to see which version of mod_perl is in use and then executes the
appropriate method call.
@@ -150,8 +150,8 @@
=head1 Porting a Perl Module to Run under mod_perl 2.0
-Note: API changes are listed in L<the backwards compatibility
-document|docs::2.0::user::porting::compat/>.
+Note: API changes are listed in L<the mod_perl 1.0 backward
+compatibility document|docs::2.0::user::porting::compat/>.
The following sections will guide you through the steps of porting
your modules to mod_perl 2.0.
@@ -168,8 +168,8 @@
methods that live in them can be used. So the first step is to figure
out which these modules are and C<use()> them.
-The L<MethodLookup module|docs::2.0::api::ModPerl::MethodLookup>
-provided with mod_perl 2.0 allows you to find out which module
+The C<L<ModPerl::MethodLookup|docs::2.0::api::ModPerl::MethodLookup>>
+module provided with mod_perl 2.0 allows you to find out which module
contains the functionality you are looking for. Simply provide it with
the name of the mod_perl 1.0 method that has moved to a new module,
and it will tell you what the module is.
@@ -270,7 +270,7 @@
=head2 Handling Missing and Modified mod_perl 1.0 Methods and Functions
-The mod_perl 2.0 API is modelled even more closely upon the Apache API
+The mod_perl 2.0 API is modeled even more closely upon the Apache API
than was mod_perl version 1.0. Just as the Apache 2.0 API is
substantially different from that of Apache 1.0, therefore, the
mod_perl 2.0 API is quite different from that of mod_perl
@@ -284,7 +284,7 @@
it's most likely because the method doesn't exist in the mod_perl 2.0
API. It's also possible that the method does still exist, but
nevertheless it doesn't work, since its usage has changed (e.g. its
-prototype has changed, or it requires ditfferent arguments, etc.).
+prototype has changed, or it requires different arguments, etc.).
In either of these cases, refer to L<the backwards compatibility
document|docs::2.0::user::porting::compat/> for an exhaustive list of
@@ -442,12 +442,794 @@
open my $fh, $file or die "Can't open $file: $!";
+
+
+
+
+
+
+
+
+=head2 How C<Apache::MP3> was ported to mod_perl 2.0
+
+C<Apache::MP3> is an elaborate application that uses a lot of mod_perl
+API. After porting it, I have realized that if you go through the
+notes or even better try to do it by yourself, referring to the notes
+only when in trouble, you will most likely be able to port any other
+mod_perl 1.0 module to run under mod_perl 2.0. So here the log of what
+I have done while doing the porting.
+
+Please notice that this tutorial should be considered as-is and I'm
+not claiming that I have got everything polished, so if you still find
+problems, that's absolutely OK. What's important is to try to learn
+from the process, so you can attack other modules on your own.
+
+I've started to work with C<Apache::MP3> version 3.03 which you can
+retrieve from Lincoln's CPAN directory:
+http://search.cpan.org/CPAN/authors/id/L/LD/LDS/Apache-MP3-3.03.tar.gz
+Even though by the time you'll read this there will be newer versions
+available it's important that you use the same version as a starting
+point, since if you don't, the notes below won't make much sense.
+
+=head3 Preparations
+
+First of all, I scratched most of mine I<httpd.conf> and I<startup.pl>
+leaving the bare minimum to get mod_perl started. This is needed to
+ensure that once I've completed the porting, the module will work
+correct on other users systems. For example if my I<httpd.conf> and
+I<startup.pl> were loading some other modules, which in turn may load
+modules that a to-be-ported module may rely on, the ported module may
+work for me, but once released, it may not work for others. It's the
+best to create a new I<httpd.conf> when doing the porting putting only
+the required bits of configuration into it.
+
+=head4 I<httpd.conf>
+
+Next, I configure the C<Apache::Reload> module, so we don't have to
+constantly restart the server after we modify C<Apache::MP3>. In order
+to do that add to I<httpd.conf>:
+
+ PerlModule Apache::Reload
+ PerlInitHandler Apache::Reload
+ PerlSetVar ReloadAll Off
+ PerlSetVar ReloadModules "ModPerl::* Apache::*"
+ PerlSetVar ReloadConstantRedefineWarnings Off
+
+You can refer to C<L<the Apache::Reload
+manpage|docs::2.0::api::Apache::Reload>> for more information if
+you aren't familiar with this module. The part:
+
+ PerlSetVar ReloadAll Off
+ PerlSetVar ReloadModules "ModPerl::* Apache::*"
+
+tells C<Apache::Reload> to monitor only modules in the C<ModPerl::>
+and C<Apache::> namespaces. So C<Apache::MP3> will be monitored. If
+your module is named C<Foo::Bar>, make sure to include the right
+pattern for the C<ReloadModules> directive. Alternatively simply have:
+
+ PerlSetVar ReloadAll On
+
+which will monitor all modules in C<%INC>, but will be a bit slower,
+as it'll have to C<stat(3)> many more modules on each request.
+
+Finally, C<Apache::MP3> uses constant subroutines. Because of that you
+will get lots of warnings every time the module is modified, which I
+wanted to avoid. I can safely shut those warnings off, since I'm not
+going to change those constants. Therefore I've used the setting
+
+ PerlSetVar ReloadConstantRedefineWarnings Off
+
+If you do change those constants, refer to the section on
+C<L<ReloadConstantRedefineWarnings
+|docs::2.0::api::Apache::Reload/Silencing__Constant_subroutine_____redefined_at__Warnings>>.
+
+Next I configured C<Apache::MP3>. In my case I've followed the
+C<Apache::MP3> documentation, created a directory I<mp3/> under the
+server document root and added the corresponding directives to
+I<httpd.conf>.
+
+Now my I<httpd.conf> looked like this:
+
+ #file:httpd.conf
+ #---------------
+ Listen 127.0.0.1:8002
+ #... standard Apache configuration bits omitted ...
+
+ LoadModule perl_module modules/mod_perl.so
+
+ PerlSwitches -wT
+
+ PerlModule Apache::Reload
+ PerlInitHandler Apache::Reload
+ PerlSetVar ReloadAll Off
+ PerlSetVar ReloadModules "ModPerl::* Apache::*"
+ PerlSetVar ReloadConstantRedefineWarnings Off
+
+ AddType audio/mpeg mp3 MP3
+ AddType audio/playlist m3u M3U
+ AddType audio/x-scpls pls PLS
+ AddType application/x-ogg ogg OGG
+ <Location /mp3>
+ SetHandler perl-script
+ PerlResponseHandler Apache::MP3
+ PerlSetVar PlaylistImage playlist.gif
+ PerlSetVar StreamBase http://localhost:8002
+ PerlSetVar BaseDir /mp3
+ </Location>
+
+
+=head4 I<startup.pl>
+
+Since chances are that no mod_perl 1.0 module will work out of box
+without at least preloading some modules, I've enabled the
+C<Apache::compat> module. Now my I<startup.pl> looked like this:
+
+ #file:startup.pl
+ #---------------
+ use Apache2 ();
+ use lib qw(/home/httpd/2.0/perl);
+ use Apache::compat;
+
+=head4 I<Apache/MP3.pm>
+
+Before I even started porting C<Apache::MP3>, I've added the warnings
+pragma to I<Apache/MP3.pm> (which wasn't there because mod_perl 1.0
+had to work with Perl versions prior to 5.6.0, which is when the
+C<warnings> pragma was added):
+
+ #file:apache_mp3_prep.diff
+ --- Apache/MP3.pm.orig 2003-06-03 18:44:21.000000000 +1000
+ +++ Apache/MP3.pm 2003-06-03 18:44:47.000000000 +1000
+ @@ -2,6 +2,9 @@
+ # $Id$
+
+ use strict;
+ +use warnings;
+ +no warnings 'redefine'; # XXX: remove when done with porting
+ +
+
+From now on, I'm going to use unified diffs which you can apply using
+C<patch(1)>. Though you may have to refer to its manpage on your
+platform since the usage flags may vary. On linux I'd apply the above
+patch as:
+
+ % cd ~/perl/blead-ithread/lib/site_perl/5.9.0/
+ % patch -p0 < apache_mp3_prep.diff
+
+assuming that I<Apache/MP3.pm> is located in the directory
+I<~/perl/blead-ithread/lib/site_perl/5.9.0/>.
+
+I've enabled the C<warnings> pragma even though I did have warnings
+turned globally in I<httpd.conf> with:
+
+ PerlSwitches -wT
+
+it's possible that some badly written module has done:
+
+ $^W = 0;
+
+without localizing the change, affecting other code. Also notice that
+the I<taint> mode was enabled from I<httpd.conf>, something that you
+shouldn't forget to do.
+
+I have also told the C<warnings> pragma not to complain about
+redefined subs via:
+
+ no warnings 'redefine'; # XXX: remove when done with porting
+
+I will remove that code, once porting is completed.
+
+At this point I was ready to start the porting process and I have
+started the server.
+
+ % hup2
+
+I'm using the following aliases to save typing:
+
+ alias err2 "tail -f ~/httpd/prefork/logs/error_log"
+ alias acc2 "tail -f ~/httpd/prefork/logs/access_log"
+ alias stop2 "~/httpd/prefork/bin/apachectl stop"
+ alias start2 "~/httpd/prefork/bin/apachectl start"
+ alias restart2 "~/httpd/prefork/bin/apachectl restart"
+ alias graceful2 "~/httpd/prefork/bin/apachectl graceful"
+ alias hup2 "stop2; sleep 3; start2; err2"
+
+(I also have a similar set of aliases for mod_perl 1.0)
+
+=head3 Porting with C<Apache::compat>
+
+I have configured my server to listen on port 8002, so I issue a
+request http://localhost:8002/mp3/ in one console:
+
+ % lynx --dump http://localhost:8002/mp3/
+
+keeping the I<error_log> open in the other:
+
+ % err2
+
+which expands to:
+
+ % tail -f ~/httpd/prefork/logs/error_log
+
+When the request is issued, the I<error_log> file tells me:
+
+ [Thu Jun 05 15:29:45 2003] [error] [client 127.0.0.1]
+ Usage: Apache::RequestRec::new(classname, c, base_pool=NULL)
+ at .../Apache/MP3.pm line 60.
+
+Looking at the code:
+
+ 58: sub handler ($$) {
+ 59: my $class = shift;
+ 60: my $obj = $class->new(@_) or die "Can't create object: $!";
+
+The problem is that handler wasn't invoked as method, but had C<$r>
+passed to it (we can tell because C<new()> was invoked as
+C<Apache::RequestRec::new()>, whereas it should have been
+C<Apache::MP3::new()>. Why I<Apache::MP3> wasn't passed as the first
+argument? I go to L<the mod_perl 1.0 backward compatibility
+document|docs::2.0::user::porting::compat/> and find that
+L<method handlers|docs::2.0::user::porting::compat/Method_Handlers>
+are now marked using the I<method> subroutine attribute. So I modify
+the code:
+
+ --- Apache/MP3.pm.0 2003-06-05 15:29:19.000000000 +1000
+ +++ Apache/MP3.pm 2003-06-05 15:38:41.000000000 +1000
+ @@ -55,7 +55,7 @@
+ my $NO = '^(no|false)$'; # regular expression
+ my $YES = '^(yes|true)$'; # regular expression
+
+ -sub handler ($$) {
+ +sub handler : method {
+ my $class = shift;
+ my $obj = $class->new(@_) or die "Can't create object: $!";
+ return $obj->run();
+
+and issue the request again (no server restart needed).
+
+This time we get a bunch of looping redirect responses, due to a bug
+in mod_dir which kicks in to handle the existing dir and messing up
+with C<$r-E<gt>path_info> keeping it empty at all times. I thought I
+could work around this by not having the same directory and location
+setting, e.g. by moving the location to be I</songs/> while keeping
+the physical directory with mp3 files as I<$DocumentRoot/mp3/>, but
+C<Apache::MP3> won't let you do that. So a solution suggested by
+Justin Erenkrantz is to simply shortcut that piece of code with:
+
+ --- Apache/MP3.pm.1 2003-06-06 14:50:59.000000000 +1000
+ +++ Apache/MP3.pm 2003-06-06 14:51:11.000000000 +1000
+ @@ -253,7 +253,7 @@
+ my $self = shift;
+ my $dir = shift;
+
+ - unless ($self->r->path_info){
+ + unless ($self->r->path_info eq ''){
+ #Issue an external redirect if the dir isn't tailed with a '/'
+ my $uri = $self->r->uri;
+ my $query = $self->r->args;
+
+which is equivalent to removing this code, until the bug is fixed (it
+was still there as of Apache 2.0.46). But the module still works
+without this code, because if you issue a request to I</mp3> (w/o
+trailing slash) mod_dir, will do the redirect for you, replacing the
+code that we just removed. In any case this got me past this problem.
+
+Since I have turned on the warnings pragma now I was getting loads of
+I<uninitialized value> warnings from C<$r-E<gt>dir_config()> whose
+return value were used without checking whether they are defined or
+not. But you'd get them with mod_perl 1.0 as well, so they are just an
+example of not-so clean code, not really a relevant obstacle in my
+pursuit to port this module to mod_perl 2.0. Unfortunately they were
+cluttering the log file so I had to fix them. I've defined several
+convenience functions:
+
+ sub get_config {
+ my $val = shift->r->dir_config(shift);
+ return defined $val ? $val : '';
+ }
+
+ sub config_yes { shift->get_config(shift) !~ /$YES/oi; }
+ sub config_no { shift->get_config(shift) !~ /$NO/oi; }
+
+and replaced them as you can see in this patch:
+F<code/apache_mp3_2.diff>, it was 194 lines long so I didn't inline it
+here, but it was quick to create with a few regexes search-n-replace
+manipulations in xemacs.
+
+Now I have the browsing of the root I</mp3/> directory and its
+sub-directories working. If I click on I<'Fetch'> of a particular song
+it works too. However if I try to I<'Stream'> a song, I get a 500
+response with error_log telling me:
+
+ [Fri Jun 06 15:33:33 2003] [error] [client 127.0.0.1] Bad arg length
+ for Socket::unpack_sockaddr_in, length is 31, should be 16 at
+ .../5.9.0/i686-linux-thread-multi/Socket.pm line 370.
+
+It would be certainly nice for I<Socket.pm> to use C<Carp::carp()>
+instead of C<warn()> so we will know where in the C<Apache::MP3> code
+this problem was triggered. However reading the I<Socket.pm> manpage
+reveals that C<sockaddr_in()> in the list context is the same as
+calling an explicit C<unpack_sockaddr_in()>, and in the scalar context
+it's calling C<pack_sockaddr_in()>. So I have found C<sockaddr_in> was
+the only I<Socket.pm> function used in C<Apache::MP3> and I have found
+this code in the function C<is_local()>:
+
+ my $r = $self->r;
+ my ($serverport,$serveraddr) = sockaddr_in($r->connection->local_addr);
+ my ($remoteport,$remoteaddr) = sockaddr_in($r->connection->remote_addr);
+ return $serveraddr eq $remoteaddr;
+
+Since something is wrong with function calls
+C<$r-E<gt>connection-E<gt>local_addr> and/or
+C<$r-E<gt>connection-E<gt>remote_addr> and I referred to L<the
+mod_perl 1.0 backward compatibility
+document|docs::2.0::user::porting::compat> and found L<the relevant
+entry|docs::2.0::user::porting::compat/C__connection_E_gt_remote_addr_>
+on these two functions. Indeed the API have changed. Instead of
+returning a packed C<SOCKADDR_IN> string, Apache now returns an
+C<L<APR::SocketAddr|docs::2.0::api::APR::SocketAddr>> object, which I
+can query to get the bits of information I'm interested in. So I
+applied this patch:
+
+ --- Apache/MP3.pm.3 2003-06-06 15:36:15.000000000 +1000
+ +++ Apache/MP3.pm 2003-06-06 15:56:32.000000000 +1000
+ @@ -1533,10 +1533,9 @@
+ # allows the player to fast forward, pause, etc.
+ sub is_local {
+ my $self = shift;
+ - my $r = $self->r;
+ - my ($serverport,$serveraddr) = sockaddr_in($r->connection->local_addr);
+ - my ($remoteport,$remoteaddr) = sockaddr_in($r->connection->remote_addr);
+ - return $serveraddr eq $remoteaddr;
+ + my $c = $self->r->connection;
+ + require APR::SockAddr;
+ + return $c->local_addr->ip_get eq $c->remote_addr->ip_get;
+ }
+
+ # Check if the requesting client is on the local network, as defined by
+
+And voila, the streaming option now works. I get a warning on I<'Use
+of uninitialized value'> on line 1516 though, but again this is
+unrelated to the porting issues, just a flow logic problem, which
+wasn't triggered without the warnings mode turned on. I have fixed it
+with:
+
+ --- Apache/MP3.pm.4 2003-06-06 15:57:15.000000000 +1000
+ +++ Apache/MP3.pm 2003-06-06 16:04:48.000
+ @@ -1492,7 +1492,7 @@
+ my $suppress_auth = shift;
+ my $r = $self->r;
+
+ - my $auth_info;
+ + my $auth_info = '';
+ # the check for auth_name() prevents an anno
+ # the apache server log when authentication
+ if ($r->auth_name && !$suppress_auth) {
+ @@ -1509,10 +1509,9 @@
+ }
+
+ my $vhost = $r->hostname;
+ - unless ($vhost) {
+ - $vhost = $r->server->server_hostname;
+ - $vhost .= ':' . $r->get_server_port unless
+ - }
+ + $vhost = $r->server->server_hostname unless
+ + $vhost .= ':' . $r->get_server_port unless $
+ +
+ return "http://${auth_info}${vhost}";
+ }
+
+This completes the first part of the porting. I have tried to use all
+the visible functions of the interface and everything seemed to work
+and I haven't got any warnings logged. Certainly I may have missed
+some usage patterns which may be still problematic. But this is good
+enough for this tutorial.
+
+=head3 Getting Rid of the C<Apache::compat> Dependency
+
+The final stage is going to get rid of C<Apache::compat> since this is
+a CPAN module, which must not load C<Apache::compat> on its own. I'm
+going to make C<Apache::MP3> work with mod_perl 2.0 all by itself.
+
+The first step is to comment out the loading of C<Apache::compat> in
+I<startup.pl>:
+
+ #file:startup.pl
+ #---------------
+ use Apache2 ();
+ use lib qw(/home/httpd/2.0/perl);
+ #use Apache::compat ();
+
+=head3 Ensuring that C<Apache::compat> is not loaded
+
+The second step is to make sure that C<Apache::compat> doesn't get
+loaded indirectly, through some other module. So I've added this line
+of code to I<Apache/MP3.pm>:
+
+ --- Apache/MP3.pm.5 2003-06-06 16:17:50.000000000 +1000
+ +++ Apache/MP3.pm 2003-06-06 16:21:14.000000000 +1000
+ @@ -1,6 +1,10 @@
+ package Apache::MP3;
+ # $Id$
+
+ +BEGIN {
+ + die "Apache::compat is loaded loaded" if $INC{'Apache/compat.pm'};
+ +}
+ +
+ use strict;
+ use warnings;
+ no warnings 'redefine'; # XXX: remove when done with porting
+
+and indeed, even though I've commented out the loading of
+C<Apache::compat> from I<startup.pl>, this module was still getting
+loaded. I knew that because the request to I</mp3> were failing with
+the error message:
+
+ Apache::compat is loaded loaded at ...
+
+There are several ways to find the guilty party, you can C<grep(1)>
+for it in the perl libraries, you can override
+C<CORE::GLOBAL::require()> in I<startup.pl>:
+
+ BEGIN {
+ use Carp;
+ *CORE::GLOBAL::require = sub {
+ Carp::cluck("Apache::compat is loaded") if $_[0] =~ /compat/;
+ CORE::require(@_);
+ };
+ }
+
+or you can modify I<Apache/compat.pm> and make it print the calls
+trace when it gets compiled:
+
+ --- Apache/compat.pm.orig 2003-06-03 16:11:07.000000000 +1000
+ +++ Apache/compat.pm 2003-06-03 16:11:58.000000000 +1000
+ @@ -1,5 +1,9 @@
+ package Apache::compat;
+
+ +BEGIN {
+ + use Carp;
+ + Carp::cluck("Apache::compat is loaded by");
+ +}
+
+I've used this last technique, since it's the safest one to use.
+Remember that C<Apache::compat> can also be loaded with:
+
+ do "Apache/compat.pm";
+
+in which case, neither C<grep(1)>'ping for C<Apache::compat>, nor
+overriding C<require()> will do the job.
+
+When I've restarted the server and tried to use C<Apache::MP3> (I
+wasn't preloading it at the server startup since I wanted the server
+to start normally and cope with problem when it's running), the
+I<error_log> had an entry:
+
+ Apache::compat is loaded by at .../Apache2/Apache/compat.pm line 6
+ Apache::compat::BEGIN() called at .../Apache2/Apache/compat.pm line 8
+ eval {...} called at .../Apache2/Apache/compat.pm line 8
+ require Apache/compat.pm called at .../5.9.0/CGI.pm line 169
+ require CGI.pm called at .../site_perl/5.9.0/Apache/MP3.pm line 8
+ Apache::MP3::BEGIN() called at .../Apache2/Apache/compat.pm line 8
+
+(I've trimmed the whole paths of the libraries and the trace itself,
+to make it easier to understand.)
+
+We could have used C<Carp::carp()> which would have told us only the
+fact that C<Apache::compat> was loaded by C<CGI.pm>, but by using
+C<Carp::cluck()> we've obtained the whole stack backtrace so we also
+can learn which module has loaded C<CGI.pm>.
+
+Here I've learned that I had an old version of C<CGI.pm> (2.89) which
+automatically loaded C<Apache::compat> (which L<should be never done
+by CPAN
+modules|docs::2.0::api::Apache::compat/Use_in_CPAN_Modules>). Once
+I've upgraded C<CGI.pm> to version 2.93 and restarted the server,
+C<Apache::compat> wasn't getting loaded any longer.
+
+=head3 Installing the C<ModPerl::MethodLookup> Helper
+
+Now that C<Apache::compat> is not loaded, I need to deal with two
+issues: modules that need to be loaded and APIs that have changed.
+
+For the second issue I'll have to refer to the L<the mod_perl 1.0
+backward compatibility document|docs::2.0::user::porting::compat>.
+
+But the first issue can be easily worked out using
+C<L<ModPerl::MethodLookup|docs::2.0::user::api::ModPerl::MethodLookup>>.
+As explained in the section L<Using C<ModPerl::MethodLookup>
+Programmatically|/Using_C_ModPerl__MethodLookup__Programmatically>
+I've added the
+C<L<AUTOLOAD|docs::2.0::api::ModPerl::MethodLookup/C_AUTOLOAD_>> code to
+my I<startup.pl> so it'll automatically lookup the packages that I
+need to load based on the request method and the object type.
+
+So now my I<startup.pl> looked like:
+
+ #file:startup.pl
+ #---------------
+ use Apache2 ();
+ use lib qw(/home/httpd/2.0/perl);
+
+ {
+ package ModPerl::MethodLookupAuto;
+ use ModPerl::MethodLookup;
+
+ use Carp;
+ sub handler {
+
+ # look inside mod_perl:: Apache:: APR:: ModPerl:: excluding DESTROY
+ my $skip = '^(?!DESTROY$';
+ *UNIVERSAL::AUTOLOAD = sub {
+ my $method = $AUTOLOAD;
+ return if $method =~ /DESTROY/;
+ my ($hint, @modules) =
+ ModPerl::MethodLookup::lookup_method($method, @_);
+ $hint ||= "Can't find method $AUTOLOAD";
+ croak $hint;
+ };
+ return 0;
+ }
+ }
+ 1;
+
+and I add to my I<httpd.conf>:
+
+ PerlChildInitHandler ModPerl::MethodLookupAuto
+
+=head3 Adjusting the code to run under mod_perl 2
+
+I restart the server and off I go to complete the second porting
+stage.
+
+The first error that I've received was:
+
+ [Fri Jun 06 16:28:32 2003] [error] failed to resolve handler `Apache::MP3'
+ [Fri Jun 06 16:28:32 2003] [error] [client 127.0.0.1] Can't locate
+ object method "boot" via package "mod_perl" at .../Apache/Constants.pm
+ line 8. Compilation failed in require at .../Apache/MP3.pm line 12.
+
+I go to line 12 and find the following code:
+
+ use Apache::Constants qw(:common REDIRECT HTTP_NO_CONTENT
+ DIR_MAGIC_TYPE HTTP_NOT_MODIFIED);
+
+Notice that I did have mod_perl 1.0 installed, so the
+C<Apache::Constant> module from mod_perl 1.0 couldn't find the
+C<boot()> method which doesn't exist in mod_perl 2.0. If you don't
+have mod_perl 1.0 installed the error would simply say, that it can't
+find I<Apache/Constants.pm> in C<@INC>. In any case, we are going to
+replace this code with mod_perl 2.0 equivalent:
+
+ --- Apache/MP3.pm.6 2003-06-06 16:33:05.000000000 +1000
+ +++ Apache/MP3.pm 2003-06-06 17:03:43.000000000 +1000
+ @@ -9,7 +9,9 @@
+ use warnings;
+ no warnings 'redefine'; # XXX: remove when done with porting
+
+ -use Apache::Constants qw(:common REDIRECT HTTP_NO_CONTENT DIR_MAGIC_TYPE
HTTP_NOT_MODIFIED);
+ +use Apache::Const -compile => qw(:common REDIRECT HTTP_NO_CONTENT
+ + DIR_MAGIC_TYPE HTTP_NOT_MODIFIED);
+ +
+ use Apache::MP3::L10N;
+ use IO::File;
+ use Socket 'sockaddr_in';
+
+and I also had to adjust the constants, since what used to be C<OK>,
+now has to be C<Apache::OK>, mainly because in mod_perl 2.0 there is
+an enormous amount of constants (coming from Apache and APR) and most
+of them are grouped in C<Apache::> or C<APR::> namespaces. The
+C<L<Apache::Const|docs::2.0::user::api::Apache::Const>> and
+C<L<APR::Const|docs::2.0::user::api::APR::Const>> manpage provide more
+information on available constants.
+
+This search and replace accomplished the job:
+
+ % perl -pi -e 's/return\s(OK|DECLINED|FORBIDDEN| \
+ REDIRECT|HTTP_NO_CONTENT|DIR_MAGIC_TYPE| \
+ HTTP_NOT_MODIFIED)/return Apache::$1/xg' Apache/MP3.pm
+
+As you can see the regex explicitly lists all constants that were used
+in C<Apache::MP3>. Your situation may vary. Here is the patch:
+F<code/apache_mp3_7.diff>.
+
+I had to manually fix the C<DIR_MAGIC_TYPE> constant which didn't fit
+the regex pattern:
+
+ --- Apache/MP3.pm.8 2003-06-06 17:24:33.000000000 +1000
+ +++ Apache/MP3.pm 2003-06-06 17:26:29.000000000 +1000
+ @@ -1055,7 +1055,7 @@
+
+ my $mime = $self->r->lookup_file("$dir/$d")->content_type;
+
+ - push(@directories,$d) if !$seen{$d}++ && $mime eq DIR_MAGIC_TYPE;
+ + push(@directories,$d) if !$seen{$d}++ && $mime eq
Apache::DIR_MAGIC_TYPE;
+
+ # .m3u files should be configured as audio/playlist MIME types in
your apache .conf file
+ push(@playlists,$d) if $mime =~
m!^audio/(playlist|x-mpegurl|mpegurl|x-scpls)$!;
+
+And I move on, the next error is:
+
+ [Fri Jun 06 17:28:00 2003] [error] [client 127.0.0.1]
+ Can't locate object method "header_in" via package
+ "Apache::RequestRec" at .../Apache/MP3.pm line 85.
+
+The L<porting document|docs::2.0::user::porting::compat> quickly
+L<reveals|docs::2.0::user::porting::compat/C__r_E_gt_err_header_out_>
+me that C<header_in()> and its brothers C<header_out()> and
+C<err_header_out()> are R.I.P. and that I have to use the
+corresponding functions C<headers_in()>, C<headers_out()> and
+C<err_headers_out()> which are available in mod_perl 1.0 API as well.
+
+So I adjust the code to use the new API:
+
+ % perl -pi -e 's|header_in\((.*?)\)|headers_in->{$1}|g' Apache/MP3.pm
+ % perl -pi -e 's|header_out\((.*?)\s*=>\s*(.*?)\);|headers_out->{$1} =
$2;|g' Apache/MP3.pm
+
+which results in this patch: F<code/apache_mp3_9.diff>.
+
+On the next error C<L<ModPerl::MethodLookup's
+AUTOLOAD|docs::2.0::api::ModPerl::MethodLookup/AUTOLOAD>> kicks in.
+Instead of complaining:
+
+ [Fri Jun 06 18:35:53 2003] [error] [client 127.0.0.1]
+ Can't locate object method "FETCH" via package "APR::Table"
+ at .../Apache/MP3.pm line 85.
+
+I now get:
+
+ [Fri Jun 06 18:36:35 2003] [error] [client 127.0.0.1]
+ to use method 'FETCH' add:
+ use APR::Table ();
+ at .../Apache/MP3.pm line 85
+
+So I follow the suggestion and load C<APR::Table()>:
+
+ --- Apache/MP3.pm.10 2003-06-06 17:57:54.000000000 +1000
+ +++ Apache/MP3.pm 2003-06-06 18:37:33.000000000 +1000
+ @@ -9,6 +9,8 @@
+ use warnings;
+ no warnings 'redefine'; # XXX: remove when done with porting
+
+ +use APR::Table ();
+ +
+ use Apache::Const -compile => qw(:common REDIRECT HTTP_NO_CONTENT
+ DIR_MAGIC_TYPE HTTP_NOT_MODIFIED);
+
+I continue issuing the request and adding the missing modules again
+and again till I get no more complaints. During this process I've
+added the following modules:
+
+ --- Apache/MP3.pm.11 2003-06-06 18:38:47.000000000 +1000
+ +++ Apache/MP3.pm 2003-06-06 18:39:10.000000000 +1000
+ @@ -9,6 +9,14 @@
+ use warnings;
+ no warnings 'redefine'; # XXX: remove when done with porting
+
+ +use Apache::Connection ();
+ +use Apache::SubRequest ();
+ +use Apache::Access ();
+ +use Apache::RequestIO ();
+ +use Apache::RequestUtil ();
+ +use Apache::RequestRec ();
+ +use Apache::ServerUtil ();
+ +use Apache::Log;
+ use APR::Table ();
+
+ use Apache::Const -compile => qw(:common REDIRECT HTTP_NO_CONTENT
+
+The AUTOLOAD code helped me to trace the modules that contain the
+existing APIs, however I still have to deal with APIs that no longer
+exist. Rightfully the helper code says that it doesn't know which
+module defines the method: C<send_http_header()> because it no longer
+exists in Apache 2.0 vocabulary:
+
+ [Fri Jun 06 18:40:34 2003] [error] [client 127.0.0.1]
+ Don't know anything about method 'send_http_header'
+ at .../Apache/MP3.pm line 498
+
+So I go back to the L<porting
+document|docs::2.0::user::porting::compat> and find the L<relevant
+entry|docs::2.0::user::porting::compat/C__r_E_gt_send_http_header_>.
+In 2.0 lingo, we just need to set the C<content_type()>:
+
+ --- Apache/MP3.pm.12 2003-06-06 18:43:42.000000000 +1000
+ +++ Apache/MP3.pm 2003-06-06 18:51:23.000000000 +1000
+ @@ -138,7 +138,7 @@
+ sub help_screen {
+ my $self = shift;
+
+ - $self->r->send_http_header( $self->html_content_type );
+ + $self->r->content_type( $self->html_content_type );
+ return Apache::OK if $self->r->header_only;
+
+ print start_html(
+ @@ -336,7 +336,7 @@
+ my $r = $self->r;
+ my $base = $self->stream_base;
+
+ - $r->send_http_header('audio/mpegurl');
+ + $r->content_type('audio/mpegurl');
+ return Apache::OK if $r->header_only;
+
+ # local user
+ @@ -495,7 +495,7 @@
+ return Apache::DECLINED unless my
($directories,$mp3s,$playlists,$txtfiles)
+ = $self->read_directory($dir);
+
+ - $self->r->send_http_header( $self->html_content_type );
+ + $self->r->content_type( $self->html_content_type );
+ return Apache::OK if $self->r->header_only;
+
+ $self->page_top($dir);
+
+also I've noticed that there was this code:
+
+ return Apache::OK if $self->r->header_only;
+
+This technique is no longer needed in 2.0, since Apache 2.0
+automatically discards the body if the request is of type C<HEAD> --
+the handler should still deliver the whole body, which helps to
+calculate the content-length if this is relevant to play nicer with
+proxies. So you may decide not to make a special case for C<HEAD>
+requests.
+
+At this point I was able to browse the directories and play files via
+most options without relying on C<Apache::compat>.
+
+There were a few other APIs that I had to fix in the same way, while
+trying to use the application, looking at the I<error_log> referring
+to the L<porting document|docs::2.0::user::porting::compat> and
+applying the suggested fixes. I'll make sure to send all these fixes
+to Lincoln Stein, so the new versions will work correctly with
+mod_perl 2.0. I also had to fix other C<Apache::MP3::> files, which
+come as a part of the C<Apache-MP3> distribution, pretty much using
+the same techniques explained here. A few extra fixes of interest in
+C<Apache::MP3> were:
+
+=over
+
+=item C<send_fd()>
+
+As of this writing we don't have this function in the core, because
+Apache 2.0 doesn't have it (it's in C<Apache::compat> but implemented
+in a slow way). However we may provide one in the future. Currently
+one can use the function C<sendfile()> which requires a filename as an
+argument and not the file descriptor. So I have fixed the code:
+
+ - if($r->request($r->uri)->content_type eq 'audio/x-scpls'){
+ - open(FILE,$r->filename) || return 404;
+ - $r->send_fd(\*FILE);
+ - close(FILE);
+ +
+ + if($r->content_type eq 'audio/x-scpls'){
+ + $r->sendfile($r->filename) || return Apache::NOT_FOUND;
+
+=item C<log_reason>
+
+C<log_reason> is now C<log_error>:
+
+ - $self->r->log_reason('Invalid parameters -- possible attempt to
circumvent checks.');
+ + $r->log_error('Invalid parameters -- possible attempt to circumvent
checks.')
+;
+
+=back
+
+I have found the porting process to be quite interesting, especially
+since I have found several bugs in Apache 2.0 and documented a few
+undocumented API changes. It was also fun, because I've got to listen
+to mp3 files when I did things right, and was getting silence in my
+headphones and a visual irritation in the form of I<error_log>
+messages when I didn't ;)
+
+
+
=head1 Porting a Module to Run under both mod_perl 2.0 and mod_perl 1.0
-Sometimes code needs to work with both mod_perl versions. This is the
-case for writers of publically available and CPAN modules who wish to
-continue to maintain a single code base, rather than supplying two
-separate implementations.
+Sometimes code needs to work with both mod_perl versions. For example
+this is the case with CPAN module developers who wish to continue to
+maintain a single code base, rather than supplying two separate
+implementations.
=head2 Making Code Conditional on Running mod_perl Version
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]