1 Net::HTTP 标准库
Ruby 标准库内置了 net/http 模块,无需安装任何 gem 即可发送 HTTP 请求。虽然 API 略显繁琐,但在不引入外部依赖的脚本中非常实用。
GET 请求
require 'net/http'
require 'uri'
require 'json'
uri = URI('https://jsonplaceholder.typicode.com/posts/1')
response = Net::HTTP.get_response(uri)
if response.is_a?(Net::HTTPSuccess)
data = JSON.parse(response.body)
puts data['title']
else
puts "请求失败: #{response.code} #{response.message}"
end
body = Net::HTTP.get(uri)
puts body
带参数和 Header 的 GET
uri = URI('https://api.example.com/users')
uri.query = URI.encode_www_form(page: 1, per_page: 20)
request = Net::HTTP::Get.new(uri)
request['Accept'] = 'application/json'
request['Authorization'] = 'Bearer your-token-here'
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
users = JSON.parse(response.body)
users.each { |u| puts u['name'] }
POST 请求(发送 JSON)
uri = URI('https://api.example.com/users')
request = Net::HTTP::Post.new(uri)
request['Content-Type'] = 'application/json'
request.body = { name: '张三', email: 'zhangsan@example.com' }.to_json
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.open_timeout = 5
http.read_timeout = 10
http.request(request)
end
case response
when Net::HTTPSuccess
result = JSON.parse(response.body)
puts "创建成功: ID=#{result['id']}"
when Net::HTTPClientError
puts "客户端错误: #{response.code}"
when Net::HTTPServerError
puts "服务器错误: #{response.code}"
end
POST 表单数据
uri = URI('https://api.example.com/login')
response = Net::HTTP.post_form(uri, {
'username' => 'admin',
'password' => 'secret'
})
puts response.body
⚡ Net::HTTP 要点
Net::HTTP.get直接返回 body 字符串,get_response返回完整响应对象- HTTPS 请求需设置
use_ssl: true - 使用
start块可复用 TCP 连接发送多个请求 - 对复杂场景(重试、中间件等),推荐使用 Faraday 等第三方库
2 Faraday HTTP 客户端
Faraday 是 Ruby 社区最流行的 HTTP 客户端库,提供统一接口、中间件机制和可插拔的适配器,大幅简化 HTTP 请求的编写。
gem install faraday faraday-multipart
基础请求
require 'faraday'
require 'json'
response = Faraday.get('https://jsonplaceholder.typicode.com/posts/1')
puts response.status
puts response.headers['content-type']
data = JSON.parse(response.body)
puts data['title']
response = Faraday.post(
'https://api.example.com/users',
{ name: '张三', email: 'zhangsan@example.com' }.to_json,
'Content-Type' => 'application/json'
)
创建连接实例(推荐)
conn = Faraday.new(url: 'https://api.example.com') do |f|
f.request :json
f.response :json
f.response :raise_error
f.adapter Faraday.default_adapter
f.headers['Authorization'] = 'Bearer your-token'
f.options.timeout = 10
f.options.open_timeout = 5
end
response = conn.get('/users', page: 1, per_page: 20)
users = response.body
users.each { |u| puts u['name'] }
response = conn.post('/users') do |req|
req.body = { name: '李四', email: 'lisi@example.com', age: 32 }
end
puts "创建成功: #{response.body}"
response = conn.put("/users/#{id}") do |req|
req.body = { name: '李四更新' }
end
conn.delete("/users/#{id}")
错误处理
conn = Faraday.new(url: 'https://api.example.com') do |f|
f.request :json
f.response :json
f.response :raise_error
end
begin
response = conn.get('/users/999')
rescue Faraday::ResourceNotFound => e
puts "资源不存在: #{e.message}"
rescue Faraday::ClientError => e
puts "客户端错误: #{e.response[:status]}"
rescue Faraday::ServerError => e
puts "服务器错误: #{e.response[:status]}"
rescue Faraday::ConnectionFailed => e
puts "连接失败: #{e.message}"
rescue Faraday::TimeoutError
puts "请求超时"
end
自定义中间件
class LoggingMiddleware < Faraday::Middleware
def call(env)
puts "[HTTP] #{env.method.upcase} #{env.url}"
start = Time.now
response = @app.call(env)
elapsed = ((Time.now - start) * 1000).round(1)
puts "[HTTP] #{response.status} (#{elapsed}ms)"
response
end
end
conn = Faraday.new(url: 'https://api.example.com') do |f|
f.use LoggingMiddleware
f.request :json
f.response :json
f.adapter Faraday.default_adapter
end
3 JSON 处理
Ruby 标准库内置了 json 模块,提供高效的 JSON 编解码。所有 Ruby 对象(Hash、Array、String、Numeric 等)都可以直接序列化为 JSON。
编解码基础
require 'json'
data = { name: '张三', age: 30, skills: ['Ruby', 'Rails'] }
json_str = JSON.generate(data)
puts json_str
json_str = data.to_json
puts json_str
pretty = JSON.pretty_generate(data)
puts pretty
parsed = JSON.parse('{"name":"张三","age":30}')
puts parsed['name']
puts parsed.class
parsed = JSON.parse('{"name":"张三"}', symbolize_names: true)
puts parsed[:name]
错误处理
begin
data = JSON.parse('invalid json')
rescue JSON::ParserError => e
puts "JSON 解析失败: #{e.message}"
end
json_str = '{"key": "value"}'
data = JSON.parse(json_str) rescue nil
puts data ? data['key'] : '解析失败'
自定义对象的 JSON 序列化
class User
attr_accessor :name, :email, :age
def initialize(name:, email:, age:)
@name = name
@email = email
@age = age
end
def to_json(*args)
{ name: @name, email: @email, age: @age }.to_json(*args)
end
def self.from_json(json_str)
data = JSON.parse(json_str, symbolize_names: true)
new(**data)
end
end
user = User.new(name: '张三', email: 'zhangsan@example.com', age: 28)
puts user.to_json
user2 = User.from_json('{"name":"李四","email":"lisi@example.com","age":32}')
puts user2.name
处理 API 响应
require 'net/http'
require 'json'
uri = URI('https://jsonplaceholder.typicode.com/users')
response = Net::HTTP.get(uri)
users = JSON.parse(response)
users.each do |user|
puts "#{user['name']} (#{user['email']})"
address = user.dig('address', 'city')
puts " 城市: #{address}" if address
end
geo = users.first.dig('address', 'geo', 'lat')
puts "纬度: #{geo}"
⚡ JSON 处理要点
JSON.parse默认返回 String key 的 Hash,symbolize_names: true转为 Symbol key.to_json方法在 Hash/Array 上直接可用(需 require 'json')dig方法安全地深层访问嵌套数据,不存在返回 nil 而非抛异常JSON.pretty_generate生成格式化输出,方便调试
4 Sinatra Web 框架入门
Sinatra 是 Ruby 的微型 Web 框架,用极少的代码就能构建 Web 应用和 API。它的设计哲学是简约——路由、处理器、模板一目了然。
gem install sinatra puma
基础路由
require 'sinatra'
get '/' do
'Hello, World!'
end
get '/hello/:name' do
"Hello, #{params[:name]}!"
end
get '/users' do
content_type :json
users = [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' },
]
users.to_json
end
post '/users' do
data = JSON.parse(request.body.read)
content_type :json
status 201
{ id: 3, name: data['name'], message: '创建成功' }.to_json
end
put '/users/:id' do
data = JSON.parse(request.body.read)
content_type :json
{ id: params[:id].to_i, name: data['name'], message: '更新成功' }.to_json
end
delete '/users/:id' do
content_type :json
{ message: "用户 #{params[:id]} 已删除" }.to_json
end
ERB 模板
require 'sinatra'
get '/profile/:name' do
@name = params[:name]
@skills = ['Ruby', 'Rails', 'Sinatra']
erb :profile
end
__END__
@@layout
<!DOCTYPE html>
<html>
<body>
<nav><a href="/">首页</a></nav>
<%= yield %>
</body>
</html>
@@profile
<h1><%= @name %> 的主页</h1>
<ul>
<% @skills.each do |skill| %>
<li><%= skill %></li>
<% end %>
</ul>
完整 JSON API 示例
require 'sinatra/base'
require 'json'
class TodoAPI < Sinatra::Base
set :default_content_type, :json
@@todos = []
@@next_id = 1
before do
if request.content_type&.include?('application/json') && request.body.size > 0
request.body.rewind
@json = JSON.parse(request.body.read, symbolize_names: true)
end
end
get '/api/todos' do
@@todos.to_json
end
get '/api/todos/:id' do
todo = @@todos.find { |t| t[:id] == params[:id].to_i }
halt 404, { error: '未找到' }.to_json unless todo
todo.to_json
end
post '/api/todos' do
halt 422, { error: '标题不能为空' }.to_json unless @json&.dig(:title)
todo = {
id: @@next_id,
title: @json[:title],
completed: false,
created_at: Time.now.iso8601
}
@@next_id += 1
@@todos << todo
status 201
todo.to_json
end
patch '/api/todos/:id' do
todo = @@todos.find { |t| t[:id] == params[:id].to_i }
halt 404, { error: '未找到' }.to_json unless todo
todo[:title] = @json[:title] if @json&.key?(:title)
todo[:completed] = @json[:completed] if @json&.key?(:completed)
todo.to_json
end
delete '/api/todos/:id' do
todo = @@todos.find { |t| t[:id] == params[:id].to_i }
halt 404, { error: '未找到' }.to_json unless todo
@@todos.delete(todo)
{ message: '已删除' }.to_json
end
run! if app_file == $0
end
🔄 微框架对比
| 特性 | Ruby Sinatra | Node Express | Python Flask | PHP Slim |
|---|---|---|---|---|
| 路由定义 | get '/' do | app.get('/', fn) | @app.route('/') | $app->get('/') |
| 模板引擎 | ERB, Haml | EJS, Pug | Jinja2 | Twig |
| 中间件 | Rack | Express MW | WSGI | PSR-15 |
| 核心理念 | DSL 风格极简 | 回调链 | 装饰器路由 | PSR-7 标准 |
5 REST API 客户端实践
结合前面学到的知识,实现一个完整的 GitHub API 客户端,包含错误处理和重试逻辑。
GitHub API 客户端
require 'faraday'
require 'json'
class GitHubClient
BASE_URL = 'https://api.github.com'
MAX_RETRIES = 3
def initialize(token: nil)
@conn = Faraday.new(url: BASE_URL) do |f|
f.request :json
f.response :json
f.response :raise_error
f.headers['Accept'] = 'application/vnd.github.v3+json'
f.headers['Authorization'] = "Bearer #{token}" if token
f.headers['User-Agent'] = 'Ruby GitHub Client'
f.options.timeout = 15
end
end
def user(username)
with_retry { @conn.get("/users/#{username}").body }
end
def repos(username, sort: 'updated', per_page: 30)
with_retry do
@conn.get("/users/#{username}/repos", {
sort: sort,
per_page: per_page
}).body
end
end
def search_repos(query, sort: 'stars', per_page: 10)
with_retry do
response = @conn.get('/search/repositories', {
q: query,
sort: sort,
per_page: per_page
})
response.body['items']
end
end
def create_issue(owner, repo, title:, body: nil, labels: [])
@conn.post("/repos/#{owner}/#{repo}/issues") do |req|
req.body = { title: title, body: body, labels: labels }
end.body
end
private
def with_retry(retries: MAX_RETRIES)
attempts = 0
begin
attempts += 1
yield
rescue Faraday::ServerError, Faraday::ConnectionFailed, Faraday::TimeoutError => e
if attempts < retries
wait = 2 ** attempts
warn "[重试 #{attempts}/#{retries}] #{e.class}: #{e.message},等待 #{wait}s"
sleep(wait)
retry
end
raise
end
end
end
使用示例
client = GitHubClient.new(token: ENV['GITHUB_TOKEN'])
user = client.user('matz')
puts "#{user['name']} - #{user['bio']}"
puts "公开仓库: #{user['public_repos']}, 粉丝: #{user['followers']}"
repos = client.repos('matz', sort: 'stars', per_page: 5)
repos.each do |repo|
puts " #{repo['name']} ⭐ #{repo['stargazers_count']}"
end
results = client.search_repos('ruby web framework', per_page: 5)
results.each do |repo|
puts "#{repo['full_name']} ⭐ #{repo['stargazers_count']}"
puts " #{repo['description']}"
end
⚡ API 客户端最佳实践
- 封装为类,集中管理 base URL、认证、超时配置
- 实现指数退避重试(2n 秒),只对瞬态错误重试
- 使用 Faraday 的
:json请求/响应中间件自动处理序列化 - 敏感信息(Token)通过环境变量传入,不硬编码
6 本章要点
🌐 Net::HTTP
标准库内置,零依赖;get_response 获取完整响应,start 块复用连接
🔗 Faraday
社区首选 HTTP 客户端;中间件机制、自动 JSON 序列化、可插拔适配器
📋 JSON
标准库内置;parse/generate,symbolize_names 转 Symbol key,dig 安全嵌套访问
🎸 Sinatra
微型 Web 框架;DSL 风格路由定义,ERB 模板,构建 REST API 极简
🔄 重试机制
指数退避策略,只重试瞬态错误(超时、5xx),封装到 with_retry 方法
🏗️ 客户端封装
将 API 调用封装为 Ruby 类,集中管理认证、超时、错误处理
下一章预告:第六章将讲解 Ruby 的文件操作能力——文件读写、目录遍历、CSV/JSON/YAML 数据格式处理,打通数据持久化的最后一环。