96

How do I send a JSON request in ruby? I have a JSON object but I dont think I can just do .send. Do I have to have javascript send the form?

Or can I use the net/http class in ruby?

With header - content type = json and body the json object?

Brian Knight
  • 4,970
  • 28
  • 34
Andy
  • 1,129
  • 1
  • 9
  • 13

11 Answers11

91
uri = URI('https://myapp.com/api/v1/resource')
body = { param1: 'some value', param2: 'some other value' }
headers = { 'Content-Type': 'application/json' }
response = Net::HTTP.post(uri, body.to_json, headers)
matyus
  • 13
  • 5
CarelZA
  • 1,217
  • 1
  • 10
  • 12
  • 9
    I like your suggestion to use URI to handle the hostname and port, which otherwise is quite tedious. But you forgot to set uri.path in Post.new(...): `req = Net::HTTP::Post.new(uri.path, initheader = {'Content-Type' =>'application/json'})` – ArnauOrriols May 27 '14 at 12:30
  • 1
    Simplest, cleanest response. This is great. – joelc Jul 20 '15 at 04:50
  • 1
    `http.request(req).read_body` to read the response body. Great! – iGian Jun 26 '18 at 18:40
  • 1
    I'm pretty sure it's changed in 2.4.1 but my god. That syntax is gross. It knows it has the URI from the Post.new() so why do you have to repass the values, split up, in start(). Gross. No wonder there's so many other packages in ruby that deal with http. – Rambatino Aug 02 '18 at 09:26
55
require 'net/http'
require 'json'

def create_agent
    uri = URI('http://api.nsa.gov:1337/agent')
    http = Net::HTTP.new(uri.host, uri.port)
    req = Net::HTTP::Post.new(uri.path, 'Content-Type' => 'application/json')
    req.body = {name: 'John Doe', role: 'agent'}.to_json
    res = http.request(req)
    puts "response #{res.body}"
rescue => e
    puts "failed #{e}"
end
neoneye
  • 50,398
  • 25
  • 166
  • 151
17

HTTParty makes this a bit easier I think (and works with nested json etc, which didn't seem to work in other examples I've seen.

require 'httparty'
HTTParty.post("http://localhost:3000/api/v1/users", body: {user: {email: 'user1@example.com', password: 'secret'}}).body
Brian Armstrong
  • 19,707
  • 17
  • 115
  • 144
8

This works on ruby 2.4 HTTPS Post with JSON object and the response body written out.

require 'net/http' #net/https does not have to be required anymore
require 'json'
require 'uri'

uri = URI('https://your.secure-url.com')
Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') do |http|
  request = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json')
  request.body = {parameter: 'value'}.to_json
  response = http.request request # Net::HTTPResponse object
  puts "response #{response.body}"
end
szabcsee
  • 373
  • 4
  • 12
7

real life example, notify Airbrake API about new deployment via NetHttps

require 'uri'
require 'net/https'
require 'json'

class MakeHttpsRequest
  def call(url, hash_json)
    uri = URI.parse(url)
    req = Net::HTTP::Post.new(uri.to_s)
    req.body = hash_json.to_json
    req['Content-Type'] = 'application/json'
    # ... set more request headers 

    response = https(uri).request(req)

    response.body
  end

  private

  def https(uri)
    Net::HTTP.new(uri.host, uri.port).tap do |http|
      http.use_ssl = true
      http.verify_mode = OpenSSL::SSL::VERIFY_NONE
    end
  end
end

project_id = 'yyyyyy'
project_key = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
url = "https://airbrake.io/api/v4/projects/#{project_id}/deploys?key=#{project_key}"
body_hash = {
  "environment":"production",
  "username":"tomas",
  "repository":"https://github.com/equivalent/scrapbook2",
  "revision":"live-20160905_0001",
  "version":"v2.0"
}

puts MakeHttpsRequest.new.call(url, body_hash)

Notes:

in case you doing authentication via Authorisation header set header req['Authorization'] = "Token xxxxxxxxxxxx" or http://api.rubyonrails.org/classes/ActionController/HttpAuthentication/Token.html

equivalent8
  • 13,754
  • 8
  • 81
  • 109
  • ...but honestly this is cool and all but in real live I would just use HTTParty https://stackoverflow.com/a/14491995/473040 :) ...especially if you are dealing with https handling – equivalent8 Mar 26 '18 at 16:25
  • requiring uri is useless as it is already required by net/http – noraj Aug 21 '18 at 13:27
  • @equivalent8: "in real life I would just use HTTParty" - that is, unless you're building a lean gem, or otherwise don't want another dependency. :) – Sergio Tulentsev Dec 07 '18 at 10:18
  • @SergioTulentsev agree ...unless you are building gem/lib (or Ruby based microservice) where you don't want to introduce unnecessary dependencies ;) – equivalent8 Dec 11 '18 at 11:32
3

A simple json POST request example for those that need it even simpler than what Tom is linking to:

require 'net/http'

uri = URI.parse("http://www.example.com/search.json")
response = Net::HTTP.post_form(uri, {"search" => "Berlin"})
Christoffer
  • 7,436
  • 4
  • 40
  • 42
  • 20
    This looks like it should work, but post_form translates the parameters into the ?key=value&key=value syntax. If you want to do a POST with the request body set to a JSON string, I think you need a different solution. – Ben Gotow Apr 24 '13 at 18:43
  • This doesn't work with deeply nested json. Anything beyond the first level becomes a string. – neoneye May 15 '14 at 11:29
  • Doens't just look like. It WORKS. It's simple sure. but for simple things like the example I have given it works just fine – Christoffer Sep 30 '15 at 07:22
  • 5
    This is fundamentally not a JSON request. This is a urlencoded body. There is no JSON. The header even says as much. This does not work with any example, ever. – raylu Oct 21 '16 at 00:48
  • 4
    This answer is incorrect. It's a POST in mime/multipart, to a url that says "json" in it. – John Haugeland Dec 21 '16 at 16:05
  • You can make this a JSON request like so: `require "json"; uri = …; Net::HTTP.post(uri, { "search" => "Berlin" }.to_json, "Content-Type" => "application/json")` – Henrik N Dec 16 '19 at 19:08
3

It's 2020 - nobody should be using Net::HTTP any more and all answers seem to be saying so, use a more high level gem such as Faraday - Github


That said, what I like to do is a wrapper around the HTTP api call,something that's called like

rv = Transporter::FaradayHttp[url, options]

because this allows me to fake HTTP calls without additional dependencies, ie:

  if InfoSig.env?(:test) && !(url.to_s =~ /localhost/)
    response_body = FakerForTests[url: url, options: options]

  else
    conn = Faraday::Connection.new url, connection_options

Where the faker looks something like this

I know there are HTTP mocking/stubbing frameworks, but at least when I researched last time they didn't allow me to validate requests efficiently and they were just for HTTP, not for example for raw TCP exchanges, this system allows me to have a unified framework for all API communication.


Assuming you just want to quick&dirty convert a hash to json, send the json to a remote host to test an API and parse response to ruby this is probably fastest way without involving additional gems:

JSON.load `curl -H 'Content-Type:application/json' -H 'Accept:application/json' -X POST localhost:3000/simple_api -d '#{message.to_json}'`

Hopefully this goes without saying, but don't use this in production.

bbozo
  • 7,075
  • 3
  • 30
  • 56
3

I like this light weight http request client called `unirest'

gem install unirest

usage:

response = Unirest.post "http://httpbin.org/post", 
                        headers:{ "Accept" => "application/json" }, 
                        parameters:{ :age => 23, :foo => "bar" }

response.code # Status code
response.headers # Response headers
response.body # Parsed body
response.raw_body # Unparsed body
tokhi
  • 21,044
  • 23
  • 95
  • 105
2

The net/http api can be tough to use.

require "net/http"

uri = URI.parse(uri)

Net::HTTP.new(uri.host, uri.port).start do |client|
  request                 = Net::HTTP::Post.new(uri.path)
  request.body            = "{}"
  request["Content-Type"] = "application/json"
  client.request(request)
end
Moriarty
  • 3,957
  • 1
  • 31
  • 27
  • 1
    This code doesn't work. You need to initialize Net::HTTP with #start like so: `Net::HTTP.start(uri.host, uri.port, :use_ssl => true) do |client|` – Tyler Dec 16 '15 at 19:45
  • Works with ruby 2.3.7p456 (2018-03-28 revision 63024) [universal.x86_64-darwin18] – Moriarty Jul 18 '19 at 23:02
0
data = {a: {b: [1, 2]}}.to_json
uri = URI 'https://myapp.com/api/v1/resource'
https = Net::HTTP.new uri.host, uri.port
https.use_ssl = true
https.post2 uri.path, data, 'Content-Type' => 'application/json'
phil pirozhkov
  • 4,740
  • 2
  • 33
  • 40
0

Using my favourite http request library in ruby:

resp = HTTP.timeout(connect: 15, read: 30).accept(:json).get('https://units.d8u.us/money/1/USD/GBP/', json: {iAmOne: 'Hash'}).parse
resp.class    
  => Hash 
hd1
  • 33,938
  • 5
  • 80
  • 91