How to use standard library to parse URL params in Ruby? - ruby

I am using the following code with "uri" and "CGI" to parse the params of the URL:
require 'socket'
require 'uri'
require 'CGI'
server = TCPServer.new 8888
while session = server.accept
request = session.gets
p "request", request
url = "http://somewebsite.com" + request.sub("GET ", "").sub(" HTTP/1.1", "").gsub(/(\r|\n)/, "")
uri = URI(url)
params = CGI.parse(uri.query)
p "params", params
session.print "HTTP/1.1 200\r\n" # 1
session.print "Content-Type: text/html\r\n" # 2
session.print "\r\n" # 3
session.print "Hello world! The time is #{Time.now}" #4
session.close
end
I had to "make up" a full URL by adding the http://somewebsite.com to the path, and use uri and CGI functions to do it. If the browser uses http://localhost:8888/?a=123&b=hello then it works well. But if the browser tried to access http://localhost:8888/favicon.ico or http://localhost:8888 then it broke right away, saying cannot split. (failing at CGI.parse(uri.query))
I can change the line to
params = uri.query ? CGI.parse(uri.query) : nil
but the whole thing seems a bit hacky, needing to make up a URL and then CGI.parse would break if query doesn't exist. Is there actually a better way to use standard library to do it? (should something else be used instead of uri and CGI?)
(Using standard library has the advantage of automatically handling the cases for %20 and multiple params as in http://localhost:8888/?a=123&b=hello%20world&b=good, giving
{"a"=>["123"], "b"=>["hello world", "good"]}
as the result.)

Why do you need to make up anything? You don't need a full URI to use CGI.parse. Something like this should work:
require 'socket'
require 'CGI'
server = TCPServer.new 8888
while session = server.accept
request = session.gets
method, full_path = request.split(' ')
path, params = full_path.split('?')
params = CGI.parse(params.gsub('?','')) if params
session.print "HTTP/1.1 200\r\n" # 1
session.print "Content-Type: text/html\r\n" # 2
session.print "\r\n" # 3
session.print "Hello world! The time is #{Time.now}" #4
session.print "\nparams: #{params}"
p "params:", params
session.close
end

You could also just use Rack if you don't want to learn about reinventing the CGI wheel. Rack is not technically part of the standard library but is as close as you get.
# Gemfile.rb
gem 'rack'
run $ bundle install.
# application.rb
class Application
# This is the main entry point for Rack
# #param env [Hash]
# #return [Array] status, headers, body
# #see https://www.rubydoc.info/gems/rack/Rack/Request
# #see https://www.rubydoc.info/gems/rack/Rack/Response
def self.call(env)
request = Rack::Request.new(env)
Rack::Response.new(
"Hello " + request.params["name"] || "World",
200,
{ "Content-Type" => "text/plain" }
).finish
end
end
request.params is a hash that contains query string parameters and parameters from the request body for POST requests.
# config.ru
require 'rack'
require_relative 'application'
run Application
Run $ rackup to start the server.

Related

how to enable CORS in standalone ruby file

`
http_server.rb
require 'socket'
require 'json'
server = TCPServer.new 5678
while session = server.accept
request = session.gets
puts request
session.print "HTTP/1.1 200\r\n" # 1
session.print "Content-Type: text/html\r\n" # 2
session.print "\r\n" # 3
output = {
"error" => false,
"total_marks" => "0"
}
session.puts(output.to_json);
session.close
end
`
So this standalone ruby http server file works perfectly locally , I would prefer not to have to use rails because I also need to make it a docker container . is there any way to enable cors solely inside this file for this simple server ?
I'm new to docker and ruby so the less complex the better.
CORS is just an Access-Control-Allow-Origin header (see the documentation here https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) so it's easy to add it to your example.
session.print "Access-Control-Allow-Origin: something.com\r\n"

HTTP PUT request with Ruby Sinatra

I created a body for a PUT request with Ruby. When I print out the body, I don't see any problems. But when I try to print out the body from the actual PUT request, I start getting an error.
To elaborate, here is my code:
#data={param1: "nameserver",
param2: {code: "XYZ", name: "NAME", start: "2017"}}
puts "data = #{#data} " #This works fine
#putResponse = HTTParty.put(base_url,
:body => #data.to_json,
:headers => header)
puts "putResponse.body is #{#putResponse.body}" #This is where I get the error
So as you can see, the line puts "data = #{#data} " works fine. It prints out
data = {:param1=>"nameserver", :param2=>{:code=>"XYZ", :name=>"NAME", :start=>"2017"}}
But the line puts puts "putResponse.body is #{#putResponse.body}" doesn't work. This is what it prints out:
putResponse.body is {"errors":[{"message":"The specified resource does not exist."}],"error_report_id":443}
So what's the problem here? (I'm using HTTParty to make my PUT request)
EDIT:
Here's how I got the host and header:
config = JSON.parse(File.read('config.json'))
puts "config: #{config}"
access_token = config['canvas']['access_token']
puts "access_token: #{access_token}"
host = config['canvas']['host']
puts "host: #{host}"
base_url = 'http://#{host}/api/v1/users/self/custom_data/program_of_study'
puts "base_url: #{base_url}"
header = {'Authorization': 'Bearer ' "#{$access_token}", "Content-Type" => 'application/json', 'Accept' => 'application/json'}
Dealing with PUT in Sinatra, is similar to dealing with POST - this is why the documentation may be scarce in this aspect.
This is a simple example of a Sinatra endpoint that receives PUT arguments from a curl request:
# server.rb
require 'sinatra'
put '/' do
params.inspect
end
And test it with this curl command:
$ curl -X PUT -d n=hello http://localhost:4567/
The params hash will be available to you inside any Sinatra endpoint, including parameters received by any HTTP method.
In response to your comment, it is hard to debug without seeing your entire code.
I suggest you run the tests provided in this answer, and gradually modify it to fit your actual code, to understand what breaks.
With the above server.rb running, the below test works without errors:
# test.rb
require 'httparty'
data = {
param1: "nameserver",
param2: { code: "XYZ", name: "NAME", start: "2017" }
}
response = HTTParty.put 'http://localhost:4567/', body: data
puts response.body
# => {"param1"=>"nameserver", "param2"=>{"code"=>"XYZ", "name"=>"NAME", "start"=>"2017"}}

Post png image to pngcrush with Ruby

In ruby, I want to get the same result than the code below but without using curl:
curl_output = `curl -X POST -s --form "input=##{png_image_file};type=image/png" http://pngcrush.com/crush > #{compressed_png_file}`
I tried this:
#!/usr/bin/env ruby
require "net/http"
require "uri"
# Image to crush
png_image_path = "./media/images/foo.png"
# Crush with http://pngcrush.com/
png_compress_uri = URI.parse("http://pngcrush.com/crush")
png_image_data = File.read(png_image_path)
req = Net::HTTP.new(png_compress_uri.host, png_compress_uri.port)
headers = {"Content-Type" => "image/png" }
response = req.post(png_compress_uri.path, png_image_data, headers)
p response.body
# => "Input is empty, provide a PNG image."
The problem with your code is you do not send required parameter to the server ("input" for http://pngcrush.com/crush). This works for me:
require 'net/http'
require 'uri'
uri = URI.parse('http://pngcrush.com/crush')
form_data = [
['input', File.open('filename.png')]
]
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Post.new uri
# prepare request parameters
request.set_form(form_data, 'multipart/form-data')
response = http.request(request)
# save crushed image
open('crushed.png', 'wb') do |file|
file.write(response.body)
end
But I suggest you to use RestClient. It encapsulates net/http with cool features like multipart form data and you need just a few lines of code to do the job:
require 'rest_client'
resp = RestClient.post('http://pngcrush.com/crush',
:input => File.new('filename.png'))
# save crushed image
open('crushed.png', 'wb') do |file|
file.write(resp)
end
Install it with gem install rest-client

Hash/string gets escaped

This is my hyperresource client:
require 'rubygems'
require 'hyperresource'
require 'json'
api = HyperResource.new(root: 'http://127.0.0.1:9393/todos',
headers: {'Accept' => 'application/vnd.127.0.0.1:9393/todos.v1+hal+json'})
string = '{"todo":{"title":"test"}}'
hash = JSON.parse(string)
api.post(hash)
puts hash
The hash output is: {"todo"=>{"title"=>"test"}}
At my Sinatra with Roar API I have this post function:
post "/todos" do
params.to_json
puts params
#todo = Todo.new(params[:todo])
if #todo.save
#todo.extend(TodoRepresenter)
#todo.to_json
else
puts 'FAIL'
end
end
My puts 'params' over here gets: {"{\"todo\":{\"title\":\"test\"}}"=>nil}
I found out, these are 'escaped strings' but I don't know where it goes wrong.
EDIT:
I checked my api with curl and postman google extension, both work fine. It's just hyperresource I guess
You are posting JSON, ergo you either need to register a Sinatra middleware that will automatically parse incoming JSON requests, or you need to do it yourself.
require 'rubygems'
require 'hyperresource'
require 'json'
api = HyperResource.new(root: 'http://127.0.0.1:9393/todos',
headers: {'Accept' => 'application/vnd.127.0.0.1:9393/todos.v1+hal+json'})
string = '{"todo":{"title":"test"}}'
hash = JSON.parse(string)
api.post({:data => hash})
puts hash
---
post "/todos" do
p = JSON.parse(params[:data])
puts p.inspect
#todo = Todo.new(p[:todo])
if #todo.save
#todo.extend(TodoRepresenter)
#todo.to_json
else
puts 'FAIL'
end
end
Should do what you need.

Simplest way to do a XMLHttpRequest in Ruby?

I want to do a XMLHttpRequest POST in Ruby. I don't want to use a framework like Watir. Something like Mechanize or Scrubyt would be fine. How can I do this?
Mechanize:
require 'mechanize'
agent = Mechanize.new
agent.post 'http://www.example.com/', :foo => 'bar'
Example with 'net/http', (ruby 1.9.3):
You only have to put an additional header for the XMLHttpRequest to your POST-request (see below).
require 'net/http'
require 'uri' # convenient for using parts of an URI
uri = URI.parse('http://server.com/path/to/resource')
# create a Net::HTTP object (the client with details of the server):
http_client = Net::HTTP.new(uri.host, uri.port)
# create a POST-object for the request:
your_post = Net::HTTP::Post.new(uri.path)
# the content (body) of your post-request:
your_post.body = 'your content'
# the headers for your post-request (you have to analyze before,
# which headers are mandatory for your request); for example:
your_post['Content-Type'] = 'put here the content-type'
your_post['Content-Length'] = your_post.body.size.to_s
# ...
# for an XMLHttpRequest you need (for example?) such header:
your_post['X-Requested-With'] = 'XMLHttpRequest'
# send the request to the server:
response = http_client.request(your_post)
# the body of the response:
puts response.body
XMLHTTPRequest is a browser concept, but since you're asking about Ruby, I assume all you want to do is simulate such a request from a ruby script? To that end, there's a gem called HTTParty which is very easy to use.
Here's a simple example (assuming you have the gem - install it with gem install httparty):
require 'httparty'
response = HTTParty.get('http://twitter.com/statuses/public_timeline.json')
puts response.body, response.code, response.message, response.headers.inspect

Resources