Apache

Apache and PHP HTTP PUT Voodoo

While trying to work out the details for a PHP REST utility I kept running into a wall when it came to using HTTP PUT (and HTTP DELETE) with Apache 2.2 and PHP 5. There are plenty of scattered tidbits of information relating to this on forums about the web, many old, and many more incomplete or even unhelpful. [As a side note: if someone on a forum you frequent is asking for help with getting HTTP PUT to work in Apache, telling them “Don’t use PUT it lets the hax0rs put files on your server! N00b! Use POST LOL!!11!” is not helping, nor does it make you look intelligent.]

The first hint I came across was putting Script PUT put.php in your httpd.conf in the <Directory> section. (That is, of course, assuming that your script for handling PUT requests is called put.php.)

I tried that and on restarting Apache got the error “Invalid command ‘Script’, perhaps misspelled or defined by a module not included in the server configuration” – which lead to a short bit of research (thanks Google!) that pointed out that the Script directive requires mod_actions be enabled in Apache. I did that and then tried to hit my script with a PUT request, to which I got a 405 error: “The requested method PUT is not allowed for the URL /test/put.php”.

Well, that was certainly strange, so I added <Limit> and <LimitExcept> blocks to my <Directory> section, but to no avail. So I changed the <Directory> directive from <Directory /var/www/test> to <Directory /var/www/test/put.php>. It looked strange, but what the heck, worth a try. I could now do PUT requests, but only as long as the url was /test/put.php, and that is not what is wanted when putting together a RESTful application. Trying to do anything useful, like a PUT to /test/put.php/users/ resulted in more 405 errors, now saying “The requested method PUT is not allowed for the URL /test/put.php/users/”.

So, back to the httpd.conf to change the <Directory> back to the previous. And then on to the other method I saw in a few places, using mod_rewrite to forward PUT (and DELETE) requests to the script. Of course, everywhere I saw this listed it was claimed that this alone (without the Script directive) was enough to enable PUT. So, I commented out the Script directive and added some mod_rewrite statements to the .htaccess file (which is always preferable in development as you can make changes on the fly without reloading or restarting the server.) So I added a RewriteCond %{REQUEST_METHOD} (PUT|DELETE) and a RewriteRule .* put.php.

And, I went back to test it again and, big surprise, got a 405 error again. Now, even when pointing directly at /test/put.php I got a 405 error. So, I decided to try combining the two. I uncommented the lines in the httpd.conf and bumped the server and was pleasantly surprised that PUT (and DELETE) requests to the /test/ directory were properly handled by the script. Now I could do something useful, like add another mod_rewrite rule to send all traffic for /api/ to the /test/put.php and call /api/users/ with a PUT (or DELETE) request and it was properly handled!

So, putting it all together:

In Apache: enable mod_actions and mod_rewrite. In Gentoo: make sure the lines

LoadModule actions_module modules/mod_actions.so

and

LoadModule rewrite_module modules/mod_rewrite.so

in httpd.conf are not commented out. In Debian the commands

a2enmod actions

and

a2enmod rewrite

do the trick.

In the httpd.conf add the following:

<Directory /var/www/test>
    <Limit GET POST PUT DELETE HEAD OPTIONS>
        Order allow,deny
        # You might want something a little more secure here, this is a dev setup
        Allow from all
    </Limit>
    <LimitExcept GET POST PUT DELETE HEAD OPTIONS>
        Order deny,allow
        Deny from all
    </LimitExcept>
    Script PUT /var/www/test/put.php
    Script DELETE /var/www/test/put.php
</Directory>

And finally, in the .htaccess add the rewrite voodoo:

RewriteEngine On
RewriteBase /test
RewriteRule ^/?(api)/? put.php [NC]
RewriteCond %{REQUEST_METHOD} (PUT|DELETE)
RewriteRule .* put.php

Hopefully this works as well for you as it did for me. Now to get back to business of actually writing the code to deal with the request and dispatch it appropriately (which may be a post for another day, or you can have a look at how some others have done it.)

By the way, for testing I have found the Firefox plugin Poster to be immensely useful, as well as the Java based RESTClient.

5 comments Apache and PHP HTTP PUT Voodoo

[…] This post was mentioned on Twitter by Sjan Evardsson, Sjan Evardsson. Sjan Evardsson said: New Post: Apache and PHP HTTP PUT Voodoo – http://bit.ly/9eu5qO #apache #development #php #rest […]

[…] lg Try with Limit directive June 7, 2010 12:14 pm Abhi http://www.evardsson.com/blog/2010/04/27/apache-and-php-http-put-voodoo/ June 29, 2010 8:22 […]

ramzez says:

Can you make put.php script make available? thanks!

Sure thing. This is a test script that just spits back your request. I am sure you can see how to extend this to pass the data through to your API. Cheers.

 <?php
$key = ($_REQUEST['apikey'])?$_REQUEST['apikey']:'';
function getHeaders()
{
	$headers = array();
	if (function_exists('apache_request_headers'))
	{
		$headers = apache_request_headers();
	}
	else
	{
		foreach ($_SERVER as $k => $v)
		{
			if (substr($k, 0, 5) == "HTTP_")
			{
				$k = str_replace(' ', '-', str_replace('_', ' ', ucwords(strtolower(substr($k, 5)))));
				$headers[$k] = $v;
			}
		}
	}
	return $headers;
}
$verb = strtolower($_SERVER['REQUEST_METHOD']);
$data	= array();
switch ($verb)
{
	case 'get':
		$data = $_GET;
		break;
	case 'post':
		$data = $_POST;
		break;
	case 'put':
		parse_str(file_get_contents('php://input'), $put_data);
		$data = $put_data;
		break;
}

print <<<EOD1
<!DOCTYPE html>
<html>
<head><title>test</title></head>
<body><pre>
<b>Request:</b>


EOD1;
print_r($_REQUEST);

echo "\n\n<b>Headers:</b>\n\n";
print_r(getHeaders());

echo "\n\n<b>Verb:</b>\n\n";
print_r($verb);

echo "\n\n<b>Data:</b>\n\n";
print_r($data);
print <<<EOD
</pre>
</body></html>

EOD;
?>

[…] 1,797 You're trying to do an HTTP PUT to an Apache server. I don't believe that Apache supports PUT out of the box, hence the "Method not allowed" error. You need to properly configure Apache to support PUT. Here's one link to that end. […]

Comments are closed.