Error http 422 with DirectUpload / ActiveStorage / Rails - rails-activestorage

I try to add some images with ajax via DirectUpload / ActiveStorage / Rails 6.
I use the prerequisites into of ActiveStorage support, for use DirectUpload with Jquery :
https://edgeguides.rubyonrails.org/active_storage_overview.html#integrating-with-libraries-or-frameworks
const upload = new DirectUpload(file, url)
upload.create((error, blob) => {
if (error) {
// Handle the error
} else {
// Add an appropriately-named hidden input to the form with a
[..]
console.log(blob.key);
}
})
On my host, it works for all files. But when I try to publish my app into my hoster, I have an error for some files, always the same, after the request of DirectUpload :
Completed 422 Unprocessable Entity in 2ms (ActiveRecord: 0.0ms | Allocations: 689)
I looked the XHR requests into my webtools browser, but the payload seems the same into a file which works and another which fails :
{id: 219, key: "v2v1aqlk8gyygcc4smjeh0bbuc59", filename: "groupama logo.jpeg",…}
id: 219
key: "v2v1aqlk8gyygcc4smjeh0bbuc59"
filename: "logo.jpeg"
content_type: "image/jpeg"
metadata: {}
byte_size: 17805
checksum: "3GIVi2kNKClfH+d9HGYOfkA=="
created_at: "2020-04-09T08:25:40.000+02:00"
signed_id: "eyJfcmFpbHMiOnsibWVzc2zaFnZSI6IkJBaHBBZHM9IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--7c0750cb8c86a955a04fa9a11dc5389cdeb5e7b0"
attachable_sgid: "BAh7CEkiCGdpZAY6BkVUSSIxsaZ2lkOi8vYXBwL0FjdGl2ZVN0b3JhZ2U6OkJsb2IvMjE5P2V4cGlyZXNfaW4GOwBUSSIMcHVycG9zZQY7AFRJIg9hdHRhY2hhYmxlBjsAVEkiD2V4cGlyZXNfYXQGOwBUMA==--64a945c38dc5d85c05156da50b9c38819b106e10"
direct_upload: {,…}
url: "http://localhost:8491/rails/active_storage/disk/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdDVG9JYTJWNVNTSWhkakoyTVdGeGJHczRaM2w1WjJOak5ITnRhbVZvTUdKaWRXTTFPUVk2QmtWVU9oRmpiMjaUwWlc1MFgzUjVjR1ZKSWc5cGJXRm5aUzlxY0dWbkJqc0dWRG9UWTI5dWRHVnVkRjlzWlc1bmRHaHBBbzFGT2cxamFHVmphM04xYlVraUhUTkhTVlpwTW10T1MwTnNaa2dyT1VoSFdVOW1hMEU5UFFZN0JsUT0iLCJleHAiOiIyMDIwLTA0LTA5VDA2OjMwOjQwLjg5NFoiLCJwdXIiOiJibG9iX3Rva2VuIn19--a2acedc0924f735c5cc08db8c4b76f76accc3c8d"
headers: {Content-Type: "image/jpeg"}
I tried this solution, by the monkey patch doesn't works for me, and another solution seems not working :
Rails API ActiveStorage DirectUpload produce 422 Error InvalidAuthenticityToken
I noticed, when I try to upload the logo image file without use DirectUpdate into input file, the file is correctly well send to my server.
= f.file_field :logos, direct_upload: true
Do you have any idea to test ?

My issue was coming with the IO which was use for copy the file. Into the ActiveStorage::DiskController#update, Rails use the request.body and IO.CopyStream for create the file, and a checksum file was done for verifiy the file created.
And the check fail and throw the 422 http error.
I noticed than the IO stream, in dev mode was a String_IO, whereas on my hoster, the IO was a Uswgi_IO. Because my hoster deliver the ruby on rails application with Uswgi.
The uwsgi_io not contains a length or size methods, and when ActiveStorage create the file with this IO, the size of file was weird. Weirdly too large.
I noticed if the RAW_POST_DATA was assign, then the request.body return a String_IO. And into the request.raw_post method, the body was read directly with the request.content_length :
raw_post_body.read(content_length)
https://api.rubyonrails.org/classes/ActionDispatch/Request.html#method-i-body
I created a new controller which inherits of ActiveStorage::DiskController, in order to assign the RAW_POST_DATA, before the Disk#update action.
class UploadController < ActiveStorage::DiskController
def update
request.env['RAW_POST_DATA'] = request.body.read(request.content_length)
super
end
end
And after, I override the ActiveStorage disk#update route by mine :
put '/rails/active_storage/disk/:encoded_token', to: 'upload#update
And it works !
ActionText works with ActiveStorage for store the images, and I had the same issue with the images too large.
My patch allows to make works ActionText on my hoster too.

Kudos for finding the culprit here, I was having the exact same issue on a uwsgi setup and I was trying to figure out why the content length was different when I found your post. Thanks for sharing !
I just took a slightly different approach with the fix as I didn't want to hack the AS routes, so I created a config/initializers file with the following code :
Rails.configuration.to_prepare do
ActiveStorage::DiskController.class_eval do
before_action :set_raw_post_data, only: :update
def set_raw_post_data
request.env['RAW_POST_DATA'] = request.body.read(request.content_length)
end
end
end
Maybe it would be worth creating an issue in https://github.com/unbit/uwsgi to let them know about this ?

Related

AVPlayer Not Able to Handle Relative Path URL in HLS Streams

We are running into an issue that seems unique to AVPlayer. We've built a new architecture for our HLS streams which makes use of relative URLs. We can play these channels on a number of other players just fine, but when trying to build using AVPlayer, the channel gets 400 errors requesting either child manifests/segments with relative URLs.
Using curl, we are able to get a 200 success by getting to a url like: something.com/segmentfolder/../../abcd1234/videosegment_01.mp4
Curl is smart enough to get rid of the ../../ and create a valid path so the actual request (which can be seen using curl -v) looks something like: something.com/abcd1234/videosegment_01.mp4
Great. But AVPlayer doesn't do this. So it makes the request as is, which leads to a 400 error and it can't download the segment.
We were able to simulate this problem with Swift playground fairly easily with this code:
let hlsurl = URL(string: "website.com/segmentfolder/../../abc1234/videosegment.mp4")!
print(hlsurl)
print(hlsurl.standardized)
The first print shows the URL as is. Trying to GET that URL leads to a 400. The second print properly handles it by adding .standardized to the URL. This leads to a 200. The problem is, this only works for the top level/initial manifest, it doesn't work for all the child manifests and segments.
let url = URL(string: "website.com/segmentfolder/../../abc1234/videosegment.mp4")!
let task = URLSession.shared.dataTask(with: url.standardized) {(data, response, error) in
guard let data = data else { return }
print(String(data: data, encoding: .utf8)!)
}
So question, does AVPlayer support relative URLs? If so, why can't it handle the ../../ in the URL path like other players and curl can? Is there a special way to get it to trigger standardizing ALL URL requests?
Any help would be appreciated.

When is ActiveStorage::IntegrityError raised?

My app (locally) raises ActiveStorage::IntegrityError error, whenever it tries to attach a file. How can I get out of this error?
I have only one has_one_attached and I don't know how that error gets in the way.
# model
has_one_attached :it_file
Tempfile.open do |temp_file|
# ...
it_file.attach(io: temp_file, filename: 'filename.csv', content_type: 'text/csv')
end
# storage.yml
local:
service: Disk
root: <%= Rails.root.join("storage") %>
EDIT: it can be related with deleting storage/ directory (it happened after I deleted that) or it can be because it's happening in a job (the full error was Error performing ActivityJob (Job ID: .. ) from Async( .. ) in .. ms: ActiveStorage::IntegrityError (ActiveStorage::IntegrityError)
And this does not add files to storage/ folder but it's generating folders under it when I tried to attach them.
As mentioned in the comments, one reason this can happen is that the file object is at the end of the file, which was the problem in this case. It could be fixed here with temp_file.rewind.
Very weird. After update to rails 6.0 I must recalculate some checksums. Yes I used dokku,docker. It was fine before update.
# Disk service is in use for ActiveStorage
class ProjectImage < ApplicationRecord
has_one_attached :attachment
end
# update all checksums
ProjectImage.all.each do |image|
blob = image.attachment.blob
blob.update_column(:checksum, Digest::MD5.base64digest(File.read(blob.service.path_for(blob.key))))
end;
This was happening to me not because of anything mentioned above.
In my case I was defining a test var using one dummy file, but I was attaching it to 2 different records.
let(:file) { File.open(Rails.root.join('spec', 'fixtures', 'files', 'en.yml')) }
let(:data) { [file, file] }
The function in question received a list of ids and data and attaching the files to the records. This is a simplified version of the code
record_0.file.attach(
io: data[0],
filename: 'en.yml',
content_type: 'application/x-yaml'
)
record_1.file.attach(
io: data[1],
filename: 'en.yml',
content_type: 'application/x-yaml'
)
Once I defined 2 test vars, one for each record, using the same file I got it to work.
let(:file_0) { File.open(Rails.root.join('spec', 'fixtures', 'files', 'en.yml')) }
let(:file_1) { File.open(Rails.root.join('spec', 'fixtures', 'files', 'en.yml')) }
let(:data) { [file_0, file_1] }
Background
In my case I faced this error when I was trying to upgrade the rails config defaults.
I was activating configs from config/initializers/new_framework_defaults_6_1.rb which was generated by rails app:update rake task.
Cause
I activated this setting
Rails.application.config.active_storage.track_variants = true
which collided with our existing mechanism to handle variant generation. We are reading variants size/types from account-settings so its complicated.
Technical cause
As mentioned above, this is causes due to mismatch in checksum of the file and checksum stored in blob record in database.
# activestorage-6.1.7.1/lib/active_storage/downloader.rb:37
def verify_integrity_of(file, checksum:)
unless Digest::MD5.file(file).base64digest == checksum
raise ActiveStorage::IntegrityError
end
end
Solution
I commented it back
To make sure its always inactive, I moved the setting to config/initializers/active_storage.rb like below
# Track Active Storage variants in the database.
# *Note*: Its Rails 6.1.0 feature and we have our own way of handling variants which also depends upon the
# thumbnail_sizes setting values and varies from account to account.
# so we are disabling it for now.
Rails.application.config.active_storage.track_variants = false
Summary
You may want to use this awesome feature so see workarounds.
In your case, causes can be something else so look deeper.
disabling this feature solves my issue.

Vimeo API: Upload from a File Using a Form

I followed the docs for the vimeo node.js api to upload a file. It's quite simple and I have it working by running it directly in node, with the exception that it requires me to pass the full path of the file I want to upload. Code is here:
function uploadFile() {
let file = '/Users/full/path/to/file/bulls.mp4';
let video_id; //the eventual end URI of the uploaded video
lib.streamingUpload(file, function(error, body, status_code, headers) {
if (error) {
throw error;
}
lib.request(headers.location, function (error, body, status_code, headers) {
console.log(body);
video_id = body.uri;
//after it's done uploading, and the result is returned, update info
updateVideoInfo(video_id);
});
}, function(upload_size, file_size) {
console.log("You have uploaded " +
Math.round((upload_size/file_size) * 100) + "% of the video");
});
}
Now I want to integrate into a form generated in my react app, except that the result of evt.target.files[0] is not a full path, the result is this:
File {name: "bulls.mp4", lastModified: 1492637558000, lastModifiedDate: Wed Apr 19 2017 14:32:38 GMT-0700 (PDT), webkitRelativePath: "", size: 1359013595…}
Just for the sake of it, I piped that into my already working upload function and it didn't work for the reasons specified. Am I missing something? If not I just want to clarify what I actually have to do then. So now I'm looking at the official Vimeo guide and wanted to make sure that was the right road to go down. See: https://developer.vimeo.com/api/upload/videos
So if I'm reading it right, you do several requests to achieve the same goal?
1) Do a GET to https://api.vimeo.com/me to find out the remaining upload data they have.
2) Do a POST to https://api.vimeo.com/me/videos to get an upload ticket. Use type: streaming if I want a resumable upload such as those provided by the vimeo streamingUpload() function
3) Do a PUT to https://1234.cloud.vimeo.com/upload?ticket_id=abcdef124567890
4) Do a PUT to https://1234.cloud.vimeo.com/upload?ticket_id=abcdef124567890 but without file data and the header Content-Range: bytes */* anytime I want to check the bytes uploaded.
Sound right? Or can you simply use a form and I got it wrong somewhere. Let me know. Thanks.
There's some example code in this project that might be worth checking out: https://github.com/websemantics/vimeo-upload.
Your description is mostly correct for the streaming system, but I want to clarify the last two points.
3) In this step, you should make a PUT request to that url with a Content-Length header describing the full size of the file (as described here: https://developer.vimeo.com/api/upload/videos#upload-your-video)
4) In this step, the reason you are checking bytes uploaded is if you have completed the upload, or if your connection in the PUT request dies. We save as many bytes possible, and we will respond to the request in step 4. with how many bytes we received. This lets you resume step 3 where you left off instead of at the very beginning.
For stability we highly recommend the resumable uploader, but if you are looking for simplicity we do offer a simple POST uploader that uses an HTML form. The docs for that are here: https://developer.vimeo.com/api/upload/videos#simple-upload

How do I init XOD in WebViewer? "DisplayModes" is undefined

I'm trying to load a XOD document into a PDFTron WebViewer. As far as I can read in the documentation and samples, this should be a simple "plug and play"-operation - it should simply work when you point at a file. Ideally, in my example, the document should be fetched from a service, as so:
fetch('/myservice/GetXOD')
.then(function(data) {
$(function() {
var viewerElement = document.getElementById("viewer");
var myWebViewer = new PDFTron.WebViewer({
initialDoc: data.body
}, viewerElement);
});
});
Unfortunately I get the following error:
Uncaught TypeError: Cannot read property 'DisplayModes' of undefined
The reason I'm doing it in a fetch, is because I'm rendering a Handlebars template, and pass the data to instantiate in a callback. However, I've isolated the code into an otherwise "empty" HTML-document, and in the simplified example below, I'm simply pointing at the XOD provided by PDFTron on page load (no fetch this time).
$(function() {
var viewerElement = document.getElementById("viewer");
var myWebViewer = new PDFTron.WebViewer({
initialDoc: 'GettingStarted.xod' // Using the XOD provided by PDFTron
}, viewerElement);
});
This unfortunately returns a different error (HTTP status 416).
Uncaught Error: Error loading document: Error retrieving file: /doc/WebViewer_Developer_Guide.xod?_=-22,. Received return status 416.
The same error appears when I run the samples from PDFTron on localhost.
I'm at a complete loss of how I should debug this further - all the samples assume everything is working out of the box.
I should note that I can actually get PDFs working just fine on localhost, but not on the server. XODs are problematic both on the server and on localhost.
I'm sorry to hear you are having troubles running our samples.
Your error message says 416 which means "Requested range not satisfiable". Perhaps your development servers do not support byte range requests (https://en.wikipedia.org/wiki/Byte_serving).
Could you try passing an option streaming: true? When streaming is true you're just requesting the entire file up front which shouldn't be a problem for any servers but it is a problem for WebViewer if the file is large because it will need to be completely downloaded and parsed at the start which is slow.

SWF SecurityError: Error #2000: No active security context

Hi
I have a flash image gallery that worked just fine, until few days a go it stopped loading the images. the debugger throws this error :
SecurityError: Error #2000: No active security context.
can someone explain what can be the cause?
I've run into this problem when working with loading images where the path is located in an external XML file. So... I load the XML get the path from it but then the problem I had was I was loading 30+ images and the error was popping up only 6 times so.. I had no idea which file locations where the bad ones.
If you want flash to out put more info than just :
SecurityError: Error #2000: No active security context.
Add this event listener to your Loader:
loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
and finally this function:
protected function ioErrorHandler(e:IOErrorEvent):void{
trace(e.text);
}
With this in place your Security Error will convert to a URL Not Found Error with the file location you supplied. With this information in hand it should be easier for you to debug the problem.
Error #2035: URL Not Found. URL: file:////Volumes/Macintosh%20HD/Users/cleanshooter/Documents/Website%20/here/there/everywhere/30805/filename.jpg
I faced this issue before,the final conclusion was related to incorrect image path or name
Did your images extensions change, possibly from like .jpg to .JPG or something?
Typically this is called if there is a problem with your external media. Here's a workaround for it, but I typically try and solve versus make it go away.
setTimeout( function():void{fileReference.load();}, 1);
Hope this helps.
I ran across this issue and used the above setTimeout example but for a slightly different purpose. I was calling a php script that hit Twitter and got the same security issue in Flash debug player. I just wanted to add my example which builds on the above to show how you can use this "workaround" for URLLoader as well as fileReference.
var myXMLLoader:URLLoader = new URLLoader();
var urlStr:String = "http://www.yourdomain.com/php/twitter.php";
var myVariables:URLVariables = new URLVariables();
myVariables.twitterID = "yourtwitterID";
var myURLRequest:URLRequest = new URLRequest(urlStr)
myURLRequest.data = myVariables;
setTimeout(function():void { myXMLLoader.load( myURLRequest ); }, 1);
myXMLLoader.addEventListener(Event.COMPLETE, onXMLLoadHandler);
You need to handle the error:
loader.contentLoaderInfo.addEventListener(HTTPStatusEvent.HTTP_STATUS, onHTTPError);
protected function onHTTPError(e:HTTPStatusEvent):void{
trace("HTTPError"+e.status);
}
This way it will handle the error and works fine.
In response to headwinds:
In AS3 you need to import flash.utils.setTimeout. The syntax for setTimeout is setTimeout(A, B, ...rest);
Where B is the function to get called afterwards,
A is the delay in ms (e.g. 1000 for a second)
and C is any number of parameters you need to provide for the function, separated by a comma.
E.g.
import flash.utils.setTimeout;
// package, etc
//main function
setTimeout(respond, 500, true, false);
private function respond(A : Boolean, B : Boolean) : void {
var result : Boolean = A == B;
trace(result);
}

Resources