-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.rb
More file actions
149 lines (126 loc) · 3.94 KB
/
server.rb
File metadata and controls
149 lines (126 loc) · 3.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# frozen_string_literal: true
require "sinatra"
require "json"
require "liquid"
require "ferrum"
require "tempfile"
require "open3"
require "tmpdir"
require "fileutils"
require "uri"
set :bind, "0.0.0.0"
set :port, 8080
set :server, :puma
TEMPLATE_PATH = ENV.fetch("TEMPLATE_PATH", "template.liquid")
# Make OUTPUT_DIR absolute so we never depend on process CWD
OUTPUT_DIR = File.expand_path(ENV.fetch("OUTPUT_PATH", ""), Dir.pwd) # e.g., "/output/"
OUTPUT_BASE = "output.png"
MUTEX = Mutex.new
helpers do
def output_png_for(slug)
FileUtils.mkdir_p(OUTPUT_DIR) if OUTPUT_DIR != "" # safe no-op if ""
fname = [slug, OUTPUT_BASE].compact.join("-").sub(/^-/, "")
OUTPUT_DIR == "" ? File.expand_path(fname, Dir.pwd) : File.join(OUTPUT_DIR, fname)
end
def chromium_path
ENV["CHROMIUM_PATH"] # e.g., "/usr/bin/chromium" in Docker
end
def render_liquid(template_str, data)
Liquid::Template.parse(template_str).render(data, strict_filters: false, strict_variables: false)
end
def read_template_from_request_or_file(data)
if data.is_a?(Hash) && data["template"].is_a?(String) && !data["template"].empty?
data["template"]
else
File.read(TEMPLATE_PATH)
end
end
# Main worker
def generate_screenshot(html, slug)
# Absolute per-slug temp file path
screenshot_tmp = File.join(Dir.tmpdir, "screenshot-#{slug.to_s.empty? ? 'default' : slug}.png")
out_png = output_png_for(slug)
# 1) Write HTML to a temp file, render with Ferrum, capture bytes
Tempfile.create(["render-", ".html"]) do |tmp|
tmp.write(html)
tmp.flush
browser_opts = {
headless: true,
timeout: 20,
browser_options: { "no-sandbox": nil, "disable-gpu": nil }
}
path = chromium_path
browser_opts[:path] = path if path && !path.empty?
browser = Ferrum::Browser.new(**browser_opts)
page = browser.create_page
page.go_to("file://#{tmp.path}")
page.set_viewport(width: 800, height: 480, scale_factor: 1)
browser.screenshot(path: screenshot_tmp)
browser.quit
end
unless File.exist?(screenshot_tmp)
warn "Screenshot file missing: #{screenshot_tmp}"
raise "Screenshot file was not created"
end
# 2) Convert to 1-bit PNG using ImageMagick 7 `magick`
convert_args = [
"convert",
screenshot_tmp,
"-dither", "FloydSteinberg",
"-remap", "pattern:gray50",
"-depth", "1",
"-strip",
"png:#{out_png}"
]
puts "INFO: converting temp=#{screenshot_tmp} -> out=#{out_png}"
MUTEX.synchronize do
_stdout, stderr, status = Open3.capture3(*convert_args)
File.delete(screenshot_tmp) rescue nil
unless status.success?
warn "ImageMagick convert failed: #{stderr}"
raise "ImageMagick convert failed"
end
end
nil
rescue => e
warn "generate_screenshot error: #{e.class}: #{e.message}"
warn "CHROMIUM_PATH=#{chromium_path.inspect}"
warn "TMPDIR=#{Dir.tmpdir}, OUTPUT_DIR=#{OUTPUT_DIR}"
raise
end
end
get "/up" do
"OK"
end
post "/render/:slug" do
request.body.rewind
data = JSON.parse(request.body.read) rescue nil
halt 400, "Invalid JSON" unless data.is_a?(Hash)
begin
template_str = read_template_from_request_or_file(data)
rescue => e
halt 500, "Failed to read template: #{e.message}"
end
begin
html = render_liquid(template_str, data)
rescue => e
halt 500, "Failed to render template: #{e.message}"
end
slug = params[:slug]
Thread.new do
begin
generate_screenshot(html, slug)
rescue => e
warn "Screenshot generation failed: #{e.message}"
end
end
status 202
"Rendering started. Visit /screenshot.png/#{slug} to retrieve the result."
end
get "/screenshot.png/:slug" do
path = output_png_for(params[:slug])
halt 404, "Screenshot not ready" unless File.exist?(path)
content_type "image/png"
headers "Content-Length" => File.size(path).to_s
File.binread(path)
end