HTTParty returning a string - ruby

I am using an API which is returning the wrong mime type, it's coming out as text/html rather than application/json.
Some of the responses are application/json so I know that the problem is due to mime type.
But for the text/html (which returns valid json with the wrong mime type) httparty will only parse this into a string rather than a hash.
Is there a way to parse this string into a hash?
I've tried using require 'json' but using JSON.parse comes up with an unexpected key error.

If you're extending a class with HTTParty try adding
format :json
to the class
also make sure you're parsing the body of the response and not the response object.
JSON.parse(get(self.class.get("some_url","some_params").body)

Without code or API URL I can only guess. Perhaps the API can respond in multiple ways and you need the appropriate Accept header.
class Foo
include HTTParty
headers 'Accept' => 'application/json'
end

Related

Accept file upload (without a form) in Sinatra

I have this Sinatra::Base code:
class Crush < Sinatra::Base
post '/upload' do
erb params.inspect
end
end
I am using Postman and its interface for uploading a file. So I send a POST request with form-data, where in the body of the request the name is hello and the value is a file test.txt which contains just a simple string hey there.
When I do params.inspect I get this long string
{"------WebKitFormBoundaryocOEEr26iZGSe75n\r\nContent-Disposition: form-data; name"=>"\"hello\"; filename=\"test.txt\"\r\nContent-Type: text/plain\r\n\r\nhey there\r\n------WebKitFormBoundaryocOEEr26iZGSe75n--\r\n"}
So basically a long has with a single key and a single value. Reading most Sinatra tutorials (where the file is accepted from a form), there's a nice way Sinatra handles this using params[:file], but this doesn't seem to be the case when the file is coming straight from the body of an HTTP request.
I tried a non-modular approach too withou Sinatra::Base, thinking it's some parsing middle-ware missing, but got the same result.
Is there something I'm missing here? Must I go and make my own custom parser to get the content of this long hash? Or is there an easier way?
I figured it's Postman issue. When I switch from 'x-www-form-urlencoded' to 'form-data' in Postman, in the Header section, the field: Content-Type => application/x-www-form-urlencoded is NOT removed. So for those who encounter this problem, make sure you remove it manually.

Supporting multiple JSON content types in Grape

I’ve been using Grape to write an Evernote-like API, and have been using Collection+JSON (MIME type "application/vnd.collection+json"). I now want to also support JSON API (mime type "application/vnd.api+json").
(The reason I want to do this is so it will be easier to write an Ember client, since Ember Data has support for JSON API built in. It might make more sense to write client-side JavaScript to solve my problem, but I’m stronger in Ruby than JavaScript.)
My goal is to have the server support either format, and choose which one based on the Accept header. Is this a reasonable thing to do, or does it not make sense to have multiple JSON types? It appears that Grape doesn’t support this. If I just have the wrong idea, then the code below is probably not relevant to that answer.
Here are some relevant pieces of my server code:
class NotesServer < Grape::API
content_type :json, ‘application/json’
content_type :json_api, 'application/vnd.api+json'
formatter :json_api, lambda { |object, env| object.to_json_api }
content_type :collection_json, 'application/vnd.collection+json'
formatter :collection_json, lambda { |object, env| object.to_collection_json }
resource :notes do
desc 'Get a note.'
params do
requires :id, type: Integer, desc: 'Note ID.'
end
route_param :id do
get do
NoteRepresenter.new Note.find(params[:id])
end
end
end
…where NoteRepresenter defines both to_json_api and to_collection_json.
The idea is to use the same Representer in either case, but to call the appropriate method depending on the Accept header.
When I run curl against this with any Accept header, the response has Content-Type: application/json, and “to_json” is called on the representer. If I leave out the content_type :json, ‘application/json’ line, I get a 406 Not Acceptable response every time.
I figured out that in the Grape library, in lib/grape/middleware/formatter.rb, the "format" of a request is considered JSON for any of these MIME types, since the vnd.collection+ or vnd.api+ parts are parsed out of the header. So my question is, is this a matter of Grape not supporting what I’m trying to do, or am I trying to do something that makes no sense?
I found a reasonable way to do this that I’mr reasonably happy with, but I don’t think it totally aligns with how Grape expects you to do things.
I created a custom formatter called JsonFormatter, so my server code looks like this:
class NotesServer < Grape::API
formatter :json, JsonFormatter
# …
end
And the formatter itself:
module JsonFormatter
def self.call(object, env)
case env['HTTP_ACCEPT']
when 'application/vnd.api+json'
object.to_json_api
when 'application/vnd.collection+json'
object.to_collection_json
else
object.to_json
end
end
end
So now either to_json_api or to_collection_api (or to_json) will be called on the presenter object, depending on the Accept header. One issue I haven’t solved yet is that the Content-Type is set to application/json in all cases.

HTTParty parsed_response returns a String instead of Hash

HTTParty's parsed_response method returns a Hash if you get a response code 200 but otherwise it will return a String regardless if the webserver returns a XML response.
HTTParty.get(post_url).parsed_response.class # Depends on response code
Amazon will provide XML (explaining what went wrong) even on a 403.
Am I missing something?
HTTParty parses its #parsed_response based on what the HTTP response's Content-Type header is. Verify what the value is for that HTTP header. In your case, you'd want it to be application/xml.
In case anyone is still encountering this issue today, get can take a format parameter, which can help you ensure your HTTParty::Response object is a Hash:
url = HTTParty.get('http://domain.com', format: :json) # class is a Hash

Creating a route with Sinatra to only accept a certain Content-type

I'm trying to create a route with Sinatra that only accepts POST with an Content-type: application/json without success.
My approach is as follows:
post '/dogs', :provides => :json do
# returns here a json response
end
Testing with curl, I have seen that :provides => :json configures the route to respond with an Content-Type: application/json.
That's right because I want also to respond with a JSON message to the POST request but I really need that this route only respond to POST requests with a Content-Type: application/json and not, for example, to others (e.g. Content-Type: application/xml).
Is there any way in Sinatra to restrict the route to only accept requests with a certain Content-Type?
Requests do not contain "Content-Type" header, but rather have "Accept". Sinatra should basically only respond to requests with "Accept" containing "application/json". Just to make sure:
post '/gods', :provides => :json do
pass unless request.accept? 'application/json'
...
end
Read this
http://rack.rubyforge.org/doc/classes/Rack/Request.html
request.content_type will tell you
Phil might be right regarding RFC but in reality many things put a content-type in a POST request, therefore it is useful to know what it is.
i would think it is something like:
pass unless request.accept? == 'application/json'

Post request with body_stream and parameters

I'm building some kind of proxy.
When I call some url in a rack application, I forward that request to an other url.
The request I forward is a POST with a file and some parameters.
I want to add more parameters.
But the file can be quite big. So I send it with Net::HTTP#body_stream instead of Net::HTTP#body.
I get my request as a Rack::Request object and I create my Net::HTTP object with that.
req = Net::HTTP::Post.new(request.path_info)
req.body_stream = request.body
req.content_type = request.content_type
req.content_length = request.content_length
http = Net::HTTP.new(#host, #port)
res = http.request(req)
I've tried several ways to add the proxy's parameters. But it seems nothing in Net::HTTP allows to add parameters to a body_stream request, only to a body one.
Is there a simpler way to proxy a rack request like that ? Or a clean way to add my parameters to my request ?
Well.. as i see it, this is a normal behaviour. I'll explain why. If you only have access to a Rack::Request,(i guess that) your middleware does not parse the response (you do not include something like ActionController::ParamsParser), so you don't have access to a hash of parameters, but to a StringIo. This StringIO corresponds to a stream like:
Content-Type: multipart/form-data; boundary=AaB03x
--AaB03x
Content-Disposition: form-data; name="param1"
value1
--AaB03x
Content-Disposition: form-data; name="files"; filename="file1.txt"
Content-Type: text/plain
... contents of file1.txt ...
--AaB03x--
What you are trying to do with the Net::HTTP class is to: (1). parse the request into a hash of parameters; (2). merge the parameters hash with your own parameters; (3). recreate the request. The problem is that Net::HTTP library can't do (1), since it is a client library, not a server one.
Therefore, you can not escape parsing some how your request before adding the new parameters.
Possible solutions:
Insert ActionController::ParamsParser before your middleware. After that, you may use the excellent rest-client lib to do something like:
RestClient.post ('http://your_server' + request.path_info), :params => params.merge(your_params)
You can attempt to make a wrapper on the StringIO object, and add, at the end of stream,your own parameters. However, this is not trivial nor advisable.
Might be one year too late, but I had the same issue verifying Paypal IPNs. I wanted to forward back the IPN request to Paypal for verification but needed to add :cmd => '_notify-validate'.
Instead of modifying the body stream, or body, I appended it as part of the URL path, like so:
reply_request = Net::HTTP::Post.new(url.path + '?cmd=_notify-validate')
It seems a bit of a hack, but I think it's worth it if you aren't going to use it for anything else.

Resources