dingo / api

A RESTful API package for the Laravel and Lumen frameworks.
BSD 3-Clause "New" or "Revised" License
9.33k stars 1.25k forks source link

Internal Routes - Passing on content as parameters to internal routes #902

Open wayne5w opened 8 years ago

wayne5w commented 8 years ago

Been pulling my hair out all night on this one. I created a little BatchController for executing rest calls in a group. It utilizes the Internal Routes. It works great on homestead but fails on forge. To use the BatchController I make a post to BatchController.store() and in the body is a json object with multiple routes specified. The store() function pulls the json apart and makes the appropriate internal route calls.

I have narrowed it down. On forge the Request object on the internal route contains parameters equivalent to the the original json object that was sent to the BatchController store() function. On homestead the parameters are properly empty. Below is a snippet of the BatchController. Any help is much appreciated, can't seem to narrow it down anymore. When I dump the dispatcher before making the internal route call it has the json object as content, and no parameters. After making the internal route call if I dump the dispatcher, it has parameters equivalent to the json object.

class BatchController extends AbstractControllerBase
{
    public function store(Request $request) {
        $dispatcher = app('Dingo\Api\Dispatcher');
        $result = [];
        $batchRequests = json_decode($request->getContent());
        $request->replace([]);
        foreach ($batchRequests->requests as $batchRequest) {
            $response = $dispatcher->get($batchRequest->endpoint, []);
            $result = array_add($result, $response);
        }
        return $result;
    }
} 
wayne5w commented 8 years ago

I should have said, this is Laravel 5.1.31 and the most recent dingo.

catalinux commented 8 years ago

i have something like this, and it works

    function post(\Dingo\Api\Http\Request $multiRequest)
    {
        $multiresult = [];
        $requests = $multiRequest->json();
        $index = 0;
        foreach ($requests as $r) {

            $body = array_key_exists('body', $r) ? urldecode($r['body']) : null;
            $request = \Dingo\Api\Http\Request::create($r['relative_url'], $r['method'], [], [], [], [], $body);
            /** @var Router $api */
            $api = app('api.router');
            $response = $api->dispatch($request);
            $result = [];
            $result['body'] = $response->content();
            $result['status_code'] = $response->getStatusCode();
            $result['content_id'] = ++$index;
            $multiresult[] = $result;
        }

        return $multiresult;

    }
catalinux commented 8 years ago

and my post body would loook like this


[
  {
    "method": "POST",
    "relative_url": "api/users",
    "body": "urlencodeddata"

  },
  {
    "method": "GET",
    "relative_url": "api/contacts"
  }
]
jasonlewis commented 8 years ago

Any ideas as to what's different on the Forge server?

wayne5w commented 8 years ago

great question. Been racking my brain over that. Seems slightly different versions of linux. Laravel and all depends should be identical as I deploy to forge from the same git repo which drives homestead.

Forge linux - Welcome to Ubuntu 14.04.3 LTS (GNU/Linux 3.13.0-71-generic x86_64)

Homestead linux - Welcome to Ubuntu 14.04.3 LTS (GNU/Linux 3.19.0-25-generic x86_64)

jasonlewis commented 8 years ago

Nuh that would've have anything to do with it I wouldn't think.

So how do these parameters make it error out? What's the error your seeing exactly?

wayne5w commented 8 years ago

Wish I could figure out what is different. I have a feeling there is some linux config difference that is causing this.

In my controllers I have a parser which parses any parameters looking for keywords in order to introduce various eloquent model functions such as sort, with, fields returned. The parser is not expecting parameters as an array which causes it to blow up.

jasonlewis commented 8 years ago

And so these are query string parameters or post body stuff? I'm just trying to get my head around where the data is being added in... there's got to be something simple missing here.

wayne5w commented 8 years ago

sorry, should have been more specific. Yes the controllers are looking for the query string parameters ?_with=Relation for example. Before the dispatch, the Dispatcher has the json in the content property and the parameters property is empty. After the dispatch the json is both in content and the parameters property on forge. On homestead the parameters remains empty as expected.

jasonlewis commented 8 years ago

So do you internally call the BatchController::store method as well or is it called externally? And is it an API endpoint or a regular route?

wayne5w commented 8 years ago

I call BatchController::store from PHPunit and externally from Postman. While debugging this I have been focused on externally via Postman.

$api = app('Dingo\Api\Routing\Router'); $api->version('v1', function ($api) { $api->resource('batch', 'App\Http\Controllers\BatchController'); }

wayne5w commented 8 years ago

I've been working on this more and realized forge/homestead was a red herring. When I had tried postman against homestead and it had worked, I must not have run composer update for the latest dingo commits. So the issue is actually the different behavior between calling BatchController::store from phpunit where the parameters are not present on the call to the internal route and calling it externally when they are present. Sorry for any confusion.

wayne5w commented 8 years ago

It seems to me the content from the call to BatchController::store is being passed to the internal route call even though I am not passing the content.

jasonlewis commented 8 years ago

Any chance you can replicate this on a fresh install and put it up somewhere? I'm still not 100% following and can't seem to recreate it with the code you've provided.

wayne5w commented 8 years ago

I will give it a shot.

On Fri, Mar 18, 2016, 1:31 AM Jason Lewis notifications@github.com wrote:

Any chance you can replicate this on a fresh install and put it up somewhere? I'm still not 100% following and can't seem to recreate it with the code you've provided.

— You are receiving this because you authored the thread. Reply to this email directly or view it on GitHub https://github.com/dingo/api/issues/902#issuecomment-198227334

Regards, ---Wayne.

zawilliams commented 8 years ago

@catalinux nice solution for batching. I've setup a route (api/batch) to post to and I'm able to get back data using the code you provided.

Running into one issue - when using pagination, I'm getting the "next_page_url" returning with "api/batch?page=2" as the URL for all of the different content as opposed to something like "api/posts?page=2" and "api/categories?page=2".

Any ideas of what to do to fix this behavior?

zawilliams commented 8 years ago

FYI - I got it working using a combo of the two methods above:

public function batch() {
        $results = [];

        $dispatcher = app('Dingo\Api\Dispatcher');
        $batchRequests = json_decode($this->request->getContent());

        foreach ($batchRequests as $batchRequest) {
            $response = $dispatcher->raw()->get($batchRequest->url, []);
            $result = [];
            $result['url'] = $batchRequest->url;
            $result['code'] = $response->getStatusCode();
            $result['body'] = json_decode($response->content());
            $results[] = $result;
        }

        return  $this->response->array($results);
    }

with a post body of:

[{"method": "GET", "url": "endpoint1"}, {"method": "GET", "url": "endpoint2"}, {"method": "GET", "url": "endpoint2/segment2"}]
hskrasek commented 8 years ago

@wayne5w Is this issue still persisting? You made the comment:

It seems to me the content from the call to BatchController::store is being passed to the internal route call even though I am not passing the content.

And I'm assuming you meant that this bit of logic wasn't persisting to the internal route call:

$batchRequests = json_decode($request->getContent());
$request->replace([]);

That would be because when the dispatcher fires, any class that DI's the Request object is getting a fresh instance out of the container. You would need to do this for the changes to persist:

$batchRequests = json_decode($request->getContent());
$request->replace([]);
app()->instance('request', $request); //This replaces the built out request object in the container with you're modified version