2.09.2011

Fat-Free Framework for PHP


One thing I enjoy is when a technology just works. I personally find that most PHP frameworks try and extend themselves too far into complete solutions, trying to solve the problems few people have at the expense of complexity. This is why I have written my own frameworks over and over. Each time iterating over the model and trying to improve the performance and sensibility of the system. Even though I am pretty proud of my efforts, late last year I stumbled onto a framework that really caught my attention, the PHP Fat-Free Framework. It has the elements of simplicity I feel really allow a developer to push out code quickly. It's very well thought out and feels very tornado-like in it's design style. One problem I have always had with PHP frameworks were their lack of a good routing solution.

I like python, however, one issue I have with the general attitude of pythons core development is it's inability to accept that it too has flaws, even with some of it's core libraries. These are often dismissed when discussed as something you just need to work around and I find it hard to swallow the pill that just because it's more structured than PHP, the problems with the core (anyone who has used datetime and had to deal with timezones and mongodb cannot say there are no flaws in python) are not acknowledged well enough, or even documented well enough, to make coding as pleasant as I feel it could be. That being said, I did enjoy using twisted as a web framework. It had the simplicity in it's design I was looking for. It also is very powerful, and is probably the default framework I will go with for any python-based project I work on.

With all that in mind, I am a PHP developer, and I found Fat-Free to be a breath of fresh air. I finally feel I don't need to write a new framework and really have a place to begin all my future projects. Fat-Free PHP 5.3+, which is what I try to write everything in now. I like namespaces, even if the syntax is a little annoying, I understand the intention and the reasons we have to go with ye olde backspace. I also feel that web page rendering and routing fits very well with OOP and having a stronger OOP core in 5.3 is key.

Fat-Free, to me, feels very similar to tornado in it's framework design. You define your routing in the main file and it's all handled by included classes. The automatic importing of your classes is very easy to understand, and the routing is extremely simple yet powerful. The ORM tools provided are at least good enough to do anything you really need to, however, if it's not to your liking you just don't use it. I love that about F3. Nothing but the core is required. I like Smarty, especially V3, and I use it whenever I can. Even though the built-in templating supports a lot of the same features, I can do everything I need to do in Smarty, and I am more comfortable with extending it and it's syntax. As with the ORM libraries, you simply don't use f3 and you're off and running with whatever you want to use for rendering your pages.

So check it out. I would like to see your feedback and hear your feelings on the overall state of PHP frameworks.

7.23.2007

Reverse Proxy in PHP5, Rev2

It's gotten a bit more complex; The proxy handler didn't pass all the client headers to the proxy server. This caused problems with having the wrong client type, no Etag caching, cookie passing, etc. Here's the current rev, which solves a lot of these issues.

The cookie handling was broken because I wasn't using cookies on my back-end app. My SSO implementation was caching the cookies to the back-end server in the session.

So, here you go!


<?php

class ProxyHandler
{
private
$url;
private
$proxy_url;
private
$proxy_host;
private
$proxy_proto;
private
$translated_url;
private
$curl_handler;
private
$cache_control=false;
private
$pragma=false;
private
$client_headers=array();

function
__construct($url, $proxy_url)
{
// Strip the trailing '/' from the URLs so they are the same.
$this->url = preg_replace(',/$,','',$url);
$this->proxy_url = preg_replace(',/$,','',$proxy_url);

// Parse all the parameters for the URL
if (isset($_SERVER['PATH_INFO']))
{
$proxy_url .= $_SERVER['PATH_INFO'];
}
else
{
// Add the '/' at the end
$proxy_url .= '/';
}

if (
$_SERVER['QUERY_STRING'] !== '')
{
$proxy_url .= "?{$_SERVER['QUERY_STRING']}";
}

$this->translated_url = $proxy_url;

$this->curl_handler = curl_init($this->translated_url);

// Set various options
$this->setCurlOption(CURLOPT_RETURNTRANSFER, true);
$this->setCurlOption(CURLOPT_BINARYTRANSFER, true); // For images, etc.
$this->setCurlOption(CURLOPT_USERAGENT,$_SERVER['HTTP_USER_AGENT']);
$this->setCurlOption(CURLOPT_WRITEFUNCTION, array($this,'readResponse'));
$this->setCurlOption(CURLOPT_HEADERFUNCTION, array($this,'readHeaders'));

// Process post data.
if (count($_POST))
{
// Empty the post data
$post=array();

// Set the post data
$this->setCurlOption(CURLOPT_POST, true);

// Encode and form the post data
foreach($_POST as $key=>$value)
{
$post[] = urlencode($key)."=".urlencode($value);
}

$this->setCurlOption(CURLOPT_POSTFIELDS, implode('&',$post));

unset(
$post);
}
elseif (
$_SERVER['REQUEST_METHOD'] !== 'GET') // Default request method is 'get'
{
// Set the request method
$this->setCurlOption(CURLOPT_CUSTOMREQUEST, $_SERVER['REQUEST_METHOD']);
}

// Handle the client headers.
$this->handleClientHeaders();

}

public function
setClientHeader($header)
{
$this->client_headers[] = $header;
}

// Executes the proxy.
public function execute()
{
$this->setCurlOption(CURLOPT_HTTPHEADER, $this->client_headers);
curl_exec($this->curl_handler);
}

// Get the information about the request.
// Should not be called before exec.
public function getCurlInfo()
{
return
curl_getinfo($this->curl_handler);
}

// Sets a curl option.
public function setCurlOption($option, $value)
{
curl_setopt($this->curl_handler, $option, $value);
}

protected function
readHeaders(&$cu, $string)
{
$length = strlen($string);
if (
preg_match(',^Location:,', $string))
{
$string = str_replace($this->proxy_url, $this->url, $string);
}
elseif(
preg_match(',^Cache-Control:,', $string))
{
$this->cache_control = true;
}
elseif(
preg_match(',^Pragma:,', $string))
{
$this->pragma = true;
}
if (
header !== "\r\n")
{
header(rtrim($string));

}
return
$length;
}

protected function
handleClientHeaders()
{
$headers = apache_request_headers();

foreach (
$headers as $header => $value) {
switch(
$header)
{
case
'Host':
break;
default:
$this->setClientHeader(sprintf('%s: %s', $header, $value));
break;
}
}
}

protected function
readResponse(&$cu, $string)
{
static
$headersParsed = false;

// Clear the Cache-Control and Pragma headers
// if they aren't passed from the proxy application.
if ($headersParsed === false)
{
if (!
$this->cache_control)
{
header('Cache-Control: ');
}
if (!
$this->pragma)
{
header('Pragma: ');
}
$headersParsed = true;
}
$length = strlen($string);
echo
$string;
return
$length;
}
}

?>






Update: Added a google code project for php5rp at Google Code and here's the Subversion Link for downloading.

7.17.2007

Writing A Reverse Proxy in PHP5

So I have been working on a little class to run a reverse proxy from PHP using cURL. I have extended this class for my own purposes (single-sign-on) to handle some special request parameters, but here it is. It has some warts, but it's a good starting point. I would appreciate any pointers anyone has to offer.


<?php

class ProxyHandler
{
private $url;
private $translated_url;
private $curl_handler;

function __construct($url, $proxy_url)
{
$this->url = $url;
$this->proxy_url = $proxy_url;

// Parse all the parameters for the URL
if (isset($_SERVER['PATH_INFO']))
{
$proxy_url .= $_SERVER['PATH_INFO'];
}
else
{
$proxy_url .= '/';
}

if ($_SERVER['QUERY_STRING'] !== '')
{
$proxy_url .= "?{$_SERVER['QUERY_STRING']}";
}

$this->translated_url = $proxy_url;

$this->curl_handler = curl_init($proxy_url);

// Set various options
$this->setCurlOption(CURLOPT_RETURNTRANSFER, true);
$this->setCurlOption(CURLOPT_BINARYTRANSFER, true); // For images, etc.
$this->setCurlOption(CURLOPT_USERAGENT,$_SERVER['HTTP_USER_AGENT']);
$this->setCurlOption(CURLOPT_WRITEFUNCTION, array($this,'readResponse'));
$this->setCurlOption(CURLOPT_HEADERFUNCTION, array($this,'readHeaders'));

// Process post data.
if (count($_POST))
{
// Empty the post data
$post=array();

// Set the post data
$this->setCurlOption(CURLOPT_POST, true);

// Encode and form the post data
foreach($_POST as $key=>$value)
{
$post[] = urlencode($key)."=".urlencode($value);
}

$this->setCurlOption(CURLOPT_POSTFIELDS, implode('&',$post));

unset($post);
}
elseif ($_SERVER['REQUEST_METHOD'] !== 'GET') // Default request method is 'get'
{
// Set the request method
$this->setCurlOption(CURLOPT_CUSTOMREQUEST, $_SERVER['REQUEST_METHOD']);
}

}

// Executes the proxy.
public function execute()
{
curl_exec($this->curl_handler);
}

// Get the information about the request.
// Should not be called before exec.
public function getCurlInfo()
{
return curl_getinfo($this->curl_handler);
}

// Sets a curl option.
public function setCurlOption($option, $value)
{
curl_setopt($this->curl_handler, $option, $value);
}

protected function readHeaders(&$cu, $string)
{
$length = strlen($string);
if (preg_match(',^Location:,', $string))
{
$string = str_replace($this->proxy_url, $this->url, $string);
}
header($string);
return $length;
}

protected function readResponse(&$cu, $string)
{
$length = strlen($string);
echo $string;
return $length;
}
}
?>


And here's an example .htaccess file:


RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f [NC]
RewriteCond %{REQUEST_FILENAME} !-d [NC]
RewriteCond %{REQUEST_URI} !^/index.php
RewriteRule ^(.+)$ index.php/$1 [QSA]


And an example usage:


$proxy = new ProxyHandler('http://publicsite.example.com','http://privatesite.example.com');
$proxy->execute();



Cheers.