Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

clean the baseUrl so restler can be not in root #671

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

timmit-nl
Copy link

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

@timmit-nl
Copy link
Author

any change someone can review this and merge it into the code base?

@Arul- Arul- self-assigned this Oct 29, 2021
@Arul-
Copy link
Member

Arul- commented Oct 30, 2021

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?

@timmit-nl
Copy link
Author

timmit-nl commented Nov 22, 2021

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 ;-)

@Arul-
Copy link
Member

Arul- commented Nov 23, 2021

You are using Apache server or Nginx?

@timmit-nl
Copy link
Author

timmit-nl commented Nov 23, 2021

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.

@timmit-nl
Copy link
Author

timmit-nl commented Dec 2, 2021

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....

@Arul-
Copy link
Member

Arul- commented Dec 2, 2021

Here is an example that is tested in my localhost endpoint.zip under Apache webserver. Unzip the content under webroot and try it out

Screenshot 2021-12-02 at 10 22 43 PM

It shows BMI example API under /endpoint/rest/BMI

Hope this helps

@timmit-nl
Copy link
Author

timmit-nl commented Dec 2, 2021

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.

@Arul-
Copy link
Member

Arul- commented Dec 2, 2021

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

@timmit-nl
Copy link
Author

timmit-nl commented Dec 3, 2021

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! ;-))

@timmit-nl
Copy link
Author

timmit-nl commented Dec 3, 2021

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.

@timmit-nl
Copy link
Author

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

@timmit-nl
Copy link
Author

Hi Arul,

Sorry for the ping, but had you time to check this merge?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants