How to do a Post/Redirect/Get using Sinatra? - ruby

What's Sinatra's equivalent of Rails' redirect_to method? I need to follow a Post/Redirect/Get flow for a form submission whilst preserving the instance variables that are passed to my view. The instance variables are lost when using the redirect method.

Redirect in Sinatra is the most simple to use.
So the code below can explain:
require 'rubygems'
require 'sinatra'
get '/' do
redirect "http://example.com"
end
You can also redirect to another path in your current application like this, though this sample will delete a method.
delete '/delete_post' do
redirect '/list_posts'
end
A very common place where this redirect instruction is used is under Authentication
def authorize!
redirect '/login' unless authorized?
end
You can see more samples under:
Sinatra Manual
FAQ
Extensions
As for your second question, passing variables into views, it's possible like this:
get '/pizza/:id' do
# makeing lots of pizza
#foo = Foo.find(params[:id])
erb '%h1= #foo.name'
end

The Sinatra Book should clear your question. Especially the "Redirect" part.
Quoted from the book:
The redirect actually sends back a Location header to the browser, and the browser makes a followup request to the location indicated. Since the browser makes that followup request, you can redirect to any page, in your application, or another site entirely.
The flow of requests during a redirect is: Browser –> Server (redirect to ’/’) –> Browser (request ’/’) –> Server (result for ’/’)

Related

Simplest method of enforcing HTTPS for Heroku Ruby Sinatra app

I have an app I created on Heroku which is written in Ruby (not rails) and Sinatra.
It is hosted on the default herokuapp domain so I can address the app with both HTTP and HTTPS.
The app requests user credentials which I forward on to an HTTPS call so the forwarding part is secure.
I want to ensure my users always connect securely to my app so the credentials aren't passed in clear text.
Despite lots of research, I've not found a solution to this simple requirement.
Is there a simple solution without changing my app to Ruby rails or otherwise?
Thanks,
Alan
I use a helper that looks like this:
def https_required!
if settings.production? && request.scheme == 'http'
headers['Location'] = request.url.sub('http', 'https')
halt 301, "https required\n"
end
end
I can then add it to any single route I want to force to https, or use it in the before filter to force on a set of urls:
before "/admin/*" do
https_required!
end
Redirect in a Before Filter
This is untested, but it should work. If not, or if it needs additional refinement, it should at least give you a reasonable starting point.
before do
redirect request.url.sub('http', 'https') unless request.secure?
end
See Also
Filters
Request Object
RackSsl::Enforcer

Jruby sinatra app routing issue within the post route handler ( warbler generated war file sub-uri deployment )

I implemented a small jruby sinatra app and if i run it directly on
WEBrick locally all the routings work perfectly. However when I deploy
the war file (i use warbler) to a server instance (like
"example.com/myapp" or "localhost:8080/myapp") I have routing
issues within the post requests.
For example:
get '/login' do
slim :login
end
post '/login' do
session.clear
login_correct? = check_password (params[:user], params[:pass])
if(login_correct?)
session[:user] = params[:user]
redirect to('/')
else
redirect to('/login')
end
end
get '/redirect' do
redirect to('/login')
end
Here the 3rd route handler (get '/redirect' do ..) redirects to
localhost:8080/myapp/login properly with status code 303, however 2nd route handler redirects
to localhost:8080/login with status code 404.
What should i do so that redirections in post route handler works
properly when i deploy the app?
Thanks a lot!
UPDATE on Solution: After checking the code again and again I realized that the problem was me using form action = '/login' in slim:login instead of form action= "#{url('/login')}". So it wasn't even handled by the post route handler since the post request was sent to localhost:8080/login but I thought it was route handler who is redirecting it to there..
try setting set :prefixed_redirects, true with Sinatra
(it should than use rack.env['SCRIPT_NAME'] with redirects)
UPDATE: even without the set :prefixed_redirects, true (default is false) works perfectly fine under Trinidad (which should behave the same as Warbler since bot use JRuby-Rack) ... request.env['SCRIPT_NAME'] is set to the /context_path correctly.
I would try updating (if they're not the latest) JRuby-Rack/Warbler as well as Sinatra as a last resort, otherwise this probably needs a detailed look at what's going on (esp. if SCRIPT_NAME is set correctly).

Testing Sinatra's redirect back in rspec

I am running a sinatra app and have a testing suite setup using rspec 2.7.0 and webrat 0.7.3 (both the most recent versions). I have an extensive set of tests for all of my request actions and it seems to be working fine. Today I discovered Sinatra's redirect back request-level helper and implemented it in a couple of areas of my application that were rendering forms with get requests which were taking parameters.
The nice thing about the redirect back helper is that if I have an action say:
get '/login' do
#used_var = params[:var]
haml :login
end
Which renders a form, I can have validation on the post request receiving the form:
post '/login' do
# pretend User.authenticate pulls back a user entry from the database if there
# is a valid username/password combination
unless User.authenticate(params[:username], params[:password]).nil?
redirect '/content'
else
flash[:notice] = "Invalid username/password combo"
redirect back # will redirect back to the get '/login' request
end
end
And if the form doesn't validate properly, it will redirect back to the page with the from and retain any parameters that were passed in without me having to worry about storing it to a session variable. The only problem is that rspec doesn't seem to want to play nicely with the redirect back helper. i.e. if I have a spec action that does this:
it 'should redirect to login when invalid username/password combo is received.' do
get '/login', :var => 'value'
fill_in 'username', :with => 'invalid_username'
fill_in 'password', :with => 'invalid_password'
click_button 'Submit'
last_response.should be_redirect; follow_redirect!
last_request.url.should include("/login")
end
The spec fails to pass because for some reason it seems that rspec or webrat isn't picking up on the redirect back helper and is instead redirecting the request back to the root url for my application ('/').
What I want to know is whether there is a way to get rspec to redirect to the proper location in these instances? The actual application functions as expected when I test it with my browser (it redirects me to the first page with parameters), but the rspec tests don't pass properly.
try to pass :referer => '/login' to your requests, so redirect_back can know where the actually 'back' is
Apparently this was a bug in rack, and it appears to have been fixed with the release of rack 1.3.0. I tested this spec with rack 1.2.5 (most recent version prior to 1.3.0 release), and it failed, but upon upgrading to 1.3, it started passing.
After some digging around, pretty sure that this pull request (here is the commit) was the change that fixed it.
So it is no longer an issue in rack ~> 1.3.

ruby mechanize in Facebook

I'm trying to click the Settings button on the home page, but when I do I get this page back:
#<WWW::Mechanize::Page
{url
#<URI::HTTP:0x1023c5fc0 URL:http://www.facebook.com/editaccount.php?ref=mb&drop>}
{meta}
{title nil}
{iframes}
{frames}
{links}
{forms}>
which is.. kinda empty! Is there some problems with these iframes and frames stuff maybe?
As roja mentioned, following redirects might be what you need. Here's an example of how to do this:
#agent = Mechanize.new
#agent.redirect_ok = :all
#agent.follow_meta_refresh = :anywhere
Then you can pretty much ignore the fact that there's redirects involved - Mechanize will simply put you on the resulting page.
Facebook redirects me to: https://register.facebook.com/editaccount.php which I assume is the final destination. Assuming that WWW::Mechanize is set up to follow https redirects you should end up there too.
Much of facebook like most modern websites is generated by javascript which I think that WWW::Mechanize is unable to cope with, this could be the source of your problem. I recommend trying to scrape while appending "?_fb_noscript=1" to the url's you visit. This turns off much of facebooks javascript system and should enable a smoother ride for your little bot.
(Do remember this is only an idea and doubtless whatever you do is against facebooks usage policy and this makes you a "baddy." I don't condone such badness and beleve that baddies should be forced to go to bed early etc... ad nauseum)

POSTing an HTML form to remote.cgi - written in Ruby?

I am working on a website hosted on microsoft's office live service. It has a contact form enabling visitors to get in touch with the owner. I want to write a Ruby script that sits on a seperate sever and which the form will POST to. It will parse the form data and email the details to a preset address. The script should then redirect the browser to a confirmation page.
I have an ubuntu hardy machine running nginx and postfix. Ruby is installed and we shall see about using Thin and it's Rack functionality to handle the script. Now it's come to writing the script and i've drawn a blank.
It's been a long time and if i remember rightly the process is something like;
read HTTP header
parse parameters
send email
send redirect header
Broadly speaking, the question has been answered. Figuring out how to use the answer was more complicated than expected and I thought worth sharing.
First Steps:
I learnt rather abruptly that nginx doesn't directly support cgi scripts. You have to use some other process to run the script and get nginx to proxy requests over. If I was doing this in php (which in hind sight i think would have been a more natural choice) i could use something like php-fcgi and expect life would be pretty straight forward.
Ruby and fcgi felt pretty daunting. But if we are abandoning the ideal of loading these things at runtime then Rack is probably the most straight forward solution and Thin includes all we need. Learning how to make basic little apps with them has been profoundly beneficial to a relative Rails newcomer like me. The foundations of a Rails app can seem hidden for a long time and Rack has helped me lift the curtain that little bit further.
Nonetheless, following Yehuda's advice and looking up sinatra has been another surprise. I now have a basic sinatra app running in a Thin instance. It communicates with nginx over a unix socket in what i gather is the standard way. Sinatra enables a really elegant way to handle different requests and routes into the app. All you need is a get '/' {} to start handling requests to the virtual host. To add more (in a clean fashion) we just include a routes/script.rb into the main file.
# cgi-bin.rb
# main file loaded as a sinatra app
require 'sinatra'
# load cgi routes
require 'routes/default'
require 'routes/contact'
# 404 behaviour
not_found do
"Sorry, this CGI host does not recognize that request."
end
These route files will call on functionality stored in a separate library of classes:
# routes/contact.rb
# contact controller
require 'lib/contact/contactTarget'
require 'lib/contact/contactPost'
post '/contact/:target/?' do |target|
# the target for the message is taken from the URL
msg = ContactPost.new(request, target)
redirect msg.action, 302
end
The sheer horror of figuring out such a simple thing will stay with me for a while. I was expecting to calmly let nginx know that .rb files were to be executed and to just get on with it. Now that this little sinatra app is up and running, I'll be able to dive straight in if I want to add extra functionality in the future.
Implementation:
The ContactPost class handles the messaging aspect. All it needs to know are the parameters in the request and the target for the email. ContactPost::action kicks everything off and returns an address for the controller to redirect to.
There is a separate ContactTarget class that does some authentication to make sure the specified target accepts messages from the URL given in request.referrer. This is handled in ContactTarget::accept? as we can guess from the ContactPost::action method;
# lib/contact/contactPost.rb
class ContactPost
# ...
def action
return failed unless #target.accept? #request.referer
if send?
successful
else
failed
end
end
# ...
end
ContactPost::successful and ContactPost::failed each return a redirect address by combining paths supplied with the HTML form with the request.referer URI. All the behaviour is thus specified in the HTML form. Future websites that use this script just need to be listed in the user's own ~/cgi/contact.conf and they'll be away. This is because ContactTarget looks in /home/:target/cgi/contact.conf for the details. Maybe oneday this will be inappropriate, but for now it's just fine for my purposes.
The send method is simple enough, it creates an instance of a simple Email class and ships it out. The Email class is pretty much based on the standard usage example given in the Ruby net/smtp documentation;
# lib/email/email.rb
require 'net/smtp'
class Email
def initialize(from_alias, to, reply, subject, body)
#from_alias = from_alias
#from = "cgi_user#host.domain.com"
#to = to
#reply = reply
#subject = subject
#body = body
end
def send
Net::SMTP.start('localhost', 25) do |smtp|
smtp.send_message to_s, #from, #to
end
end
def to_s
<<END_OF_MESSAGE
From: #{#from_alias}
To: #{#to}
Reply-To: #{#from_alias}
Subject: #{#subject}
Date: #{DateTime::now().to_s}
#{#body}
END_OF_MESSAGE
end
end
All I need to do is rack up the application, let nginx know which socket to talk to and we're away.
Thank you everyone for your helpful pointers in the right direction! Long live sinatra!
It's all in the Net module, here's an example:
#net = Net::HTTP.new 'http://www.foo.com', 80
#params = {:name => 'doris', :email => 'doris#foo.com'}
# Create HTTP request
req = Net::HTTP::Post.new( 'script.cgi', {} )
req.set_form_data #params
# Send request
response = #net.start do |http|
http.read_timeout = 5600
http.request req
end
Probably the best way to do this would be to use an existing Ruby library like Sinatra:
require "rubygems"
require "sinatra"
get "/myurl" do
# params hash available here
# send email
end
You'll probably want to use MailFactory to send the actual email, but you definitely don't need to be mucking about with headers or parsing parameters.
CGI class of Ruby can be used for writing CGI scripts. Please check: http://www.ruby-doc.org/stdlib/libdoc/cgi/rdoc/index.html
By the way, there is no need to read the HTTP header. Parsing parametres will be easy using CGI class. Then, send the e-mail and redirect.

Resources