Restler
Restler copied to clipboard
clean the baseUrl so restler can be not in root
I have noticed the baseUrl doens't work as I would expect. If baseUrl is /endpoint/rest/ and the explorer lives in "explorer" (so with path /endpoint/rest/explorer) the route can't be found, because it will do in router.php /endpoint/rest/explorer==explorer so it can't be found.
If you remove the baseUrl from the path it will work path /endpoint/rest/explorer wil be cleaned to explorer in in router.php explorer==explorer
any change someone can review this and merge it into the code base?
Let's discuss this here!
What is your use case? I could not understand your statement above
Once you provide your use case, let me come up with an example that makes it work without your fix. If I could not do that we can see how we can fix the issue
Agreed?
First sorry for the delay:
Our use case is that we have an endpoint in our application. Our application is / and the endpoint lives in /endpoint/ the rest endpoint lives in /endpoint/rest/ (and the soap in /endpoint/soap/). As we really like the integration and api view and security this package provides: we are embeding your package so it is only called on /endpoint/rest/ by our application.
As we can set an baseUrl (so you can let the package live not in / but in /endpoint/rest/) the path's are not "cleaned" against the baseUrl.
We are initation restler:
Defaults::$throttle = 20; //time in milliseconds for bandwidth throttling
$cacheDir = Config::$dirs['tmp'] . "/restler/";
if (!file_exists($cacheDir)) {
mkdir($cacheDir);
}
Defaults::setProperty('cacheDirectory', $cacheDir);
Defaults::setProperty('apiAccessLevel', 0);
$productionMode = false;
if (strtolower(Config::getEnv()) != 'dev') {
$productionMode = true;
}
$Restler = new Restler($productionMode);
Restler::addListener('onRespond', function () {
header('X-Powered-By: Mosquito Framework');
});
$Restler->setBaseUrls(Router::generateUrlFromHostName() . Router::addVars2activePath(array('rest')));
$Restler->addAPIClass('\\Luracast\\Restler\\Explorer\\v2\\Explorer', 'explorer'); //this creates resources.json at API Root
$Restler->addFilterClass('\\Luracast\\Restler\\Filter\\RateLimit'); //Add Filters as needed
foreach ($routes as $key => $className) {
preg_match_all("/\{vars\[[\d]\]\}/", $className, $varsA);
foreach ($varsA[0] as $var) {
$index = ((int)str_replace(array('{vars[', ']}'), array('', ''), $var) + $offset);
if (!empty($vars[$index])) {
$className = str_replace($var, $vars[$index], $class);
}
}
if (class_exists($className)) {
$Restler->addAPIClass($className, $key);
}
}
$Restler->addAPIClass(__NAMESPACE__ . '\\Endpoint\\Auth', 'auth');
$Restler->addAuthenticationClass(__NAMESPACE__ . '\\Endpoint\\AccessControl');
$Restler->handle();
then the path isn't reconised because 'sims' != '/endpoint/rest/sims'
So this fix will '/endpoint/rest/sims' convert to sims because the basepath is Router::generateUrlFromHostName() . Router::addVars2activePath(array('rest'))
and that generated /endpoint/rest/
. So /endpoint/rest/sims 'minus' /endpoint/rest/
is sims
So this fixes makes that if you use $Restler->setBaseUrls()
it now works correctly.
hope this clarifies more ;-)
You are using Apache server or Nginx?
Both. That is for the user of our simpel inhouse framework, mostly it is apache. But we write everything so it is compatible with both.
Ok I have an possible other fix:
If we can set the path from our end and the getPath
uses that as the input for the $path
instead of the
list($base, $path) = Util::splitCommonPath(
strtok(urldecode($_SERVER['REQUEST_URI']), '?'), //remove query string
$_SERVER['SCRIPT_NAME']
);
then a user can bypass the hole $_SERVER
part, because it can feed that to Restler.
So an simple setPath() that sets the $this->path
and the getPath begin that changes from:
/**
* Parses the request url and get the api path
*
* @return string api path
*/
protected function getPath()
{
// fix SCRIPT_NAME for PHP 5.4 built-in web server
if (false === strpos($_SERVER['SCRIPT_NAME'], '.php'))
$_SERVER['SCRIPT_NAME']
= '/' . substr($_SERVER['SCRIPT_FILENAME'], strlen($_SERVER['DOCUMENT_ROOT']) + 1);
list($base, $path) = Util::splitCommonPath(
strtok(urldecode($_SERVER['REQUEST_URI']), '?'), //remove query string
$_SERVER['SCRIPT_NAME']
);
to
/**
* Parses the request url and get the api path
*
* @return string api path
*/
protected function getPath()
{
if (isset($this->path) && is_string($this->path)) {
$base = '';
$path = $this->path;
} else {
// fix SCRIPT_NAME for PHP 5.4 built-in web server
if (false === strpos($_SERVER['SCRIPT_NAME'], '.php'))
$_SERVER['SCRIPT_NAME']
= '/' . substr($_SERVER['SCRIPT_FILENAME'], strlen($_SERVER['DOCUMENT_ROOT']) + 1);
list($base, $path) = Util::splitCommonPath(
strtok(urldecode($_SERVER['REQUEST_URI']), '?'), //remove query string
$_SERVER['SCRIPT_NAME']
);
}
this way, you can use possible more systems then apache/nginx if you feed restler on your own and correctly....
Here is an example that is tested in my localhost endpoint.zip under Apache webserver. Unzip the content under webroot and try it out

It shows BMI example API under /endpoint/rest/BMI
Hope this helps
No that doesn't help.
Let me explain furter:
/endpoint/rest/ is in your example handled by the script /endpoint/rest/index.php. So restler sees the basepath /endpoint/rest/
But with our code the /endpoint/rest/ is handled by: /index.php but also a normal page like /dashboard/ is handled by the same code.
So /endpoint/rest/ will call the script below because our internal router will call only that part of the script when on /endpoint/rest/ if it is /endpoint/soap/ it will initiate an soap server and so on.
Defaults::$throttle = 20; //time in milliseconds for bandwidth throttling
$cacheDir = Config::$dirs['tmp'] . "/restler/";
if (!file_exists($cacheDir)) {
mkdir($cacheDir);
}
Defaults::setProperty('cacheDirectory', $cacheDir);
Defaults::setProperty('apiAccessLevel', 0);
$productionMode = false;
if (strtolower(Config::getEnv()) != 'dev') {
$productionMode = true;
}
$Restler = new Restler($productionMode);
Restler::addListener('onRespond', function () {
header('X-Powered-By: Mosquito Framework');
});
$Restler->setBaseUrls(Router::generateUrlFromHostName() . Router::addVars2activePath(array('rest')));
$Restler->addAPIClass('\\Luracast\\Restler\\Explorer\\v2\\Explorer', 'explorer'); //this creates resources.json at API Root
$Restler->addFilterClass('\\Luracast\\Restler\\Filter\\RateLimit'); //Add Filters as needed
foreach ($routes as $key => $className) {
preg_match_all("/\{vars\[[\d]\]\}/", $className, $varsA);
foreach ($varsA[0] as $var) {
$index = ((int)str_replace(array('{vars[', ']}'), array('', ''), $var) + $offset);
if (!empty($vars[$index])) {
$className = str_replace($var, $vars[$index], $class);
}
}
if (class_exists($className)) {
$Restler->addAPIClass($className, $key);
}
}
$Restler->addAPIClass(__NAMESPACE__ . '\\Endpoint\\Auth', 'auth');
$Restler->addAuthenticationClass(__NAMESPACE__ . '\\Endpoint\\AccessControl');
$Restler->handle();
Do you understand it now? We are not looking to add an fysical folder in the webroot with an custom index.php. Our webroot only contains /index.php and an .htaccess.
If you want the index.php
in the webroot, you can add the API as follows
<?php
require_once '../vendor/autoload.php';
use Luracast\Restler\Restler;
$r = new Restler();
$r->addAPIClass('BMI', 'endpoint/rest/bmi');
$r->handle();
I would strongly recommend the above method instead using symlinked public folder as a subfolder in the webroot. This gives the possibility of
- keeping each app sleek
- different framework / language for each app
If I do it like you says, the explorer is completly empty: (but the rest api will work)
{
"swagger": "2.0",
"host": "tim.dev.tool.nl",
"basePath": "",
"produces": [
"application/json"
],
"consumes": [
"application/json"
],
"paths": [],
"definitions": {},
"securityDefinitions": {
"api_key": {
"type": "apiKey",
"name": "api_key",
"in": "query"
}
},
"info": {
"version": "1",
"title": "Restler API Explorer",
"description": "Live API Documentation",
"contact": {
"name": "Restler Support",
"email": "[email protected]",
"url": "luracast.com/products/restler"
},
"license": {
"name": "LGPL-2.1",
"url": "https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html"
}
}
}
location of json: https://tim.dev.tool.nl/endpoint-v2/rest/explorer/swagger.json explorer lives at: https://tim.dev.tool.nl/endpoint-v2/rest/explorer/
and the endpoint lives at: https://tim.dev.tool.nl/endpoint-v2/rest/
We are NOT symlinking. As those would not survive git correctly so that is not an option. We have an internal php router that simply does: when url contains at the beginning: /endpoint-v2/rest/
run the class with the code above in previous posting.
In my opinion I am now poluting restler routes with endpoint-v2/rest/ as all restler routes will start with that. And the baseUrl is in fact the hostname with port/http scheme... But in my humble opinion a baseUrl is more then a hostname+scheme. The baseUrl is: https://tim.dev.tool.nl/endpoint-v2/rest/ and so the routes are clean.
(And we need the explorer for our customers! ;-))
also with my fix the explorer is correctly populated with the correct info, so you can also use an external swagger:
{
"swagger": "2.0",
"host": "tim.dev.tool.nl",
"basePath": "/endpoint-v2/rest",
"produces": [
"application/json"
],
"consumes": [
"application/json"
],
"paths": {
"/say/hello": {
"post": {
"operationId": "sayHello",
"tags": [
"say"
],
"parameters": [
{
"name": "sayHelloModel",
"description": "**session** (required) \nto \n",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/sayHelloModel"
}
}
],
"summary": "hello 🔐",
"description": "",
"responses": {
"200": {
"description": "Success",
"schema": {
"type": "string"
}
}
},
"security": [
{
"api_key": []
}
]
}
},
"/say/hi": {
"get": {
"operationId": "sayHi",
"tags": [
"say"
],
"parameters": [
{
"name": "to",
"type": "string",
"description": "",
"in": "query",
"required": true
}
],
"summary": "hi ◑",
"description": "",
"responses": {
"200": {
"description": "Success",
"schema": {
"type": "string"
}
}
},
"security": [
{
"api_key": []
}
]
}
},
"/auth/inloggen": {
"post": {
"operationId": "authCreateInloggen",
"tags": [
"auth"
],
"parameters": [
{
"name": "authCreateInloggenModel",
"description": "**username** (required) \n**password** (required) \n",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/authCreateInloggenModel"
}
}
],
"summary": "Functie om in te loggen op de webservice 🔓",
"description": "",
"responses": {
"200": {
"description": "$session",
"schema": {
"type": "string"
}
}
}
}
}
},
"definitions": {
"sayHelloModel": {
"properties": {
"session": {
"type": "string",
"description": ""
},
"to": {
"type": "string",
"description": "",
"defaultValue": "world"
}
},
"required": [
"session"
]
},
"authCreateInloggenModel": {
"properties": {
"username": {
"type": "string",
"description": ""
},
"password": {
"type": "string",
"description": ""
}
},
"required": [
"username",
"password"
]
}
},
"securityDefinitions": {
"api_key": {
"type": "apiKey",
"name": "api_key",
"in": "query"
}
},
"info": {
"version": "1",
"title": "API Explorer",
"description": "Live API Documentation",
"contact": {
"name": "tool.nl",
"email": "[email protected]",
"url": "https://tim.dev.tool.nl"
},
"license": {
"name": "",
"url": "https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html"
}
}
}
as you can see the basePath is correctly filled. I can even execute from the external swagger to our server. We feed Restlker with this populated BaseUrl: $Restler->setBaseUrls('https://tim.dev.tool.nl/endpoint-v2/rest');
As the explorer and swagger are correctly populated, I am more certain that our fix is the correct one.
Hi Arul,
Can you please make the suggested change and merge this?
I hate it when we are on p[roduction with an temparly forked version because of an merge that is stall
Hi Arul,
Sorry for the ping, but had you time to check this merge?