#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 всегда пишет подробные ответы, и именно так должен выглядеть ваш код.