roadrunner-php / laravel-bridge

πŸŒ‰ RoadRunner ⇆ Laravel bridge πŸ‡ΊπŸ‡¦β€οΈ
https://roadrunner.dev/docs/integration-laravel
MIT License
372 stars 25 forks source link

Request attach UploadedFile param from previous Request #97

Closed Opekunov closed 2 years ago

Opekunov commented 2 years ago

Describe the bug

Discovered the error. After loading a file, any subsequent request merges the previous request.

Expected behaviour

  1. Request to route put('/image/1') with formData: {"image": file, "id": string}
  2. Another request to post('/another/2') with data: {"view": string}

Actual behaviour

  1. Request to route put('/example/1') β€” all ok
  2. Another request to post('/another/2') β€” request data merge "image"

Steps to reproduce

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class MediaController extends Controller
{
  public function storeImage(Request $request)
  {
  if($request->file('image')->isFile())
    \Log::debug('File controller: Request has file', ["keys" => $request->keys(), "file" => $request->file()]);

  //During testing, removed all code from this controller. Only left Log 
  }

  public function destroy(int $id, Request $request)
  {
    \Log::debug('Second another controller:', ["keys" => $request->keys(), "dump" => $request->all()]);
  }
}

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class AnotherController extends Controller {

  public function test(Request $request)
  {
    \Log::debug('Another controller:', ["keys" => $request->keys(), "dump" => $request->all()]);
  }

}

```php
// ../routes/api.php

Route::put('image', [MediaController::class, 'storeImage'])->middleware('throttle:4,1');
Route::delete('image/{id}', [MediaController::class, 'destroy'])->middleware('throttle:4,1');
Route::post('another/{id}', [AnotherController::class, 'test']);

```js
//These functions are called on click, not one by one 

/* ... */
let formData = new FormData()
formData.append('image', file)
formData.append('id', id)

api.call('put', '/image', formData, true) // axios custom function
/* ... */

/* ... */
api.call('post', '/another/1', {view: 'some_view'}, true)
/* ... */

/* ... */
api.call('delete', '/image/3', null, true)
/* ... */

I started the image download. Then I ran the other functions several times. In different browsers. As you can see in the log file below, image was added to the next few requests to other controllers. But after 19 seconds this problem was no longer present. Sometimes this time is longer, sometimes less. I want to note that the storeImage function is empty and only writes to the log file. I was hoping #84 would help me, but no.

[2022-05-05 19:38:25] local.DEBUG: File controller: Request has file {"keys":["id","image"],"file":{"image":{"Illuminate\\Http\\UploadedFile":"/tmp/symfony627429a0502e68.45060554eeEPIl"}}} 
[2022-05-05 19:38:29] local.DEBUG: Another controller: {"keys":["view","image"],"dump":{"image":{"Illuminate\\Http\\UploadedFile":"/tmp/symfony627427b590d531.49483253lfALkE"}}}
[2022-05-05 19:38:33] local.DEBUG: Second another controller: {"keys":["image"],"dump":{"image":{"Illuminate\\Http\\UploadedFile":"/tmp/symfony627427b9e289c3.01216694JPpKGg"}}}
[2022-05-05 19:39:44] local.DEBUG: Another controller: {"keys":["view"],"dump":{"view":"technical_halftime"}} 

System information

Please, complete the following information:

Key Value
PHP version PHP 8.1.5 (cli) (built: Apr 18 2022 23:48:54) (NTS)
Current package version "spiral/roadrunner-laravel": "^5.9"
RoadRunner version FROM spiralscout/roadrunner:2.8.8
Environment Docker version 20.10.14, build a224086
Laravel 8.83.11

RoadRunner configuration file content


rpc:
  listen: tcp://127.0.0.1:6001

server:
  command: "php ./vendor/bin/rr-worker start --relay-dsn unix:///var/run/rr/rr-relay.sock"
  relay: "unix:///var/run/rr/rr-relay.sock"
  relay_timeout: 60s

http:
  address: 0.0.0.0:8080
  access_logs: true
  max_request_size: 256
  middleware: ["static", "headers", "gzip"]

  uploads:
    dir: "/tmp"
    forbid: [".php", ".exe", ".bat", ".sh"]

  headers:
    response:
      X-Powered-By: "RoadRunner"

  static:
    dir: "/app/public"
    forbid: [".htaccess", ".php"]
    response:
      X-Powered-By: "RoadRunner"

  pool:
    num_workers: 0
    max_jobs: 0
    allocate_timeout: 10s
    destroy_timeout: 10s
    supervisor:
      max_worker_memory: 128
      exec_ttl: 60s
  ssl:
    address: 0.0.0.0:8443
    redirect: false
    cert: /etc/ssl/certs/selfsigned.crt
    key: /etc/ssl/private/selfsigned.key
  http2:
    h2c: false
    max_concurrent_streams: 128
status:
  address: 127.0.0.1:8082
  unavailable_status_code: 503
reload:
  interval: 1s
  patterns: [".php"]
  services:
    http:
      dirs: ["."]
      recursive: true
      ignore: ["vendor"]

Package configuration file content

<?php

use Spiral\RoadRunnerLaravel\Events;
use Spiral\RoadRunnerLaravel\Defaults;
use Spiral\RoadRunnerLaravel\Listeners;
use Spiral\RoadRunner\Environment\Mode;

return [
    'force_https' => (bool) env('APP_FORCE_HTTPS', false),

    'listeners' => [
        Events\BeforeLoopStartedEvent::class => [
            ...Defaults::beforeLoopStarted(),
        ],

        Events\BeforeLoopIterationEvent::class => [
            ...Defaults::beforeLoopIteration(),
        ],

        Events\BeforeRequestHandlingEvent::class => [
            ...Defaults::beforeRequestHandling(),
            Listeners\InjectStatsIntoRequestListener::class,
        ],

        Events\AfterRequestHandlingEvent::class => [
            ...Defaults::afterRequestHandling(),
        ],

        Events\AfterLoopIterationEvent::class => [
            ...Defaults::afterLoopIteration(),
            Listeners\RunGarbageCollectorListener::class, // keep the memory usage low
            \App\Listeners\ClearCachedListener::class,
            Listeners\CleanupUploadedFilesListener::class, // remove temporary files
        ],

        Events\AfterLoopStoppedEvent::class => [
            ...Defaults::afterLoopStopped(),
        ],

        Events\LoopErrorOccurredEvent::class => [
            ...Defaults::loopErrorOccurred(),
            Listeners\SendExceptionToStderrListener::class,
            Listeners\StopWorkerListener::class,
        ],
    ],

    'warm' => [
        ...Defaults::servicesToWarm(),
    ],

    'clear' => [
        ...Defaults::servicesToClear(),
        // 'auth', // is not required for Laravel >= v8.35
    ],

    'reset_providers' => [
        ...Defaults::providersToReset(),
        // Illuminate\Auth\AuthServiceProvider::class,             // is not required for Laravel >= v8.35
        // Illuminate\Pagination\PaginationServiceProvider::class, // is not required for Laravel >= v8.35
    ],

    'workers' => [
        Mode::MODE_HTTP => \Spiral\RoadRunnerLaravel\Worker::class,
        // Mode::MODE_JOBS => ...,
        // Mode::MODE_TEMPORAL => ...,
    ],
];

UPD

Problem not reproduced from Feature tests. But repoduced by Postman.

In this test, i remove throttle and auth middlewares

<?php

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'http://localhost:8080/api/image',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'PUT',
  CURLOPT_POSTFIELDS => array('image'=> new CURLFILE('/Users/username/Desktop/rest.png')),
));

$response = curl_exec($curl);

curl_close($curl);
echo $response;
<?php

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'http://localhost:8080/api/image/1?test=test',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'DELETE',
));

$response = curl_exec($curl);

curl_close($curl);
echo $response;
[2022-05-05 20:39:22] local.DEBUG: File controller: Request has file {"keys":["image"],"file":{"image":{"Illuminate\\Http\\UploadedFile":"/tmp/symfony627435fa4e03f5.66579937FhLMDD"}}} 
[2022-05-05 20:39:24] local.DEBUG: Second another controller: {"keys":["test","image"],"dump":{"test":"test","image":{"Illuminate\\Http\\UploadedFile":"/tmp/symfony627435fc93dc47.00211737ejGbJo"}}} 
[2022-05-05 20:39:30] local.DEBUG: Second another controller: {"keys":["test","image"],"dump":{"test":"test","image":{"Illuminate\\Http\\UploadedFile":"/tmp/symfony62743602c08170.56470976iagnNl"}}} 

UPD 2: Trying with empty project

Clone, make install, init, up tarampampam/laravel-roadrunner-in-docker

// App\Http\Controllers\TestController.php

/**
* Test Request after upload
* @param Request $request
*
* @return JsonResponse
*/
public function afterUpload(Request $request): JsonResponse
{
    if($request->has('data'))
      return new JsonResponse([
                    'success' => false,
                    'error'   => 'Request has another file data',
                    'keys'   => $request->keys(),
                  ], 400);

    return new JsonResponse([
                  'success' => true,
                  'keys'   => $request->keys(),
                ], 200);
}

//....
// routes/web

//....

Route::post('/after-upload', [\App\Http\Controllers\TestController::class, 'afterUpload'])
    ->withoutMiddleware([\App\Http\Middleware\VerifyCsrfToken::class]);

//....

And this two Postman requests for import (need change test.webp file to your):

{
  "info": {
    "_postman_id": "0f1dadbd-e5d9-4dd5-a29b-a5041d27aeed",
    "name": "Test Laravel RR File uploading",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "item": [
    {
      "name": "Test Image Upload",
      "request": {
        "method": "POST",
        "header": [],
        "body": {
          "mode": "formdata",
          "formdata": [
            {
              "key": "data",
              "type": "file",
              "src": "./test.webp"
            }
          ]
        },
        "url": {
          "raw": "http://localhost:8080/test/upload",
          "protocol": "http",
          "host": [
            "localhost"
          ],
          "port": "8080",
          "path": [
            "test",
            "upload"
          ],
          "query": [
            {
              "key": "image",
              "value": null,
              "disabled": true
            }
          ]
        }
      },
      "response": []
    },
    {
      "name": "Test After Upload",
      "request": {
        "method": "POST",
        "header": [],
        "url": {
          "raw": "http://localhost:8080/test/after-upload?test=test",
          "protocol": "http",
          "host": [
            "localhost"
          ],
          "port": "8080",
          "path": [
            "test",
            "after-upload"
          ],
          "query": [
            {
              "key": "test",
              "value": "test"
            }
          ]
        }
      },
      "response": []
    }
  ]
}

And..... Run first POST http://localhost:8080/test/upload

{
    "success": true,
    "content_size": 7944,
    "duration_sec": 0.014369010925292969,
    "memory_bytes": 9312
}

Run second POST http://localhost:8080/test/after-upload

{
    "success": false,
    "error": "Request has another file data",
    "keys": [
        "test",
        "data"
    ]
}
Opekunov commented 2 years ago

Today try test with empty project. Update Issue -> see UPD 2 block.

Opekunov commented 2 years ago

Fixed

The problem is on the RR side. It has been resolved in v2.9.3 https://github.com/roadrunner-server/http/pull/25

But:

But in version 2.9.3 there is a problem with the size of the downloaded files, see the link #1115

UPD:

Problem solved in RR 2.9.4