Skip to content

Instantly share code, notes, and snippets.

@akitaonrails
Forked from fnando/rubygems_proxy.rb
Created May 8, 2011 07:08
Show Gist options
  • Save akitaonrails/961187 to your computer and use it in GitHub Desktop.
Save akitaonrails/961187 to your computer and use it in GitHub Desktop.
Rack app for caching RubyGems files. Very useful in our build server that sometimes fails due to our network or rubygems.org timeout.
.DS_Store
*.swp
*.swo
log/**
tmp/**
cache/**
<html>
<head>
<title>Page Not Found</title>
</head>
<body>
<h1>Page Not Found</h1>
<p>Please, type the correct address for the file.</p>
</body>
</html>
require "./rubygems_proxy"
run RubygemsProxy
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<title>Rubygems Mirror</title>
</head>
<body>
<h1>RubyGems Mirror</h1>
<h2>List of Available Gems:</h2>
<ul>
<% @gem_list.each do |file| %>
<li><%= file[:name] %> (<%= file[:versions].join(", ") %>)</li>
<% end %>
</ul>
<p>Total: <strong><%= @gem_list.size %> gems.</strong></p>
</body>
</html>
require "open-uri"
require "fileutils"
require "logger"
require "erb"
class RubygemsProxy
attr_reader :env
def self.call(env)
new(env).run
end
def initialize(env)
@env = env
end
def run
logger.info "GET #{env["PATH_INFO"]}"
if env["PATH_INFO"] == "/"
[200, {"Content-Type" => "text/html"}, [listing]]
else
[200, {"Content-Type" => "application/octet-stream"}, [contents]]
end
rescue Exception => error
# Just try to load from file if something goes wrong.
# This includes HTTP timeout, or something.
# If it fails again, we won't have any files anyway!
logger.error "Error: #{error.class} => #{error.message}"
if File.exists?(filepath)
content = open(filepath).read
[200, {"Content-Type" => "application/octet-stream"}, [content]]
else
content = open(File.expand_path("../public/404.html", __FILE__))
[404, {"Content-Type" => "text/html"}, [content]]
end
end
private
def logger
FileUtils.mkdir_p File.expand_path("../log", __FILE__)
@logger ||= Logger.new(File.expand_path("../log/proxy.log", __FILE__), "monthly")
end
def cache_dir
File.expand_path "../cache", __FILE__
end
def listing
last_gem = ""
gem_versions = []
@gem_list = []
Dir.glob(File.expand_path("../cache/gems/*.gem", __FILE__)).sort.each do |file|
file = File.basename(file)
if file =~ /^(.*?)\-(\d+.*?)\.gem$/
if last_gem != $1
@gem_list << { :name => last_gem, :versions => gem_versions } unless last_gem == ""
gem_versions = [$2]
last_gem = $1
else
gem_versions << $2
end
end
end
rhtml = ERB.new(File.read(File.expand_path("../index.erb", __FILE__)), nil, "%")
rhtml.result(binding)
end
def contents
if cached? && valid?
logger.info "Read from cache: #{filepath}"
open(filepath).read
else
logger.info "Read from interwebz: #{url}"
open(url).read.tap {|content| save(content)}
end
end
def save(contents)
FileUtils.mkdir_p File.dirname(filepath)
File.open(filepath, "wb") {|handler| handler << contents}
end
def valid?
filepath =~ /specs\..+\.gz$/ ? Time.now - File.mtime(filepath) < 300 : true
end
def cached?
File.file?(filepath)
end
def filepath
File.join(cache_dir, env["PATH_INFO"])
end
def url
File.join("http://rubygems.org", env["PATH_INFO"])
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment