Skip to content
This repository has been archived by the owner on Sep 10, 2024. It is now read-only.

Various changes #17

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ To include some fancy Ajax stuff with jquery

```js

var url = '/my_page.pdf'
var url = '/my_page.pdf';
var statusCodes = {
200: function() {
return window.location.assign(url);
Expand All @@ -178,6 +178,12 @@ To include some fancy Ajax stuff with jquery

```

The URL in the above example can also contain a query string:

```js
var url = '/my_page.pdf?answer=42';
```

## Contributing

1. Fork it
Expand Down
1 change: 1 addition & 0 deletions lib/shrimp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
require 'shrimp/phantom'
require 'shrimp/middleware'
require 'shrimp/configuration'
require 'shrimp/response'
21 changes: 21 additions & 0 deletions lib/shrimp/conditions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
require 'shrimp/path_validator'

module Shrimp
class Conditions
def initialize(conditions = {})
@conditions = conditions
end

def path_is_valid?(path)
validator = PathValidator.new(path)

if @conditions[:only]
validator.is_in_conditions?(@conditions[:only])
elsif @conditions[:except]
validator.is_not_in_conditions?(@conditions[:except])
else
true
end
end
end
end
197 changes: 68 additions & 129 deletions lib/shrimp/middleware.rb
Original file line number Diff line number Diff line change
@@ -1,175 +1,114 @@
require 'shrimp/conditions'
require 'shrimp/phantom_request'

module Shrimp
class Middleware
def initialize(app, options = { }, conditions = { })
@app = app
@options = options
@conditions = conditions
@conditions = Conditions.new(conditions)
@options[:polling_interval] ||= 1
@options[:polling_offset] ||= 1
@options[:cache_ttl] ||= 1
@options[:request_timeout] ||= @options[:polling_interval] * 10
end

def call(env)
@request = Rack::Request.new(env)
if render_as_pdf? #&& headers['Content-Type'] =~ /text\/html|application\/xhtml\+xml/
if already_rendered? && (up_to_date?(@options[:cache_ttl]) || @options[:cache_ttl] == 0)
if File.size(render_to) == 0
File.delete(render_to)
remove_rendering_flag
return error_response
end
return ready_response if env['HTTP_X_REQUESTED_WITH']
file = File.open(render_to, "rb")
body = file.read
file.close
File.delete(render_to) if @options[:cache_ttl] == 0
remove_rendering_flag
response = [body]
headers = { }
headers["Content-Length"] = (body.respond_to?(:bytesize) ? body.bytesize : body.size).to_s
headers["Content-Type"] = "application/pdf"
[200, headers, response]
else
if rendering_in_progress?
if rendering_timed_out?
remove_rendering_flag
error_response
else
reload_response(@options[:polling_interval])
end
else
File.delete(render_to) if already_rendered?
set_rendering_flag
fire_phantom
reload_response(@options[:polling_offset])
end
end
@request = PhantomRequest.new(env)

if render_as_pdf?
render_pdf
else
@app.call(env)
end
end

private

# Private: start phantom rendering in a separate process
def fire_phantom
Process::detach fork { Phantom.new(@request.url.sub(%r{\.pdf$}, ''), @options, @request.cookies).to_pdf(render_to) }
def render_pdf
if pdf_ready?
send_pdf
elsif @request.rendering_in_progress?
wait_for_rendering
else
start_rendering
end
end

def render_to
file_name = Digest::MD5.hexdigest(@request.path) + ".pdf"
file_path = @options[:out_path]
"#{file_path}/#{file_name}"
def pdf_ready?
already_rendered? && (up_to_date?(@options[:cache_ttl]) || @options[:cache_ttl] == 0)
end

def already_rendered?
File.exists?(render_to)
end
def send_pdf
if File.zero?(render_to)
File.delete(render_to)
@request.remove_rendering_flag
return Response.error("PDF file invalid")
end

def up_to_date?(ttl = 30)
(Time.now - File.new(render_to).mtime) <= ttl
return Response.ready(@request.path) if @request.xhr?

body = read_pdf_contents
File.delete(render_to) if @options[:cache_ttl] == 0
@request.remove_rendering_flag
Response.file(body)
end

def read_pdf_contents
file = File.open(render_to, "rb")
body = file.read
file.close
body
end

def remove_rendering_flag
@request.session["phantom-rendering"] ||={ }
@request.session["phantom-rendering"].delete(render_to)
def wait_for_rendering
if rendering_timed_out?
@request.remove_rendering_flag
Response.error("Rendering timeout")
else
Response.reload(@options[:polling_interval])
end
end

def set_rendering_flag
@request.session["phantom-rendering"] ||={ }
@request.session["phantom-rendering"][render_to] = Time.now
def start_rendering
File.delete(render_to) if already_rendered?
@request.set_rendering_flag
fire_phantom
Response.reload(@options[:polling_offset])
end

def rendering_timed_out?
Time.now - @request.session["phantom-rendering"][render_to] > @options[:request_timeout]
# Private: start phantom rendering in a separate process
def fire_phantom
Process::detach fork { Phantom.new(@request.phantom_request_url, @options, @request.cookies).to_pdf(render_to) }
end

def rendering_in_progress?
@request.session["phantom-rendering"]||={ }
@request.session["phantom-rendering"][render_to]
def render_to
file_path = @options[:out_path]
"#{file_path}/#{render_file_name}"
end

def render_as_pdf?
request_path_is_pdf = !!@request.path.match(%r{\.pdf$})

if request_path_is_pdf && @conditions[:only]
rules = [@conditions[:only]].flatten
rules.any? do |pattern|
if pattern.is_a?(Regexp)
@request.path =~ pattern
else
@request.path[0, pattern.length] == pattern
end
end
elsif request_path_is_pdf && @conditions[:except]
rules = [@conditions[:except]].flatten
rules.map do |pattern|
if pattern.is_a?(Regexp)
return false if @request.path =~ pattern
else
return false if @request.path[0, pattern.length] == pattern
end
end
return true
else
request_path_is_pdf
end
def render_file_name
"#{@request.session_key}.pdf"
end

def concat(accepts, type)
(accepts || '').split(',').unshift(type).compact.join(',')
def already_rendered?
File.exists?(render_to)
end

def reload_response(interval=1)
body = <<-HTML.gsub(/[ \n]+/, ' ').strip
<html>
<head>
</head>
<body onLoad="setTimeout(function(){ window.location.reload()}, #{interval * 1000});">
<h2>Preparing pdf... </h2>
</body>
</ html>
HTML
headers = { }
headers["Content-Length"] = body.size.to_s
headers["Content-Type"] = "text/html"
headers["Retry-After"] = interval.to_s

[503, headers, [body]]
def up_to_date?(ttl = 30)
(Time.now - File.new(render_to).mtime) <= ttl
end

def ready_response
body = <<-HTML.gsub(/[ \n]+/, ' ').strip
<html>
<head>
</head>
<body>
<a href="#{@request.path}">PDF ready here</a>
</body>
</ html>
HTML
headers = { }
headers["Content-Length"] = body.size.to_s
headers["Content-Type"] = "text/html"
[200, headers, [body]]
def rendering_timed_out?
@request.rendering_timeout? @options[:request_timeout]
end

def error_response
body = <<-HTML.gsub(/[ \n]+/, ' ').strip
<html>
<head>
</head>
<body>
<h2>Sorry request timed out... </h2>
</body>
</ html>
HTML
headers = { }
headers["Content-Length"] = body.size.to_s
headers["Content-Type"] = "text/html"
[504, headers, [body]]
def render_as_pdf?
if @request.path_is_pdf?
@conditions.path_is_valid? @request.path
else
false
end
end
end
end
32 changes: 32 additions & 0 deletions lib/shrimp/path_validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
module Shrimp
class PathValidator
def initialize(path)
@path = path
end

def is_in_conditions?(conditions)
rules = [conditions].flatten
is_in_rules?(rules)
end

def is_not_in_conditions?(conditions)
!is_in_conditions?(conditions)
end

private

def is_in_rules?(rules)
rules.any? do |pattern|
matches_pattern?(pattern)
end
end

def matches_pattern?(pattern)
if pattern.is_a?(Regexp)
@path =~ pattern
else
@path[0, pattern.length] == pattern
end
end
end
end
41 changes: 41 additions & 0 deletions lib/shrimp/phantom_request.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
require 'digest/md5'

module Shrimp
class PhantomRequest < Rack::Request

def session_key
Digest::MD5.hexdigest(url)
end

def path_is_pdf?
!!path.match(%r{\.pdf$})
end

def phantom_request_url
url.sub(%r{\.pdf(\?.*)?$}, '\1')
end

def remove_rendering_flag
phantom_session.delete(session_key)
end

def set_rendering_flag
phantom_session[session_key] = Time.now
end

def rendering_timeout?(timeout)
Time.now - phantom_session[session_key] > timeout
end

def rendering_in_progress?
!!phantom_session[session_key]
end

private

def phantom_session
session["phantom-rendering"] ||= {}
end

end
end
2 changes: 1 addition & 1 deletion lib/shrimp/rasterize.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function print_usage() {
}

window.setTimeout(function () {
error("Shit's being weird no result within: " + time_out + "ms");
error("Timeout, no result within: " + time_out + "ms");
}, time_out);

function renderUrl(url, output, options) {
Expand Down
Loading