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

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'

Related

How to get Headers from request?

I am trying to create a webhook. I am brand new to Ruby, but am setting up a very simple practice server. Each time a new issue/ticket is created for one of my github repos, a POST request is made to a URL that I have setup to grab the payload. I am loosely following this tutorial.
I am able to access all the elements that are being posted in the actual payload/body using this code:
require 'sinatra'
require 'json'
post '/payload' do
request.body.rewind
payload_body = request.body.read
puts payload_body
How can I grab the contents of the header? I have been scrolling through documentation, but have had no luck. I tried accessing the header through response and request.headers and received this error: NoMethodError - undefined method `headers' for #<Sinatra::Request:0x0000000006de49e0>
Please let me know if I am missing something here. Thank you.
Here is an example of the headers from my POST request:
Request URL: http://505db39aa8ef.ngrok.io:
Request method: POST
Accept: */*
content-type: application/json
User-Agent: GitHub-Hookshot/cf3e7e0
X-GitHub-Delivery: 8d7fe080-ecc2-11ea-9b34-d35efaaf3885
X-GitHub-Event: issues
X-GitHub-Hook-ID: 245792914
X-GitHub-Hook-Installation-Target-ID: 279687508
X-GitHub-Hook-Installation-Target-Type: repository
X-Hub-Signature: sha1=ed12f9e7a31b08f9109557d6a9dd899ca85de9b5
request.get_header('HTTP_X_GITHUB_HOOK_INSTALLATION_TARGET_TYPE')
# or
request.env['HTTP_X_GITHUB_HOOK_INSTALLATION_TARGET_TYPE']
It is worth mentioning that,sinatra will process custom header:
add prefix HTTP_
capitalize every letter
gsub - to _
You can use request.env to access all headers.
For detail information about what variable will be added HTTP_ you can refer there

is there a way to modify or send custom headers on grape?

I'm using Goliath and Grape. On my goliath server it calls the grape api like so:
when '/posts' then FrameworksAPI::API.call(env)
On my grape api class, my method is as simple as this:
get '/:id' do
Post.find(params[:id])
end
I'd like to modify the headers - specifically the 'Content-Length' but unsure how to.
Also i'd like to ask an additional question. How do i create callback/filters specifically before the method GET returns the result i'd like to modify the result.
Grape has a header helper for a few versions now.
header 'Content-Length`, 42.to_s
For your second question on modifying the body, try using after do ... at API level.
The return from your FrameworksAPI::API.call(env) method will be a triplet [status_code, headers, body]. So, instead of just returning that from your case you'd do something like:
when '/posts' then
status, headers, body = FrameworksAPI::API.call(env)
headers['whatever'] = blah
[status, headers, body]
You can also change the body, just be careful as the body maybe an array.
There is also a Content-Length middleware that is provided by Goliath. Content-Length is loaded by default although if you set a custom Content-Length it will take precedence. Just be carefull that other middlewares like the formattings don't change the body after you set your content-length.

Why am I getting a 405 on a POST request for JSON?

So I've got a Padrino app with a controller that looks roughly like this (simplified for brevity):
App.controllers :questions do
get :index, :with => :id, :provides => [:html, :json] do
# Stuff
end
post :index, :with => :id, :provides => [:html, :json] do
# Other stuff
end
end
If I hit "questions/1" in my browser, I see the HTML page for the given question. If I hit "questions/1.json", I see the JSON representation of the question. Exactly like I'd expect.
Now, if I submit a POST request to "questions/1" via a web form, it works. But if I try to send a POST request to "questions/1.json" (signaling that I want the response in JSON format—or at least that's how I thought it worked), I get a 405 Method Not Allowed.
I'm guessing there's something basic I'm misunderstanding here. What am I missing? More importantly, how should I define a route to accept POST requests and provide either HTML or JSON responses?
Well, I'm not really sure why this was happening; but for now I've gotten around the issue by setting the "ACCEPT" header in my POST request to "application/json" instead of tacking ".json" onto the end of the URL (and upon my limited internet research, this may be the preferred approach anyway).

HTTParty returning a string

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

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