How to keep Rails from Processing Large XML Post - ruby-on-rails

In our rails application we have a many actions that do regular webapp actions. But, we have a single action that accepts a large XML file. I would like to keep rails from parsing the XML into params. Instead, I would like to be able to get the URL params ( /documents/{id}/action ) and then write out the xml file to a specific directory. How do I keep Rails from processing it?
How would I define the action to handle this?
def handle_xml
# what to put here
end
The upload is done using Content-Type: application/xml It is a single file, and not part of a multipart form. The sample curl statement would be:
curl-H 'Accept: application/xml' -H 'Content-Type: application/xml' -X POST -d '<?xml version="1.0" encoding="UTF-8"?><test></test>' http://0.0.0.0:3000/controller/handle_xml

If you want to prevent rails from automatically parsing the XML data into a hash of parameters, you'll have to replace the ParamsParser middleware with your own custom version.
When a file is posted to rails, the ParamsParser middleware modifies the request parameters and turns it into a Hash if the data format is xml. You can find the details in the params_parser.rb file in rails.
Here's a RoR mailing list message similar to the question that you've asked
Unfortunately, as a new user I can't post any more links, but you should search google with "Sanitizing POST params with custom Rack middleware" for some more details on writing custom rack middleware.

I too have come across this problem recently. However mine is in an internal application where I have full control over both the Rails app and the clients connecting to it.
In my app the client POSTs a large XML data set to the Rails app. I wanted to process the XML document in a delayed job (resque). My workaround was to make the client use an alternate content-type. I used application/octet-stream. This prevents Rails from parsing the POST data. The data is available in request.raw_post.

The action should receive it as a file (through way of multipart form upload) and then store it as a temporary file for you.

Have you tried sending the xml file has one variable in the http uri request? So something like
#xml_file = xml..xml...xml...
parameters => {
query => {
xml_file => #xml_file
}
}
Httparty.post("url", parameters)
Then in your method:
def handle_xml
#xml_file = params[:xml_file]
#xml_file.save (or whatever you want here..)
end

Related

How to POST JSON in body for functional tests in Symfony 1.4

I'm writing some functional tests for a POST API endpoint. I've reviewed the documentation and can't find a way to add content to the POST body. The post method for sfBrowser:
post('some url',array('x'=>'y'))
Only creates POST parameters (in this case x=y). Is there anyway of adding content to the post body using sfBrowser?
From what I have found here, here and here, the POST format takes parameter:value format, so you can send your JSON with some code like:
post('some url', array('json_data' => json_encode($toJson))
and then decode in your action with
$jsonObj = json_decode($request->getParameter('json_data'));
but you need to associate your JSON data with a parameter name in your POST to retrieve it on the server side.
As a side note, after looking at the Symfony code, the parameters are given straight to $_POST except for CSRF, which is tweaked.

Posting XML to Rails

I am trying to
1) post some XML to my Rails App (without using forms)
2) have my Rails App then parse the posted XML to create entries within my database
For example, I'd like to post
<transaction>
<date>12-01-2010</date>
<amount>1.00</amount>
</transaction>
<transaction>
<date>12-02-2010</date>
<amount>2.00</amount>
</transaction>
Assuming I have a transaction controller. I'd then like to create an "upload" action that would allow me to parse the snippet above and create entries within my database.
From my research it seems like 1) can be accomplished with curl. But I'm not sure if that is right, because I don't know what URI to point the curl command to.
I am also not clear how to get the XML data within the "upload" action of my controller.
Any help would be appreciated.
Thank you!
You have to set Header Content-type, send as POST request and then you have only worry about the XML posted content
Here is a example of how to post xml by using curl
curl -X POST -H "Content-Type:text/xml" -d "<xml>Your XML Data</xml>" "http://your url"
XML parameter parser is removed in Rails 4, so if you're using Rails 4, you need to install actionpack-xml-parser gem first, refer to https://github.com/rails/actionpack-xml_parser

Rails Paperclip XML POST File

I am able to 'POST' to a Rails application (with Paperclip) using XML instead of the standard web form (trying to do it from another Ruby script). However, I would like to include a binary file.
Is there any way to include the binary data within an XML tag? Or can I do something like B64 encode the data on the client and then decode it before it hits the Paperclip plugin?
UPDATE:
The browser sends a POST with this data (among others):
Content-Disposition: form-data; name="upload[upload]"; filename="foo.jpg"
Content-Type: image/jpeg
ÿØÿà�JFIF��`�`��ÿþ�Created by AccuSoft Corp.ÿÛ�C�...
I'd like to replicate that, but within XML
The short version is: use type="file", base64-encode the file, and put it inside a CDATA block. I originally found an explanation at this link:
http://techblog.floorplanner.com/2010/02/15/restful-uploading-of-files-using-xml/
That link appears to have died, so I recommend checking out the Internet Archive copy of the blog post:
http://web.archive.org/web/20100825030057/http://techblog.floorplanner.com/2010/02/15/restful-uploading-of-files-using-xml/
Also linked from that post is a gem that implements an encoder for files posted to Rails as XML: https://github.com/nragaz/encoded_attachment

Using restclient with multipart posts

I'm using restclient for a multipart form to send data to a restful web service (it's Panda video encoding service).
The trick though, is that the file I am passing into restclient (Technoweenie branch) is coming from my own form that a user submits.
So, lets walk through this. A user posts a file to my rails app. In my controller, it receives the file from params[:file]. I then want to pass params[:file] down to Panda using RestClient.
The error I'm getting is on the Panda server follows. I noticed that the file param in the stack trace is in a string as well (which I assume is Panda turning into a string for a nicer stacktrace).
~ Started request handling: Wed Aug 12 18:05:15 +0000 2009
~ Params: {"format"=>"html", "multipart"=>"true", "account_key"=>"SECURE_KEY", "action"=>"upload", "id"=>"SECURE_ID", "controller"=>"videos", "file"=>"#<File:0xcf02ca4>"}
~ 9bfb1750-6998-012c-4509-12313900b0f6: (500 returned to client) InternalServerErrorcan't convert nil into String
/var/local/www/panda/app/models/video.rb:246:in `extname'
/var/local/www/panda/app/models/video.rb:246:in `initial_processing'
/var/local/www/panda/app/controllers/videos.rb:79:in `upload'
I doubt you can really pass a CGI-style upload param from Rails into restclient and expect it to work.
A regular upload in Rails would have quite some extra attributes which do not belong in a posted resource (like the original filename and so on), and a Rails upload contains an IO with the actual file data. Also a file upload object in Rails might be a Tempfile handle and might be a StringIO - depending on the size of the upload.
What you effectively need to do is "repackage" your upload for rest-client to handle it properly, and pass the repackaged and rewound Tempfile object to restclient. Maybe you can get away with just picking the upload object itself instead of the whole params[:file]
Confirm that your restclient action can save locally first. If the action cannot save locally, then you will have a better idea where to look while trouble shooting.
Looks like the problem is with rest-client's posting of the file, check out an alternative method for posting like curb.
Lots of examples for posting multipart form data on this question: Ruby: How to post a file via HTTP as multipart/form-data?

Load testing multipart form

I'm trying to load-test a Rails application using JMeter. A critical part of the application involves a form that includes both text inputs and file uploads. It works fine in a browser, but when I try to post that page in JMeter, Rails is saving all of the parts of the multipart form as temp files, which causes things to break when it's looking for a string and gets a tempfile instead.
It appears that the difference is that, from a browser, the piece of the multipart request that contains a text input looks like this:
-----------------------------7d93b4186074c
Content-Disposition: form-data; name="field_name"
test
-----------------------------7d93b4186074c
while from JMeter it looks like this:
-----------------------------7d159c1302d0y0
Content-Disposition: form-data; name="field_name"
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 8bit
test
-----------------------------7d159c1302d0y0
So apparently Rails sees the former and interprets it as a plain text value and treats it as a string, but sees the latter and saves it to a temp file.
I have not been able to find a setting to convince JMeter not to send the additional headers in the multipart form for non-file fields.
Is there a way to convince Rails to ignore those headers and treat the text/plain text as strings instead of text files? Or a quick way to put a filter in front of my controller that will strip the extra headers?
Alternately, is there a better tool to load-test a Rails application that includes file upload?
Turns out these days you can just tick "use browser compatible headers" in JMeter. Could've saved myself a hell of a lot of time there :-)
So, I have customized JMeter's multipart request posting part in the source code to put out the request that rails understand. The change is easy as shown below but to create compiling Java/JMeter environment took time. :(
Anyways, now I can successfully upload a file by multipart post via JMeter.
in src/protocol/http/org/apache/jmeter/protocol/http/sampler/PostWriter.java
writeStartFileMultipart()
//writeln(out, "Content-Transfer-Encoding: binary"); // $NON-NLS-1$
writeFormMultipart()
/*****
writeln(out, "Content-Type: text/plain; charset=" + charSet); // $NON-NLS-1$
writeln(out, "Content-Transfer-Encoding: 8bit"); // $NON-NLS-1$
*****/
P.S.
A tip tip to create the build environment for 2.4 is
to comment out the 3rd party libraries check in build.xml file.
copy lib/xstream-1.3.1.jar from binary archive into lib/ directory
There may be a better way, but I ended up adding a quick filter to turn the text/plain tempfiles into strings within the parameter hash:
def change_text_files_to_strings
params.each_pair do |key, value|
params[key] = value.read if (value.class.to_s=='Tempfile' && value.content_type.start_with?('text/plain') )
end
end
By the way, it turns out that jmeter is correct here, and rails incorrect: according to RFC 2388, each item in a multipart request should have a content type (not just files), so Rails really shouldn't be using the presence of a content-type header to determine whether it's a file. Ah well.
I also used the solution above as ColdFusion was sending similar headers (minus the Content-Transfer-Encoding) with each piece of form data. I wonder if there's a better way.
EDIT: Anyone know if this has been fixed in Rails 3?
What kind of error do you get? Something like
NoMethodError (undefined method `rewind' for "1":String):
There is an issue with Rack that could explain your problem. See https://github.com/rack/rack/issuesearch?state=open&q=rewind#issue/116
We were also having a similar issue, In addition to the above answers we also correlate the X-CSRF-Token of HTTP Header Manager in that request and were
successfully able to upload the required media as many as times we wanted.

Resources