如何选择一个完美的HTTP客户端?Ruby生态系统提供了大量的客户端来进行HTTP请求。有些是纯Ruby的,有些是基于Ruby原生的Net::HTTP
的,有些是现有库的封装器或libcurl的Ruby绑定。在这篇文章中,我将通过提供简短的描述和向Dad Jokes API发出请求的代码片断来介绍最流行的客户端。这些客户端将按照从下载量最大到最小的顺序提供。最后,我将以表格的形式对它们进行比较,并提供一个快速总结,以及关于选择哪种客户端的指导。
注意:虽然有众多的客户端供你使用,但我将介绍最流行的,我通过下载量和Github星级来定义。
设置
要求
在比较最流行的Ruby HTTP客户端时,我们将提出一个GET和一个POST请求。
当向Dad Jokes API发出GET请求时,我们将期望看到这样的输出:
{ :id=>"5wHexPC5hib", :joke=>"I made a belt out of watches once... It was a waist of time.", :status=>200 }
我们将向JSONPlaceholder发出POST请求,它是一个API测试工具。我们将提供这个占位符数据:
{ :title=>"Why I love Hitchhiker's Guide to the Galaxy", :body=>"42", :userId=>42 }
运行你的代码
你可以在终端的irb
控制台工作,或者创建一个http_client.rb
文件,其中的内容你将会替换。要运行该文件,请导航到它所在的目录,然后运行。
$ ruby http_client.rb
关于benchmarking的说明
你会看到,我在最后的Ruby HTTP 客户端概述部分提供了性能比较。你在那里看到的数字是Benchmark.measure
方法的输出,来自benchmark
Ruby gem。如果你想自己检查,通过你的终端安装该客户端($ gem install benchmark
),然后在代码片段中添加这三行:
require 'benchmark' puts Benchmark.measure{ # code goes here }
这将导致以下输出:
0.810000 0.000000 0.810000 ( 0.816964)
这些数字代表:用户CPU时间(执行你的代码的时间),系统CPU时间(在内核中花费的时间),用户和系统CPU时间相加,以及括号中的实际时间(或挂钟时间),该块执行的时间。
注意:不同的机器,不同的用户,其输出结果都会有所不同!
Faraday
Faraday是一个友好的、支持良好的、广受欢迎的Ruby HTTP客户端–事实上,它的下载量在过去两年里翻了一番。它的官方文档指出。
Faraday是一个HTTP客户端库,它在许多适配器(如Net::HTTP)上提供了一个通用接口,并在处理请求/响应周期时拥抱Rack中间件的概念。
现在让我们看一下GET请求:
require 'faraday' require 'json' url = 'https://icanhazdadjoke.com/' response = Faraday.get(url, {a: 1}, {'Accept' => 'application/json'}) joke_object = JSON.parse(response.body, symbolize_names: true) # => {:id=>"FdNZTnWvHBd", :joke=>"I’ve just been reading a book about anti-gravity, it’s impossible to put down!", :status=>200} joke_object[:joke] # "I’ve just been reading a book about anti-gravity, it’s impossible to put down!"
这里是POST请求:
require 'faraday' require 'json' url = 'https://jsonplaceholder.typicode.com/posts' response = Faraday.post(url, {:title=>"Why I love Hitchhiker's Guide to the Galaxy", :body=>"42", :userId=>42}) puts JSON.parse(response.body, symbolize_names: true) # {:id=>"FdNZTnWvHBd", :joke=>"I’ve just been reading a book about anti-gravity, it’s impossible to put down!", :status=>200}
正如你所看到的,调用很短 — 但这不是Faraday的唯一好处。它的文档清晰而简明。它支持有主体和无主体的请求、表单上传、详细的HTTP请求和建立连接,这使得它可以存储一个共同的URL基本路径或HTTP头,以应用于每个请求。
值得一提的是,Faraday提供了很多中间件,这一点也很重要,也很令人兴奋。
- Request Middleware在适配器运行前修改请求细节
- BasicAuthentication将授权头设置为
user:password base64
表示,TokenAuthentication将授权头设置为指定的token。 - Multipart将请求体的哈希值转换为表单请求,UrlEncoded将其转换为URL编码的请求体
- Retry,在不同的失败情况下自动重试请求
- 使用不同的工具对请求进行仪器Instrumentation
- Logger,记录请求和响应的主体和头信息
- RaiseError检查响应的HTTP代码,并标记出4xx或5xx代码。
这种对开发者幸福的承诺水平令人印象深刻,并反映在开源社区中。这就是给我们带来TurboVax的客户端,一个以光速反应帮助数百万纽约人接种疫苗的网站,或者处理最大的编码训练营Flatiron School的学生每天数以吨计的请求,它创造了一个客户端,将github repos与教育CMS连接起来。
毫不奇怪,Ruby Toolbox将这个客户端列为所有Ruby HTTP客户端的领导者。
Rest-Client
Rest-client是 “一个简单的HTTP和REST的Ruby客户端,灵感来自于Sinatra微框架的指定动作风格:get, put, post, delete”。由于rest-client似乎是为了使RESTful API请求变得直观而建立的,所以真的没有什么复杂的东西。
这里是GET请求:
require 'rest-client' require 'json' response = RestClient.get('https://icanhazdadjoke.com/', headers={ 'Accept' => 'application/json' }) joke_object = JSON.parse(response.body) # => {"id"=>"szItcaFQnjb", "joke"=>"Why was the broom late for the meeting? He overswept.", "status"=>200} puts joke_object["joke"] # "Why was the broom late for the meeting? He overswept."
还有POST请求:
require 'rest-client' require 'json' response = RestClient.post('https://jsonplaceholder.typicode.com/posts', :body => {:title=>"Why I love Hitchhiker's Guide to the Galaxy", :body=>"42", :userId=>42}) puts JSON.parse(response.body, symbolize_names: true) # {"title"=>"Why I love Hitchhiker's Guide to the Galaxy", "body"=>"42", "userId"=>"42", "id"=>101}
这个创业板让很多开发者感觉很舒服,但是,地平线上有一个警告信号:在过去3年里,这个创业板的提交活动非常少,而且一年多来没有新的版本。这到底是什么意思呢?它基本上意味着可能很少有支持,甚至意味着该创业板不再被维护。鉴于Ruby和Rails最近的新版本,这尤其成问题。这可能表明潜在的维护不力、高度脆弱和次优的支持。
HTTParty
HTTParty gem的创建是为了 “make http fun”。事实上,凭借其直观和直接的语法,该客户端在Ruby新手和副业项目中特别受欢迎。
下面两行是我们需要的全部内容,以使GET请求成功:
require "HTTParty" require 'json' response = HTTParty.get('https://icanhazdadjoke.com/' , :headers => { 'Accept' => 'application/json' } ) joke_object = JSON.parse(response.body) # {"id"=>"xXgyXvXgFlb", "joke"=>"Doctor you've got to help me, I'm addicted to Twitter. Doctor: I don't follow you.", "status"=>200} puts joke_object["joke"] # "Doctor you've got to help me, I'm addicted to Twitter. Doctor: I don't follow you."
还有一个POST请求:
require 'HTTParty' require 'json' response = HTTParty.post('https://jsonplaceholder.typicode.com/posts', :body => {:title=>"Why I love Hitchhiker's Guide to the Galaxy", :body=>"42", :userId=>42}) puts JSON.parse(response.body, symbolize_names: true) # {:title=>"Why I love Hitchhiker's Guide to the Galaxy", :body=>"42", :userId=>"42", :id=>101}
由于其语法看似简单,HTTParty曾经被视为HTTP客户端。直到一年前,GitLab还在使用它来实现一些功能,但正如本期所介绍的,他们决定不使用它,因为HTTParty “既慢又耗费大量资源”,其中包括荒谬的内存分配(见HTTP客户端概述中的表格)。
http.rb
http.rb是一个可靠的、令人愉快的客户端,适合那些需要一个易于使用的客户端库来从Ruby发出请求的人。根据文档的内容。
它使用一个简单的方法链系统来构建请求,类似于Python的Requests。在后台,通过Ruby FFI绑定,http.rb使用Node.js http-parser,一个快速的HTTP解析本地扩展。这个库并不只是Net::HTTP的另一个封装器。它在本地实现了HTTP协议,并将解析工作外包给本地扩展。
它的整个使命和愿景是,它是 “一个易于使用的API,在使用像Net::HTTP这样的东西之后,应该是一股新鲜空气”(见下面关于Net:HTTP的部分)。除此之外,它还支持持久性连接和细粒度超时等功能,并以 “在所有用Ruby而不是C语言实现HTTP协议的Ruby HTTP库中性能最好 “而自豪。
这里是GET请求:
require "http" http = HTTP.accept(:json) response = http.get('https://icanhazdadjoke.com/') joke_object = response.parse # => {"id"=>"UnWS0wcF6Ed", "joke"=>"Every machine in the coin factory broke down all of a sudden without explanation. It just doesn’t make any cents.", "status"=>200} puts joke_object["joke"] # "Every machine in the coin factory broke down all of a sudden without explanation. It just doesn’t make any cents."
还有POST请求:
require "http" require 'json' response = HTTP.post('https://jsonplaceholder.typicode.com/posts', :form => {:title=>"Why I love Hitchhiker's Guide to the Galaxy", :body=>"42", :userId=>42}) puts JSON.parse(response.body)["joke"] # {"title"=>"Why I love Hitchhiker's Guide to the Galaxy", "body"=>"42", "userId"=>"42", "id"=>101}
但是,这还不是全部:http.rb提供了灵活性,可以轻松地修改你的请求,愉快地处理异常,甚至可以将响应转换成不同的数据类型。你还可以使用超时、压缩和持久化连接。这个客户端绝对是令人愉快的,而且是可靠的。
然而,有一个红旗:就像Rest-Client一样,http.rb在一年多的时间里没有看到任何新的更新,而且有大量的开放问题。这可能预示着潜在的维护不力、高度的脆弱性和次优的支持。
Net::HTTP
现在我们已经涵盖了流行的、带来快乐的HTTP客户端,让我们在开发快乐的另一面提供一个参考点。Net::HTTP
在一些开发者的圈子里无孔不入,说实话,这让人很惊讶–我将在本节中解释这种情绪。
Ruby的标准库已经配备了一个HTTP客户端:net/http
gem。它也许是最常见的,也是最被诅咒的API请求的解决方案。它之所以常见,是因为它是Ruby自带的,而且许多其他的客户端也是在它上面运行的。对这个客户端有很多批评,其中包括杂乱的语法,或者它经常在请求失败后立即重试的事实(这个bug早在2012年就有记录,这增加了普遍的不满)。
然而,考虑到它在Ruby生态系统中的重要性,让我们来看看我们如何提出接收笑话的请求,以减轻一些挫折感:
require 'net/http' require 'uri' require 'json' uri = URI.parse('https://icanhazdadjoke.com/') request = Net::HTTP::Get.new(uri) request['Accept'] = 'application/json' req_options = { use_ssl: uri.scheme == 'https', } response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http| http.request(request) end joke_object = JSON.parse(response.body) # => {'id'=>'7wciy59MJe', 'joke'=>'What’s Forest Gump’s Facebook password? 1forest1', 'status'=>200} puts joke_object[:joke] # 'What’s Forest Gump’s Facebook password? 1forest1'
而这就是我们如何创建一个关于我们最喜欢的小说的新帖子:
require 'net/http' require 'uri' uri = URI('https://jsonplaceholder.typicode.com/posts') response = Net::HTTP.post_form(uri, {:title=>"Why I love Hitchhiker's Guide to the Galaxy", :body=>"42", :userId=>42}) puts JSON.parse(response.body) # {"title"=>"Why I love Hitchhiker's Guide to the Galaxy", "body"=>"42", "userId"=>"42", "id"=>101} [文中代码源自Scrapingbee]
这绝对是一个啰嗦的请求方式。
似乎没有很好的理由将任何开发人员提交给这个怪物般的客户端,特别是它的速度并不快,而且有灾难性的内存分配率(见下一节的表格)。
Ruby HTTP 客户端概述
现在你已经看到了五个主要的Ruby客户端,让我们把它们放大并进行比较。
命名 | 下载量(单位:米) | 明星 | 问题结案率 | GET基准 | POST基准 | 内存分配(单位:MB) |
---|---|---|---|---|---|---|
Faraday | 322 | 4,994 | 94% | 0.015469 | 0.003083 | 不适用 |
http.rb | 47 | 2,722 | 73% | 0.004946 | 0.003870 | 0.02 |
httparty | 130 | 5,372 | 92% | 0.003956 | 0.004761 | 12.59 |
rest-client | 185 | 5,025 | 84% | 0.002574 | 0.004073 | 12.57 |
Net::HTTP (standard lib) | 不适用 | 不适用 | 不适用 | 0.003086 | 0.005176 | 12.36 |
注意:内存分配的比较来自这个gist,涉及下载的内存分配。
如你所见,Faraday在几乎所有方面都处于领先地位:下载量(表明其受欢迎程度)、问题关闭率(表明维护者的支持)和POST性能。
毫不奇怪,Net::HTTP在任何类别中都不处于领先地位。
总 结
现在我已经提供了所有客户端的概述、代码片段和指标,以及与之相关的Ruby社区更新,我想提供一个快速的建议:如果你想知道哪个客户端应该用于你的代码库或一个充满激情的副项目,请选择Faraday。它有最好的支持,最大的社区,它是广泛流行的,令人震惊的良好维护,在内存方面的快速和性能。毫不奇怪,这个观点也被Ruby toolbox所认同。