I'm using Rails 5.2 with the Shrine gem for image upload. On the client side I'm using NativeScript 6.0 with Angular 8.0.
I've installed Shrine and it's working on the Rails side and direct upload via Uppy.
On the frontend (Android mobile) using NativeScript I can take a photo (using nativescript-camera) and send it using nativescript-background-http to the nativescript-background-http demo server (node based).
The problem I have is sending from NativeScript to Shrine.
On the backend I have these routes
Rails.application.routes.draw do
resources :asset_items
mount ImageUploader.upload_endpoint(:cache) => "/images/upload" # POST /images/upload
end
In my shrine setup I have
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
On the frontend
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
The handlers look like this
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")
}
When I take a picture it tries to send it to the backend and I get the following log on the server;
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
I think the problem is in the headers I'm sending to Shrine, what is the correct way of sending the image from NativeScript to Shine?
Update: Try multipartUpload
I tried the multipartUpload and also got a 400 error code
// 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);
**Update: Try nativescript-background-http demo server
This works, I can successfully send a multipartUpload to the demo-server
Update: middleware to log request
Started to put together a middleware rack class to print out the response
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
When I send a file, it sends the response
Middleware called. Status: 400, Headers: {"Content-Type"=>"text/plain", "Content-Length"=>"16"}
Having a look at headers,status and body in byebug when sending from NativeScript;
(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>
A successful send via Uppy on the Rails side shows this result
(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>
Info from Chrome Network on Successful upload via 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)
Update: Can upload through Angular using a blob
I can upload from Angular 8.0 to Shrine using a blob.
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);
});
}
Update: Try nativescript-http-formdata
Here is my implementation of sending the picture via 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);
}
Error: After reviewing this error I think the okhttp3 is loaded but the syntax is wrong for NativeScript 6.0
LOG from device Galaxy S8: Error: java.lang.Exception: Failed resolving method create on class okhttp3.RequestBody
Update: Upload via Curl to Shrine = works
Tried the following and got the same Bad Request 400 error;
Send picture
curl -X POST --form "[email protected]" 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'*
Send text form (optional)
curl -X POST -d "asset_item[name]=curl" http://localhost:9000/asset_items.json
Convert output to JSON
irb
{id:"7276dc618cdd23bf3f5a9243d3c59399.jpg",storage:"cache",metadata:{filename:"t-bird.jpg",size:1478512,mime_type:"image/jpeg"}}.to_json
Result
"{\"id\":\"7276dc618cdd23bf3f5a9243d3c59399.jpg\",\"storage\":\"cache\",\"metadata\":{\"filename\":\"t-bird.jpg\",\"size\":1478512,\"mime_type\":\"image/jpeg\"}}"
POST text with the image data
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
Current throughts
I think my best chance at the moment is to use nativescript-background-http to send a multi-part post to shrine in the correct format. What ever that is.
multipart/form-data
file upload. The error message seems like it's coming from theweb-console
gem, though I'd be surprised if this was an actual exception and not just a warning. What is the response status of thePOST
request to Shrine's upload endpoint in the browser network tab? – Babbittrymultipart/form-data
is just a request body format that anyone can send, doesn't have to be a web form (e.g.curl
can do it). The question is just whether the HTTP library you're using on the client side supports it, and it appears it does – usingmultipartUpload
instead ofuploadFile
– github.com/NativeScript/… – Babbittry