How to get Headers from request? - ruby

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

Related

POST a JSON and a CSV in a multipart/form-data request through a NiFi 1.15 InvokeHTTP processor

I'm working on a NiFi 1.15 flow where I have to send a request to a service that requires 2 pieces of form-data sent in a POST request as a multipart/form-data. The first part is a simple JSON object with a few parameters, while the second part is a CSV file. Here is an example of what I am trying to achieve.
Content-Type: multipart/form-data; boundary=1cf28ed799fe4325b8cd0637a67dc612
--1cf28ed799fe4325b8cd0637a67dc612
Content-Disposition: form-data; name="json"; filename="json"
{"Param1": "Value1","Param2": "Value2","Param3": true}
--1cf28ed799fe4325b8cd0637a67dc612
Content-Disposition: form-data; name="file"; filename="body.csv"
Field1,Field2,Field3,Field4,Field5
VALUE_FIELD_1,VALUE_FIELD_2,VALUE_FIELD_3,"Some value for field 4",VALUE_FIELD_5
--1cf28ed799fe4325b8cd0637a67dc612--
Another acceptable output would have the Content-Disposition lines empty.
Due to a few restrictions in the environment I am working on, I am not allowed to use scripting processors, such as ExecuteGroovyScript as suggested in another SO question.
Instead, I started creating a GenerateFlowFile -> InvokeHTTP flow. The GenerateFlowFile would output to a flow file a text similar to the one mentioned above. Here is the screenshot of the GenerateFlowFile.
The connected InvokeHTTP processor is configured to use the POST Http Method and to send headers (the Authorization header in my case) and Send Message Body is set to true. It also extracts the Content-Type from the flow file previsously generated attribute through a ${mime.type} function. You can see the details in the following screenshots.
Sadly, this does not work. The server responds with an "Unexpected end of MIME multipart stream. MIME multipart message is not complete." error.
After searching for a while in SO, I found another question describing what looks like a similar problem, but there they are getting a different error and is also posting parameters through a different method.
I am also aware about the blog post from Otto Fowler where he shows how InvokeHTTP supports POSTs with multipart/form-data. I did try this approach too, but did not manage to get it working. The service throws an error stating that NiFi does not send one of my post:form:parts.
Right now I am stuck and am not able to send that data. I did manage to write a simple Python script to test if the server is working properly and it is. For reference, here is the script:
import requests
server = 'https://targetserver.com'
#Authentication
result = requests.post(server + '/authentication',
{'grant_type': 'password',
'username': 'username',
'password': 'password'})
token = result.json()['access_token']
#Build the request
headers = {'Authorization': 'bearer ' + token}
json_data = '{"Param1": "Value1","Param2": "Value2","Param3": true}'
# First the JSON then the csv file.
files = {'json': json_data,
'file': open('body.csv', 'rb')}
result = requests.post(server + '/endpoint', headers = headers, files = files)
print(result.text)
Does anyone have a suggestion on how to get around this situation?

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.

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'

Parse body of POST reqest in self-made server in Ruby

I am writing mock Ruby servers to test components of API. I send a POST request with a body, and I'd like my mock server to return a body of that POST request. Currently i have this code:
require 'socket'
webserver = TCPServer.new('127.0.0.1', 7125)
loop do
session = webserver.accept
session.print "HTTP/1.1 200/OK\r\nContent-type:text/html\r\n\r\n"
request = session.gets
session.puts request
session.close
end
A POST request with body FOO returns a response with body that contains only POST / HTTP/1.1 How to fix it?
Writing your own HTTP server is really going to get you into trouble because the specification, while superficially simple, has a number of subtle nuances that can trip you up. In this case, you're reading one line with gets and ignoring the bulk of the submission. You're going to have to address that by reading in and properly decoding the posted data.
For something with a familiar interface you might start with Net::HTTP::Server instead.

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