Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

Most of the time, when we need to make an HTTP request in Ruby, we reach for a gem like Faraday or HTTParty. They're great. But as an engineer, I think it's important to understand Net::HTTP. It's the foundation those gems are built on, and sometimes you're working in a locked-down environment where you can't just add another dependency to the Gemfile.

Let's dive in by trying to do something simple: I want to grab the public profile information for a GitHub user. I'll use the GitHub API because it's reliable, but it has a few quirks that make it perfect for this exploration.

The "Quick and Dirty" Attempt

If I just want a string of data, I might try the most basic method available:

require 'net/http'
require 'json'

uri = URI('https://api.github.com/users/matz')
response = Net::HTTP.get(uri)
puts JSON.parse(response)['name']
# => "Matz"

That worked. Easy, right? But here's the problem: Net::HTTP.get is a "one-and-done" helper. It opens a connection, grabs the data, and closes it immediately. If I wanted to make five different API calls to fetch a user's profile, their repos, and their followers, I'd be performing a full TCP handshake and SSL negotiation five separate times. That's incredibly inefficient.

Hitting the Header Wall

Now, let's say I want to do something a bit more professional, like specifying a User-Agent. GitHub actually requires a User-Agent header; if you're making a lot of requests without one, they'll start blocking you. The simple .get method doesn't let me set headers.

I'll try to switch to a more explicit request object:

require 'net/http'
require 'uri'

uri = URI('https://api.github.com/users/matz')
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Get.new(uri)
request['User-Agent'] = 'MyRubyExperiment/1.0'

response = http.request(request)
puts response.body
# Wait... this is hanging or throwing an error.

I just realized why this failed. I'm hitting an https URL, but Net::HTTP.new doesn't assume SSL by default. It's trying to talk plain HTTP to a port that expects encrypted traffic. I need to explicitly tell the client to use SSL.

Handling the HTTPS Hurdle

I'll adjust the setup to enable SSL. While I'm at it, I'm going to use Net::HTTP.start. This is the "pro" way to do it because it ensures the connection is closed automatically after the block executes, and it allows me to reuse the same connection for multiple requests.

require 'net/http'
require 'uri'

uri = URI('https://api.github.com/users/matz')

Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
  request = Net::HTTP::Get.new(uri)
  request['User-Agent'] = 'MyRubyExperiment/1.0'
  
  response = http.request(request)
  puts response.code # "200"
end

Now we're talking. By using Net::HTTP.start, I've created a session. If I had more requests to make, I could just call http.request again inside that same block, and it would be significantly faster.

Dealing with the Unpredictable

One thing that always trips people up with Net::HTTP is that it doesn't raise an exception when it gets a 404 or a 500 error. It just gives you a response object with a non-200 status code. If I assume the request worked and try to parse the body, my app might crash on a nil value or a malformed JSON error page.

I'll rewrite this to handle the response types properly. Ruby provides a nice class hierarchy for responses (like Net::HTTPSuccess, Net::HTTPNotFound, etc.) that we can use with a case statement.

require 'net/http'
require 'uri'
require 'json'

def fetch_github_user(username)
  uri = URI("https://api.github.com/users/#{username}")
  
  Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
    request = Net::HTTP::Get.new(uri)
    request['User-Agent'] = 'MyRubyExperiment/1.0'
    
    case http.request(request)
    when Net::HTTPSuccess
      JSON.parse(http.request(request).body) # Wait, I just called the request twice!
    when Net::HTTPNotFound
      puts "User not found!"
    else
      puts "Something went wrong."
    end
  end
end

Hold onβ€”I just caught a bug in my own code. In my case statement, I called http.request(request) inside the when and then again inside the block. That's two API calls for one piece of data. I should capture the response in a variable first.

Here is the refined, production-ready pattern:

Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
  request = Net::HTTP::Get.new(uri)
  request['User-Agent'] = 'MyRubyExperiment/1.0'
  
  response = http.request(request)
  
  case response
  when Net::HTTPSuccess
    JSON.parse(response.body)
  when Net::HTTPNotFound
    nil
  else
    raise "HTTP Error: #{response.code}"
  end
end

This approach is robust. We've handled the connection efficiently, satisfied the API's header requirements, secured the transport via SSL, and handled the response states without making redundant calls.




πŸ“‹ Practical Task

The GitHub Repository Star-Checker

Build a script that takes a GitHub username and a repository name as arguments (or hardcoded variables). The script should:

  • Use Net::HTTP.start with use_ssl: true to connect to the GitHub API.
  • Set a custom User-Agent header.
  • Fetch the repository data from https://api.github.com/repos/#{username}/#{repo}.
  • Use a case statement to check the response:
    • If it's a success, print the number of stars (stargazers_count) the repo has.
    • If it's a 404, print "Repository not found".
    • For any other error, print the HTTP status code.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.