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]