在 Ruby 中使用 HTTP 請求

zhang89jt 8年前發布 | 99K 次閱讀 Ruby HTTP Ruby開發

有時候我們需要通過原生的 HTTP 調用來同 API 進行連接,而有時候我們又只需要進行普通的 HTTP 調用。那么在 Ruby 中有哪些可供我們選用呢?

一個 API 不會因為它自己而存在,它總會涉及到兩方: 客戶端服務端。

在 Rails 中,我們的應用程序常常是扮演這其中的服務端, 而我們通常都指導如何來處理那些不可避免的問題。我們可以輸出日志來查看進入的請求到底是什么(路徑、消息頭、參數), 如何做出響應,諸如此類。但隨著后臺開發越來越趨向于微服務架構,我們的 Rails 應用會更多地扮演除了服務端之外的客戶端這一角色。

作為客戶端很可能意味著要進行HTTP請求和解析JSON類型的響應數據。更具體的說,這意味著使用正確的URL,身份認證所需HTTP消息頭,頁數信息,響應格式,和請求消息體。請求消息體通常是表單( application/x-www-form-urlencoded )或者是JSON。

得益于APIs的日益普及,例如推ter, Slack, and Stripe,已經有相關客戶端類庫對底層HTTP請求的構建和響應解析進行封裝。

但是,當你需要連接自己內部服務或者知名度較低的APIs時,可能還沒有構建相關客戶端類庫,我們只有構建我們自己的類庫。 這篇文章的余下部分對Ruby進行探索——如何以多種可用的方式處理HTTP請求。當出現問題或者你希望可視化更多細節時,我們將使用一些技術去設計實現代碼和排除故障。

在Ruby中可使用哪些HTTP客戶端?

在Ruby生態系統中,有一些HTTP庫可用作HTTP請求,我無法每個都提及到。下面我列出幾個較為流行的:

Net::HTTP : Ruby標準庫的一部分。當它可以獲取你所需要的所有東西時,你就會明白為什么會有那么多第三方庫存在了。個人認為,它的工作方式并不簡單,所以我傾向于在此列表的其他選項。

Curb : 這個精簡庫提供 libcurl 編程的綁定(與你使用 curl 命令的命令行方式效果一樣)。運行速度快,主要是因為,在Ruby外部做了大量繁雜工作,并且 libcurl 編程效率很高。

HTTParty : 一個非常流行的庫(2200萬的下載量)封裝了 Net::HTTP   代碼,提供一個更加簡單的API以供調用。

HTTP : 為什么不如其他庫一樣流行呢,當它與APIs聯系起來,最近它變成我的最愛。它提供一個鏈式接口來構建HTTP請求,提供你所期待一個HTTP庫擁有的所有常用功能的支持。

Excon : 另一個非常流行的庫(2500萬下載量)使用純Ruby編寫。它有一個簡潔API并且簡單調用。

我不會在這里展示每個范例,來教大家如何使用這些庫,你可以在它們的網站主頁自行查看。在下一個章節,我們將討論,如何 使用 HTTP 庫,以及我們如何封裝和組織我們的代碼。

對 HTTP 客戶端進行包裝

最好是給與API端點或者服務的交互提供某些形式的接口,隱藏比較底層的HTTP請求和響應細節。其它的編程工作應該不需要展示執行的細節, 而對于同一個執行應該要能夠在可以進行修改/重構的同時,無需對公開的API進行修改。

讓我們來看看針對一個API我們應該怎么做。我們將會使用 lcboapi.com API。它提供了一個漂亮小巧的接口來訪問跟LCBO的飲料和店鋪相關的信息 (加拿大安大略省的一家企業,負責面向全省的酒精飲料零售和批發業務)。

我們會用到4個類:

  • Lcbo::Products: 我們使用這個公共接口來獲取特定產品的詳細信息。

  • Lcbo::ProductRequest: 處理針對特定產品ID的HTTP請求。

  • Lcbo::ProductResponse: 處理HTTP響應并且知道如何去構建一個 Lcbo::Product.

  • Lcbo::Product: 實際我們要獲取的一個產品詳情。

在深入實現細節之前,讓我們先看看如何使用它:

require_relative 'lib/lcbo'

key = ENV.fetch('LCBO_API_KEY')
product = Lcbo::Products.new(key).fetch(438457)

puts product.name
# Hopsta La Vista
puts product.tags.inspect
# ["hopsta", "la", "vista", "beer", "ale", "canada", "ontario", "longslice", "brewery", "inc", "can"]

控制器

這個類作為公共接口獲取關于一個或多個LCBO產品細節。在這一點上,我只實現了獲取方法,該方法將返回一個產品的細節。這類的工作是構建請求并處理響應;它控制流和知道訂單必須在API調用。

module Lcbo
  require_relative 'product_request'
  require_relative 'product_response'

  class Products
    attr_accessor :key

    def initialize(key)
      @key = key
    end

    def fetch(product_id)
      connection = HTTP

      product_response = ProductRequest.new(key, product_id, connection).response
      fail LcboError, product_response.error_message unless product_response.success?

      product_response.product
    end
  end
end

請求

每個請求類知道如何使一個單一的請求API(基本上一個端點)。構建HTTP請求,填寫標題的細節和任何其他信息需要包含在請求。這也有可能,你可以引入緩存,如果注意到你有一個本地版本的反應已經。它將回應響應對象。

module Lcbo
  class ProductRequest
    attr_reader :key, :product_id, :connection

    def initialize(key, product_id, connection)
      @key = key
      @product_id = product_id
      @connection = connection
    end

    def response
      http_response = connection
        .headers('Authorization' => "Token #{key}")
        .get(url)
      ProductResponse.new(http_response)
    end

    def url
      "https://lcboapi.com/products/#{product_id}"
    end
  end
end

響應

響應對象知道如何處理從一個API端點的響應。可以返回響應的具體細節和/或構建其他對象的響應。

module Lcbo
  class ProductResponse
    attr_reader :http_response

    DEFAULT_ERROR_MESSAGE = 'There was an error retrieving product details.'.freeze

    def initialize(http_response)
      @http_response = http_response
    end

    def success?
      http_response.status == 200
    end

    def error_message
      data.fetch('message', DEFAULT_ERROR_MESSAGE)
    end

    def product
      Product.new(data.fetch('result'))
    end

    private

    def data
      http_response.parse(:json)
    end
  end
end

產品類

最后一個類我們將關注Lcbo::Product 類。它由ProductResponse構成,并在LCBO API中代表一個單一的產品。

module Lcbo
  class Product
    attr_accessor :details

    def initialize(details)
      @details = details
    end

    def name
      details['name']
    end

    def tags
      details.fetch('tags', '').split(' ')
    end
  end
end

日志輸入通信

編程很容易當一切工作第一次嘗試(提示:永遠不會發生)。api可能會非常棘手,因為HTTP請求可能會以一個非常具體的方式格式化,這頭或身體。可以棘手的Ruby代碼僅僅通過觀察找出最終的HTTP請求和響應實際的樣子。

伴隨httplog

httplog 是一個很棒的精華,monkey解決了大部分HTTP庫能夠記錄傳入和傳出的交通到控制臺($stdout或無論你想要的什么)。它所提供的信息可以幫助你意識到你拼寫的標題錯了,丟失了一些東西。

如下面例子:

D, [2016-09-11T22:05:11.353063 #5345] DEBUG -- : [httplog] Sending: GET https://lcboapi.com/products/438457
D, [2016-09-11T22:05:11.353158 #5345] DEBUG -- : [httplog] Header: Authorization: Token MY_API_KEY
D, [2016-09-11T22:05:11.353184 #5345] DEBUG -- : [httplog] Header: Connection: close
D, [2016-09-11T22:05:11.353202 #5345] DEBUG -- : [httplog] Header: Host: lcboapi.com
D, [2016-09-11T22:05:11.353234 #5345] DEBUG -- : [httplog] Header: User-Agent: http.rb/2.0.3
D, [2016-09-11T22:05:11.353268 #5345] DEBUG -- : [httplog] Data:
D, [2016-09-11T22:05:11.353320 #5345] DEBUG -- : [httplog] Connecting: lcboapi.com:443
D, [2016-09-11T22:05:11.502537 #5345] DEBUG -- : [httplog] Status: 200
D, [2016-09-11T22:05:11.502658 #5345] DEBUG -- : [httplog] Benchmark: 0.1491872170008719 seconds
D, [2016-09-11T22:05:11.502815 #5345] DEBUG -- : [httplog] Header: Server: nginx/1.6.2
etc...

使用 mitmproxy

用代理服務器監視出站請求和入站響應則是一種更為有效的工具。 mitmproxy 是一個用于觀測app(或者手機或者電腦)產生HTTP流量的優秀工具。

使用mitmproxy 的第一步是安裝并且下載一個pem證書文件。之后,便可以在啟動Rails服務器或者運行Ruby腳本時,添加指向你SSL證書的ENV變量。如下:

  • Rails 應用: SSL_CERT_FILE=/Users/leighhalliday/mitmproxy-ca-cert.pem bundle exec rails s -p 3000

  • Rails 控制臺: SSL_CERT_FILE=/Users/leighhalliday/mitmproxy-ca-cert.pem bundle exec rails c

  • Ruby 腳本: SSL_CERT_FILE=/Users/leighhalliday/mitmproxy-ca-cert.pem ruby demo.rb

通過mitmproxy命令啟動mitmproxy并監聽localhost的8080(默認)端口。

然后就可以讓HTTP庫將流量路由到mitmproxy,這樣就能看到傳進來的信息了。mitmproxy可以通過規則過濾請求、重試請求、以及導出這些信息以方便共享或后續查看。它能讓我聯想到Chrome或者火狐的網絡監視標簽。

使用函數庫的時候,需要將connect = HTTP改為connect = HTTP.via('localhost', 8080)。

結論

找出與API 的 抽象交互 或者微服務十分重要。它簡化測試,封裝了底層細節,并且提供了可重用的代碼片段。為深入了解  httplogger 和 mitmproxy  做出的請求和響應,我們需要給予他們更充分的時間做出嘗試。

 

來自:https://www.oschina.net/translate/http-calls-in-ruby

 

 本文由用戶 zhang89jt 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!