request.format returning */* - ruby-on-rails

I'm currently developing an API for my application on RoR
As an example, I created some XML, loaded with all the info I need to create the object, let's say a Person, and using Curl I submitted it to my application
I'm able to call exactly the create action I want from the controller and the hash params of the object are being passed correctly
But now I need to apply a different behaviour if request was made or not with XML, what is bothering me is why in the controller request.format gives */*.
Any clues?
curl -v -H "Content-Type: application/xml; charset=utf-8" --data-ascii #client.xml http://foo.com:3000/clients?api_key=xxx
def create
logger.debug request.format # produces "*/*"
if request.format.xml?
# never gets here
end
end

*/* means that the user-agent accepts all formats and doesn't care what format you give it. I believe Safari does this, among others. By default, curl sends an Accept header of */*.
Here is a dump of the headers curl sends by default:
User-Agent: curl/7.18.1 (i386-apple-darwin9.6.0) libcurl/7.18.1 zlib/1.2.3
Host: example.com
Accept: */*
Content-Type:
However, in this case, it looks like you want to send back XML if the payload sent to you was XML? If that's the case, you want to check the request's Content-Type header directly. i.e., request.content_type is the method you want.
Addenda: I thought a bit more about this, and I think the best approach is to first check request.format and only if that is inconclusive check request.content_type. Essentially, the HTTP spec provides for clients being able to tell servers that "I'm giving you XML, but I want JSON back." The Accept header is how clients tell you what they want back, and if someone actually sends it, you should honor that. Only use the request's Content-Type as a hint if the client didn't specify.

*/* simply means that all MIME types are accepted.
Looking at the code for the request.format method, the MIME type is determined by the file extension, or if that's not present then by the value of the HTTP Accept header. So you either need to pass Curl an XML file saved to disk, or get Curl to set the Accept header to an XML MIME type (e.g. text/xml) when it makes the request to your API.

Related

Require content-type 'application/json' for appropriate requests in Rails

I have an API that handles almost exclusively application/json in Rails. A few endpoints can handle application/x-www-form-urlencoded for file-uploads.
I want to enforce the content-types: when a client adds anything other than application/json I want to send an 406 - not acccepted error back, in my ApplicationController
before_action :require_content_type_json
def require_content_type_json
return if request.content_type == Mime::JSON.to_s
head status: 406
end
However, RFC 7231 (http) states that:
A sender that generates a message containing a payload body SHOULD
generate a Content-Type header field in that message unless the
intended media type of the enclosed representation is unknown to the
sender. If a Content-Type header field is not present, the recipient
MAY either assume a media type of "application/octet-stream"
([RFC2046], Section 4.5.1) or examine the data to determine its type.
I roughtly interpret that as "when the request comes with a body, the content-type must be provided by the client, else the server should assume "application/octet-stream".
Since I cannot handle octet-stream, I want to return 406 - not accepted too, there. In other words: when there is a body, a header setting the content-type to "application/json" should be present. Always.
So, how do I detect whether a user should have sent a content-type header along? Is it enough to simply check for !request.body.empty?: body is not empty?
Or should I assume that only GET/OPTIONS/HEAD requests have no body and all others do and should require a content-type?
Does Rails have any helpers or classes in place to deal with this?

How to get Rails to respond with json based on headers

I'm building an API with Rails 4 and in my controller I'm using respond_to to differentiate between html and json request which all works fine. While testing my API in Postman, I've added the following header: Content-Type: application/json but in my request I still need to add .json at the end of it like so: https://myapi.com/api/users.json otherwise Rails will respond with html even though the aforementioned header is present.
My question is this: is there a way that Rails can recognise the Content-Type: application/json header and respond with json without me using the .json part in the url like so: https://mypai.com/api/users?
You also need to set the Accept header to Accept: application/json
If you're curious as to why, and the difference between the two headers, the answers to this post explain it best: https://webmasters.stackexchange.com/questions/31212/difference-between-the-accept-and-content-type-http-headers
If you are using any link helper inside a view, simple add the format information, like posts_path(format: :json).

zf2 decodeGzip, why is it protected?

I'm getting back what appears to be gzipped data in a call to a zf2 api. The content-type is gzip, and the content body looks encoded. So I tried this:
$decoded = $response->decodeGzip($response->getContent());
and I got back this error:
Call to protected method Zend\Http\Response::decodeGzip()
Why is it protected? It seems like decoding gzipped data would be a handy thing to be able to do.
You should use $response->getBody(). The getBody() method checks the Content-Encoding header and if this is gzip, it will extract the body from the Gzipped content.
You can check this method in the online repository: Zend\Http\Response

How to keep Rails from Processing Large XML Post

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

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