Ruby base_auth с net / http — неопределенным методом `user’ для строки

#ruby

#ruby

Вопрос:

У меня есть чисто Ruby-приложение, в котором я хочу создать запрос к внешнему API. Для этого я использую стандартный Ruby Net::HTTP, как показано ниже:

 require 'net/http'
require 'uri'

  class Api
    BASE_URI = 'https://staging.test.com'
    WORKFLOW = 'tests'
    QUIZ_PATH = "/v3/accounts/workflows/#{WORKFLOW}/conversations"

    def initialize(payload:)
      @payload = payload
    end

    def post_quiz
      handle_response(Net::HTTP.post_form("#{BASE_URI}#{QUIZ_PATH}", options))
    end

    attr_reader :payload

    private

    def options
      {
        basic_auth: basic_auth,
        body: payload.to_json,
        headers: headers
      }
    end

    def basic_auth
      {
        username: Settings.ln_username,
        password: Settings.ln_password
      }
    end

    def headers
      {
        'User-Agent' => 'Mozilla/5.0',
        'Accept-Language' => 'en-US,en;q=0.5',
        'Content-Type' => 'application/json'
      }
    end

    def handle_response(response)
      return response.body if response.success?
    end
  end
 

Но вместо ответа я получаю сообщение об ошибке:

Ошибка NoMethodError: неопределенный метод `user’ для #String:0x00007f80eef9e6f8 Вы имели в виду? супер

/Users/usr/.rvm/rubies/ruby-2.7.0/lib/ruby/2.7.0/net/http.rb:527: в ‘post_form’

У меня там нет пользователя, что это?

Комментарии:

1. Пожалуйста, добавьте больше из этой трассировки ошибок.

2. @razvans Я добавил /Users/usr/.rvm/rubies/ruby-2.7.0/lib/ruby/2.7.0/net/http.rb:527:in post_form' , кроме того, есть пути к файлу, в котором он вызывается

Ответ №1:

Net::HTTP.post_form используется для отправки пар FormData — это не то, что вы хотите отправить в формате JSON, и это даже не позволяет отправлять заголовки (вы фактически помещаете их в тело запроса!).

Если вы хотите отправить POST-запрос с HTTP Basic auth и пользовательскими заголовками и телом JSON, вам необходимо создать объект запроса вручную:

 require 'net/http'
require 'uri'

class Api
  BASE_URI = 'https://staging.test.com'
  WORKFLOW = 'tests'
  QUIZ_PATH = "/v3/accounts/workflows/#{WORKFLOW}/conversations"
  attr_reader :payload

  def initialize(payload:)
    @payload = payload
  end

  def post_quiz
    url = URI.join(BASE_URI, QUIZ_PATH)
    request = Net::HTTP::Post.new(url, headers)
    request.basic_auth = Settings.ln_username, Settings.ln_password
    request.body = @payload.to_json
    # open a connection to the server
    response = Net::HTTP.start(url.hostname, url.port, use_ssl: true) do |http|
      http.request(request)
    end
    handle_response(response)
  end

  private


  def headers
    {
      'User-Agent' => 'Mozilla/5.0',
      'Accept-Language' => 'en-US,en;q=0.5',
      'Content-Type' => 'application/json'
    }
  end
 
  # How to respond from an API client is a whole topic in itself but a tuple or hash might 
  # be a better choice as it lets consumers decide what to do with the response and handle stuff like logging
  # errors
  def handle_response(response)
     # Net::HTTP doesn't have a success? method - you're confusing it with HTTParty
    case response
    when Net::HTTPSuccess, Net::HTTPCreated 
      response.body
    else  
      false
    end
  end
end
 

Комментарии:

1. Как вы, возможно, уже поняли, Net::HTTP смехотворно неуклюж и является худшей частью стандартной библиотеки Ruby, поскольку она так плохо спроектирована и фактически не претерпела существенных изменений с самых ранних дней языка. Если вы не хотите мучить себя, используйте HTTP-библиотеку, такую как HTTParty или Faraday.

2. Голосование завершено. В конце концов, я хотел написать что-то подобное, после перехода туда и обратно с помощью OP. Спасибо.

3. Согласен, это очень раздражает, но, к сожалению, HTTParty не работает внутри AWS Lambda (не проверял с помощью Faraday, но это очень сложный камень, поэтому я предполагаю, что это тот же случай)

4. Хорошо, в этом случае, если вы застряли с ним, основная суть заключается в том, что вы создаете объекты запроса, которые являются подклассами HTTP::GenericRequest , а затем отправляете их, и это возвращает вам подкласс HTTP::Response в зависимости от кода ответа. Остальное — это всего лишь множество удобных методов различной полезности — это было написано задолго до аргументов ключевого слова, где вещь, поэтому интерфейсы ужасны.

5. Я думаю, что это должно быть request.basic_auth(Settings.ln_username, Settings.ln_password) request.basic_auth = Settings.ln_username, Settings.ln_password вместо из-за ошибки: NoMethodError: undefined method basic_auth=' for #<Net::HTTP::Post POST> но даже после этого изменения он показывает мне NameError: undefined local variable or method uri' for #<LexisNexis::Api:0x00007fa3a8a18788> Did you mean? URI

Ответ №2:

Вот исходный код, который вызывает ошибку:

 def HTTP.post_form(url, params)
  req = Post.new(url)
  req.form_data = params
  >> req.basic_auth url.user, url.password if url.user
  start(url.hostname, url.port,
        :use_ssl => url.scheme == 'https' ) {|http|
    http.request(req)
  }
end
 

Из документов:

post_form(url, параметры)

Отправляет данные HTML-формы в указанный объект URI. Данные формы должны быть предоставлены в виде хэш-сопоставления из строки в строку.

Это означает Net::HTTP.post_form(URI("#{BASE_URI}#{QUIZ_PATH}"), options) , что это исправляет. В настоящее время вы отправляете строку в качестве URL-адреса вместо URI.

Комментарии:

1. Net::HTTP.post_form(URI("#{BASE_URI}#{QUIZ_PATH}"), options) выдает мне сообщение об ошибке: TypeError: no implicit conversion of Net::HTTPUnauthorized into String .

2. Эта ошибка — нечто совершенно иное. Вероятно, у вас есть логин на вашей конечной точке. Ответ исправляет код для успешной отправки запроса в конечную точку. Обновите с помощью трассировки для этой новой ошибки.

3. Нет, это не так. Ваш ответ не устранил мою проблему — у меня есть basic_auth, как вы видите, и ваш код неверен. Я проверил все с помощью HTTParty, и он работает хорошо, но я не могу использовать этот драгоценный камень внутри AWS lambda, поэтому мне нужна версия net // http.

4. Я повторяю это еще раз. Мой ответ исправляет именно то, что вы упомянули в вопросе. Это поможет вам перейти от ошибки к … еще один. Еще одна ошибка, которая нигде не записана, и мы, ребята, которые отвечают, не можем знать все время. Нечестно голосовать против ответа, который помогает. @max всегда пишет подробные ответы, и именно так должен выглядеть ваш код.