Home > Enterprise >  Ruby base_auth with net/http - undefined method `user' for String
Ruby base_auth with net/http - undefined method `user' for String

Time:12-04

I've got pure Ruby app where I want to create request to external API. To do so I'm using standard Ruby Net::HTTP like below:

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

But instead of response I'm getting an error:

NoMethodError: undefined method `user' for #String:0x00007f80eef9e6f8 Did you mean? super

/Users/usr/.rvm/rubies/ruby-2.7.0/lib/ruby/2.7.0/net/http.rb:527:in `post_form'

I don't have any user there, what is it?

CodePudding user response:

Net::HTTP.post_form is used to send FormData pairs - its not what you want to send JSON and it doesn't even allow you to send headers (You're actually putting them in the request body!).

If you want to send a POST request with HTTP Basic auth and custom headers and JSON body you need to create the request object manually:

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
    request = Net::HTTP::Post.new(URI.join(BASE_URI, QUIZ_PATH), 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

CodePudding user response:

Any chance that you can share the code for Settings functions being used here?

CodePudding user response:

Here is the source code that raises the error:

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

From the docs:

post_form(url, params)

Posts HTML form data to the specified URI object. The form data must be provided as a Hash mapping from String to String.

That means Net::HTTP.post_form(URI("#{BASE_URI}#{QUIZ_PATH}"), options) fixes it. You are currently sending a string as url instead of a URI.

  •  Tags:  
  • ruby
  • Related