You are viewing a plain text version of this content. The canonical link for it is here.
Posted to modperl@perl.apache.org by "David E. Wheeler" <da...@justatheory.com> on 2015/03/02 01:46:12 UTC

How Do I change the Document Root Per Request

Hi,

I want to set the document root for a request to map to the basic auth username. I tried this in a PerlFixupHandler:

    sub handler {
        my $r = shift;

        # We only want to do this once per request.
        return DECLINED unless $r->is_initial_req;

        # Get the username.
        my $user = $r->user or return HTTP_UNAUTHORIZED;

        # Return forbidden if the username subdiectory does not exist.
        my $doc_root = File::Spec->catdir($r->document_root, $user);
        return HTTP_FORBIDDEN unless -d $doc_root;

        # Set the document root for the duration of this request and return.
        $r->document_root($doc_root);
        return DECLINED;
    }

But alas, it still serves the original document root. How can I get it to change the document root on a per-request basis? If I cant, should I change the URI or the filename, instead?

Thnks,

David

AcceptPathInfo usefulness (was: Re: How Do I change the Document Root Per Request)

Posted by Randolf Richardson <ra...@modperl.pl>.
> On Wed, Mar 11, 2015 at 1:25 PM, David E. Wheeler <da...@justatheory.com>
> wrote:
> 
> > On Mar 11, 2015, at 9:59 AM, David E. Wheeler <da...@justatheory.com>
> > wrote:
> >
> > >      # Set the filename.
> > >      my $file = File::Spec->catfile($sub_root, substr $r->uri, 1);
> > >      $r->filename($file);
> > >      $r->finfo(APR::Finfo::stat($file, APR::Const::FINFO_NORM,
> > $r->pool));
> > >      return DECLINED;
> > > }
> > >
> > > I have no PerlReponseHandler, so Apache handles the response...and returns
> > 404. Note that a request for /hi.txt; it´s only a request in a
> > subdirectory, /foo/bar.html, that fails. Why? Why isn´t Apache able to find
> > that file? Is there some other attribute of $r I need to set or unset?
> >
> > Turns out I was able to fix this issue by unsetting path_info. I just added
> >
> >     $r->path_info(undef);
> >
> > To the above code, and now Apache can find the file.
> >
> > I do not understand the relationship between filename (which is often not
> > the full path to a file) and path_info. Fortunately, it looks like I don´t
> > have to. But we might want to consider updating the docs to recommend
> > unsetting path_info when setting the full path to a file in filename.
> >
> >
> That is very curious. What was in path_info before? Is this a difference
> between undef and ""? Because path_info shouldn't be involved in finding
> the file, unless Apache is configured to ignore path info. And even that
> doesn't make sense because you weren't testing with
> /foo/bar.html/extra/pathinfo
> 
> You could also check how this directive affects the problem (whether it
> should be turned off, or if your handler needs accept / deny the path info
> as with the 'default' argument:
> 
> AcceptPathInfo On
> 
> I found this on apache 2.4's docs:
> http://httpd.apache.org/docs/current/mod/core.html#AcceptPathInfo

	FYI:  This is a very important feature in Apache that allows for 
dynamic image scripts to function (amongst other things).  For 
example, I have captcha images stored in a BYTEA column in 
PostgreSQL, and I have a custom mod_perl2 script called "captcha" 
which, to the general public, simply looks like another directory (if 
you want a working example of this script, you can see it in use here 
for now:  http://www.inter-corporate.com/test/captcha.pl ).

	The GET method in HTTP is used to download the image from 
/images/captcha/-number-.jpeg which doesn't actually exist anywhere 
on the file system.  If the number is incorrect (e.g., a row with 
that ID number doesn't exist in the SQL table), then my script simply 
returns the Apache2 constant for NOT_FOUND.

	This technique can be used for other things too, such as the output 
of messages in a web-based forum, or calendar events, etc.  The end 
result is that the address bar is very tidy with numbers or other 
identifying information, without need for any special characters like 
interrogation marks and equal signs, which can be cryptic to users 
who are not computer literate -- the following two example URIs show 
the difference between what I consider to be a more user-friendly URI 
and a more cryptic [to many non-computer-literate users] URI:

		More user-friendly
		http://www.example.com/events/2015/02/25/today.pl

		More cryptic
		http://www.example.com/events.pl?d=20150225&v=today

		Even more cryptic
		http://www.example.com/events.pl?y=2015&m=2&d=25&v=today

	I tend to include the ".pl" suffix as opposed to the ".html" suffix 
(or no suffix at all) because I like to promote Perl, but this part 
is optional if you want that first link to be even simpler.

	The first example.com link above practically speaks for itself, so 
users can share such links with little or no explanation of what the 
link is for.  The second and third example.com links may require some 
explanation, and then there are problems where users sometimes remove 
the various parameters inadvertently or insert other characters 
without noticing that they've done so.

	An additional advantage I've encountered with this is that it 
effectively offloads GET parameters into the URI, which are processed 
via splitting $r->path_info by a slash and then that leaves any POST 
parameters from an HTML form for processing more cleanly 
behind-the-scenes.

	It's really nice because I can parse the last portion, "today.pl" 
(from the first example.com link above), and if it was changed to 
something like "banner.pl" or "logo.pl" then I could output an image 
file instead to serve HTML code output by "today.pl" that references 
an image as so:  <img src=logo.pl width=65 height=65 border=0>

	Using the relative URI, again the processing of $r->path_info 
results in my initial "/events" Perl script determining which event 
to look at (at least the date in this example, for which an event ID 
number could be used instead of the date, etc.) and transmitting the 
correct logo file when the web browser sends the request for it.

	I hope this information will be of interest to someone.

Randolf Richardson - randolf@inter-corporate.com
Inter-Corporate Computer & Network Services, Inc.
Beautiful British Columbia, Canada
http://www.inter-corporate.com/



Re: How Do I change the Document Root Per Request

Posted by "David E. Wheeler" <da...@justatheory.com>.
On Mar 11, 2015, at 10:39 AM, Lathan Bidwell <la...@andrews.edu> wrote:

> That is very curious. What was in path_info before? Is this a difference between undef and ""? Because path_info shouldn't be involved in finding the file, unless Apache is configured to ignore path info. And even that doesn't make sense because you weren't testing with /foo/bar.html/extra/pathinfo

If I requested /foo/bar/hi.html, filename would be /foo/bar and path_info would be /hi.html. It might be that Apache decided to do something funky here because its map to storage handler could not find $docroot/foo/bar/hi.html, and it’s only later that my PerlTypeHandler sets the file to $docroot/$user/foo/bar/hi.html.

> You could also check how this directive affects the problem (whether it should be turned off, or if your handler needs accept / deny the path info as with the 'default' argument:
> AcceptPathInfo On
> I found this on apache 2.4's docs: http://httpd.apache.org/docs/current/mod/core.html#AcceptPathInfo

No idea, I’m loathe to mess with it anymore.

David


Re: How Do I change the Document Root Per Request

Posted by Lathan Bidwell <la...@andrews.edu>.
On Wed, Mar 11, 2015 at 1:25 PM, David E. Wheeler <da...@justatheory.com>
wrote:

> On Mar 11, 2015, at 9:59 AM, David E. Wheeler <da...@justatheory.com>
> wrote:
>
> >      # Set the filename.
> >      my $file = File::Spec->catfile($sub_root, substr $r->uri, 1);
> >      $r->filename($file);
> >      $r->finfo(APR::Finfo::stat($file, APR::Const::FINFO_NORM,
> $r->pool));
> >      return DECLINED;
> > }
> >
> > I have no PerlReponseHandler, so Apache handles the response…and returns
> 404. Note that a request for /hi.txt; it’s only a request in a
> subdirectory, /foo/bar.html, that fails. Why? Why isn’t Apache able to find
> that file? Is there some other attribute of $r I need to set or unset?
>
> Turns out I was able to fix this issue by unsetting path_info. I just added
>
>     $r->path_info(undef);
>
> To the above code, and now Apache can find the file.
>
> I do not understand the relationship between filename (which is often not
> the full path to a file) and path_info. Fortunately, it looks like I don’t
> have to. But we might want to consider updating the docs to recommend
> unsetting path_info when setting the full path to a file in filename.
>
>
That is very curious. What was in path_info before? Is this a difference
between undef and ""? Because path_info shouldn't be involved in finding
the file, unless Apache is configured to ignore path info. And even that
doesn't make sense because you weren't testing with
/foo/bar.html/extra/pathinfo

You could also check how this directive affects the problem (whether it
should be turned off, or if your handler needs accept / deny the path info
as with the 'default' argument:

AcceptPathInfo On

I found this on apache 2.4's docs:
http://httpd.apache.org/docs/current/mod/core.html#AcceptPathInfo


Best,
>
> David
>
>

Re: How Do I change the Document Root Per Request

Posted by "David E. Wheeler" <da...@justatheory.com>.
On Mar 11, 2015, at 9:59 AM, David E. Wheeler <da...@justatheory.com> wrote:

>      # Set the filename.                                                                  
>      my $file = File::Spec->catfile($sub_root, substr $r->uri, 1);
>      $r->filename($file);
>      $r->finfo(APR::Finfo::stat($file, APR::Const::FINFO_NORM, $r->pool));
>      return DECLINED;
> }
> 
> I have no PerlReponseHandler, so Apache handles the response…and returns 404. Note that a request for /hi.txt; it’s only a request in a subdirectory, /foo/bar.html, that fails. Why? Why isn’t Apache able to find that file? Is there some other attribute of $r I need to set or unset?

Turns out I was able to fix this issue by unsetting path_info. I just added

    $r->path_info(undef);

To the above code, and now Apache can find the file.

I do not understand the relationship between filename (which is often not the full path to a file) and path_info. Fortunately, it looks like I don’t have to. But we might want to consider updating the docs to recommend unsetting path_info when setting the full path to a file in filename.

Best,

David


Re: How Do I change the Document Root Per Request

Posted by "David E. Wheeler" <da...@justatheory.com>.
On Mar 11, 2015, at 10:15 AM, Lathan Bidwell <la...@andrews.edu> wrote:

> I am not an expert at this, so I don't have an answer.

Thanks for your reply, I appreciate it.

> But I can suggest a few debugging steps to clear out of the way:
> 
> 1) Confirm that your document root is showing properly in the error log
> Does the error log report "/var/html/foo/bar.html" is not found, or does it only show the request? On my local apache instance if I do /blah/foo (non existant), I get: File does not exist: /home/www/html/blah (which has my document root in it). 

Yeah, the document root is correct.

> 2) Confirm that path exists, Look for a folder structure you can create that will work
> if /var/html/foo/bar.html doesn't exist, is it looking for /var/www/foo/bar.html or /foo/bar.html

Yes, I was logging the value I was putting into filename(), and yes, it did exist.

> 3) Do you have any rewrite urls or other location / directory directives that could be overriding hits?
> Check your config files, and then put in some print STDERR / smart comments to confirm if your routing program is returning the DECLINED

Nope.

> 4) Does it work for DirectoryIndexes?

I don’t know, but it turns out that unsetting path_info() fixed the issue for me. How Apache uses filename and path_info together is opaque to me.

Best,

David


Re: How Do I change the Document Root Per Request

Posted by Lathan Bidwell <la...@andrews.edu>.
David,

I am not an expert at this, so I don't have an answer.

But I can suggest a few debugging steps to clear out of the way:

1) Confirm that your document root is showing properly in the error log

Does the error log report "/var/html/foo/bar.html" is not found, or does it
only show the request? On my local apache instance if I do /blah/foo (non
existant), I get: File does not exist: /home/www/html/blah (which has my
document root in it).

2) Confirm that path exists, Look for a folder structure you can create
that will work

if /var/html/foo/bar.html doesn't exist, is it looking for
/var/www/foo/bar.html or /foo/bar.html

3) Do you have any rewrite urls or other location / directory directives
that could be overriding hits?

Check your config files, and then put in some print STDERR / smart comments
to confirm if your routing program is returning the DECLINED

4) Does it work for DirectoryIndexes?

Lathan

On Wed, Mar 11, 2015 at 12:59 PM, David E. Wheeler <da...@justatheory.com>
wrote:

> On Mar 6, 2015, at 5:29 PM, David E. Wheeler <da...@justatheory.com>
> wrote:
>
> > And now it works just how I want.
>
> I take it back. It works for files in the root, but not subdirectories.
>
> So say my document root is /var/html, and a request comes in for
> /foo/bar.html. Apache has mapped it to /var/html/foo/bar.html, but in my
> PerlTypeHandler, I re-map it to /var/html/david/foo/bar.html and set finfo:
>
>   sub handler {
>       my $r = shift;
>
>       # We only want to do this once per request.
>       return DECLINED unless $r->is_initial_req;
>
>       # Get the username.
>       my $user = $r->user or return HTTP_UNAUTHORIZED;
>
>       # Return forbidden if the subscriber ID subdirectory does not exist.
>       my $doc_root = $r->document_root;
>       my $sub_root = File::Spec->catdir($doc_root, $user);
>       return HTTP_FORBIDDEN unless -d $sub_root;
>
>       # Set the filename.
>       my $file = File::Spec->catfile($sub_root, substr $r->uri, 1);
>       $r->filename($file);
>       $r->finfo(APR::Finfo::stat($file, APR::Const::FINFO_NORM, $r->pool));
>       return DECLINED;
>  }
>
> I have no PerlReponseHandler, so Apache handles the response…and returns
> 404. Note that a request for /hi.txt; it’s only a request in a
> subdirectory, /foo/bar.html, that fails. Why? Why isn’t Apache able to find
> that file? Is there some other attribute of $r I need to set or unset?
>
> Thanks,
>
> David
>
>

Re: How Do I change the Document Root Per Request

Posted by "David E. Wheeler" <da...@justatheory.com>.
On Mar 6, 2015, at 5:29 PM, David E. Wheeler <da...@justatheory.com> wrote:

> And now it works just how I want.

I take it back. It works for files in the root, but not subdirectories.

So say my document root is /var/html, and a request comes in for /foo/bar.html. Apache has mapped it to /var/html/foo/bar.html, but in my PerlTypeHandler, I re-map it to /var/html/david/foo/bar.html and set finfo:

  sub handler {
      my $r = shift;

      # We only want to do this once per request.
      return DECLINED unless $r->is_initial_req;

      # Get the username.
      my $user = $r->user or return HTTP_UNAUTHORIZED;

      # Return forbidden if the subscriber ID subdirectory does not exist.
      my $doc_root = $r->document_root;
      my $sub_root = File::Spec->catdir($doc_root, $user);
      return HTTP_FORBIDDEN unless -d $sub_root;

      # Set the filename.                                                                  
      my $file = File::Spec->catfile($sub_root, substr $r->uri, 1);
      $r->filename($file);
      $r->finfo(APR::Finfo::stat($file, APR::Const::FINFO_NORM, $r->pool));
      return DECLINED;
 }

I have no PerlReponseHandler, so Apache handles the response…and returns 404. Note that a request for /hi.txt; it’s only a request in a subdirectory, /foo/bar.html, that fails. Why? Why isn’t Apache able to find that file? Is there some other attribute of $r I need to set or unset?

Thanks,

David


Re: How Do I change the Document Root Per Request

Posted by "David E. Wheeler" <da...@justatheory.com>.
On Mar 6, 2015, at 9:21 AM, David E. Wheeler <da...@justatheory.com> wrote:

>> 
>>   PerlMapToStorageHandler Apache2::Const::OK
>> 
>> Otherwise it just fails super early. By adding this line, I am able to freely set the filename later.
> 
> Alas, this does *not* work for directory requests, just files. Directories return 404. Is there some other attribute other than filename and finfo I need to set so that, when I decline a request, Apache will find the directory and use mod_autoindex to display it? It does do so if I remove the above PerlMapToStorageHandler statement, so there must be some crucial attribute that needs settings for it to find a directory, right?

I managed to fix this by changing my handler to a fixup handler and not setting any request handler at all. The fixup handler returns OK for HTML requests (it uses content negotiation and a GET param to decide what to return) and installs a relevant handler for other formats:

    return OK if $format eq 'html';
    $r->handler('modperl');
    if ($format eq 'json') {
        $r->set_handlers(PerlResponseHandler => \&handle_json);
    } elsif ($format eq 'xml') {
        $r->set_handlers(PerlResponseHandler => \&handle_xml);
    } elsif ($format eq 'text') {
        $r->set_handlers(PerlResponseHandler => \&handle_text);
    }

And now it works just how I want.

Thanks,

David


Re: How Do I change the Document Root Per Request

Posted by "David E. Wheeler" <da...@justatheory.com>.
On Mar 3, 2015, at 2:27 PM, David E. Wheeler <da...@justatheory.com> wrote:

> And now I got it to work. The key was to add.
> 
>    PerlMapToStorageHandler Apache2::Const::OK
> 
> Otherwise it just fails super early. By adding this line, I am able to freely set the filename later.

Alas, this does *not* work for directory requests, just files. Directories return 404. Is there some other attribute other than filename and finfo I need to set so that, when I decline a request, Apache will find the directory and use mod_autoindex to display it? It does do so if I remove the above PerlMapToStorageHandler statement, so there must be some crucial attribute that needs settings for it to find a directory, right?

Thanks,

David

Re: How Do I change the Document Root Per Request

Posted by "David E. Wheeler" <da...@justatheory.com>.
On Mar 3, 2015, at 11:34 AM, David E. Wheeler <da...@justatheory.com> wrote:

> I managed to get a little further by switching from PerlFixupHandler to PerlTypeHandler. I still get some 404s for files, but all the directories serve properly. As it happens, I have a response handler that handles directory requests, but it declines file requests. I assume, then, that the default Apache response handler doesn’t know that I’ve updated the filename. Is there some way to get it to do that?

And now I got it to work. The key was to add.

    PerlMapToStorageHandler Apache2::Const::OK

Otherwise it just fails super early. By adding this line, I am able to freely set the filename later.

Thanks,

David



Re: How Do I change the Document Root Per Request

Posted by "David E. Wheeler" <da...@justatheory.com>.
On Mar 2, 2015, at 9:35 PM, David E. Wheeler <da...@justatheory.com> wrote:

>    PerlLoadModule My::UserFixup
>    <Location />
>        PerlFixupHandler  My::UserFixup
>        AuthType          Basic
>        AuthName          "User File Service"
>        Require           valid-user
>    </Location>

I managed to get a little further by switching from PerlFixupHandler to PerlTypeHandler. I still get some 404s for files, but all the directories serve properly. As it happens, I have a response handler that handles directory requests, but it declines file requests. I assume, then, that the default Apache response handler doesn’t know that I’ve updated the filename. Is there some way to get it to do that?

Thanks,

David


Re: How Do I change the Document Root Per Request

Posted by "David E. Wheeler" <da...@justatheory.com>.
On Mar 2, 2015, at 8:27 PM, Fred Moyer <fr...@redhotpenguin.com> wrote:

> Can you show us your relevant httpd.conf snippet? No guesses right
> now, but that might help.

Sure.

    PerlLoadModule My::UserFixup
    <Location />
        PerlFixupHandler  My::UserFixup
        AuthType          Basic
        AuthName          "User File Service"
        Require           valid-user
    </Location>

There’s also a simple PerlAuthenHandler. Pretty simple stuff.

I’ve gotten a little further since my post on Friday, changing the filename instead of the doc_root:

   sub handler {
       my $r = shift;

       # We only want to do this once per request.
       return DECLINED unless $r->is_initial_req;

       # Get the username.
       my $user = $r->user or return HTTP_UNAUTHORIZED;

       # Return forbidden if the subscriber ID subdirectory does not exist.
       my $doc_root = $r->document_root;
       my $sub_root = File::Spec->catdir($doc_root, $user);
       return HTTP_FORBIDDEN unless -d $sub_root;

       # Set the filename.                                                                  
       my $file = File::Spec->catfile($sub_root, substr $r->uri, 1);
       $r->filename($file);
       $r->finfo(APR::Finfo::stat($file, APR::Const::FINFO_NORM, $r->pool));
       return DECLINED;
  }

This works for root requests (/), but not anything else, which always return 404. It’s driving me crazy. Maybe there’s something else i need to set? This has *got* to be a common thing people do, right? It’s driving me bananas (and towards Plack [again!]).

Thanks,

David


Re: How Do I change the Document Root Per Request

Posted by Fred Moyer <fr...@redhotpenguin.com>.
Can you show us your relevant httpd.conf snippet? No guesses right
now, but that might help.

On Sun, Mar 1, 2015 at 4:46 PM, David E. Wheeler <da...@justatheory.com> wrote:
> Hi,
>
> I want to set the document root for a request to map to the basic auth username. I tried this in a PerlFixupHandler:
>
>     sub handler {
>         my $r = shift;
>
>         # We only want to do this once per request.
>         return DECLINED unless $r->is_initial_req;
>
>         # Get the username.
>         my $user = $r->user or return HTTP_UNAUTHORIZED;
>
>         # Return forbidden if the username subdiectory does not exist.
>         my $doc_root = File::Spec->catdir($r->document_root, $user);
>         return HTTP_FORBIDDEN unless -d $doc_root;
>
>         # Set the document root for the duration of this request and return.
>         $r->document_root($doc_root);
>         return DECLINED;
>     }
>
> But alas, it still serves the original document root. How can I get it to change the document root on a per-request basis? If I cant, should I change the URI or the filename, instead?
>
> Thnks,
>
> David