Mod-Rewrite or PHP router?
I am debating routing my requests with one of the two options:
Option 1: simple capture route with Mod-Rewrite and funnel writ开发者_开发知识库ten $_GET
route to index.php for loading...
#default routing
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^blog/([0-9]+)?$ index.php?rt=blog¶ms=$1 [L,QSA]
// ..more custom routes, and then a default route
RewriteRule ^([A-Za-z]+)/([A-Za-z]+)/(.*)?$ index.php?rt=$1/$2¶ms=$3 [L,QSA]
Option 2: simply route requests to Front Controller, and create a PHP routing class to handle the routing...
#default routing
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?rt=$1 [L,QSA]
/* --- on front controller, process $_GET['rt'] --- */
at the end of the day, which will run faster, be easier to secure, and be easier to maintain?
any other ideas?
NOTE: I am not running a known framework. I am building my own MVC pattern to learn it.
Usually in MVC frameworks, this sort of thing is usually best handled by a front controller (named index.php
or the like). You use mod_rewrite to then hide index.php
from all of the URLs so your users see nice clean paths.
It's also way easier to handle in PHP than in Apache's rewrite directives. PHP is much more flexible and easier to write/understand. I'm not sure I've ever seen mod_rewrite used as the sole routing engine for any web framework out there, now that I think of it.
Your second snip of code is the way to go for your rewrite directives.
I, too, am in the process of building a LAMP MVC system from scratch.
MVC Router Class Question
Performance
Having created a bash shell script to compile Apache 2.4.x from source, you notice that a Perl Compatible Regular Expression library is baked into the process. Anytime the regular expression code must be used, the response of the http server will be slower. Hence, option number one is a no go if performance is your concern. There is a cost to the analytics involved with regular expressions. The Apache web server is borrowing the logic for regular expressions. It's not Apaches home grown code.
Security
Security is a different issue. Firstly, nothing about rewriting URLs makes them secure. It is simple security through obscurity and making things look pretty on the client side. From time to time, security holes are found in the regular expression engine code. Thus, in terms of true security, you are left in the same basic position you would be in without any rewriting: people, or bots, can send bad stuff your server and you need a way to filter and validate input in a systematic way. Be sure to filter and validate all input, especially any part of the rewritten query string you intend to use.
Thus, option number one presents a cleaner interface (INPUT_GET
/ $_GET
) for kicking off your security duties. Option number two requires (if you are trying to be thorough) you to filter and validate the entire string as a first step. The second step (generally) will be to break up and extract what you hope to gather from the larger string.
Again, you should be filtering and validating each piece of the string. Hence, while perhaps it is more manageable (easier, convenient) to filter/validate/breakup/extract/ the larger string in PHP (say, with a method in security class of some kind), you still need to do work for each piece, for every request. Option number one saves you the trouble of having to break up the larger string for the cost of executing the regex engine on every request. But, for the most part, you can just start filtering and validating the elements you expect to receive in INPUT_GET
or $_GET
.
Note, however, that option one is mainly for people that truly understand how regular expressions work and how this applies to potential URLs your server may receive. If you need more than one RewriteRule
, it could be so that you can have something like this (or other reasons) come into the server.
index.php?0=model
index.php?0=model&1=method
index.php?0=model&1=method&2=methodArgs
This makes it easier to filter and validate your inputs. Notice, though, that the last line implies you may still need to do some line splitting and further filtering/validating (but that may not have to happen on every request, like it does in option number 1).
Code Sample: Getting URL parameters using Option 2 (start at the bottom!)
Note: These are just some things to consider, not "the" way to do it.
const QS_ARRAY_LIMIT = 3;
private function getPairValue($delimiter, $string)
{
return explode('$delimiter', $string)[1]; //Get the value for a pair.
}
private function isMultiValuedQueryString()
{
return (mb_strpos($this->queryStr, '&', 0, 'UTF-8') > 2);
}
private function getAllowedPairs($argsStr)
{
$pairs = explode('&', $argsStr);
$numPairs = count($pairs);
if($numPairs > self::QS_ARRAY_LIMIT)
{
throw new SecurityException("Too many query string pairs ({$numPairs}) submitted to the router.\n");
}
return $pairs;
}
private function isQueryStrPair($pair)
{
$equalPos = null;
$pairLength = mb_strlen($pair, 'UTF-8');
if($pairLength < 3)
{
throw new SecurityException("Query string pair is too short: Length: {$pairLength}!, Suspect: {$pair}\n"); //Sends to '/'
}
$equalPos = mb_strpos($pair, '=', 0, 'UTF-8');
if($equalPos === 0) //The first position.
{
throw new SecurityException("Query sting pair cannot *start* with an equal sign (=): Suspect: {$pair}\n"); //Sends to '/'
}
if($equalPos === ($pairLength - 1)) //The last position.
{
throw new SecurityException("Query sting pair cannot *end* with an equal sign (=): Suspect: {$pair}\n"); //Sends to '/'
}
return true;
}
private function getQueryStringArgs($url)
{
$delimiter = '?';
if(mb_strpos($url, $delimiter, 0, 'UTF-8') > 0)
{
return $this->getPairValue($delimiter, $url);
}
throw new RuntimeException("Malformed URL passed to query string parser.");
}
private function associateArgPairs(array $argPairs, array $values)
{
$i = 0;
foreach($argPairs as $key => &value)
{
if(isset($values[$i]))
{
$value[$key] = $values[$i];
++$i;
}
}
return $argPairs;
}
private function getQueryStrValues($url)
{
$delimiter = '=';
$argPairs = ['model' => null, 'method' => null, 'methodArgs' => null]
$inputValues = [];
// =================================================
// Valid query strings might look like (amongst many combinations):
//
// index.php?arg1=foo&agr2=bar&arg3=/baz/bang/boom
//
// Or, just one pair with no ampersand,'&'.
//
// index.php?arg1=foo
// ==================================================
// Get everything after the question mark, '?'.
$queryStringArgsStr = $this->getQueryStringArgs($url);
if($this->isMultiValuedQueryString($queryStringArgsStr)) //Check if '&' exists.
{
foreach($this->getAllowedPairs($queryStringArgsStr) as $pair)
{
if($this->isQueryStrPair($pair))
{
//Get the value for each pair., using '=' as the string delimiter.
$inputValues[] = $this->getPairValue($delimiter, $pair);
}
}
}
else
{
if($this->isQueryStrPair($queryStringArgsStr))
{
$inputValues[] = $this->getPairValue($delimiter, $queryStringArgsStr); //Get the value for each pair.
}
}
return $this->associateArgPairs($argPairs, $inputValues);
//Remember, you will still need to split up $argPairs[$methodArgs] if necessary.
//With option #1, you could start closer to this point,
//and hence filter and validate sooner.
}
Summary
If security is your primary concern (that is, your interface into your security scheme), bite the bullet and use option number one. Learning mod_rewrite and URL rewriting makes you pretty powerful. Why leave that power on the table? Apache is a strange animal when it comes to configuring it. But, if you understand URLs and regular expressions, I say man/woman up and go for it. :-) If speed, comprehension, and ease of use are your primary concerns, go for option number one. There are zealots that want everything to be coded in PHP, but you have to judge for yourself what the pros and cons are of both situations.
Update: My bad. This code would actually work better for option number 1. In option number 2, 1=blah, where blah would be something like /trt/43ff/3335/f/3/fr3r/
or whatever. You won't have to look for ampersands.
PHP: filter_inpur_array(), (use INPUT_GET)
PHP: Superglobals
PHP: explode()
PHP: foreach (array_expression as $key => $value)
PHP: Multibtye String Functions
精彩评论