【问题标题】:Upload file bigger than 40MB to Google App Engine?将大于 40MB 的文件上传到 Google App Engine?
【发布时间】:2011-10-17 10:39:21
【问题描述】:

我正在创建一个 Google App Engine 网络应用来“转换”10K~50M 的文件

场景:

  1. 用户在网络浏览器中打开http://fixdeck.appspot.com
  2. 用户点击“浏览”,选择文件,提交
  3. Servlet 将文件作为 InputStream 加载
  4. Servlet 转换文件
  5. Servlet 将文件保存为 OutputStream
  6. 用户的浏览器接收到转换后的文件并询问将其保存在哪里,直接作为对步骤 2 中的请求的响应

(目前我没有实现第 4 步,servlet 将文件发回而不进行转换。)

问题:它适用于 15MB 文件,但不适用于 40MB 文件,提示:“错误:请求实体太大。您的客户端发出的请求太大。”我>

有什么解决办法吗?

源码:https://github.com/nicolas-raoul/transdeck
理由:http://code.google.com/p/ankidroid/issues/detail?id=697

【问题讨论】:

    标签: google-app-engine file-upload upload


    【解决方案1】:

    GAE 有一个 32MB 的 hard limits 用于 HTTP 请求和 HTTP 响应。这将限制直接向/从 GAE 应用上传/下载的大小。

    修订后的答案(使用 Blobstore API。)

    Google 提供给Blobstore API 用于处理 GAE 中的较大文件(最大 2GB)。概述文档提供了完整的示例代码。您的 Web 表单会将文件上传到 blobstore。然后,blobstore API 将 POST 重写回您的 servlet,您可以在其中进行转换并将转换后的数据保存回 blobstore(作为新的 blob)。

    原始答案(不考虑选择 Blobstore。)

    对于下载,我认为 GAE 唯一的解决方法是将文件在服务器上分成多个部分,然后在下载后重新组装。不过,这可能无法使用直接的浏览器实现。

    (作为替代设计,也许您可​​以将转换后的文件从 GAE 发送到外部下载位置(例如 S3),在那里它可以由浏览器下载而不受 GAE 限制的限制。我不相信 GAE 发起的连接具有相同的请求/响应大小限制,但我不肯定。无论如何,您仍然会受到 30 秒最大请求时间的限制。要解决这个问题,您必须查看 GAE Backend instances 并提出某种异步下载策略。)

    对于上传较大的文件,我已经了解了使用 HTML5 文件 API 将文件分割成多个块以进行上传,然后在服务器上重建的可能性。示例:http://www.html5rocks.com/en/tutorials/file/dndfiles/#toc-slicing-files。但是,由于规范和浏览器功能的变化,我不知道真正的解决方案有多实用。

    【讨论】:

    • 这完全忽略了blobstore,它正好适合这种情况。
    • 我同意在我的回答中没有提到 blobstore 是一个重大疏忽。我最初不认为这是一个选项,但我已经意识到它是(尤其是具有以编程方式创建 blob 的实验能力)。我正在考虑重写我的答案,即使它已经被接受了。
    • 由于它已经被接受,我编辑了我的答案以包含 blobstore API。不过,德鲁·西尔斯的回答值得称赞。
    • 现在已经是 2019 年了,不知道 Python3 的答案是什么? Blobstore 似乎不是一种选择。
    • 根据 Blogstore API 文档,现在的答案是 Google Cloud Storage。 cloud.google.com/storage/docs
    【解决方案2】:

    您可以使用the blobstore 上传最大 2 GB 的文件。

    【讨论】:

    • 虽然读取文件需要多次 API 调用,因为每次 blobstore API 调用最多读取 32MB。
    • 实际上,每个 API 调用的限制是 1MB,但这不应该是相关的 - 它公开了一个类似文件的接口,我能想到的东西很少需要非常大的读取。
    • @Nick Johnson - blobstore 的每个 API 调用限制为 32MB。 (数据存储区为 1MB)。见:code.google.com/appengine/docs/java/blobstore/…
    • @kaliatech 哎呀,你说的很对 - 不知何故,这种变化完全错过了我。
    • 请注意,在 python 上,如果您从应用引擎移动数据,则限制仅为 10MB。很遗憾,您必须跳过这些障碍来上传/下载大文件。我正在将数据从服务器传输到服务器,并选择将其分成块而不是处理这个 blobstore url 混乱。
    【解决方案3】:

    您也可以使用 blobstore api 直接上传到云存储。吹的是链接

    https://cloud.google.com/appengine/docs/python/blobstore/#Python_Using_the_Blobstore_API_with_Google_Cloud_Storage

    upload_url = blobstore.create_upload_url(
      '/upload_handler',
      gs‌​_bucket_name = YOUR.BUCKET_NAME)
    
    template_values = { 'upload_url': upload_url } 
    _jinjaEnvironment = jinjaEnvironment.JinjaClass.getJinjaEnvironemtVariable()
    
    if _jinjaEnvironment: 
      template = _jinjaEnvironment.get_template('import.html')
    

    然后在index.html:

    <form action="{{ upload_url }}" 
          method="POST" 
          enctype="multipart/form-data">
      Upload File:
      <input type="file" name="file">
    </form>
    

    【讨论】:

    • 能否提供更多细节? Web UI 会绕过 GAE 直接与云存储对话吗?
    • Appengine -upload_url=blobstore.create_upload_url('/upload_handler',gs_bucket_name = YOUR.BUCKET_NAME) template_values={ 'upload_url': upload_url } _jinjaEnvironment = jinjaEnvironment.JinjaClass.getJinjaEnvironemtVariable() if(_jinjaEnvironment):模板 = _jinjaEnvironment.get_template("import.html")
    • index.html-
      上传文件:
    • 是的,UI可以直接上传云存储,如果上面的代码有帮助,请告诉我。
    • 您介意将代码放入您的答案中吗?评论往往会被删除。谢谢! :-)
    【解决方案4】:

    上传较大的文件时,您可以考虑将文件分块为 Google App Engine 支持的小请求集(应小于 32MB,这是当前的限制)。

    用例子检查这个包 - https://github.com/pionl/laravel-chunk-upload

    以下是使用上述包的工作代码。

    查看

    <div id="resumable-drop" style="display: none">
            <p><button id="resumable-browse" class="btn btn-outline-primary" data-url="{{route('AddAttachments', Crypt::encrypt($rpt->DRAFT_ID))}}" style="width: 100%;
    height: 91px;">Browse Report File..</button> 
        </div>
    

    Javascript

     <script>
    var $fileUpload = $('#resumable-browse');
    var $fileUploadDrop = $('#resumable-drop');
    var $uploadList = $("#file-upload-list");
    
    if ($fileUpload.length > 0 && $fileUploadDrop.length > 0) {
        var resumable = new Resumable({
            // Use chunk size that is smaller than your maximum limit due a resumable issue
            // https://github.com/23/resumable.js/issues/51
            chunkSize: 1 * 1024 * 1024, // 1MB
            simultaneousUploads: 3,
            testChunks: false,
            throttleProgressCallbacks: 1,
            // Get the url from data-url tag
            target: $fileUpload.data('url'),
            // Append token to the request - required for web routes
            query:{_token : $('input[name=_token]').val()}
        });
    
    // Resumable.js isn't supported, fall back on a different method
        if (!resumable.support) {
            $('#resumable-error').show();
        } else {
            // Show a place for dropping/selecting files
            $fileUploadDrop.show();
            resumable.assignDrop($fileUpload[0]);
            resumable.assignBrowse($fileUploadDrop[0]);
    
            // Handle file add event
            resumable.on('fileAdded', function (file) {
                $("#resumable-browse").hide();
                // Show progress pabr
                $uploadList.show();
                // Show pause, hide resume
                $('.resumable-progress .progress-resume-link').hide();
                $('.resumable-progress .progress-pause-link').show();
                // Add the file to the list
                $uploadList.append('<li class="resumable-file-' + file.uniqueIdentifier + '">Uploading <span class="resumable-file-name"></span> <span class="resumable-file-progress"></span>');
                $('.resumable-file-' + file.uniqueIdentifier + ' .resumable-file-name').html(file.fileName);
                // Actually start the upload
                resumable.upload();
            });
            resumable.on('fileSuccess', function (file, message) {
                // Reflect that the file upload has completed
                location.reload();
            });
            resumable.on('fileError', function (file, message) {
                 $("#resumable-browse").show();
                // Reflect that the file upload has resulted in error
                $('.resumable-file-' + file.uniqueIdentifier + ' .resumable-file-progress').html('(file could not be uploaded: ' + message + ')');
            });
            resumable.on('fileProgress', function (file) {
                // Handle progress for both the file and the overall upload
                $('.resumable-file-' + file.uniqueIdentifier + ' .resumable-file-progress').html(Math.floor(file.progress() * 100) + '%');
                $('.progress-bar').css({width: Math.floor(resumable.progress() * 100) + '%'});
            });
        }
    
    }
    </script>
    

    控制器

     public function uploadAttachmentAsChunck(Request $request, $id) {
        // create the file receiver
        $receiver = new FileReceiver("file", $request, HandlerFactory::classFromRequest($request));
    
        // check if the upload is success, throw exception or return response you need
        if ($receiver->isUploaded() === false) {
            throw new UploadMissingFileException();
        }
    
        // receive the file
        $save = $receiver->receive();
    
        // check if the upload has finished (in chunk mode it will send smaller files)
        if ($save->isFinished()) {
            // save the file and return any response you need, current example uses `move` function. If you are
            // not using move, you need to manually delete the file by unlink($save->getFile()->getPathname())
            $file = $save->getFile();
    
            $fileName = $this->createFilename($file);
            // Group files by mime type
            $mime = str_replace('/', '-', $file->getMimeType());
            // Group files by the date (week
            $dateFolder = date("Y-m-W");
    
            $disk = Storage::disk('gcs');
            $gurl = $disk->put($fileName, $file);
    
            $draft = DB::table('draft')->where('DRAFT_ID','=', Crypt::decrypt($id))->get()->first();
    
            $prvAttachments = DB::table('attachments')->where('ATTACHMENT_ID','=', $draft->ATT_ID)->get();
            $seqId = sizeof($prvAttachments) + 1;
    
            //Save Submission Info
            DB::table('attachments')->insert(
                [   'ATTACHMENT_ID' => $draft->ATT_ID,
                    'SEQ_ID' => $seqId,
                    'ATT_TITLE' => $fileName,
                    'ATT_DESCRIPTION' => $fileName,
                    'ATT_FILE' => $gurl
                ]
            );
    
             return response()->json([
                'path' => 'gc',
                'name' => $fileName,
                'mime_type' => $mime,
                'ff' =>  $gurl
            ]);
    
           
        }
    
        // we are in chunk mode, lets send the current progress
        /** @var AbstractHandler $handler */
        $handler = $save->handler();
    
            return response()->json([
                "done" => $handler->getPercentageDone(),
            ]);
        }
    
        
    
        /**
         * Create unique filename for uploaded file
         * @param UploadedFile $file
         * @return string
         */
        protected function createFilename(UploadedFile $file)
        {
            $extension = $file->getClientOriginalExtension();
            $filename = str_replace(".".$extension, "", $file->getClientOriginalName()); // Filename without extension
    
            // Add timestamp hash to name of the file
            $filename .= "_" . md5(time()) . "." . $extension;
    
            return $filename;
        }
    

    【讨论】:

    • 您好,您能否提供更多详细信息。我有一个 laravel 应用程序,我正在使用 Superbalist/laravel-google-cloud-storage 包,它不起作用。一个如何使用这个包的例子会很棒。
    • @Coola ,用我已经在使用的示例工作代码更新了答案。
    • 谢谢。这真的很有帮助。会试试看。
    猜你喜欢
    • 2010-10-24
    • 2020-10-05
    • 2020-11-24
    • 1970-01-01
    • 2020-04-04
    • 2020-06-08
    • 1970-01-01
    • 1970-01-01
    • 2015-06-30
    相关资源
    最近更新 更多