- Key Takeaways
- What is REST and Why Should You Care?
- Simple: Testing
- Simple: GET Request
- Simple: POST Request
- Simple: PUT Request
- Simple: DELETE Request
- Advanced: Testing
- Advanced: HTTP Blocks
- Advanced: 301 Redirects
- Advanced: Headers
- Advanced: Basic Auth
- Advanced: File Downloads
- Advanced: SSL
- Conclusion
- Further Reading
- Frequently Asked Questions (FAQs) about Ruby Net::HTTP Library
Every so often, we find ourselves in need of accessing REST APIs. There are some great libraries out there for this, but what about the library behind these libraries? In this article, we’re going to take a look at the Ruby’s Net::HTTP
library. We’ll go through simple REST API principals like:
GET
POST
PUT
DELETE
I’ll also cover more advanced topics like:
- HTTP Blocks
- 301 Redirects
- Headers
- Basic Auth
- File Downloads
Key Takeaways
- Ruby’s Net::HTTP library offers a straightforward way to interact with REST APIs using standard HTTP methods like GET, POST, PUT, and DELETE, providing a solid foundation for accessing and manipulating web resources.
- The library supports advanced HTTP features such as handling HTTP blocks, managing redirects (e.g., 301), customizing headers, and authenticating via basic auth, making it versatile for more complex web interactions.
- Techniques for handling file downloads and SSL configurations demonstrate Net::HTTP’s capability to handle diverse tasks from simple data retrieval to secure communication over HTTPS.
- Despite its utility, exploring alternative gems like HTTParty, Rest-Client, and Curb could provide simpler interfaces and additional functionalities that might be better suited for specific use cases.
What is REST and Why Should You Care?
REST stands for REpresentational State Transfer. It was theorized by Roy Fielding in his doctoral dissertation: Architectural Styles and the Design of Network-based Software Architectures, then later implemented by the World Wide Web Consortium (W3C) for HTTP 1.1. It’s basically a software (mainly web) architecture style that allows for easy API access, along with some other helpful goals.
REST APIs focuses on four main verbs: GET
, POST
, PUT
, and DELETE
. We can use Ruby’s Net::HTTP
library to access REST API’s. In this article, we’ll learn how to do this, as well as some other goodies. If you want full definitions of all HTTP 1.1 methods, then you should visit this W3C page.
Excited? Let’s get started.
Simple: Testing
Let’s use Sinatra as our little API server. If you’re unfamiliar with Sinatra, you can learn the basics in 10 minutes. Here’s our server:
# file: rest-server.rb
require 'sinatra'
# all status codes given are based on W3C standards
get '/' do
'Here is some data.'
end
post '/' do
puts "Data: '#{params[:foo]}' recieved, creating object."
status 201
end
put '/' do
puts "Data: '#{params[:foo]}' recieved, updating object."
end
delete '/' do
puts 'Deleting data.'
end
Now to start it up, just open a new terminal tab:
$ ruby rest-server.rb
== Sinatra/1.4.4 has taken the stage on 4567 for development with backup from Thin
Thin web server (v1.6.1 codename Death Proof) # might be WEBrick instead
Maximum connections set to 1024
Listening on localhost:4567, CTRL+C to stop
You now have a functional REST API running on your computer.
Simple: GET Request
A GET
request gets, or reads information from a server. This request is quite common. When you visited this page, your browser made a GET
request which returned a hypertext document from the server. You can do this same thing in Ruby. In our example, we’re going to be getting data from our server.
require 'net/http'
require 'uri'
url = 'http://localhost:4567/' # trailing slash is important
uri = URI.parse(url)
Net::HTTP.get(uri) # GET request
# => "Here is some data."
This simple example pulls data from our server and returns it. Elegant, right? Here it is in ugly one-liner form:
require 'net/http' # URI is required by Net::HTTP by default
Net::HTTP.get(URI 'http://localhost:4567/')
# => "Here is some data."
As an intelligent SitePoint Ruby reader like you can obviously see, this code has some issues. Don’t worry, we’ll get to most of these later in the advanced Net::HTTP
bits.
Simple: POST Request
A POST
request posts, or sends data/parameters to a server. APIs use this for recieving data from a user. This request is also quite common. Every time you send a tweet, you’re sending a POST
request to Twitter with your tweet as parameters. In this example, we’re going to be send our server a POST
request.
require 'net/http'
require 'uri'
url = 'http://localhost:4567/'
uri = URI.parse(url)
params = {foo: "bar"}
Net::HTTP.post_form(uri, params)
# => #<Net::HTTPCreated 201 Created readbody=true>
And of course, here is its ugly one-liner equivalent:
require 'net/http'
# ugly, right?
Net::HTTP.post_form(URI('http://localhost:4567/'), {foo:"bar"})
# => #<Net::HTTPCreated 201 Created readbody=true>
Simple: PUT Request
A PUT
request puts, or updates something on a server. APIs use this mainly when updating data on the server. For example, if you edit a pull request on GitHub, you’re sending a PUT
request with your data. Unfortunately, PUT
is not very well documented in the Ruby Net::HTTP
library, so there is no elegant solution to it.
require 'net/http'
require 'uri'
url = 'http://localhost:4567/'
uri = URI.parse(url)
params = {foo: "change"}
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Put.new(uri.path)
request.set_form_data(params)
http.request(request) # the actual PUT request
# => #<Net::HTTPOK 200 OK readbody=true>
I’m not going to show this as a one liner because it would be over 80 characters, which would not only be unreadable, but also goes against the Ruby Style Guide.
Simple: DELETE Request
A DELETE
request deletes something from the server. If you were to delete a tweet, you would be sending a delete request to Twitter. This example is very similar to the last.
require 'net/http'
require 'uri'
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Delete.new(uri.path)
http.request(request)
# => #<Net::HTTPOK 200 OK readbody=true>
Great. We’ve gone through the basics and REST verbs using Ruby. Keep in mind, there are multiple ways of doing most of these HTTP requests. We’ll go through some of these in the next section.
Advanced: Testing
For the advanced section, our server is going to have to be more complicated in order to return and recieve data like the API we are trying to mimic in each example. So, the server will need to be changed in each example.
From now on, we will be focussing on the GET
and POST
HTTP methods. If you need to apply the techniques below to the other methods, just mix the simple version of the request with the advanced technique.
Advanced: HTTP Blocks
In the principle of DRY, HTTP blocks are important. HTTP blocks are simply Net::HTTP
blocks. This may seem a little confusing, but it’s quite a simple concept.
First we create a new method. We’ll call it http_request
and give it one argument, uri
.
require 'net/http'
def http_request(uri)
end
Now for the fun part. Call Net::HTTP.new
and yield it to a block.
require 'net/http'
def http_request(uri)
Net::HTTP.start(uri.host, uri.port) do |http|
yield(http, uri)
end
end
This means that we can do really cool things like this: (using the simple server we made)
# ... http block code above ...
uri = URI 'http://localhost:4567/'
http_request(uri) do |http, uri|
http.get(uri.path).body
end
# => "Here is some data."
We’ll be using these in the next examples, so get familiar. Just keep in mind that http_request
is the same thing as Net::HTTP.start
, it’s just a shortcut.
Advanced: 301 Redirects
Oh no! Our server just took a step up and now has the ability to redirect requests.
require 'sinatra'
get '/foo/?' do
redirect '/bar'
end
get '/bar/?' do
'Hello!'
end
If we try the simple GET
method from earlier, let’s see what happens now…
require 'net/http'
uri = URI 'http://localhost:4567/foo'
Net::HTTP.get(uri)
# => ""
Hmm… that gave us nothing. But, being the smart people that we are, we try a second simple method.
require 'net/http'
def http_request(uri)
Net::HTTP.start(uri.host, uri.port) do |http|
yield(http, uri)
end
end
uri = URI 'http://localhost:4567/foo'
http_request uri do |http, uri|
http.get(uri.path)
end
# => #<Net::HTTPFound 302 Moved Temporarily readbody=true>
That gave us more information, but still no 200 OK
. What can we do to follow these redirects without getting lost? There are two methods.
Method one uses Net::HTTP.get_response
to get information from the server, not just the body.
require 'net/http'
uri = URI 'http://localhost:4567/foo'
def get_follow_redirects(uri, request_max = 5)
raise "Max number of redirects reached" if request_max <= 0
response = Net::HTTP.get_response(uri)
case response.code.to_i
when 200
response.body
when 301..303
get_follow_redirects(URI(response['location']), request_max - 1)
else
response.code
end
end
get_follow_redirects(uri)
# => "Hello!"
Method two involves using HTTP blocks in a similar method.
require 'net/http'
uri = URI 'http://localhost:4567/foo'
def http_request(uri)
Net::HTTP.start(uri.host, uri.port) do |http|
yield(http, uri)
end
end
def get_follow_redirects(uri, request_max = 5)
http_request uri do |http, uri|
response = http.head(uri.path)
case response.code.to_i
when 200
http.get(uri.path).body
when 301..303
get_follow_redirects(URI(response['location']), request_max - 1)
else
response.code
end
end
end
get_follow_redirects(uri)
# => "Hello!"
As you can see, this method is very similar to the other one. It all depends on preference and how much control you want over the request.
Advanced: Headers
In the previous examples, we used headers to find the 301/302 redirect location. In the next example, we’ll isolate this ability so we can access any part of the headers.
First, let’s make a request and see what headers we get. We’ll be using the first, simple server in this example.
require 'net/http'
uri = URI 'http://localhost:4567/'
response = Net::HTTP.get_response(uri)
response.header.to_hash.inspect
# => "{\"content-type\"=>[\"text/html;charset=utf-8\"], \"content-length\"=>[\"18\"], \"x-xss-protection\"=>[\"1; mode=block\"], \"x-content-type-options\"=>[\"nosniff\"], \"x-frame-options\"=>[\"SAMEORIGIN\"], \"connection\"=>[\"keep-alive\"], \"server\"=>[\"thin 1.6.1 codename Death Proof\"]}"
Pretty sweet, huh? If you don’t need the all the headers, you can just use response['something']
as a shortcut.
Of course, there is also an HTTP block method version of this…
require 'net/http'
# ... http block code ...
uri = URI 'http://localhost:4567/'
http_request(uri) do |http, uri|
response = http.head(uri.path)
response.to_hash.inspect
end
# => "{\"content-type\"=>[\"text/html;charset=utf-8\"], \"content-length\"=>[\"18\"], \"x-xss-protection\"=>[\"1; mode=block\"], \"x-content-type-options\"=>[\"nosniff\"], \"x-frame-options\"=>[\"SAMEORIGIN\"], \"connection\"=>[\"keep-alive\"], \"server\"=>[\"thin 1.6.1 codename Death Proof\"]}"
HTTP headers are quite useful. You can get all sorts of information about the data source and leverage this information to your advantage.
Advanced: Basic Auth
Here’s a less common situation, but equally as useful as the last one. Sometimes APIs offer basic http authentication. Let’s simulate that in our server:
require 'sinatra'
# source: http://recipes.sinatrarb.com/p/middleware/rack_auth_basic_and_digest#label-HTTP+Basic+Authentication
use Rack::Auth::Basic, "Protected Area" do |username, password|
username == 'foo' && password == 'bar'
end
get '/' do
'Classified data.'
end
Now we have to find a way to access that classified data. Let’s do this.
require 'net/http'
def http_request(uri)
Net::HTTP.start(uri.host, uri.port) do |http|
yield(http, uri)
end
end
uri = URI 'http://localhost:4567/'
req = Net::HTTP::Get.new(uri)
req.basic_auth 'foo', 'bar'
http_request uri do |http, uri|
response = http.request(req)
response.body
end
# => "Classified data."
Of course, this type of authentication is not very common with OAuth being in existance, but it’s always good to know.
Advanced: File Downloads
Sometimes as developers, we need a programmatic way to download files. This is as easy as making a GET
request and piping each line into a new file. For this example, we’re going to use a real-world file from the internet. There’s no point in rewriting our server for this situation.
For our first example, we’ll modify the simple GET
request that we made earlier.
require 'net/http'
content = Net::HTTP.get(URI 'http://www.google.com/humans.txt') # get the contents of the file
File.open('humans.txt', 'w+') do |file|
file.write(content) # write the contents
end
Simple right? But we’re savvy programmers. What about live streaming? Let’s have some fun.
require 'net/http'
uri = URI 'http://www.google.com/humans.txt'
def http_request(uri)
Net::HTTP.start(uri.host, uri.port) do |http|
yield(http, uri)
end
end
http_request uri do |http, uri|
request = Net::HTTP::Get.new(uri)
http.request(request) do |response|
open('humans.txt', 'w+') do |file|
response.read_body do |chunk|
file.write(chunk)
end
end
end
end
With small files, this second method is just several lines of code longer, but on larger files, this can speed up downloads greatly.
Advanced: SSL
SSL is very common in APIs and other web services. Don’t worry, Net::HTTP
has us covered.
For this next example, we’ll be using Google because it uses SSL for its connection. We won’t be using Sinatra because it’s out of the scope of this tutorial to configure it.
require 'net/https' # notice the change to https
uri = URI 'https://www.google.com/'
http = Net::HTTP.new(uri.host, 443) # hardcoded for forced SSL
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
req = Net::HTTP::Get.new(uri.path) # or any other request you might like
resp = http.request(req)
resp.body
# => ... very long response body (Google's homepage) ...
Conclusion
You can do so many things with the Net::HTTP
library. It is extensible, but also simple to use. I suggest that you also check out the HTTParty, Rest-Client, and Curb gems. They provide a very simple way to access these same methods in a better way. Whatever you choose, remember that anything your browser can do Ruby can do as well.
Further Reading
- Architectural Styles and the Design of Network-based Software Architectures
- Ruby Net::HTTP Documentation
- HTTP Code Definitions
- HTTP Method Definitions
- Ruby IO::File Mode Definitions
Frequently Asked Questions (FAQs) about Ruby Net::HTTP Library
What is the Ruby Net::HTTP Library?
The Ruby Net::HTTP Library is a built-in Ruby library designed to handle HTTP connections. It provides a rich set of methods to request and receive web pages, submit forms, and upload files. It’s a versatile tool that allows developers to interact with web services and manipulate HTTP headers and responses.
How do I use the Ruby Net::HTTP Library?
To use the Ruby Net::HTTP Library, you first need to require ‘net/http’ in your Ruby script. Then, you can create a new instance of the Net::HTTP class, passing the host and port as parameters. You can then use the instance methods to send HTTP requests. For example, to send a GET request, you can use the ‘get’ method.
How can I handle HTTP responses with Ruby Net::HTTP Library?
The Ruby Net::HTTP Library provides several methods to handle HTTP responses. For instance, the ‘response.body’ method allows you to access the body of the response as a string. The ‘response.code’ method returns the HTTP status code as a string. You can also use the ‘response.header’ method to access the headers of the response.
Can I use Ruby Net::HTTP Library to send POST requests?
Yes, you can use the Ruby Net::HTTP Library to send POST requests. The ‘post’ method allows you to send a POST request to a specified path with a given body and headers. You need to pass the path, the body, and the headers as parameters to the ‘post’ method.
How can I handle errors with Ruby Net::HTTP Library?
The Ruby Net::HTTP Library provides several ways to handle errors. For instance, you can use the ‘rescue’ keyword to catch exceptions and handle them appropriately. You can also check the ‘response.code’ to see if the request was successful or not.
Can I use Ruby Net::HTTP Library to upload files?
Yes, you can use the Ruby Net::HTTP Library to upload files. You can use the ‘post_form’ method to send a POST request with a form data. The form data should be a hash where the keys are the form field names and the values are the form field values.
How can I set custom headers with Ruby Net::HTTP Library?
You can set custom headers with the Ruby Net::HTTP Library by passing a hash to the request methods. The keys of the hash should be the header names and the values should be the header values.
Can I use Ruby Net::HTTP Library to send PUT requests?
Yes, you can use the Ruby Net::HTTP Library to send PUT requests. The ‘put’ method allows you to send a PUT request to a specified path with a given body and headers. You need to pass the path, the body, and the headers as parameters to the ‘put’ method.
How can I handle redirects with Ruby Net::HTTP Library?
The Ruby Net::HTTP Library automatically follows redirects by default. However, you can customize this behavior by setting the ‘follow_redirect’ attribute of the Net::HTTP instance to false.
Can I use Ruby Net::HTTP Library to send DELETE requests?
Yes, you can use the Ruby Net::HTTP Library to send DELETE requests. The ‘delete’ method allows you to send a DELETE request to a specified path. You need to pass the path as a parameter to the ‘delete’ method.
Jesse Herrick is an avid Ruby developer who specializes in web development. He is a back-end developer at Littlelines and loves programming. You can read his personal blog at: https://jesse.codes.