ankitpokhrel / tus-php

🚀 A pure PHP server and client for the tus resumable upload protocol v1.0.0
https://tus.io
MIT License
1.41k stars 205 forks source link

Unable to get debug information from Laravel server #339

Closed gbrits closed 3 years ago

gbrits commented 3 years ago

I have been working on using this library for the last four hours and I can't seem to get past this point. I have alternated between using Blobs and now I'm using the FileEntry File object as prescribed in your documentation. My client is the tus-js-client official.

My code is as follows for the client side:

async uploadTus(webPath: string): Promise<void> {
    this.loading = await this.loadingCtrl.create({
      message: 'Uploading...'
    });
    await this.loading.present();
    const token = await Storage.get({ key: 'user_token' });
    const parsed_token = JSON.parse(token.value);
    const filename = nanoid(10);
    const url = 'https://private-url.com.au/auth/api/tus';
    const blob = this.videoBlob;
    const upload = new Upload(blob, {
      endpoint: url,
      retryDelays: [0, 1000, 2000, 4000, 8000],
      headers: {
        Authorization: 'Bearer ' + parsed_token.access_token
      },
      chunkSize: 1024,
      addRequestId: true,
      overridePatchMethod: true,
      metadata: {
        name: `${filename}.mov`,
        type: `video/quicktime`
      },
      // uploadLengthDeferred: true,
      onError: function(error) {
          console.log("Failed because: " + error)
          this.loading?.dismiss();
      },
      onProgress: function(bytesUploaded, bytesTotal) {
          var percentage = (bytesUploaded / bytesTotal * 100).toFixed(2)
          console.log(bytesUploaded, bytesTotal, percentage + "%")
      },
      onSuccess: function() {
          this.loading?.dismiss();
      }
    });
    upload.start();
  }

Prior to uploading, I retrieve the File object like so:

          this.file.resolveLocalFilesystemUrl(resolvedPath)
          .then(entry => {
            (<FileEntry>entry).file(file => {
              // this is the actual file as File object
              // const photo = this.imageService.readFile(file);
              // console.log(photo);
              this.videoBlob = file;
            }
            );
          })
          .catch(err => {
            console.log('Error while reading file.');
          });

Finally... my Laravel serverside is:

namespace App\Providers;

use Aws\S3\S3Client;
use TusPhp\Tus\Server as TusServer;
use Aws\Credentials\Credentials;
use Illuminate\Support\ServiceProvider;
use Log;

class TusServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->singleton('tus-server', function ($app) {
            $awsAccessKey = 'AKIAXXX'; // YOUR AWS ACCESS KEY
            $awsSecretKey = 'XXX'; // YOUR AWS SECRET KEY
            $awsRegion    = 'ap-southeast-2';      // YOUR AWS BUCKET REGION
            $basePath     = 's3://privatebucket';
            $s3Client = new S3Client([
                'version' => 'latest',
                'region' => $awsRegion,
                'credentials' => new Credentials($awsAccessKey, $awsSecretKey)
            ]);
            Log::info('Are we uploading?');
            $s3Client->registerStreamWrapper();
            $server = new TusServer('file');
            $server->setUploadDir($basePath);
            return $server;
        });
    }
}

So XCode just hangs on this:

To Native Cordova ->  File readAsArrayBuffer File1251655279 ["options": [cdvfile://localhost/temporary/63568122505__E3E5E323-AB0F-41E9-8316-59E1F7B8BDAB.MOV, 0, 1024]]

And my Laravel log file just shows:

[2021-02-22 20:07:10] local.INFO: Are we uploading? 

But there's never any finalisation / update / response / error etc. That's where it all just stops. If anyone can help I'd be extremely grateful.

gbrits commented 3 years ago

I made these changes:

const blob = this.videoBlob;

console.log('NAME: ', blob.name);
console.log('SIZE: ', blob.size);
console.log('TYPE: ', blob.type);

if(blob.name && blob.size && blob.type) {
  const uploader = new Upload(blob, {
    endpoint: url,
    retryDelays: [0, 1000, 2000, 4000, 8000],
    headers: {
      Authorization: 'Bearer ' + parsed_token.access_token,
    },
    chunkSize: 1024,
    addRequestId: true,
    overridePatchMethod: true,
    metadata: {
      name: blob.name,
      type: blob.type
    },
    uploadSize: blob.size,
    uploadLengthDeferred: true,
    onError: function(error) {
        console.log("Failed because: " + error)
        this.loading?.dismiss();
    },
    onProgress: function(bytesUploaded, bytesTotal) {
        var percentage = (bytesUploaded / bytesTotal * 100).toFixed(2)
        console.log(bytesUploaded, bytesTotal, percentage + "%")
    },
    onSuccess: function() {
        this.loading?.dismiss();
    }
  });

And now I finally have an error... but that's hard to debug too, seems to be related to my metadata which I've made sure both are not null... so maybe a backend issue?

Argument 2 passed to TusPhp\File::setMeta() must be of the type int, null given, called in /home/forge/privateurl.com.au/vendor/ankitpokhrel/tus-php/src/Tus/Server.php on line 643

{"userId":2,"exception":"[object] (TypeError(code: 0): Argument 2 passed to TusPhp\\File::setMeta() must be of the type int, null given, 
called in /home/forge/privateurl.com.au/vendor/ankitpokhrel/tus-php/src/Tus/Server.php on line 643 at /home/forge/privateurl.com.au/vendor/ankitpokhrel/tus-php/src/File.php:74)
gbrits commented 3 years ago

I put a catch on the route to see if anything is being posted and the response is empty, is this normal?

        Route::any('/tus/{any?}', function (Request $request) {
            Log::debug(print_r($request->all(), true));
            $resp = app('tus-server')->serve();
            $resp->send();
            exit(0);
        })->where('any', '.*');

As seen here:

[2021-02-23 00:46:44] local.DEBUG: Array
(
)

[2021-02-23 00:46:44] local.INFO: Are we uploading?  
[2021-02-23 00:46:44] local.ERROR: Argument 2 passed to TusPhp\File::setMeta() must be of the type int, null given, called in /home/forge/privateurl.com.au/vendor/ankitpokhrel/tus-php/src/Tus/Server.php on line 643 {"userId":2,"exception":"[object] (TypeError(code: 0): Argument 2 passed to TusPhp\\File::setMeta() must be of the type int, null given, called in /home/forge/privateurl.com.au/vendor/ankitpokhrel/tus-php/src/Tus/Server.php on line 643 at /home/forge/privateurl.com.au/vendor/ankitpokhrel/tus-php/src/File.php:74)
gbrits commented 3 years ago

When I remove my authorisation header I get a different error:

[log] - Failed because: Error: tus: failed to resume upload, caused by [object XMLHttpRequestProgressEvent], originated from request (method: HEAD, url: https://privateurl.com.au/auth/api/tus, response code: n/a, response text: n/a, request id: n/a)

If I remove the uploadUrl & just leave endpoint, the deeper culprit shows up as:

tus: failed to create upload, caused by [object XMLHttpRequestProgressEvent]

gbrits commented 3 years ago

Tried implementing middleware as your README prescribes - local.ERROR: Undefined property: App\Http\Middleware\TusPass::$user

gbrits commented 3 years ago

Are these supposed to be empty?

[2021-02-23 03:51:29] local.INFO: TusPhp\Request Object
(
    [request:protected] => Symfony\Component\HttpFoundation\Request Object
        (
            [attributes] => Symfony\Component\HttpFoundation\ParameterBag Object
                (
                    [parameters:protected] => Array
                        (
                        )

                )

            [request] => Symfony\Component\HttpFoundation\ParameterBag Object
                (
                    [parameters:protected] => Array
                        (
                        )

                )

            [query] => Symfony\Component\HttpFoundation\InputBag Object
                (
                    [parameters:protected] => Array
                        (
                        )

                )
gbrits commented 3 years ago

I've drawn the conclusion that my frontend is still not sending the upload data correctly, due to using Ionic / Capacitor / FileEntry File type etc.

gbrits commented 3 years ago

In case some good Samaritan comes across this post, my frontend (Ionic / Capacitor) flow is as follows:

async submitAudit() {
const options: VideoCapturePlusOptions = { limit: 1, highquality: true }
this.recording = true;
this.videoCapturePlus.captureVideo(options).then(
  async (mediaSheet: MediaFile[]) => {
    let media = mediaSheet[0];
    console.log('Media Sheet', media);

    let resolvedPath: DirectoryEntry;
    let path = media.fullPath.substring(0, media.fullPath.lastIndexOf("/"));

    if (Capacitor.getPlatform() === "ios") {
      resolvedPath = await this.file.resolveDirectoryUrl("file://" + path);
    } else {
      resolvedPath = await this.file.resolveDirectoryUrl(path);
    }

    this.selectedVideo = resolvedPath;
    console.log('Providing to Filesystem - PATH: ',this.selectedVideo);

    const contents = await Filesystem.readFile({ path: this.selectedVideo.nativeURL + media.name });
    const base64Response = await fetch(`data:video/quicktime;base64,${contents.data}`);
    const blob = await base64Response.blob();

    this.videoBlob = blob;
    this.uploadTus(this.selectedVideo);
  },
  (error) => console.log('Error', error)
);
}

And my uploading function is

async uploadTus(webPath: string): Promise<void> {
if(!this.loading) {
  this.loading = await this.loadingCtrl.create({
    message: 'Uploading...'
  });
  await this.loading.present();
}
const token = await Storage.get({ key: 'private_user_token' });
const parsed_token = JSON.parse(token.value);
const filename = nanoid(10);
const url = 'https://privateurl.com.au/auth/api/tus';
const blob = this.videoBlob;
if(blob.size && blob.type) {
  const uploader = new Upload(
    blob,
    {
    endpoint: url,
    retryDelays: [0, 1000, 2000],
    metadata: {
      name: `${filename}.mov`,
      type: blob.type,
      access_token: `${parsed_token.access_token}`
    },
    headers: {
      'Authorization': `Bearer ${parsed_token.access_token}`
    },
    onError: function(error) {
        console.log("Failed because: " + error)
        this.loading?.dismiss();
    },
    onProgress: function(bytesUploaded, bytesTotal) {
        var percentage = (bytesUploaded / bytesTotal * 100).toFixed(2)
        console.log(bytesUploaded, bytesTotal, percentage + "%")
    },
    onSuccess: function() {
        this.loading?.dismiss();
    }
  });
  uploader.start();
}
}

And my understanding of things is pretty rudimentary, but I think it's a warning sign that both the InputBag & the ParameterBag is empty on Laravel's end, via the middleware

gbrits commented 3 years ago

Stuck here now:

[log] - Failed because: Error: tus: failed to create upload, caused by [object XMLHttpRequestProgressEvent], originated from request (method: POST, url: https://privateurl.com.au/files, response code: n/a, response text: n/a, request id: n/a)

gbrits commented 3 years ago

In the end I decided to use AWS SDK, skipped PHP & chunking altogether and just upload directly to Mr Bezos' cloud, 1GB limit uploads & all :) Hopefully someone will benefit from my random scrawls.

samundra commented 3 years ago

@gbrits I think It would make it easier to isolate the problem first whether it lies on frontend or server side.

I would also suggest to try to use other alternative client to ensure that tus-php server was configured fine.

From the PHP error log it definitely looks like data is not being passed to it correctly.

  1. Ensure sure that Tus-php is configured properly
  2. Once confident that Tus-php is configured as expected. Then debug on frontend side and check why it's not sending the data as expected by server.
ssatz commented 8 months ago

Remove uploadLengthDeferred: true, it should work