Ruby Net::HTTP - 在Ruby中处理网络请求和响应


在本教程中,我们展示了如何使用标准的Ruby Net::HTTP模块工作。我们抓取数据、发布数据、处理JSON,并连接到一个安全网页。该教程在几个例子中使用了Sinatra应用程序。Zetcode也有一个简明的 Ruby tutorial

超文本传输协议( HTTP ) 是一个用于分布式、协作式、超媒体信息系统的应用协议。HTTP是万维网的数据通信的基础。

Ruby Net::HTTP 提供了一个丰富的库,可以用来建立HTTP客户端。

Ruby Net:HTTP status

响应的 codemessage 方法给出其状态。

status_code.rb
#!/usr/bin/ruby

require 'net/http'

uri = URI 'http://webcode.me'
res = Net::HTTP.get_response uri
puts res.message
puts res.code

uri = URI 'http://www.example.com/news/'
res = Net::HTTP.get_response uri
puts res.message
puts res.code

uri = URI 'http://www.urbandicionary.com/define.php?term=Dog'
res = Net::HTTP.get_response uri
puts res.message
puts res.code

我们用 get_response 方法执行三个HTTP请求并检查返回的状态。

uri = URI 'http://www.example.com/news/'
res = Net::HTTP.get_response uri
puts res.message
puts res.code

messagecode 方法检查HTTP响应的状态。

$ ./status_code.rb 
OK
200
Not Found
404
Found
302

200是HTTP请求成功的标准响应,404告诉人们无法找到请求的资源,302告诉人们资源被暂时重定向。

Ruby Net::HTTP HEAD请求

HEAD请求是一个没有信息体的HTTP GET请求。请求/响应的头包含元数据,如HTTP协议版本或内容类型。

head 方法检索的是文件头。头信息由字段组成,包括日期、服务器、内容类型或最后修改时间。

head_req.rb
#!/usr/bin/ruby

require 'net/http'

uri = URI "http://www.webcode.me"
http = Net::HTTP.new uri.host, uri.port

res = http.head '/'

puts res['server']
puts res['date']
puts res['last-modified']
puts res['content-type']
puts res['content-length']

该例子打印了 www.something.com 网页的服务器、日期、最后修改时间、内容类型和内容长度。

$ ./http_head.rb 
nginx/1.6.2
Wed, 03 Feb 2021 10:30:30 GMT
Sat, 20 Jul 2019 11:49:25 GMT
text/html
348

Ruby Net::HTTP GET请求

HTTP GET方法请求一个指定资源的表示。

get_content.rb
#!/usr/bin/ruby

require 'net/http'

uri = URI 'http://www.webcode.me'

content = Net::HTTP.get uri

puts content

该脚本抓取webcode.me网页的内容。 net/http 的设计是为了与 uri 模块紧密合作。

require 'net/http'

这也会要求 uri ,所以我们不需要单独要求它。

content = Net::HTTP.get uri

get 方法产生了一个对指定资源的GET请求。

$ ./http_get.rb 



    
    
    My html page



    

Today is a beautiful day. We go swimming and fishing.

Hello there. How are you?

下面的程序得到一个小的网页,并剥离其HTML标签。

strip_tags.rb
#!/usr/bin/ruby

require 'net/http'

uri = URI "http://example.com"

doc = Net::HTTP.get uri

puts doc.gsub %r{?[^>]+?>}, ''

脚本剥离了 example 网页的HTML标签。

puts doc.gsub %r{?[^>]+?>}, ''

用一个简单的正则表达式来剥离HTML标签。

Ruby Net::HTTP异步请求

在下面的例子中,我们提出了多个异步请求。

async_req.rb
#!/usr/bin/ruby

require 'async'
require 'net/http'
require 'uri'

urls = [
  'http://webcode.me', 
  'https://example.com',
  'http://httpbin.org',
  'https://www.ruby-lang.org'
]

Async do
  urls.each do |url|
    Async do
      res = Net::HTTP.get_response(URI url)
      puts "#{res.message} #{res.code}"
    end
  end
end

我们向四个网站发出无阻塞的网络请求,并打印它们的状态代码和信息。

$ ./async_req.rb 
OK 200
OK 200
OK 200
OK 200

Sinatra

Sinatra 是一个流行的Ruby Web应用框架。它很容易安装和设置。我们的一些例子也将使用Sinatra应用程序。

$ sudo gem install sinatra
$ sudo gem install thin

我们安装Sinatra和Thin网络服务器。如果安装了Thin,Sinatra会自动选择Thin而不是默认的WEBrick服务器。

main.rb
#!/usr/bin/ruby

require 'sinatra'

get '/' do
    "First application"
end

应用程序对 / 路线作出反应。它发送一个简单的消息回给客户端。

$ ruby main.rb 
== Sinatra (v2.1.0) has taken the stage on 4567 for development with backup from Thin
2021-02-03 11:36:49 +0100 Thin web server (v1.8.0 codename Possessed Pickle)
2021-02-03 11:36:49 +0100 Maximum connections set to 1024
2021-02-03 11:36:49 +0100 Listening on localhost:4567, CTRL+C to stop

ruby main.rb 命令启动应用程序。服务器被启动;它在4567端口监听。

$ curl localhost:4567
First application

curl 命令行工具,我们连接到服务器并访问 / 路线。控制台上出现了一条信息。

Sinatra get方法

get 方法向服务器发出一个GET请求。GET方法请求一个指定资源的表示。

main.rb
#!/usr/bin/ruby

require 'sinatra'

get '/greet' do  
    "Hello #{params[:name]}"
end

这是Sinatra应用程序的文件。在收到 /greet 路线时,它返回一个包含由客户发送的名称的消息。

client_get.rb
#!/usr/bin/ruby

require 'net/http'

uri = URI "http://localhost:4567/greet"

params = { :name => 'Peter' }
uri.query = URI.encode_www_form params

puts Net::HTTP.get uri

脚本向Sinatra应用程序发送一个带有数值的变量。该变量是直接在URL中指定的。

params = { :name => 'Peter' }

这是我们发送给服务器的参数。

uri.query = URI.encode_www_form params

我们用 encode_www_form 方法将参数编码到URL中。

puts Net::HTTP.get uri

get 方法向服务器发送了一个GET请求。它返回响应,并打印到控制台。

$ ./client_get.rb 
Hello Peter

这是该例子的输出。

::1 - - [03/Feb/2021:11:51:46 +0100] "GET /greet?name=Peter HTTP/1.1" 200 11 0.0046

在Thin服务器的这份日志中,我们可以看到参数被编码到了URL中。

我们可以直接把参数放到URL字符串中。

client_get2.rb
#!/usr/bin/ruby

require 'net/http'

uri = URI "http://localhost:4567/greet?name=Peter"

puts Net::HTTP.get uri 

这是另一种发布GET消息的方式;它与前面的例子基本相同。

Sinatra get user agent

在这一部分,我们指定了用户代理的名称。

main.rb
#!/usr/bin/ruby

require 'sinatra'

get '/agent' do
    request.user_agent
end

Sinatra应用程序返回客户端发送的用户代理。

client_agent.rb
#!/usr/bin/ruby

require 'net/http'

uri = URI "http://localhost:4567"
http = Net::HTTP.new uri.host, uri.port

res = http.get '/agent', {'User-Agent' => 'Ruby script'}
puts res.body

这个脚本创建了一个简单的GET请求到Sinatra应用程序。

res = http.get '/agent', {'User-Agent' => 'Ruby script'}

用户代理在 get 方法的第二个参数中被指定。

$ ./client_agent.rb 
Ruby script

服务器响应了我们随请求发送的代理名称。

Sinatra发布一个值

post 方法在给定的URL上派发一个POST请求,为填写表单内容提供键/值对。

main.rb
#!/usr/bin/ruby

require 'sinatra'

post '/target' do
    "Hello #{params[:name]}"
end

Sinatra应用程序在 /target 路线上返回一个问候语。它从 params 哈希中取值。

client_post.rb
#!/usr/bin/ruby

require 'net/http'

uri = URI "http://localhost:4567/target"

params = { :name => 'Peter' }
res = Net::HTTP.post_form uri, params

puts res.body

脚本发送了一个具有 name 键和 Peter 值的请求。POST请求是用 Net::HTTP.post_form 方法发出的。

$ ./client_post.rb 
Hello Peter
::1 - - [03/Feb/2021:12:00:54 +0100] "POST /target HTTP/1.1" 200 11 0.0015

用POST方法,值不在请求的URL中发送。

从字典中检索定义

在下面的例子中,我们在www.dictionary.com,找到一个术语的定义。

$ sudo gem install nokogiri

为了解析HTML,我们使用 nokogiri 宝石。

get_term.rb
#!/usr/bin/ruby

require 'net/http'
require 'nokogiri'

term = 'cat'
uri = URI "https://www.dictionary.com/browse/#{term}"

res = Net::HTTP.get uri

doc = Nokogiri::HTML res
doc.css("span.one-click-content").map do |node|
    s = node.text.strip!
    s.gsub!(/\s{3,}/, " ") unless (s == nil)
    puts s unless (s == nil)
end

在这个脚本中,我们在 www.dictionary.com 上找到猫这个词的定义。 Nokogiri::HTML 是用来解析HTML代码的。

uri = URI "https://www.dictionary.com/browse/#{term}"

为了进行搜索,我们在URL的末尾附加该词。

doc = Nokogiri::HTML res
doc.css("span.one-click-content").map do |node|
    s = node.text.strip!
    s.gsub!(/\s{3,}/, " ") unless (s == nil)
    puts s unless (s == nil)
end

我们用 Nokogiri::HTML 类来解析内容。我们通过删除过多的空白来改善格式。

JSON

JSON (JavaScript Object Notation)是一种轻型的数据交换格式。它便于人类阅读和书写,也便于机器解析和生成。

$ sudo gem install json

如果我们以前没有安装过 json gem,我们必须安装。

main.rb
require 'sinatra'
require 'json'

get '/example.json' do
    content_type :json
    { :name => 'Jane', :age => 17 }.to_json
end

Sinatra应用程序发送JSON数据。它使用 to_json 方法来完成这项工作。

parse_json.rb
#!/usr/bin/ruby

require 'net/http'
require 'json'
 
uri = URI 'http://localhost:4567/example.json'
res = Net::HTTP.get uri 

data = JSON.parse res

puts data["name"]
puts data["age"]

该例子读取Sinatra应用程序发送的JSON数据。

$ ./parse_json.rb 
Jane
17

接下来,我们从Ruby脚本向Sinatra应用程序发送JSON数据。

main.rb
require 'sinatra'
require 'json'

post '/readjson' do
    data = JSON.parse request.body.read
    "#{data["name"]} is #{data["age"]} years old"
end

这个应用程序读取JSON数据,并将解析后的值发回一个消息。

post_json.rb
#!/usr/bin/ruby

require 'net/http'
require 'json'
 
uri = URI 'http://localhost:4567/readjson'

req = Net::HTTP::Post.new uri.path, initheader = {'Content-Type' =>'application/json'}
req.body = {:name => 'Jane', :age => 17}.to_json

res = Net::HTTP.start(uri.hostname, uri.port) do |http|
    http.request req
end

puts res.body

该脚本向Sinatra应用程序发送JSON数据并读取其响应。

req = Net::HTTP::Post.new uri.path, initheader = {'Content-Type' =>'application/json'}

'application/json' 内容类型必须在请求的标题中指定。

$ ./post_json.rb 
Jane is 17 years old

重定向

重定向是将一个URL转发到另一个URL的过程。HTTP响应状态代码302用于临时的URL重定向。

main.rb
require 'sinatra'

get "/oldpage" do  
    redirect to("/files/newpage.html"), 302
end

在Sinatra应用程序中,我们使用 redirect 命令来重定向到一个不同的位置。

public/files/newpage.html



New page


This is a new page

这是位于 public/files 子目录下的 newpage.html 文件。

redirect.rb
#!/usr/bin/ruby

require 'net/http'

uri = URI 'http://localhost:4567/oldpage'

res = Net::HTTP.get_response uri
if res.code == "302"
    res = Net::HTTP.get_response URI res.header['location']
end 

puts res.body

这个脚本访问旧的页面并跟随重定向。请注意,这只适用于单个重定向。

res = Net::HTTP.get_response URI res.header['location']

头部的location字段包含文件被重定向到的地址。

$ ./redirect.rb 



New page


This is a new page

::1 - - [03/Feb/2021:13:59:52 +0100] "GET /oldpage HTTP/1.1" 302 - 0.0012
::1 - - [03/Feb/2021:13:59:52 +0100] "GET /files/newpage.html HTTP/1.1" 200 112 0.0051

从日志中我们看到,请求被重定向到一个新的文件名。通信由两条GET信息组成。

在这个教程中,我们使用了Ruby net/http 模块。在ZetCode上也有类似的 Ruby HTTPClientRuby Faraday