Статьи

Глядя на Ruby’s Net :: HTTP Library

Снимок экрана 2014-04-26 08.43.13

Время от времени мы нуждаемся в доступе к REST API. Для этого есть несколько замечательных библиотек , но как насчет библиотеки, стоящей за этими библиотеками? В этой статье мы рассмотрим библиотеку Ruby’s Net::HTTP . Мы пройдемся по простым принципам REST API, таким как:

  • GET
  • POST
  • PUT
  • DELETE

Я также затрону более сложные темы, такие как:

  • Блоки HTTP
  • 301 перенаправления
  • Заголовки
  • Basic Auth
  • Загрузка файлов

Что такое ОТДЫХ и почему вы должны заботиться?

REST означает RE презентационный Государственный перевод. Он был теоретизирован Роем Филдингом в его докторской диссертации: « Архитектурные стили и проектирование сетевых программных архитектур» , а затем реализован Консорциумом World Wide Web (W3C) для HTTP 1.1. В основном это стиль архитектуры программного обеспечения (в основном веб), который обеспечивает легкий доступ к API, а также некоторые другие полезные цели.

API REST фокусируется на четырех основных глаголах: GET , POST , PUT и DELETE . Мы можем использовать библиотеку Ruby Net::HTTP для доступа к API REST. В этой статье мы узнаем, как это сделать, а также некоторые другие вкусности. Если вам нужны полные определения всех методов HTTP 1.1, посетите эту страницу W3C .

В восторге? Давайте начнем.

Просто: тестирование

Давайте использовать Sinatra как наш маленький сервер API. Если вы не знакомы с Синатрой, вы можете изучить основы за 10 минут . Вот наш сервер:

 # file: rest-server.rb require 'sinatra' # all status codes given are based on W3C standards get '/' do 'Here is some data.' end post '/' do puts "Data: '#{params[:foo]}' recieved, creating object." status 201 end put '/' do puts "Data: '#{params[:foo]}' recieved, updating object." end delete '/' do puts 'Deleting data.' end 

Теперь, чтобы запустить его, просто откройте новую вкладку терминала:

 $ ruby rest-server.rb == Sinatra/1.4.4 has taken the stage on 4567 for development with backup from Thin Thin web server (v1.6.1 codename Death Proof) # might be WEBrick instead Maximum connections set to 1024 Listening on localhost:4567, CTRL+C to stop 

Теперь у вас есть функциональный REST API, работающий на вашем компьютере.

Просто: ПОЛУЧИТЕ Запрос

GET запрос получает или читает информацию с сервера . Этот запрос довольно распространен. Когда вы посещали эту страницу, ваш браузер сделал запрос GET который возвратил гипертекстовый документ с сервера. Вы можете сделать то же самое в Ruby. В нашем примере мы собираемся получать данные с нашего сервера.

 require 'net/http' require 'uri' url = 'http://localhost:4567/' # trailing slash is important uri = URI.parse(url) Net::HTTP.get(uri) # GET request # => "Here is some data." 

Этот простой пример извлекает данные с нашего сервера и возвращает их. Элегантно, правда? Вот он в уродливой однострочной форме:

 require 'net/http' # URI is required by Net::HTTP by default Net::HTTP.get(URI 'http://localhost:4567/') # => "Here is some data." 

Как интеллектуальный читатель SitePoint Ruby, как вы можете видеть, у этого кода есть некоторые проблемы. Не волнуйтесь, мы рассмотрим большинство из них позже в продвинутых битах Net::HTTP .

Простой: запрос POST

Запрос POST отправляет или отправляет данные / параметры на сервер . API используют это для получения данных от пользователя. Этот запрос также довольно распространен. Каждый раз, когда вы отправляете твит, вы отправляете в Twitter запрос POST с вашим сообщением в качестве параметров. В этом примере мы собираемся отправить нашему серверу запрос POST .

 require 'net/http' require 'uri' url = 'http://localhost:4567/' uri = URI.parse(url) params = {foo: "bar"} Net::HTTP.post_form(uri, params) # => #<Net::HTTPCreated 201 Created readbody=true> 

И, конечно, вот его уродливый однострочный эквивалент:

 require 'net/http' # ugly, right? Net::HTTP.post_form(URI('http://localhost:4567/'), {foo:"bar"}) # => #<Net::HTTPCreated 201 Created readbody=true> 

Простой: запрос PUT

Запрос PUT помещает или обновляет что-либо на сервере. API используют это главным образом при обновлении данных на сервере. Например, если вы редактируете пул-запрос на GitHub, вы отправляете PUT запрос со своими данными. К сожалению, PUT не очень хорошо документирован в библиотеке Ruby Net::HTTP , поэтому нет элегантного решения.

 require 'net/http' require 'uri' url = 'http://localhost:4567/' uri = URI.parse(url) params = {foo: "change"} http = Net::HTTP.new(uri.host, uri.port) request = Net::HTTP::Put.new(uri.path) request.set_form_data(params) http.request(request) # the actual PUT request # => #<Net::HTTPOK 200 OK readbody=true> 

Я не собираюсь показывать это как одну строку, потому что это будет более 80 символов, что не только нечитабельно, но и идет вразрез с Руководством по стилю Ruby .

Просто: УДАЛИТЬ Запрос

Запрос DELETE удаляет что-то с сервера. Если бы вы удалили твит, вы бы отправили запрос на удаление в Twitter. Этот пример очень похож на последний.

 require 'net/http' require 'uri' http = Net::HTTP.new(uri.host, uri.port) request = Net::HTTP::Delete.new(uri.path) http.request(request) # => #<Net::HTTPOK 200 OK readbody=true> 

Отлично. Мы изучили основы и глаголы REST, используя Ruby. Имейте в виду, что существует множество способов выполнить большинство этих HTTP-запросов. Мы рассмотрим некоторые из них в следующем разделе.

Дополнительно: тестирование

В расширенном разделе наш сервер должен быть более сложным, чтобы возвращать и получать данные, такие как API, который мы пытаемся имитировать в каждом примере. Итак, сервер нужно будет менять в каждом примере.

С этого момента мы будем фокусироваться на методах GET и POST HTTP. Если вам нужно применить приведенные ниже методы к другим методам, просто смешайте простую версию запроса с расширенной техникой.

Дополнительно: блоки HTTP

В принципе DRY блоки HTTP важны. Блоки HTTP — это просто блоки Net::HTTP . Это может показаться немного запутанным, но это довольно простая концепция.

Сначала мы создаем новый метод. Мы назовем это http_request и приведем один аргумент, uri .

 require 'net/http' def http_request(uri) end 

Теперь самое интересное. Вызовите Net::HTTP.new и Net::HTTP.new его блоку.

 require 'net/http' def http_request(uri) Net::HTTP.start(uri.host, uri.port) do |http| yield(http, uri) end end 

Это означает, что мы можем делать действительно крутые вещи, как это: (используя простой сервер, который мы сделали)

 # ... http block code above ... uri = URI 'http://localhost:4567/' http_request(uri) do |http, uri| http.get(uri.path).body end # => "Here is some data." 

Мы будем использовать их в следующих примерах, поэтому ознакомьтесь. Просто помните, что http_request — это то же самое, что Net::HTTP.start , это просто ярлык.

Дополнительно: 301 Redirects

о нет! Наш сервер только что сделал шаг вперед и теперь имеет возможность перенаправлять запросы.

 require 'sinatra' get '/foo/?' do redirect '/bar' end get '/bar/?' do 'Hello!' end 

Если мы попробуем простой метод GET из ранее, давайте посмотрим, что происходит сейчас …

 require 'net/http' uri = URI 'http://localhost:4567/foo' Net::HTTP.get(uri) # => "" 

Хм … это нам ничего не дало. Но, будучи умными людьми, которые мы есть, мы пробуем второй простой метод.

 require 'net/http' def http_request(uri) Net::HTTP.start(uri.host, uri.port) do |http| yield(http, uri) end end uri = URI 'http://localhost:4567/foo' http_request uri do |http, uri| http.get(uri.path) end # => #<Net::HTTPFound 302 Moved Temporarily readbody=true> 

Это дало нам больше информации, но до сих пор нет 200 OK . Что мы можем сделать, чтобы следовать этим перенаправлениям, не теряясь? Есть два метода.

Net::HTTP.get_response метод использует Net::HTTP.get_response для получения информации с сервера, а не только с тела.

 require 'net/http' uri = URI 'http://localhost:4567/foo' def get_follow_redirects(uri, request_max = 5) raise "Max number of redirects reached" if request_max <= 0 response = Net::HTTP.get_response(uri) case response.code.to_i when 200 response.body when 301..303 get_follow_redirects(URI(response['location']), request_max - 1) else response.code end end get_follow_redirects(uri) # => "Hello!" 

Второй метод предполагает использование блоков HTTP в аналогичном методе.

 require 'net/http' uri = URI 'http://localhost:4567/foo' def http_request(uri) Net::HTTP.start(uri.host, uri.port) do |http| yield(http, uri) end end def get_follow_redirects(uri, request_max = 5) http_request uri do |http, uri| response = http.head(uri.path) case response.code.to_i when 200 http.get(uri.path).body when 301..303 get_follow_redirects(URI(response['location']), request_max - 1) else response.code end end end get_follow_redirects(uri) # => "Hello!" 

Как видите, этот метод очень похож на другой. Все зависит от предпочтений и того, насколько вы хотите контролировать запрос.

Дополнительно: заголовки

В предыдущих примерах мы использовали заголовки, чтобы найти местоположение перенаправления 301/302. В следующем примере мы выделим эту возможность, чтобы мы могли получить доступ к любой части заголовков.

Сначала давайте сделаем запрос и посмотрим, какие заголовки мы получаем. В этом примере мы будем использовать первый простой сервер .

 require 'net/http' uri = URI 'http://localhost:4567/' response = Net::HTTP.get_response(uri) response.header.to_hash.inspect # => "{\"content-type\"=>[\"text/html;charset=utf-8\"], \"content-length\"=>[\"18\"], \"x-xss-protection\"=>[\"1; mode=block\"], \"x-content-type-options\"=>[\"nosniff\"], \"x-frame-options\"=>[\"SAMEORIGIN\"], \"connection\"=>[\"keep-alive\"], \"server\"=>[\"thin 1.6.1 codename Death Proof\"]}" 

Довольно мило, а? Если вам не нужны все заголовки, вы можете просто использовать response['something'] в качестве ярлыка.

Конечно, есть также версия блочного метода HTTP этого…

 require 'net/http' # ... http block code ... uri = URI 'http://localhost:4567/' http_request(uri) do |http, uri| response = http.head(uri.path) response.to_hash.inspect end # => "{\"content-type\"=>[\"text/html;charset=utf-8\"], \"content-length\"=>[\"18\"], \"x-xss-protection\"=>[\"1; mode=block\"], \"x-content-type-options\"=>[\"nosniff\"], \"x-frame-options\"=>[\"SAMEORIGIN\"], \"connection\"=>[\"keep-alive\"], \"server\"=>[\"thin 1.6.1 codename Death Proof\"]}" 

Заголовки HTTP довольно полезны. Вы можете получать всевозможную информацию об источнике данных и использовать эту информацию в своих интересах.

Дополнительно: базовая аутентификация

Вот менее распространенная ситуация, но такая же полезная, как и предыдущая. Иногда API предлагают базовую http-аутентификацию. Давайте смоделируем это на нашем сервере:

 require 'sinatra' # source: http://recipes.sinatrarb.com/p/middleware/rack_auth_basic_and_digest#label-HTTP+Basic+Authentication use Rack::Auth::Basic, "Protected Area" do |username, password| username == 'foo' && password == 'bar' end get '/' do 'Classified data.' end 

Теперь нам нужно найти способ доступа к этим секретным данным. Давай сделаем это.

 require 'net/http' def http_request(uri) Net::HTTP.start(uri.host, uri.port) do |http| yield(http, uri) end end uri = URI 'http://localhost:4567/' req = Net::HTTP::Get.new(uri) req.basic_auth 'foo', 'bar' http_request uri do |http, uri| response = http.request(req) response.body end # => "Classified data." 

Конечно, этот тип аутентификации не очень распространен, когда OAuth существует, но это всегда полезно знать.

Дополнительно: загрузка файлов

Иногда, как разработчикам, нам нужен программный способ загрузки файлов. Это так же просто, как сделать запрос GET и передать каждую строку в новый файл. Для этого примера мы собираемся использовать файл из интернета. Нет смысла переписывать наш сервер для этой ситуации.

Для нашего первого примера мы изменим простой GET который мы сделали ранее.

 require 'net/http' content = Net::HTTP.get(URI 'http://www.google.com/humans.txt') # get the contents of the file File.open('humans.txt', 'w+') do |file| file.write(content) # write the contents end 

Просто верно? Но мы опытные программисты. Как насчет прямой трансляции? Давайте повеселимся.

 require 'net/http' uri = URI 'http://www.google.com/humans.txt' def http_request(uri) Net::HTTP.start(uri.host, uri.port) do |http| yield(http, uri) end end http_request uri do |http, uri| request = Net::HTTP::Get.new(uri) http.request(request) do |response| open('humans.txt', 'w+') do |file| response.read_body do |chunk| file.write(chunk) end end end end 

Для небольших файлов этот второй метод занимает всего несколько строк кода, но для больших файлов это может значительно ускорить загрузку.

Дополнительно: SSL

SSL очень распространен в API и других веб-сервисах. Не волнуйтесь, Net::HTTP покрывает нас.

В следующем примере мы будем использовать Google, потому что он использует SSL для подключения. Мы не будем использовать Sinatra, потому что это выходит за рамки этого руководства для его настройки.

 require 'net/https' # notice the change to https uri = URI 'https://www.google.com/' http = Net::HTTP.new(uri.host, 443) # hardcoded for forced SSL http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_PEER req = Net::HTTP::Get.new(uri.path) # or any other request you might like resp = http.request(req) resp.body # => ... very long response body (Google's homepage) ... 

Вывод

Вы можете сделать так много всего с библиотекой Net::HTTP . Это расширяемый, но и простой в использовании. Я предлагаю вам также проверить гемы HTTParty , Rest-Client и Curb . Они предоставляют очень простой способ получить доступ к этим же методам лучше. Что бы вы ни выбрали, помните, что все, что может сделать ваш браузер, может сделать и Ruby.

Дальнейшее чтение