Handle the PUT method in WEBrick - ruby

How do I handle PUT requests in WEBrick?
I have tried defining a do_PUT() method in an AbstractServlet class but the method is never invoked.

I had the same problem and got it working by creating my own custom WEBrick::HTTPProxyServer and adding the put method in that.
require "webrick"
require "webrick/httpproxy"
require 'cgi'
class CustomWEBrickProxyServer < WEBrick::HTTPProxyServer
def do_PUT(req, res)
perform_proxy_request(req, res) do |http, path, header|
http.put(path, req.body || "", header)
end
end
# This method is not needed for PUT but I added for completeness
def do_OPTIONS(req, res)
res['allow'] = "GET,HEAD,POST,OPTIONS,CONNECT,PUT"
end
end
Then you need to start your proxy server using your own Custom class.
my_proxy_server = CustomWEBrickProxyServer.new :Port=> proxy_port,
:ProxyVia => forward_proxy,
:ProxyURI => forward_proxy,
:RequestCallback => method(:request_callback),
:ProxyContentHandler => method(:response_callback),
:AccessLog => method(:access_log)

Related

ruby rackup: is it possible to programatically add a mapping from within another mapping?

I have a .ru file and can set up mappings without issue ( the 'register' mapping below ).
However I want services to be able to register themselves by hitting a url so I want to be able to add new mappings on the fly from within others mappings.
The below code does not work though. What am I doing wrong and is this possible?
Thanks!
map '/register' do
run Proc.new { |env|
# inside of register i want to add another mapping.
# obviously 'bar' would be a value read out of env
map '/bar' do
run Proc.new{ |env| ['200', { 'Content-Type' => 'text/html' },'bar' }
end
[ '200', {'Content-Type' => 'text/html'}, "registered"]
}
end
I don't think there's a way to add routes after-the-fact using map. One alternative is to use Rack::URLMap to define your app. You'll need to maintain your own list of registered routes (as a hash) and call Rack::URLMap#remap every time you add a new route to the hash:
url_map = Rack::URLMap.new
routes = {
"/register" => lambda do |env|
routes["/bar"] = lambda do |env|
[ "200", {"Content-Type" => "text/plain"}, ["bar"] ]
end
url_map.remap(routes)
[ "200", {"Content-Type" => "text/plain"}, ["registered"] ]
end
}
url_map.remap(routes)
run url_map
Note that you could do with with just the hash, but URLMap provides some nice conveniences, including 404 handling. It's actually a really nice little class and worth reading if you have five minutes to spare.
If you were so inclined, you could turn this into a tidy little class:
class Application
def initialize
#routes = {}
#url_map = Rack::URLMap.new
register_route "/register" do |env|
# When "/register" is requested, register the new route "/bar"
register_route "/bar" do |env|
[ 200, {"Content-Type" => "text/plain"}, ["bar"] ]
end
[ 200, {"Content-Type" => "text/plain"}, ["registered"] ]
end
end
def call(env)
#url_map.call(env)
end
private
def register_route(path, &block)
#routes[path] = block
#url_map.remap(#routes)
end
end
run Application.new
According to https://rack.github.io/, "To use Rack, provide an "app": an object that responds to the call method, taking the environment hash as a parameter, and returning an Array with three elements:
The HTTP response code
A Hash of headers
The response body, which must respond to each"
Your third element will not respond to each. Maybe wrap it in an array?

Rack Middleware in Rack Middleware?

I am trying to build a rack middleware GEM that uses rack middleware itself (RackWired).
I have an existing application, whose config.ru uses Rack::Builder. In that block (Rack::Builder), I would like to specify my middleware, and when it is called to use a third party middleware (rack-cors) inside my own to do some stuff. Confusing I know.
The problem is that Rack::Builder's context is in config.ru and my middleware (RackWired) is thus unable to access it to "use" the third party middleware (rack-cors).
The intent of my effort is here
Is there a way to use middleware within middleware?
Thank you
Right, I'm not entirely sure what you're trying to do. But you can do this
class CorsWired
def initialize(app)
#app = app
end
def call(env)
cors = Rack::Cors.new(#app, {}) do
allow do
origins '*'
resource '*', :headers => :any, :methods => [:get, :post, :put, :options, :delete], :credentials => false
end
end
cors.call(env)
end
end
Your config.ru should have use CorsWired though, not use CorsWired.new
This is I think what you were asking but I think you're missing the point of middleware. You should just change your config.ru to use rack-cors before/after your middleware depending on what you want to do.
require 'rack'
require 'rack/cors'
require './cors_wired'
app = Rack::Builder.new do
use Rack::Cors do
allow do
origins '*'
resource '*', :headers => :any, :methods => [:get, :post, :put, :options, :delete], :credentials => false
end
end
use CorsWired
run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] }
end
run app

modular Sinatra App, setting error handling & configuration globally

I am using Sinatra to build a small Ruby API, and I would like to get some of the errors and configurations set to work at a global level so that i don't need to set them at the start of each of the classes.
My structure is this:
content_api.rb
require 'sinatra/base'
require 'sinatra/namespace'
require 'sinatra/json'
require 'service_dependencies'
require 'api_helpers'
require 'json'
module ApiApp
class ContentApi < Sinatra::Base
helpers Sinatra::JSON
helpers ApiApp::ApiHelpers
include ApiApp::ServiceDependencies
before do
content_type :json
end
get '/' do
content = content_service.get_all_content
content.to_json
end
get '/audio' do
package =content_service.get_type 'Audio'
package.to_json
end
get '/video' do
package =content_service.get_type 'Video'
package.to_json
end
get '/document' do
package =content_service.get_type 'Document'
package.to_json
end
end
end
config.ru:
$LOAD_PATH.unshift *Dir[File.join(File.dirname(__FILE__), '/src/**')]
$LOAD_PATH.unshift *Dir[File.join(File.dirname(__FILE__), '/src/api/**')]
require 'content_api'
require 'package_api'
require 'utility_api'
require 'sinatra/base'
configure do
set :show_exceptions => false
end
error { |err| Rack::Response.new([{'error' => err.message}.to_json], 500, {'Content-type' => 'application/json'}).finish }
Rack::Mount::RouteSet.new do |set|
set.add_route ApiApp::ContentApi, {:path_info => %r{^/catalogue*}}, {}, :catalogue
set.add_route ApiApp::PackageApi, {:path_info => %r{^/package*}}, {}, :package
set.add_route ApiApp::UtilityApi, {:path_info => %r{^/health_check*}}, {}, :health_check
end
When I run this, it will run okay, but when I force a 500 error (shut down MongoDb) I get a standard html type error that states:
<p id="explanation">You're seeing this error because you have
enabled the <code>show_exceptions</code> setting.</p>
If I add the
configure do
set :show_exceptions => false
end
error { |err| Rack::Response.new([{'error' => err.message}.to_json], 500, {'Content-type' => 'application/json'}).finish }
inside the content_api.rb file, then I get a JSON error returned as I would like to receive.
however, as I am building this modular, I don't want to be repeating myself at the top of each class.
Is there a simple way to make this work?
The simplest way to do this would be to reopen Sinatra::Base and add the code there:
class Sinatra::Base
set :show_exceptions => false
error { |err|
Rack::Response.new(
[{'error' => err.message}.to_json],
500,
{'Content-type' => 'application/json'}
).finish
}
end
This is probably not advisable though, and will cause problems if your app includes other non-json modules.
A better solution might be to create a Sinatra extension that you could use in your modules.
module JsonExceptions
def self.registered(app)
app.set :show_exceptions => false
app.error { |err|
Rack::Response.new(
[{'error' => err.message}.to_json],
500,
{'Content-type' => 'application/json'}
).finish
}
end
end
You would then use it by registering it in your modules:
# require the file where it is defined first
class ContentApi < Sinatra::Base
register JsonExceptions
# ... as before
end
As an alternative, inheritance:
class JsonErrorController < Sinatra::Base
configure do
set :show_exceptions => false
end
error { |err| Rack::Response.new([{'error' => err.message}.to_json], 500, {'Content-type' => 'application/json'}).finish }
end
class ContentApi < JsonErrorController
# rest of code follows…
end
From Sinatra Up and Running p73:
Not only settings, but every aspect of a Sinatra class will be
inherited by its subclasses. This includes defined routes, all the
error handlers, extensions, middleware, and so on.

Mocking methods in Puppet rspec tests

I've implemented a custom Puppet function that queries a Keystone server for information. The module that defines this function includes some helper methods that perform the actual work of querying keystone. Broadly, the structure looks like this:
def authenticate(auth_url, username, password)
...
end
def list_tenants(auth_url, token)
...
end
module Puppet::Parser::Functions
newfunction(:lookup_tenant, :type => :rvalue) do |args|
...
end
end
I would like to mock out the authenticate and list_tenants methods
during testing so that I can test the rest of the Puppet module in the
absence of an actual Keystone server.
I haven't previously worked with either Ruby or Rpsec before, and I'm
having a hard time finding examples of how to provide stubs for these
internal methods.
So far I have a stub rspec file that simply verified the existence of
the function:
require 'spec_helper'
describe 'lookup_tenant' do
it "should exist" do
Puppet::Parser::Functions.function("lookup_tenant").should == "function_lookup_tenant"
end
# This will fail because there is no keystone server.
it "should fail" do
should run.with_params(
'http://127.0.0.1:35357/v2.0',
'admin_user',
'admin_password',
'admin_tenant_name',
'target_tenant_name'
).and_raise_error(KeystoneError)
end
end
I would like to be able to provide custom returns from the
authenticate and list_tenants methods (or even raise exceptions
from inside these methods) so that I can test the behavior of the
lookup_tenant function in different failure scenarios.
WebMock could be used for simulating the http requests as stubs. Here is the link to the github repo: https://github.com/bblimke/webmock
For folks who haven't seen webmock before, I wanted to leave some information here about why it's particularly awesome.
So, I have in my module some code that makes an http request:
url = URI.parse("#{auth_url}/tokens")
req = Net::HTTP::Post.new url.path
req['content-type'] = 'application/json'
req.body = JSON.generate(post_args)
begin
res = Net::HTTP.start(url.host, url.port) {|http|
http.request(req)
}
if res.code != '200'
raise KeystoneError, "Failed to authenticate to Keystone server at #{auth_url} as user #{username}."
end
rescue Errno::ECONNREFUSED
raise KeystoneError, "Failed to connect to Keystone server at #{auth_url}."
end
By simply adding a require to the start of the spec file:
require `webmock`
Attempts to open a connection will result in:
WebMock::NetConnectNotAllowedError:
Real HTTP connections are disabled. Unregistered request: POST http://127.0.0.1:35357/v2.0/tokens with body '{"auth":{"passwordCredentials":{"username":"admin_user","password":"admin_password"},"tenantName":"admin_tenant"}}' with headers {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'}
You can stub this request with the following snippet:
stub_request(:post, "http://127.0.0.1:35357/v2.0/tokens").
with(:body => "{\"auth\":{\"passwordCredentials\":{\"username\":\"admin_user\",\"password\":\"admin_password\"},\"tenantName\":\"admin_tenant\"}}",
:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'}).
to_return(:status => 200, :body => "", :headers => {})
And that's just about all the information you need to stub out the
call. You can make the stubs as granular as necessary; I ended up
using something like:
good_auth_request = {
'auth' => {
'passwordCredentials' => {
'username' => 'admin_user',
'password' => 'admin_password',
},
'tenantName' => 'admin_tenant',
}
}
auth_response = {
'access' => {
'token' => {
'id' => 'TOKEN',
}
}
}
stub_request(:post, "http://127.0.0.1:35357/v2.0/tokens").
with(:body => good_auth_request.to_json).
to_return(:status => 200, :body => auth_response.to_json, :headers => {})
And now I can test my module when there is no Keystone server
available.

Standardizing api responses in a modular Sinatra application

I'm developing an api as a modular Sinatra web application and would like to standardize the responses that are returned without having to do so explicitly. I thought this could be achieved by using middleware but it fails in most scenarios. The below sample application is what I have so far.
config.ru
require 'sinatra/base'
require 'active_support'
require 'rack'
class Person
attr_reader :name, :surname
def initialize(name, surname)
#name, #surname = name, surname
end
end
class MyApp < Sinatra::Base
enable :dump_errors, :raise_errors
disable :show_exceptions
get('/string') do
"Hello World"
end
get('/hash') do
{"person" => { "name" => "john", "surname" => "smith" }}
end
get('/array') do
[1,2,3,4,5,6,7, "232323", '3245235']
end
get('/object') do
Person.new('simon', 'hernandez')
end
get('/error') do
raise 'Failure of some sort'
end
end
class ResponseMiddleware
def initialize(app)
#app = app
end
def call(env)
begin
status, headers, body = #app.call(env)
response = {'status' => 'success', 'data' => body}
format(status, headers, response)
rescue ::Exception => e
response = {'status' => 'error', 'message' => e.message}
format(500, {'Content-Type' => 'application/json'}, response)
end
end
def format(status, headers, response)
result = ActiveSupport::JSON.encode(response)
headers["Content-Length"] = result.length.to_s
[status, headers, result]
end
end
use ResponseMiddleware
run MyApp
Examples (in JSON):
/string
Expected: {"status":"success","data":"Hello World"}
Actual: {"status":"success","data":["Hello World"]}
/hash (works)
Expected: {"status":"success","data":{"person":{"name":"john","surname":"smith"}}}
Actual: {"status":"success","data":{"person":{"name":"john","surname":"smith"}}}
/array
Expected: {"status":"success","data": [1,2,3,4,5,6,7,"232323","3245235"]}
Actual: {"status":"error","message":"wrong number of arguments (7 for 1)"}
/object
Expected: {"status":"success","data":{"name":"simon","surname":"hernandez"}}
Actual: {"status":"success","data":[]}
/error (works)
Expected: {"status":"error","message":"Failure of some sort"}
Actual: {"status":"error","message":"Failure of some sort"}
If you execute the code, you will see that /hash and /error give back the required responses, but the rest do not. Ideally, I would not like to change anything in the MyApp class. It's currently being built on top of Sinatra 1.3.3, ActiveSupport 3.2.9 and Rack 1.4.1.
With some help from #sinatra on irc.freenode.org, I managed to get it down to what I want. I added the following to MyApp:
def route_eval
result = catch(:halt) { super }
throw :halt, {"result" => result}
end
I then changed the following line in ResponseMiddleware:
response = {'status' => 'success', 'data' => body}
to
response = {'status' => 'success', 'data' => body["result"]}
and all my test cases passed.

Resources