Q: We run the HTTP server Powered by Apache on port 80 on our i. We currently have a lot of applications written for CGIDEV2 but would now like to try out some PHP applications. We'd really like to stick with just port 80 and not open up additional ports, rather than running a separate instance on port 89. Is that possible?
A: Actually, the PHP module for Apache that's provided with Zend Core will run only in PASE and won't work with the native Apache server. Zend has things set up so that the native Apache acts as a "reverse proxy" so that when you access port 89, it will (under the covers) proxy your connection to port 8000 to run against the PASE instance of Apache. Zend does this so that you can use native features (e.g., CGI and validation against IBM i user profiles) but still use the PASE version of PHP.
Therefore, if you want to enable your existing HTTP server that's on port 80 to provide access to PHP, you need to configure it to send PHP requests over the reverse proxy as well. This article shows you how.
By default, Zend Core creates an Apache configuration file named /www/zendcore/conf/httpd.conf in the IFS. Amongst other things (mostly logging directives) ,you will find the following:
LoadModule proxy_module /QSYS.LIB/QHTTPSVR.LIB/QZSRCORE.SRVPGM LoadModule proxy_http_module /QSYS.LIB/QHTTPSVR.LIB/QZSRCORE.SRVPGM LoadModule proxy_connect_module /QSYS.LIB/QHTTPSVR.LIB/QZSRCORE.SRVPGM LoadModule proxy_ftp_module /QSYS.LIB/QHTTPSVR.LIB/QZSRCORE.SRVPGM ProxyPass / http://127.0.0.1:8000/ ProxyPassReverse / http://127.0.0.1:8000/
Apache has the capability of plugging in modules that add functionality to the server. The LoadModule directives tell Apache to load the plug-in modules that provide proxy support. These plug-ins are provided with the HTTP server on IBM i, so all you should need to do is copy/paste the LoadModule statements to your normal (port 80) configuration file.
The ProxyPass and ProxyPassReverse directives are used to configure a reverse proxy.
A "normal" proxy is a program that handles a network request for you. For example, your company may set up a proxy to allow access to the web. This is a common setup, because it lets all users on your LAN share a cache of the websites visited, so if multiple people go to the same site, it runs much faster. It also provides a place where you can block access to certain sites or require a user ID/password for certain sites, thus giving you control over what your employees can do on the web.
Here's what happens when you use a proxy: You configure your browser with the host name and port number of the proxy. Whenever you make an HTTP request, it actually connects to the proxy rather than the destination website. It asks the proxy for the document, and the proxy then makes a separate request to the destination site to retrieve it.
You could say that a normal proxy receives requests for lots of different sites and brings them back to one central point.
A reverse proxy does the opposite. You don't need to configure a reverse proxy in your browser, because the browser thinks it's the destination server and that it's making a request of that server (even though, technically, it's not). The reverse proxy examines your URL and determines that it should connect to a different computer to get the data, then retrieves the data and returns it to the browser.
You could say that a reverse proxy receives requests for a single site but goes and gets the data from multiple sites. The opposite (or "reverse") of a normal proxy.
In the case of Zend Core, the reverse is used to receive requests from a native HTTP server and transparently connect to a background PASE HTTP server, run the PHP script, and bring the output back through the proxy to the browser.
Consider those directives again:
ProxyPass / http://127.0.0.1:8000/ ProxyPassReverse / http://127.0.0.1:8000/
ProxyPass says that when a URL starts with the / character, it should connect the reverse proxy to IP address 127.0.0.1 on port 8000. IP address 127.0.0.1 always means "loopback" and will connect to the same computer the proxy is running on. So it essentially forwards requests from port 89 to 8000 where the PASE version of Apache is running.
The ProxyPassReverse directive is for any URLs that are sent back through the proxy from the PASE server. If the data sent back contains http://127.0.0.1:8000/ it will be changed back to /, therefore fixing redirects that might've otherwise been broken by ProxyPass.
Since all URLs begin with /, these directives will force the use of the proxy for every request, no matter what it is. That's not what you want, because it'll prevent you from using CGIDEV2. The request will be passed on to the PASE server, and the PASE server doesn't know how to run an RPG program through its CGI interface.
So you need to be more selective about what you proxy. Don't proxy everything, just proxy PHP documents.
For example, you could do this:
ProxyPass /php http://127.0.0.1:8000 ProxyPassReverse /php http://127.0.0.1:8000
This basically says that any URL beginning with /php would be redirected to the PASE instance on port 8000. For example, if you specified the following URL:
http://example.com/php/limesoda.php
It would see that the URL starts with /php (after the host name) and so would proxy the request to the following URL:
http://127.0.0.1:8000/limesoda.php
If you think about it, it basically means that anything that begins with /php gets redirected to the same URL without /php on the PASE end of things.
If all you wanted to do is run the CGIDEV2 software from one directory, and the PHP software from another directory (but also on port 80) then you should be set. Just add the preceding LoadModule, ProxyPass and ProxyPassReverse directives to your Apache configuration, and you're set.
However, I personally prefer to have it work based on the extension of the file. I want my files that end in .php to be sent through the proxy, and I want files that don't end in .php to be handled by the native HTTP server. The ProxyPass directive can't do that, but it's possible to achieve the same thing by using Apache's RewriteRule directive.
To do that, you'd code it like this:
RewriteEngine On RewriteRule ^/(.+)\.php(.*) http://127.0.0.1:8000/$1.php$2 [P]
This directive is definitely harder to read than the other one! It uses regular expressions, which are very powerful, but in my opinion usually result in cryptic looking code.
Here's what the parts of the preceding directives do:
Understanding the ^/(.+)\.php(.*) part is a bit tricky. Ignore the parentheses for now and consider ^/.+\.php.*/. This says, "The first character must be a /, followed by one or more of any character character (. symbolizes any character, and + means "one or more"), followed by .php, followed by zero or more of any character. (Again, . means "any character," and * means "zero or more.")
The parentheses designate pieces of the pattern that can be referenced as variables. Whatever matches the .+ (one or more of any character that comes after the leading / and before the .php) will be placed in variable 1. Whatever matches the .* (any string that comes after the .php) will be placed in variable 2.
When creating the output, you'll notice the $1 and $2 fields in the new URL. These will be replaced with whatever matched the expressions in parentheses.
The RewriteRule provides only the function of the ProxyPass keyword; it does not provide the same function as ProxyPassReverse. To enable the rewriting of redirect messages sent back from the browser, I'll need to continue to use the ProxyPassReverse keyword. The tricky part is, I can't use wildcards! So I'll have it rewrite any redirects the same way I did before . . . they'll end up in the /php folder. Then, I'll continue to reverse proxy the /php folder, so that it continues to use the Zend Core PHP engine.
The resulting rewrite keywords look like this:
RewriteEngine On RewriteRule ^/(.+)\.php(.*) http://127.0.0.1:8000/$1.php$2 [P] ProxyPass /php http://127.0.0.1:8000 ProxyPassReverse /php http://127.0.0.1:8000
Now that I've done that, I can run PHP and CGIDEV2 both from port 80. Whenever I send a URL that ends in .php, it runs the PHP engine in PASE. Whenever I have a file that doesn't end in .php, it's run by the native server, so I can use RPG and CGIDEV2 if I want.
yeah, I guess ProxyPassReverse is the opposite of ProxyPass, but that might be misleading. You're correct that it's for data coming back from the PASE instance to the native one. But have you figured out why that's necessary? It's all one connection, after all, why do you need two directives? Really, it's for redirects.
Under the covers, the HTTP request sent to the proxy looks like this:
Thanks to the ProxyPass directive, it gets re-written when sent to the proxy. So because of ProxyPass, the proxy would receive a request that looks like this instead:
As you can (hopefully) see, the ProxyPass directive removed the "/php" from the URI. That will work fine. Problem solved, right? But what happens when the PASE server doesn't return the actual data, but instead tells the browser to go to a different page? Like this (this would be the response to the request)
This response is telling it to go get another document instead of displaying the one you asked for. This type of redirect is very common. But, the PASE server doesn't know it's talking to a proxy. Or even if it figured out that it was talking to a proxy, it wouldn't know what the URI to connect to the proxy would be. So it's returning it's own name and it's own path to the program it wants you to run. Which won't work, since you can't connect directly to the PASE server, you have to connect to the native one.
That's what ProxyPassReverse is for. It rewrites the "Location:" directive. It will change it to route the request through the proxy, and it'll insert the /php again. Now the browser will make a 2nd request -- a new request -- pointing to the URI in the "location:" directive. If ProxyPassReverse has done it's job correctly, that URI will point back to the proxy, and will contain the /php, so the proxy can correctly send the request to the PASE instance again.
At this point, you should understand what ProxyPass and ProxyPassReverse do.
Now what happens if you replace ProxyPass with RewriteRule? Lets say you omit the ProxyPass entirely so you have this:
Now RewriteRule takes the place of ProxyPass. Does it's job, and all is well -- until one of these "302 redirects" is sent... when that happens, ProxyPassReverse has to kick in and rewrite it (just like it did when you used ProxyPass). But ProxyPassReverse doesn't have the sophisticated wildcard stuff that RewriteRule has. So I can't simply reverse what RewriteRule does... the best I can do is what I was doing before... insert a special directory name that the proxy will understand.
Let's walk through that example. The request made to the proxy looks like this:
The proxy sees the .php in the URI, and runs the RewriteRule so it sends it on to the PASE instance like this, without any need for the extra /php directory in the URI.
The response could potentially come back from the PASE instance like this:
Now what? The proxy can't send it as-is to the browser, because the browser can't connect to 127.0.0.1 to get the data. So it runs it through the ProxyPassReverse directive, and it results in this:
So the conversation is now complete. The browser has received a message asking it to redirect to another location. Note that although I used OtherPgm.php as an example in this case, it might NOT be a .php file. (I guess that depends on your setup, but it could potentially be something else...)
Now the browser, having received this redirect request tries to connect (under the covers) with the /php directory added in. But it can't, becuase we don't have a ProxyPass this time around. That's why I added the ProxyPass and the RewriteRule -- so both variations would work:
Now, when the browser receives the redirect to the /php directory, and it tries to go back again with a new request, the proxy will automatically see the /php and redirect to the PASE instance. It may be overkill, though... I don't know how often this obscure situation will occur...
Now I'm confused when you say that the Rewrite Rule takes the place of the ProxyPass, so why do you still have both directives there if one takes the place of the other? And I guess where I'm really stuck is, "they'll end up in the /php folder" - what ends up there? You're talking about reverse proxy in that paragraph, so that would be the response coming back from the PASE server, and that response is going to go to the browser, right? I mean, it'll go thru the native server first, but it's bound for the browser eventually, right? So what do you mean when you say it'll end up in the /php folder?
Please pardon my denseness - I'm usually not this stupid (I hope) - but I just can't seem to wrap my mind around this. Thanks.
- The RewriteRule takes the place of the ProxyPass, and does use wildcards
- However, there's no method using wildcards to take the place of the ProxyPassReverse.
Do you understand the difference between ProxyPass and ProxyPassReverse, Jim?You totally lost me there. It seems as if you're saying that you can't have it work the way you prefer - basing the redirections on the file extension instead of basing it on finding /php in the URL. But I know that's not what you're saying, so...? I just don't understand the /php (folder) on the ProxyPass and the ProxyPassReverse when your object is to base it on .php (extension).
I had this need awhile back and that great support contact at Zend, Rod Flohr, provided the Apache directives to make it work for me, combining my php server and my CGIDEV2 server, but I never did get around to figuring out how they worked - until now...almost.
Any chance you could take another crack at elaborating on your "preferred" method of redirecting based on file extension? Thanks. -Jim