I am looking for an example of Faraday Middleware that handles http (status code) errors on requests and additionally network timeouts.
After reading the docs for Faraday and it's middleware it's my understanding that this is one of middleware's use cases… I just have no idea what an implementation is supposed to look like.
Thanks
Faraday has an error handling middleware in by default:
faraday.use Faraday::Response::RaiseError
For example:
require 'faraday'
conn = Faraday.new('https://github.com/') do |c|
c.use Faraday::Response::RaiseError
c.use Faraday::Adapter::NetHttp
end
response = conn.get '/cant-find-me'
#=> gems/faraday-0.8.8/lib/faraday/response/raise_error.rb:6:in `on_complete': the server responded with status 404 (Faraday::Error::ResourceNotFound)
If you want to write your own middleware to handle HTTP status code responses, heres a basic example:
require 'faraday'
class CustomErrors < Faraday::Response::Middleware
def on_complete(env)
case env[:status]
when 404
raise RuntimeError, 'Custom 404 response'
end
end
end
conn = Faraday.new('https://github.com/') do |c|
c.use CustomErrors
c.use Faraday::Adapter::NetHttp
end
response = conn.get '/cant-find-me' #=> `on_complete': Custom 404 response (RuntimeError)
For your code, you'll probably want to put it in a separate file, require it, modularise it etc.
If you want to see a good live example, the new Instagram gem has a pretty good setup to raise custom errors: GitHub link
Related
Hi I am building a Rails/Rack middleware for the Mailgun api, and doing a simple test for connection, which is fine in Postman with exact same params.
It is showing an error in test, Failure/Error: #app.call(env)
NoMethodError:
undefined method `merge!' for #<Mailgun::Tracking::MailLogger:0x0000557e6bc8bb50>
on running RSpec, and no merge method is attempted at all, so dont know where its come from. The api_key and endpoint are initialized in config/initialisers folder as per the Mailgun Tracking gem (which is being used) guidelines.
Only two other files used and code below.
MailLogger class under Mailgun::Tracking module, registered as middleware in app.
require 'logger'
require 'mailgun/tracking'
require 'rack/contrib'
require 'rack'
require 'byebug'
module Mailgun::Tracking
class MailLogger
def initialize(app, options = {
"HTTP_X_MAILGUN_API_KEY" => ENV['MAILGUN_API_KEY'],
"HTTP_X_MAILGUN_ENDPOINT" => ENV['MAILGUN_ENDPOINT']
})
#app = app
#options = options
end
def call(env)
#app.call(env)
end
end
end
The RSpec test
require 'logger'
require "json"
require "rails_helper"
Bundler.require(*Rails.groups)
require_relative "../../lib/middleware/mailgun/mailgun_tracking.rb"
RSpec.describe Mailgun::Tracking::MailLogger, type: 'Mailgun Webhook' do
subject(:response) { app.call(env) }
# env to pass in middleware, url endpoint & key
let(:app) { described_class.new(Rails.application) }
let(:env) { described_class.new(app, options = {
"HTTP_X_MAILGUN_API_KEY" => ENV['MAILGUN_API_KEY'],
"HTTP_X_MAILGUN_ENDPOINT" => ENV['MAILGUN_ENDPOINT']
}) }
it "returns a 200 status code" do
expect(response).to eq([200, {}, ["OK"]])
end
end
Am just looking for a 200, OK on connection, as I already get in Postman with the same header (api_key) and endpoint (events)
but throws this mysterious missing "merge" method error
Not encountered before.
Anyone know why?
Thanks
The actual code calling merge! is hidden in rack implementation and as such is filtered out from the error message. Your problem is that env you define in your test is not an instance of Rack::Environment but rather instance of your middleware. You can generate mocked environment instance using, for example, Rack::MockRequest.env_for("/", method: :get)
That being said, your unit test is currently testing the whole of the rack stack, including the routes and other middlewares. You can isolate your test better by using a mock application:
let(:app) { described_class.new(->(env) [200, {}, 'success']) }
I'm attempting to create a rake tasks that visits a series of URLs, logs the status code, and then prints them to console. My code is as follows:
require 'csv'
require 'net/http'
require "open-uri"
namespace :routes do
desc "check status codes"
task check_301s: :environment do
# open_csv
# edit_urls
generate_routes
# visit_urls
# log_status_codes
visit_and_log_routes
# give_results
end
def generate_routes
csv = CSV.open('lib/better.csv', headers: true)
#urls = []
csv.each do |row|
#urls << row['url'].gsub('foo', 'localhost:3000')
end
end
def visit_and_log_routes
responses = []
#urls.each do |url|
http = Net::HTTP.new(url, 3000)
response = http.request_get('/')
responses << response
end
puts responses
end
end
I'm receiving the following error code when running this from terminal:
SocketError: Failed to open TCP connection to localhost:3000/children-centre/london/greenwich/eltham-childrens-centre/join:3000 (getaddrinfo: nodename nor servname provided, or not known)
I'm not sure if there is an easier way to visit URLs and log their status code, if so please let me know how to do it, and if not then how I can correct this error message. I'm assuming I'm simply leaving out the relevant options but am not sure how / where to add them.
For anyone making the same impressive error I was... the route needs http:// at the start instead of just localhost:3000
I am using the rack-proxy gem in Rails to proxy requests to an external server. Thing is, the external endpoint requires authentication. How do I provide that information from the middleware?
Here's what I have so far:
require 'rack/proxy'
class MyProxy < Rack::Proxy
MY_REQUEST = %r{^/path/(.*)}
def initialize(app)
#app = app
end
def call(env)
if m = MY_REQUEST.match(env['PATH_INFO'])
env['PATH_INFO'] = "https://otherserver.org/#{m[1]}"
env['HTTP_HOST'] = "otherserver.org"
#the otherserver.org endpoint requires authentication
super env
else
#app.call(env)
end
end
end
Depends on what kind of authentication the other server is using. If its just plain HTTP Authentication you can do something like:
env['Authentication'] = 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='
Where the value part follows the spec at: http://en.wikipedia.org/wiki/Basic_access_authentication#cite_ref-8
Following the Rack spec here gave me good pointers - http://rubydoc.info/github/rack/rack/master/file/SPEC
Had to do:
env['HTTP_AUTHORIZATION'] = 'Basic <base64 username:password>'
I have part of my rails 2 webservice application which is used as SOAP service (historical reasons, rest of app is REST).
Just two operations AddLead and ShowLead, with wsdl on /soap/wsdl.
I want to test this operations by Rspec integrations tests.
Trying to use Savon gem (/spec/integration/soap_spec.rb):
require "spec_helper"
require 'rubygems'
require 'savon'
describe "Leads" do
before(:all) do
wsdl= "http://localhost:3000/soap/wsdl"
wsdl = "http://www.example.com/soap/wsdl"
#client = Savon.client(:wsdl => wsdl )
puts("WSDL actions: #{#client.operations}")
end
end
But I can not find which URL I should use to point to WSDL.
URL localhost:3000 does not work, ending with error:
Errno::ECONNREFUSED in 'Leads before(:all)'
Connection could not be made, because target server it actively denied. - connect(2)
URL www.example.com (which is output from test url helpers) does not work either, ending with error:
Wasabi::Resolver::HTTPError in 'Leads before(:all)'
Error: 302
Any ideas?
Foton
Try the following link: http://blog.johnsonch.com/2013/04/18/rails-3-soap-and-testing-oh-my/
Inside of your describe block use the HTTPI rack adapter, and then configure that adapter to mount your application. This will give you the ability to use a specific url.
require 'spec_helper'
require 'savon'
describe API::MyService do
HTTPI.adapter = :rack
HTTPI::Adapter::Rack.mount 'application', MyApp::Application
it 'can get a response' do
application_base = "http://application"
client = Savon::Client.new({:wsdl => application_base + '/soap/wsdl' })
...
...
end
...
I am using Rails 2.3 and I decided to provide support for JSONP. Created a brand new application. Then ran script/generate scaffold User name:string
This is my entire environment.rb
RAILS_GEM_VERSION = '2.3.2' unless defined? RAILS_GEM_VERSION
require File.join(File.dirname(__FILE__), 'boot')
require 'rack/contrib'
Rails::Initializer.run do |config|
config.middleware.use 'Rack::JSONP'
end
When I visit localhost:3000/users all I get is a hash. When I visit localhost:3000/users.js?callback=show then I get good result.
Let's look at the jsonp code . I do not understand why response is being wrapped in an array.
I created another Rack middleware where I replaced this statement
[status, headers, [response]]
with this statement
[status, headers, response]
And now everything is working fine.
I refuse to believe that this is a bug in rack-contrib.
Can someone enlighten me why response is being wrapped in an array and how could I use rack-contrib in my application.
The full source code of my application is here. Just clone it and run on localhost:3000 .
That code is wrong. Here's what it should be:
def call(env)
status, headers, response = #app.call(env)
request = Rack::Request.new(env)
if request.params.include?('callback')
response = [pad(request.params.delete('callback'), response)]
headers['Content-Length'] = response.length.to_s
end
[status, headers, response]
end
It was incorrectly wrapping the response in an array in the case where the params didn't include a callback. The reason it needs to wrap the response in an array in the case where params does include a callback is because Rack responses must respond to .each().