0

I need to have a button to save the current web site (just like clicking on "Save as"), I created a method in the controller which works great for any external site (like http://www.google.com) but doesn't work for the sites inside my application, I get a timeout error!. This has no explanation to me :(

Any clue what is the issue?

#CONTROLLER FILE
def save_current_page
  # =>  Using MECHANIZE
  agent = Mechanize.new
  page = agent.get request.referer
  send_data(page.content, :filename => "filename.txt")
end

I tried also Open URI, same problem!

#CONTROLLER FILE
def save_current_page
 # => USANDO OPEN URI
 send_data(open(request.referer).read, :filename => "filename.txt")
end

I'm using rails 3.2 and ruby 1.9, any help is appreciated, I already spent like 10 hours trying to make it work!!

Andres Calle
  • 257
  • 1
  • 3
  • 11

2 Answers2

0

Rails can only handle one request at a time. It's a never-ending standoff between the two requests - the first request is waiting for the second request, but the second request is waiting for the first request, and therefore you get a Timeout error. Even if you're running multiple instances of the app with Passenger or something, it's a bad idea.

The only way I can think to get around it would be to use conditional statements like so:

referer = URI.parse(request.referer)

if Rails.application.config.default_url_options[:host] == referer.host
  content = "via yoursite.com"
else
  agent = Mechanize.new
  page = agent.get request.referer
  content = page.content
end

send_data content, filename: "filename.txt"

A little dirty but it should get around the Timeout problem. As far a getting the actual content of a page from your own site - that's up to you. You could either render the template, grab something from cache, or just ignore it.

A much better solution would be to enqueue this code into something like Resque or Delayed Job. Then the queue could make the request and wait in line to request the page like normal. It would also mean that the user wouldn't have to wait while your application make a remote request, which is dangerous because who knows how long the page will take to respond.

bricker
  • 8,911
  • 2
  • 44
  • 54
  • Hi Bricker, I think it still doesn't solve it, this application should save the current page, which will be myapp.com/something, but the proposed solution looks like will only work when saving an external site like www.google.com and excludes the local site. However I tested it and the problem remains (can't explain why, the if should be working and avoiding the loop in the local site scenario). Hey dude thanks for taking the time for helping me out with this. – Andres Calle Oct 01 '12 at 23:36
  • Yeah, you'll need to decide how to handle getting the local site. You simply cannot have an application make a remote request to itself, and even if you are running multiple instances of the application, it could still get stuck. Make sure that your `Rails.application.config.default_url_options[:host]` matches `referer.host` when coming from a page within the application. – bricker Oct 02 '12 at 03:14
  • I see download current page button in several other aplications (in particular one made with ASP), I think it must be possible to achieve in rails, it even looks like it worked for other folks in rails, check here http://stackoverflow.com/questions/1080565/rails-emulate-save-page-as-behaviour – Andres Calle Oct 08 '12 at 03:50
0

After several hours and lots of other posts I got to a final solution:

Bricker is right in that it is not possible for rails to render more than once in a call, as taken from http://guides.rubyonrails.org/layouts_and_rendering.html "Can only render or redirect once per action"

The site also states "The rule is that if you do not explicitly render something at the end of a controller action, Rails will automatically look for the action_name.html.erb template in the controller’s view path and render it."

Then, the solution that worked great for me was to tell the controller to render to a string if a download flag (download=true) was set in :params (I also use request.url to have it working from any view in my application)

View:

= link_to 'Download', request.url+"&downloadexcel=true", :class => 'btn btn-primary btn-block'

Controller:

def acontrolleraction
  #some controller code here
  if params[:downloadexcel]
    save_page_xls
  else
    # render normally
  end
end


def save_page_xls
  #TRESCLOUD - we create a proper name for the file
  path = URI(request.referer).path.gsub(/[^0-9a-z]/i, '')
  query = URI(request.referer).query.gsub(/[^0-9a-z]/i, '')
  filename = @project_data['NOMBRE']+"_"+path+"_"+query+".xls"

  #TRESCLOUD - we render the page into a variable and process it
  page = render_to_string

  #TRESCLOUD - we send the file for download!
  send_data(page, :filename => filename, :type => "application/xls")
end

Thanks for your tips!

Andres Calle
  • 257
  • 1
  • 3
  • 11