首页 > 解决方案 > 从 NativeScript 上传到 Rails Shrine

问题描述

我正在使用带有 Shrine gem 的 Rails 5.2 来上传图片。在客户端,我使用 NativeScript 6.0 和 Angular 8.0。

我已经安装了 Shrine,它在 Rails 端工作并通过 Uppy 直接上传。

在使用 NativeScript 的前端(Android 手机)上,我可以拍摄照片(使用 nativescript-camera)并使用 nativescript-background-http 将其发送到 nativescript-background-http 演示服务器(基于节点)。

我遇到的问题是从 NativeScript 发送到 Shrine。

在后端我有这些路线

Rails.application.routes.draw do
  resources :asset_items
  mount ImageUploader.upload_endpoint(:cache) => "/images/upload" # POST /images/upload
end

在我的神社设置中,我有

require "shrine"
require "shrine/storage/file_system"

Shrine.storages = {
  cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"), 
  store: Shrine::Storage::FileSystem.new("public", prefix: "uploads"),           }

Shrine.plugin :logging, logger: Rails.logger
Shrine.plugin :upload_endpoint
Shrine.plugin :activerecord
Shrine.plugin :cached_attachment_data
Shrine.plugin :restore_cached_data

在前端

onTakePictureTap(args) {
    requestPermissions().then(
        () => {
            var imageModule = require("tns-core-modules/ui/image");

            takePicture({width: 150, height: 100, keepAspectRatio: true})
                .then((imageAsset: any) => {
                    this.cameraImage = imageAsset;
                    let image = new imageModule.Image();
                    image.src = imageAsset;
                    this._dataItem.picture_url = this.imageAssetURL(imageAsset);

                    // Send picture to backend
                    var file =  this._dataItem.picture_url;
                    var url = "https://192.168.1.4/images/upload";
                    var name = file.substr(file.lastIndexOf("/") + 1);

                    // upload configuration
                    var bghttp = require("nativescript-background-http");
                    var session = bghttp.session("image-upload");
                    var request = {
                        url: url,
                        method: "POST",
                        headers: {
                            "Content-Type": "application/octet-stream"
                        },
                        description: "Uploading " + name
                    };

                    var task = session.uploadFile(file, request);

                    task.on("error", this.errorHandler);
                    task.on("responded", this.respondedHandler);
                    task.on("complete", this.completeHandler);

                }, (error) => {
                    console.log("Error: " + error);
                });
        },
        () => alert('permissions rejected')
    );
} // onTakePictureTap

处理程序看起来像这样

errorHandler(e) {
    alert("received " + e.responseCode + " code.");
    var serverResponse = e.response;
}

respondedHandler(e) {
    alert("received " + e.responseCode + " code. Server sent: " + e.data);
}

completeHandler(e) {
    alert("received")
}

当我拍照时,它会尝试将其发送到后端,并在服务器上获得以下日志;

Started POST "/images/upload" for 103.232.216.30 at 2019-08-14 11:14:09 +1000
Cannot render console from 103.232.216.30! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255

我认为问题出在我发送到 Shrine 的标题中,将图像从 NativeScript 发送到 Shine 的正确方法是什么?

更新:尝试 multipartUpload

我尝试了 multipartUpload 并且还得到了 400 错误代码

                    // upload configuration
                    var bghttp = require("nativescript-background-http");
                    var session = bghttp.session("image-upload");
                    var request = {
                        url: url,
                        method: "POST",
                        headers: {
                            "Content-Type": "application/octet-stream"
                        },
                        description: "Uploading " + name
                    };

                    var params = [
                        {
                            name: "fileToUpload.jpg",
                            filename: file,
                            mimeType: "image/jpeg"
                        }
                    ];

                    var task = session.multipartUpload(params, request);

**更新:尝试 nativescript-background-http 演示服务器

这行得通,我可以成功地将 multipartUpload 发送到演示服务器

更新:记录请求的中间件

开始整理一个中间件机架类来打印响应

class MyMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    status, headers, body = @app.call(env)
    puts "Middleware called. Status: #{status}, Headers: #{headers}"
    [status, headers, body]
  end
end

当我发送文件时,它会发送响应

Middleware called. Status: 400, Headers: {"Content-Type"=>"text/plain", "Content-Length"=>"16"}

从 NativeScript 发送时查看 byebug 中的标题、状态和正文;

(byebug) headers
{"Content-Type"=>"text/plain", "Content-Length"=>"16", "Cache-Control"=>"no-cache", "X-Request-Id"=>"7a5d40e2-5c09-4fc7-88b5-83813cedf20e", "X-Runtime"=>"0.055892"}


(byebug) status
400


(byebug) body
#<Rack::BodyProxy:0x000056471192c580 @body=#<Rack::BodyProxy:0x000056471192c620 @body=#<Rack::BodyProxy:0x000056471192c878 @body=#<Rack::BodyProxy:0x000056471192c968 @body=#<Rack::BodyProxy:0x000056471192cdc8 @body=["Upload Not Found"], @block=#<Proc:0x000056471192cd28@/usr/local/rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/rack-2.0.7/lib/rack/tempfile_reaper.rb:16>, @closed=false>, @block=#<Proc:0x000056471192c918@/usr/local/rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/executor.rb:15>, @closed=false>, @block=#<Proc:0x000056471192c850@/usr/local/rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/railties-5.2.3/lib/rails/rack/logger.rb:39>, @closed=false>, @block=#<Proc:0x000056471192c5d0@/usr/local/rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activesupport-5.2.3/lib/active_support/cache/strategy/local_cache_middleware.rb:30>, @closed=false>, @block=#<Proc:0x000056471192c508@/usr/local/rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/executor.rb:15>, @closed=false>

在 Rails 端通过 Uppy 成功发送会显示此结果

(byebug) headers
{"Content-Type"=>"application/json; charset=utf-8", "Content-Length"=>"149", "ETag"=>"W/\"29040a3f35783193f7ba450aac8906bd\"", "Cache-Control"=>"max-age=0, private, must-revalidate", "X-Request-Id"=>"53b380b8-e902-49d3-885f-634fc9ea82dc", "X-Runtime"=>"0.028117"}


(byebug) body
#<Rack::BodyProxy:0x00007f2c801f8868 @body=#<Rack::BodyProxy:0x00007f2c801f8980 @body=#<Rack::BodyProxy:0x00007f2c801f8de0 @body=#<Rack::BodyProxy:0x00007f2c801f90b0 @body=#<Rack::BodyProxy:0x00007f2c801f98f8 @body=["{\"id\":\"85bf685af3b7965c701227478e2189a2.jpg\",\"storage\":\"cache\",\"metadata\":{\"filename\":\"DSCF3107_edited.JPG\",\"size\":3998332,\"mime_type\":\"image/jpeg\"}}"], @block=#<Proc:0x00007f2c801f9858@/usr/local/rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/rack-2.0.7/lib/rack/etag.rb:30>, @closed=false>, @block=#<Proc:0x00007f2c801f8f98@/usr/local/rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/executor.rb:15>, @closed=false>, @block=#<Proc:0x00007f2c801f8db8@/usr/local/rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/railties-5.2.3/lib/rails/rack/logger.rb:39>, @closed=false>, @block=#<Proc:0x00007f2c801f8890@/usr/local/rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activesupport-5.2.3/lib/active_support/cache/strategy/local_cache_middleware.rb:30>, @closed=false>, @block=#<Proc:0x00007f2c801f8750@/usr/local/rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/executor.rb:15>, @closed=false>

来自 Chrome 网络的关于通过 Uppy 成功上传的信息

      - General
        : Request URL: http://localhost:9000/images/upload
        : Request Method: POST
        : Status Code: 200 OK
        : Remote Address: [::1]:9000
        : Referrer Policy: strict-origin-when-cross-origin

      - Response                
        : Cache-Control: max-age=0, private, must-revalidate
        : Content-Length: 141
        : Content-Type: application/json; charset=utf-8
        : ETag: W/"8e3a470866888e1d724013e95d0a49b4"
        : X-Request-Id: 3e4222bd-e5bf-4270-bc31-1fc2c25696b1
        : X-Runtime: 0.010884

      - Request
        : Accept: */*
        : Accept-Encoding: gzip, deflate, br
        : Accept-Language: en-US,en;q=0.9
        : Cache-Control: no-cache
        : Connection: keep-alive
        : Content-Length: 110221
        : Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryBRJtv5UR0QTM2J2x
        : Cookie: _session_id=73b3a497c62bd745a789bc00b9f14361; org.cups.sid=c9eb7594a0515f4965b7a8e2f7900050; io=aArI7Q_64r2LWkc5AAAA; CSRF-Token-4MYJC=hLjA49c9bSsUhMUrYMfgSFSEnquQufo3; CSRF-Token-CAGDA=53tpJXxkvAstfeCoAKKbWgQDiQpU7xLj; CSRF-Token-TUFRR=kAWjSsQW4YCdEyGtaNKpfPT4gjToabYL; XSRF-TOKEN=HCjw%2B3WTJcSd1ddt45JGGGo8Uer43ggZZRrcsLc2NFgTdghJ852fqo0rWUx0%2FfBIOfv9YEMJ7mXw8TCix7d2cA%3D%3D; CSRF-Token-XDZDE=LyXXMXei6ci6FHrE3MfTxn3ARAKXYgMZ; _personal_property_rails_prototype_session=u65TkCvL9slUmGQQsP37lJH0BPcMw0E5%2FaDNw6frbuFw8NwqfM9gYPp%2F%2F830NFeZJqwxnYqc%2FCP%2FPIXhvPGFbD4waESKMKS1ChILCxTXZAPRFFULtu9m4Xl2G6AlF0ZamkzY7sdcE15vnpIBm8M%3D--98yhZGLNKsL5dnSX--Radl4qCShjACiTHc5UTH1A%3D%3D
        : Host: localhost:9000
        : Origin: http://localhost:9000
        : Pragma: no-cache
        : Referer: http://localhost:9000/asset_items/new
        : User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36

      - Form data
        : name: 2014-mlug.png
        : type: image/png
        : files[]: (binary)

更新:可以使用 blob 通过 Angular 上传

我可以使用 blob 从 Angular 8.0 上传到 Shrine。

sendImage(files: FileList){
    this.image = files.item(0);
    var directUrl = "http://localhost:9000/images/upload";

    // Create a formData object
    const formData: FormData = new FormData();
    formData.append('file', files.item(0), this.image.name);

    // Direct Upload
    this.http.post(directUrl, formData).subscribe(event => {
        console.log("Successfully uploaded: " + event);
        this.asset.image = JSON.stringify(event);
    });
}

更新:尝试 nativescript-http-formdata

这是我通过nativescript-http-formdata发送图片的实现;

async sendPicture(filepath, imageAsset) {
    var url = "http://localhost:9000/images/upload";
    var name = filepath.substr(filepath.lastIndexOf("/") + 1);

    // Get bitmap of file
    const imageAndroidBitmap = android.graphics.BitmapFactory.decodeFile(filepath);

    // Prepare the formdata
    let fd = new TNSHttpFormData();
    let param: TNSHttpFormDataParam = {
            data: imageAndroidBitmap,
            contentType: 'image/jpeg',
            fileName: 'test.jpg',
            parameterName: 'file1'
    };
    let params = [];
    params.push(param);
    try {
        const response: TNSHttpFormDataResponse = await fd.post(url, params, {
            headers: {}
        });
        console.log(response);
    } catch (e) {
        console.log(e);
    }

错误:查看此错误后,我认为 okhttp3 已加载,但 NativeScript 6.0 的语法错误

LOG from device Galaxy S8: Error: java.lang.Exception: Failed resolving method create on class okhttp3.RequestBody

更新:通过 Curl 上传到 Shrine = 作品

尝试以下并得到相同的错误请求 400 错误;

发送图片

  curl -X POST --form "file=@t-bird.jpg" http://localhost:9000/images/upload
   {"id":"4b4d42e77b4fa7ecddbd93cd07845cc2.jpg","storage":"cache","metadata":{"filename":"t-bird.jpg","size":1478512,"mime_type":"image/jpeg"}}
  *NOTE: when we send the picture we use 'file' instead of 'image'*

发送文本表单(可选)

  curl -X POST -d "asset_item[name]=curl" http://localhost:9000/asset_items.json

将输出转换为 JSON

  irb
  {id:"7276dc618cdd23bf3f5a9243d3c59399.jpg",storage:"cache",metadata:{filename:"t-bird.jpg",size:1478512,mime_type:"image/jpeg"}}.to_json

结果

"{\"id\":\"7276dc618cdd23bf3f5a9243d3c59399.jpg\",\"storage\":\"cache\",\"metadata\":{\"filename\":\"t-bird.jpg\",\"size\":1478512,\"mime_type\":\"image/jpeg\"}}"

带有图像数据的 POST 文本

  curl -X POST -d "asset_item[name]=curl" -d 'asset_item[image]="{\"id\":\"7276dc618cdd23bf3f5a9243d3c59399.jpg\",\"storage\":\"cache\",\"metadata\":{\"filename\":\"t-bird.jpg\",\"size\":1478512,\"mime_type\":\"image/jpeg\"}}"' http://localhost:9000/asset_items.json

电流通过

我认为我目前最好的机会是使用 nativescript-background-http 以正确的格式将多部分帖子发送到神社。那是什么。

标签: ruby-on-railsangularmobilenativescriptshrine

解决方案


As you are successfully able to upload using blob in your angular project. You should use nativescript-http-formdata plugin. You can download this from npm tns plugin add nativescript-http-formdata or you can find the repo here.

P.S. This plugin has dependency on Okhttp, so you need to add the following in your app.gradle file

dependencies {
    compile "com.squareup.okhttp3:okhttp:3.10.0"
}

推荐阅读