diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4040c6c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.gem +.bundle +Gemfile.lock +pkg/* diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..055b5aa --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source "http://rubygems.org" + +# Specify your gem's dependencies in http_resque.gemspec +gemspec diff --git a/README.markdown b/README.markdown new file mode 100644 index 0000000..675e6e8 --- /dev/null +++ b/README.markdown @@ -0,0 +1,3 @@ +HTTP Resque +=========== +Docs forthcoming. \ No newline at end of file diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..2995527 --- /dev/null +++ b/Rakefile @@ -0,0 +1 @@ +require "bundler/gem_tasks" diff --git a/bin/http_resque b/bin/http_resque new file mode 100755 index 0000000..583b65c --- /dev/null +++ b/bin/http_resque @@ -0,0 +1,83 @@ +#!/usr/bin/env ruby + +# This wraps the Resque process with a thin HTTP API which enables you to manipulate jobs using HTTP requests +# and run jobs synchronously, off-box, for the purposes of integration testing background jobs. This is +# necessary because background jobs fail notoriously often in production and so they need integration -- not +# unit -- tests. This helps you to cleanly write those integration tests. +# +# Usage: +# QUEUE=* http_resque -p 8080 +# The server uses port 4567 by default. Use -p to specify an alternate port. +# You'll note that the QUEUE environment variable is used just like it is when running `rake resque:work`. + +# Once it's started, you can access these URLs to manipulate jobs: +# GET /queues/:queue/jobs +# DELETE /queues/:queue/jobs +# POST /queues/:queue/jobs +# GET /queues/:queue/result_of_oldest_job + +require "sinatra/base" +require "thin" +require "resque" +require "rake" +require "json" + +# Load the Rakefile which should in turn require all of their Resque job classes. +# TODO(philc): The path to this Rakefile should be an argument. +load "./Rakefile" + +class HttpResque < Sinatra::Base + settings.server = "thin" + + STDOUT.sync = STDERR.sync = true + + # Run rake resque:work in a background process. It will exit when this process exits. + fork { Rake::Task["resque:work"].invoke } + + settings.port = ARGV.include?("-p") ? ARGV[ARGV.index("-p") + 1] : ENV["PORT"] + + get "/" do + "http_resque is here." + end + + # The Resque representation of up to 25 jobs in this queue, *oldest* first. Resque jobs look like this: + # { "class"=>"DeployBuild", "args"=>["my_embed_code", "my_youtube_synd_id"] } + get "/queues/:queue/jobs" do + (Resque.peek(params[:queue], 0, 25) || []).to_json + end + + delete "/queues/:queue/jobs" do + Resque.remove_queue(params[:queue]) + nil + end + + # Create a new job. + # - queue: the queue to enqueue this job into. + # - arguments: optional; an array of arguments for the Resque job. + post "/queues/:queue/jobs" do + halt(400, "Provide a valid JSON body.") unless json_body + klass = json_body["class"] + halt(400, "Specify a class.") unless klass + klass = Object.const_get(klass) + Resque.enqueue_to(params[:queue], klass, *json_body["arguments"]) + nil + end + + # Executes the job at the head of this queue (the oldest job), and blocks until it's finished. + # This is useful for scripting integration tests which verify that a background job is working correctly. + get "/queues/:queue/result_of_oldest_job" do + job = Resque::Job.reserve(params[:queue]) + halt(404, "No jobs left in #{params[:queue]}") unless job + begin + job.perform + rescue => error + halt(500, "This job raised an exception when run: " + + "#{job.inspect}\n#{error.class}: #{error.message}\n#{error.backtrace.join("\n")}") + end + nil + end + + def json_body() @json_body ||= JSON.parse(request.body.read) rescue nil end + + run! if app_file == $0 +end diff --git a/http_resque.gemspec b/http_resque.gemspec new file mode 100644 index 0000000..d0043ad --- /dev/null +++ b/http_resque.gemspec @@ -0,0 +1,25 @@ +# -*- encoding: utf-8 -*- +$:.push File.expand_path("../lib", __FILE__) +require "http_resque/version" + +Gem::Specification.new do |s| + s.name = "http_resque" + s.version = HttpResque::VERSION + s.authors = ["Phil Crosby"] + s.email = ["phil.crosby@gmail.com"] + s.homepage = "http://github.com/philc/http_resque" + s.summary = "A small HTTP wrapper around Resque, so you can schedule and test jobs over HTTP." + + s.rubyforge_project = "http_resque" + + s.files = `git ls-files`.split("\n") + s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") + s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } + s.require_paths = ["lib"] + + s.add_runtime_dependency "thin" + s.add_runtime_dependency "resque" + s.add_runtime_dependency "rake" + s.add_runtime_dependency "json" + s.add_runtime_dependency "sinatra" +end diff --git a/lib/http_resque.rb b/lib/http_resque.rb new file mode 100644 index 0000000..af6374c --- /dev/null +++ b/lib/http_resque.rb @@ -0,0 +1 @@ +require "howdy/version" \ No newline at end of file diff --git a/lib/http_resque/version.rb b/lib/http_resque/version.rb new file mode 100644 index 0000000..432fa28 --- /dev/null +++ b/lib/http_resque/version.rb @@ -0,0 +1,3 @@ +module HttpResque + VERSION = "0.0.1" +end \ No newline at end of file