caddyserver / caddy

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
https://caddyserver.com
Apache License 2.0
57.99k stars 4.03k forks source link

PATH_INFO is empty with basic fastcgi Caddyfile #3718

Closed SteffenDE closed 3 years ago

SteffenDE commented 4 years ago

Hey everyone, I'm trying to move from Caddy 1 to Caddy 2 and was in the process of installing a Moodle server (PHP) when I encountered the following issue:

moodle.domain {
    root * /datapool/www/moodle_base/moodle

    php_fastcgi 127.0.0.1:9000 {
        env SERVER_SOFTWARE Apache
    }
}

When doing a GET request with path https://moodle.domain/lib/javascript.php/1599824490/lib/requirejs/require.min.js the PATH_INFO Variable (should be /1599824490/lib/requirejs/require.min.js) is not set correctly:

{"level":"debug","ts":1599828392.7683938,"logger":"http.reverse_proxy.transport.fastcgi","msg":"roundtrip","request":{"remote_addr":"12.34.56.78:1234","proto":"HTTP/2.0","method":"GET","host":"moodle.domain","uri":"/lib/javascript.php","headers":{"Dnt":["1"],"Upgrade-Insecure-Requests":["1"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_16_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36"],"Sec-Fetch-Dest":["document"],"Cookie":["MoodleSession=sfenfg19jlpj4mt1gm9um41jtn"],"Cache-Control":["max-age=0"],"Sec-Fetch-User":["?1"],"X-Forwarded-Proto":["https"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"],"Accept-Language":["de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7"],"X-Forwarded-For":["12.34.56.78"],"Sec-Fetch-Site":["none"],"Accept-Encoding":["gzip, deflate, br"],"Sec-Fetch-Mode":["navigate"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","proto_mutual":true,"server_name":"moodle.domain"}},"dial":"127.0.0.1:9000","env":{"AUTH_TYPE":"","CONTENT_LENGTH":"","CONTENT_TYPE":"","DOCUMENT_ROOT":"/datapool/www/moodle_base/moodle","DOCUMENT_URI":"/lib/javascript.php","GATEWAY_INTERFACE":"CGI/1.1","HTTPS":"on","HTTP_ACCEPT":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9","HTTP_ACCEPT_ENCODING":"gzip, deflate, br","HTTP_ACCEPT_LANGUAGE":"de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7","HTTP_CACHE_CONTROL":"max-age=0","HTTP_COOKIE":"MoodleSession=sfenfg19jlpj4mt1gm9um41jtn","HTTP_DNT":"1","HTTP_HOST":"moodle.domain","HTTP_SEC_FETCH_DEST":"document","HTTP_SEC_FETCH_MODE":"navigate","HTTP_SEC_FETCH_SITE":"none","HTTP_SEC_FETCH_USER":"?1","HTTP_UPGRADE_INSECURE_REQUESTS":"1","HTTP_USER_AGENT":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_16_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36","HTTP_X_FORWARDED_FOR":"12.34.56.78","HTTP_X_FORWARDED_PROTO":"1234","PATH_INFO":"","QUERY_STRING":"","REMOTE_ADDR":"12.34.56.78","REMOTE_HOST":"12.34.56.78","REMOTE_IDENT":"","REMOTE_PORT":"1234","REMOTE_USER":"","REQUEST_METHOD":"GET","REQUEST_SCHEME":"https","REQUEST_URI":"/lib/javascript.php/1599824490/lib/requirejs/require.min.js","SCRIPT_FILENAME":"/datapool/www/moodle_base/moodle/lib/javascript.php","SCRIPT_NAME":"/lib/javascript.php","SERVER_NAME":"moodle.domain","SERVER_PROTOCOL":"HTTP/2.0","SERVER_SOFTWARE":"Apache","SSL_CIPHER":"TLS_AES_128_GCM_SHA256","SSL_PROTOCOL":"TLSv1.3"}}

While debugging I saw that the URL.Path is already just /lib/javascript.php here and the rest is missing: https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go#L197

I temporarily fixed this by using the origRequest, but this causes other issues as the (correct) rewrite of / to /index.php fails now.

        origReq, ok := r.Context().Value(caddyhttp.OriginalRequestCtxKey).(http.Request)
        if !ok {
                // some requests, like active health checks, don't add this to
                // the request context, so we can just use the current URL
                origReq = *r
        }
        fpath := origReq.URL.Path

I guess there is something going on with the default rewrites as there already is a split_path option there.

(caddy versions affected: 2.1.1, 2.2.0-rc.1, master)

francislavoie commented 4 years ago

Hmm, it's funky that they use PHP to serve JS, but okay :stuck_out_tongue:

I'll need to take a closer look at all this, I'll come back to this one this weekend. Thanks for the report!

francislavoie commented 4 years ago

Okay, I see what's going on. This is pretty tricky.

So, I'm comparing with the code from Caddy v1 which apparently did work correctly: https://github.com/caddyserver/caddy/blob/v1/caddyhttp/fastcgi/fastcgi.go

The actual fastcgi code is pretty much the same in v2, but the difference comes in how the request prep happens in v2 compared to v1.

In v1, this wasn't a problem because the index.php fallback wasn't handled via a rewrite internally beforehand, it was just directly handled in ServeHTTP without updating the requested path.

In v2, instead it works by delegating the decision for index.php to the built-in try_files behaviour of the php_fastcgi directive. See the expanded form to see the stuff it's doing: https://caddyserver.com/docs/caddyfile/directives/php_fastcgi#expanded-form

As you said, we could use the original URL to solve this case with .php being found in the URL, and this does work (tried moving the origReq block of code up and using that instead of fpath when splitting), but I'm pretty worried about edgecases.

I tried doing some research for documentation to figure out how PATH_INFO is meant to be handled depending on the original URL from the browser. E.g. we know that /index.php/foo should have PATH_INFO set to /foo, but should the request to /foo also have PATH_INFO set to /foo, since the server rewrites it to be handled by index.php? I'm leaning towards no, but it's unclear, and the RFC https://tools.ietf.org/html/rfc3875#section-4.1.5 is not specific about that.

Also, with that change, it would also make requests like /foo.php/foo that get rewritten to index.php also have /foo in PATH_INFO even though /index.php is what actually gets run, not /foo.php. I think we might need some extra logic to only split if the index file is found in the original path? ๐Ÿค”

I would have to spin up an environment with nginx and/or apache with mod_cgi to verify how they behave. My env is kinda in a state that would make that tricky right now though, so I haven't done that yet. I wouldn't mind help there if you're capable of testing that our quickly.

There's also security implications with any change we make here, so I'm extra nervous about it.

scara commented 4 years ago

Hi @francislavoie,

Hmm, it's funky that they use PHP to serve JS, but okay

Yes, but that is a requirement due to the way Moodle (2.x+) manages (via URL routing) its own File Storage - content-driven, via hashing - and generally the "version" (i.e revision) of the files to be served, to force an "aggressive" HTTP caching on the assets but when updating Moodle to the next version, which requires the revision to be bumped i.e. any file coming from theming and front-end logic, including JS, is served in that way. Being Moodle a PHP-only (complex) web app, file handling is just done via PHP - strictly the initial routing since when X-Sendfile support is available, the web server could jump into serving the right "file version". That's why PATH_INFO is relevant in Moodle: someone uses rewriting on their (old ) setup since Moodle could provide the same "path info" data via Query Param ?file= but it breaks some content - the HTML-based since its assets are related to the path of the HTML file.

That being said, I'll give a try on Caddy 2 and PHP-FPM, without Moodle to see how PATH_INFO actually works there i.e. outside any web application but using a plain <?php phpinfo();: indeed the OP is faking the "server software" using Apache - Moodle check the web server vendor and blocks those unsupported - and maybe Moodle is using some tricks valid w/ Apache but not w/ Caddy.

Refs:

HTH, Matteo

francislavoie commented 4 years ago

To be clear, I understand why they do it that way, I think I just find it strange that they use a separate PHP entrypoint for serving JS rather than just using index.php for everything, as most modern PHP frameworks do these days.

At my dayjob (I'm primarily a PHP dev), we have a very similar static asset management system, but we don't serve the files the same way. We instead use manifests that point to files, plus the file checksums as query params for cache busting and such.

Most PHP frameworks I'm aware of use REQUEST_URI which has full information instead of PATH_INFO, and just remove the script from the URI themselves, since SCRIPT_NAME is also passed.

We should still fix PATH_INFO though, obviously. Best to align behaviour with that of other webservers in this area, to avoid surprises for users. As I wrote, it's just unclear how the edgecases of PATH_INFO are meant to behave because the RFC is not specific about that, and a few hours of googling and reading nginx/apache docs didn't get me much further either.

I appreciate the help ๐Ÿ˜„ let me know what you find out!

scara commented 4 years ago

as most modern PHP frameworks do these days.

Eh eh... Moodle is there since 18yrs and has its own (opinionated) framework since then1 , which is IMHO nice here since we've found a potential issue in considering index.php the preferred way to manage routing.

BTW adding Caddy support in Moodle could be also a matter to code there what you quickly described, to "fix" PATH _INFO at runtime - see here what done in the years to support those listed web servers.

let me know what you find out!

I'll do some tests in my spare time, slowly but will do ๐Ÿ˜‰.

HTH, Matteo

1 There is a long and open discussion about using an "industry" one vs the current one, which is constantly, and IMHO effectively, evolving to keep up with the times.

SteffenDE commented 4 years ago

I greatly appreciate that someone is looking into this, as I sadly don't have the time to investigate this myself. For the meantime, I'm running Caddy v1 and proxy the Moodle requests from Caddy 2 to the old Caddy instance ๐Ÿ˜…

In the past, I've been running an extra Apache just for Moodle, but after discovering that setting the environment variable to Apache saves me from doing this, Moodle was running perfectly for the last two (or so) years on Caddy. Great work and a huge thank you to both the Moodle and Caddy team!

scara commented 4 years ago

HI @francislavoie, looking at the debug log to get a rough idea of what could be done on the Moodle side - though I'm pretty sure we should fix it at caddy side - I see what you're saying (REQUEST_URI - SCRIPT_NAME):

{
   "level":"debug",
   // cut
   },
   "dial":"127.0.0.1:9000",
   "env":{
   // cut
      "DOCUMENT_ROOT":"/datapool/www/moodle_base/moodle",
      "DOCUMENT_URI":"/lib/javascript.php",
      "GATEWAY_INTERFACE":"CGI/1.1",
      "HTTPS":"on",
   // cut
      "PATH_INFO":"",
      "QUERY_STRING":"",
      "REQUEST_SCHEME":"https",
      "REQUEST_URI":"/lib/javascript.php/1599824490/lib/requirejs/require.min.js",
      "SCRIPT_FILENAME":"/datapool/www/moodle_base/moodle/lib/javascript.php",
      "SCRIPT_NAME":"/lib/javascript.php",
      "SERVER_NAME":"moodle.domain",
      "SERVER_PROTOCOL":"HTTP/2.0",
      "SERVER_SOFTWARE":"Apache",
   // cut
   }
}

but playing a bit - I'm completely a newbie here w/ caddy so just trying via Caddyfile for my very first time - using a docker compose and pointing to a <?php phpinfo(INFO_VARIABLES); it looks like to work if I use a bit of the expanded form, with a path_regexp instead of path:

:80

# Set this path to your site's directory.
root * /usr/share/caddy

# https://caddyserver.com/docs/caddyfile/directives/php_fastcgi#expanded-form
# Proxy PHP files to the FastCGI responder
@phpFiles {
    #path *.php
    # Match what would be a PHP resource file eventually with 'PATH_INFO',
    # which follows "the part that identifies the script itself" (RFC 3875 ยง4.1.5):
    #      PATH_INFO = "" | ( "/" path )
    #      path      = lsegment *( "/" lsegment )
    #      lsegment  = *lchar
    #      lchar     = <any TEXT or CTL except "/">
    path_regexp php_fastcgi ^(.+\.php)(.*)$
}
reverse_proxy @phpFiles php-fpm:9000 {
    transport fastcgi {
        split .php
        env SERVER_SOFTWARE Apache
    }
}

i.e. something like what required to setup PHP on nginx, which IMHO looks like plain and safe. Next step, I'll get a bit deeper into the expanded form before looking at the source code.

@SteffenDE would you like to give it a try just for fun?

HTH, Matteo

francislavoie commented 4 years ago

Yeah that's essentially as expected.

The issue really is that the file matcher (with split_path) -> rewrite (the second block in the expanded form) is turning the path into /lib/javascript.php (stripping off the rest), which the fastcgi transport then deals with.

In Caddy v1, the "rewrite" part was essentially internal to the fastcgi directive, but in v2, we have the file matcher and rewrite handler as pieces that are separate (much more modular that way, less assumptions in individual handlers) but that "hides" the real path from the fastcgi handler (it can still be grabbed by looking at the origReq though, which is right).

The open question is how exactly does PATH_INFO need to behave in the various edgecases.

francislavoie commented 4 years ago

Oh actually I just though of an idea... since the split_path part of the file matcher is aware of both halves of the path, I think we can safely store the second half in a placeholder which the fastcgi transport can later read, if available. That might do the trick...

I'll try out an implementation later this evening or tomorrow. Still would love the help confirming that it behaves the same as apache and/or nginx.

SteffenDE commented 4 years ago

@scara your code seems to work except of course that / is not rewritten to index.php and therefore pages not ending in filename.php are broken. /lib/javascript.php/1599824490/lib/requirejs/require.min.js returns the correct file ๐Ÿ˜ƒ

scara commented 4 years ago

Hi @SteffenDE; thanks for your quick try&reply: that was expected, just a small step ahead.

Next step: create a docker compose with caddy, apache and nginx serving the same sample, info.php and index.php (<?php phpinfo(INFO_VARIABLES);), via the same PHP-FPM engine instance, to look for any difference among the different vendors to help @francislavoie (and @mholt).

HTH, Matteo

scara commented 4 years ago

Hi @francislavoie,

Oh actually I just though of an idea... since the split_path part of the file matcher is aware of both halves of the path, I think we can safely store the second half in a placeholder which the fastcgi transport can later read, if available. That might do the trick...

Sounds promising... just keep an eye to query params too just in case /usr/share/caddy/somefile.php should be the target of the relative HTTP path /web-server/vendors/caddy?p=2&no=10#top being somefile.php defined as the index file i.e. fully equivalent w/ calling /somefile.php/web-server/vendors/caddy?p=2&no=10#top.

Side note: https://www.nginx.com/resources/wiki/start/topics/examples/phpfcgi/#connecting-nginx-to-php-fpm give you an example of how nginx, functionally speaking, works:

  1. define the file match
  2. define how to extract PATH_INFO data which is maybe too much instead of your split
  3. avoid serving everything but existing files
  4. keep knowledge of what is the index file

Wondering if adding index to the fastcgi transport would be a better option even if it looks like both transport and file handling should be single responsibility items.

Still slowly studying what is pretty "new to my eyes" - kudos to caddy docs ๐Ÿ˜‰ .

HTH, Matteo

francislavoie commented 4 years ago

Sounds promising... just keep an eye to query params too just in case

The file matcher is passed {path}, i.e. not the full URI, just the path part. {uri} could be used instead if that was desired (but it's not, in this case).

define how to extract PATH_INFO data which is maybe too much instead of your split

Yeah, we use split_path as a fast substring split, it should be faster than regexp, and generally should be good enough.

avoid serving everything but existing files

Here, we just fallback to index.php if one exists, instead of returning a 404. If there's no index.php, then the file_server directive will return a 404 in turn.

keep knowledge of what is the index file

The file matcher handles this, and it's configurable in the php_fastcgi directive if necessary (rarely is). I don't think fastcgi needs to care after that point. It just needs to know the "rest of the URL" after the rewrite happens, I think. That's the only piece missing.

francislavoie commented 4 years ago

Alright, did a quick implementation in https://github.com/caddyserver/caddy/pull/3739 and it seems to work as expected. Basically, the file matcher will set a placeholder if a SplitPath happened, and the fastcgi transport will pull from that placeholder if it didn't perform a split itself.

Here's some path -> path_info pairs with this change (where index.php and lib/javascript.php are files that exist)

"/lib/javascript.php/1599824490/lib/requirejs/require.min.js" -> "/1599824490/lib/requirejs/require.min.js"
"/index.php/foo" -> "/foo"
"/foo" -> ""
"/foo.php/foo" -> ""

I think this is probably the behaviour we're expecting. I'd love if you can confirm this is how it works in nginx and/or apache @scara

scara commented 4 years ago

Hi @francislavoie, great news!

I did some tests and I can confirm it: I'm preparing a rough test suite and will share via my GH account.

HTH, Matteo

scara commented 4 years ago

FYI: https://github.com/scara/caddy-pathinfo-tests

HTH, Matteo

francislavoie commented 4 years ago

Sweet!

Do you mind posting the test results?

Edit: Ah nevermind, I see that the results are listed as fixtured in here: https://github.com/scara/caddy-pathinfo-tests/blob/master/www/tests/testsuite.php

Nice!

scara commented 4 years ago

Hi @francislavoie, here is the run output using the current 2.1.1 using just a bit of expanded form i.e. no php_fastcgi directive as-is:

Testing 'http://caddy'...
 => Contacting URL: 'http://caddy/lib/javascript.php/1599824490/lib/requirejs/require.min.js'...
    Done.
 => Contacting URL: 'http://caddy/index.php/foo'...
    Done.
 => Contacting URL: 'http://caddy/index.php/foo?a=1&b=2'...
    Done.
 => Contacting URL: 'http://caddy/foo'...
    Done.
 => Contacting URL: 'http://caddy/foo.php/foo'...
    Done.
 => Contacting URL: 'http://caddy/file.php/filename_UTF8_en+coded_ใใ‚ŒใŒๅ‹•ไฝœใ™ใ‚‹ใฏใš.png'...
    Done.
 => Contacting URL: 'http://caddy/index.php/some%20%20whitespaces'...
    Done.

Testing 'http://httpd'...
 => Contacting URL: 'http://httpd/lib/javascript.php/1599824490/lib/requirejs/require.min.js'...
    Done.
 => Contacting URL: 'http://httpd/index.php/foo'...
    Done.
 => Contacting URL: 'http://httpd/index.php/foo?a=1&b=2'...
    Done.
 => Contacting URL: 'http://httpd/foo'...
    Done.
 => Contacting URL: 'http://httpd/foo.php/foo'...
    Done.
 => Contacting URL: 'http://httpd/file.php/filename_UTF8_en+coded_ใใ‚ŒใŒๅ‹•ไฝœใ™ใ‚‹ใฏใš.png'...
    Done.
 => Contacting URL: 'http://httpd/index.php/some%20%20whitespaces'...
    Done.

Testing 'http://nginx'...
 => Contacting URL: 'http://nginx/lib/javascript.php/1599824490/lib/requirejs/require.min.js'...
    Done.
 => Contacting URL: 'http://nginx/index.php/foo'...
    Done.
 => Contacting URL: 'http://nginx/index.php/foo?a=1&b=2'...
    Done.
 => Contacting URL: 'http://nginx/foo'...
    Done.
 => Contacting URL: 'http://nginx/foo.php/foo'...
    Done.
 => Contacting URL: 'http://nginx/file.php/filename_UTF8_en+coded_ใใ‚ŒใŒๅ‹•ไฝœใ™ใ‚‹ใฏใš.png'...
    Done.
 => Contacting URL: 'http://nginx/index.php/some%20%20whitespaces'...
    Done.

Will do an image to rebuild your PR and trying the same run but using the new binary and the "vanilla" php_fastcgi directive.

HTH, Matteo

francislavoie commented 4 years ago

I just tried it out with my PR, I got this result:

Testing 'http://caddy'...
 => Contacting URL: 'http://caddy/lib/javascript.php/1599824490/lib/requirejs/require.min.js'...
    Done.
 => Contacting URL: 'http://caddy/index.php/foo'...
    Done.
 => Contacting URL: 'http://caddy/index.php/foo?a=1&b=2'...
    Done.
 => Contacting URL: 'http://caddy/foo'...
     Test 'No rewriting to the index file i.e. non existing PHP file' failed:
     => KO PATH_INFO. Expected: '<false>' vs Actual: ''.
     Test 'No rewriting to the index file i.e. non existing PHP file' failed:
     => KO QUERY_STRING. Expected: '<false>' vs Actual: ''.
    Done.
 => Contacting URL: 'http://caddy/foo.php/foo'...
     Test 'Non existing PHP file' failed:
     => KO PATH_INFO. Expected: '<false>' vs Actual: ''.
     Test 'Non existing PHP file' failed:
     => KO QUERY_STRING. Expected: '<false>' vs Actual: ''.
    Done.
 => Contacting URL: 'http://caddy/file.php/filename_UTF8_en+coded_ใใ‚ŒใŒๅ‹•ไฝœใ™ใ‚‹ใฏใš.png'...
    Done.
 => Contacting URL: 'http://caddy/index.php/some%20%20whitespaces'...
    Done.

That seems successful to me I think?

scara commented 4 years ago

Hi @francislavoie,

That seems successful to me I think?

Not sure: will look my evening. The aim is to look if redirection will add something: <false> means I cannot process that PHP file i.e. I cannot find a match for that SERVER VARIABLE while an empty string means I'm processing that file as PHP since I've found in the the output kind of [PATH_INFO> =>.

You can look at the actual HTTP response by opening:

since I've exposed the three web servers for debugging purposes.

At a first glance - i.e. error prone for sure :wink: - there could be an issue w/ that output: a. no redir to the default index file (index.php): not the expected result b. redir to the default idex file (index.php): not the expected result, since PATH_INFO should be /foo and /foo.php/foo respectively.

I could be wrong, need spare time to look into it.

HTH, Matteo

francislavoie commented 4 years ago

http://localhost:8080/foo and http://localhost:8080/foo.php/foo are both giving me empty PATH_INFO, e.g.

Array
(
    [PHP_EXTRA_CONFIGURE_ARGS] => --enable-fpm --with-fpm-user=www-data --with-fpm-group=www-data --disable-cgi
    [HOSTNAME] => ff8f136f10f4
    [PHP_INI_DIR] => /usr/local/etc/php
    [HOME] => /var/www
    [PHP_LDFLAGS] => -Wl,-O1 -pie
    [PHP_CFLAGS] => -fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
    [PHP_MD5] => 
    [PHP_VERSION] => 7.4.10
    [GPG_KEYS] => 42670A7FE4D0441C8E4632349E4FDC074A4EF02D 5A52880781F755608BF815FC910DEB46F53EA312
    [PHP_CPPFLAGS] => -fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
    [PHP_ASC_URL] => https://www.php.net/distributions/php-7.4.10.tar.xz.asc
    [PHP_URL] => https://www.php.net/distributions/php-7.4.10.tar.xz
    [PATH] => /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
    [PHPIZE_DEPS] => autoconf       dpkg-dev        file        g++         gcc         libc-dev        make        pkg-config      re2c
    [PWD] => /var/www/html
    [PHP_SHA256] => c2d90b00b14284588a787b100dee54c2400e7db995b457864d66f00ad64fb010
    [USER] => www-data
    [REMOTE_HOST] => 172.18.0.1
    [HTTP_CACHE_CONTROL] => max-age=0
    [REQUEST_URI] => /foo.php/foo
    [SERVER_PROTOCOL] => HTTP/1.1
    [CONTENT_LENGTH] => 0
    [HTTP_ACCEPT_LANGUAGE] => en-US,en;q=0.5
    [SCRIPT_NAME] => /index.php
    [REQUEST_METHOD] => GET
    [AUTH_TYPE] => 
    [HTTP_X_FORWARDED_PROTO] => http
    [HTTP_ACCEPT] => text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
    [HTTP_X_FORWARDED_FOR] => 172.18.0.1
    [SERVER_SOFTWARE] => Apache
    [GATEWAY_INTERFACE] => CGI/1.1
    [REMOTE_ADDR] => 172.18.0.1
    [HTTP_ACCEPT_ENCODING] => gzip, deflate
    [HTTP_UPGRADE_INSECURE_REQUESTS] => 1
    [SCRIPT_FILENAME] => /usr/share/caddy/index.php
    [CONTENT_TYPE] => 
    [DOCUMENT_ROOT] => /usr/share/caddy
    [SERVER_NAME] => localhost
    [REMOTE_USER] => 
    [HTTP_HOST] => localhost:8080
    [HTTP_USER_AGENT] => Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0
    [QUERY_STRING] => 
    [REQUEST_SCHEME] => http
    [REMOTE_PORT] => 51082
    [REMOTE_IDENT] => 
    [SERVER_PORT] => 8080
    [DOCUMENT_URI] => index.php
    [PATH_INFO] => 
    [FCGI_ROLE] => RESPONDER
    [PHP_SELF] => /index.php
    [REQUEST_TIME_FLOAT] => 1600675588.2357
    [REQUEST_TIME] => 1600675588
    [argv] => Array
        (
        )

    [argc] => 0
)

So I think this is fine. Caddy will fallback to index.php if the file doesn't exist, and I guess that's slightly different than the nginx or apache configs. But that's not a relevant difference here I think.

That's with this config:

:80

root * /usr/share/caddy

php_fastcgi php-fpm:9000 {
    env SERVER_SOFTWARE Apache
}

file_server
scara commented 4 years ago

Hi @francislavoie,

So I think this is fine. Caddy will fallback to index.php if the file doesn't exist, and I guess that's slightly different than the nginx or apache configs. But that's not a relevant difference here I think.

The key point IMHO is fallback vs redirection including the original path (strong remapping): if it is a plain fallback, yes you're right :+1:. I'm not confident w/ Caddy but if that could be the default configuration, it would be fine since I can change it and decide to get a "Not found" error.

Just a quick thought for myself: how can I obtain the same behavior e.g. in Apache? Guessing something like:

DirectoryIndex index.php
RewriteEngine On

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^[^.]+$ index.php [L]

or using FallbackResource. Something similar should apply to Nginx too. (Just to align the web server configurations to keep comparing apples w/ apples).

HTH, Matteo

scara commented 4 years ago

Adding a note to myself on my previous comment:

Array
(
    [HOSTNAME] => ff8f136f10f4
// ...
    [REQUEST_URI] => /foo.php/foo
// ...
    [SCRIPT_NAME] => /index.php
// ...
    [SERVER_SOFTWARE] => Apache
// ...
    [SCRIPT_FILENAME] => /usr/share/caddy/index.php
// ...
    [DOCUMENT_ROOT] => /usr/share/caddy
// ...
    [DOCUMENT_URI] => index.php
    [PATH_INFO] => 
// ...
   [argc] => 0
)

guessing the routing strategy of modern framework to be: given SCRIPT_FILENAME be DOCUMENT_ROOT + SCRIPT_NAME, REQUEST_URI starts with SCRIPT_NAME? Otherwise; we've found a fallback routing.

@francislavoie, looking at your HTTP response I've found a potential issue - a regression? - based on the previous one from the debug log of @SteffenDE and/or the output from "my tests", e.g. Apache and even Caddy v2.1.1.1:

      "DOCUMENT_URI":"/lib/javascript.php",

vs your:

    [DOCUMENT_URI] => index.php

It looks like we're missing a leading slash. Isn't it?

HTH, Matteo

francislavoie commented 4 years ago

Yeah I think that's correct re Apache config. Fallback to index.php is how modern frameworks are able to have their own routing (e.g. Laravel, Symfony, etc).

Re leading slash, that's just because the rewrite in try_files doesn't have the leading slash for the index.php fallback. It's not a regression. That path is relative to the webroot anyways, so it's not strictly necessary.

If you want to try my PR, you can grab the CI artifact for Linux (look at the GitHub actions output for the Tests, you can download the artifact from there), and use a Docker volume to replace it at /usr/bin/caddy.

scara commented 4 years ago

If you want to try my PR, you can grab the CI artifact for Linux (look at the GitHub actions output for the Tests, you can download the artifact from there), and use a Docker volume to replace it at /usr/bin/caddy.

TNX! Will do my evening.

HTH, Matteo

scara commented 4 years ago

Hi @francislavoie, I've update the "test suite" by using index.php as fallback resource in each web server instance: the only difference now in Caddy is on PATH_INFO being always defined but empty just for both /foo and /foo.php/foo which looks like not an issue in PHP: those using empty() will be fine and those using isset() will have an empty value.

@SteffenDE, would you mind to give caddy_Linux_go1.14_3e577ef (from https://github.com/caddyserver/caddy/actions/runs/263950314) a try in your setup w/o changing anything of your configuration but the caddy binary? TIA!

HTH, Matteo

SteffenDE commented 4 years ago

I can confirm that using the binary from 3e577ef works with my Moodle setup using the default php_fastcgi directive ๐Ÿ˜ƒ

mholt commented 4 years ago

Excellent!

It's slightly too bad about the potential edge case(s) the fix introduces, but probably a worthwhile trade-off, especially if the edge cases involves the user configuring something weird, right?

Looking forward to getting it in as one of the first commits for the 2.3 tree, after 2.2 is released, hopefully this week.

Edit: Heh, it was the last commit for the 2.3 tree.

flaviobarros commented 3 years ago

I just tried to replace apache for caddy but didn't get it running. It's not clear for me what is the solution.

francislavoie commented 3 years ago

@flaviobarros Please ask your usage questions on the Caddy community forums. Don't forget to fill out the thread template so we can help you!