Class: Palapala::Renderer
- Inherits:
-
Object
- Object
- Palapala::Renderer
- Defined in:
- lib/palapala/renderer.rb
Overview
Render HTML content to PDF using Chrome in headless mode with minimal dependencies
Class Method Summary collapse
- .html_to_pdf(html, params: {}) ⇒ Object
- .ping ⇒ Object
-
.reset ⇒ Object
Reset the thread-local instance of the renderer.
-
.thread_local_instance ⇒ Object
Create a thread-local instance of the renderer.
-
.websocket_url ⇒ Object
Open a new tab in the remote chrome and return the WebSocket URL.
Instance Method Summary collapse
- #close ⇒ Object
-
#current_id ⇒ Object
Get the current ID.
-
#html_to_pdf(html, params: {}) ⇒ Object
Convert HTML content to PDF See chromedevtools.github.io/devtools-protocol/tot/Page/#method-printToPDF.
-
#initialize ⇒ Renderer
constructor
A new instance of Renderer.
-
#next_id ⇒ Object
Update the current ID to the next ID (increment by 1).
-
#on_message(e) ⇒ Object
Callback to handle the incomming WebSocket messages.
- #ping ⇒ Object
-
#process_until(&block) ⇒ Object
Process the WebSocket messages until some state is true.
-
#send_and_wait(message) ⇒ Object
Method to send a message (text) and wait for a response.
-
#send_command(method, params: {}, &block) ⇒ Object
Method to send a CDP command and wait for some state to be true.
-
#send_command_and_wait_for_event(method, event_name:, params: {}) ⇒ Object
Method to send a CDP command and wait for a specific method to be called.
-
#send_command_and_wait_for_result(method, params: {}) ⇒ Hash
Method to send a CDP command and wait for the matching event to get the result.
- #websocket_url ⇒ Object
Constructor Details
#initialize ⇒ Renderer
Returns a new instance of Renderer.
10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# File 'lib/palapala/renderer.rb', line 10 def initialize puts "Initializing a renderer" if Palapala.debug # Create an instance of WebSocketClient with the WebSocket URL @client = Palapala::WebSocketClient.new(websocket_url) # Create the WebSocket driver @driver = WebSocket::Driver.client(@client) # Register the on_message callback @driver.on(:message, &method(:on_message)) @driver.on(:close) { Thread.current[:renderer] = nil } # Reset the renderer on close # Start the WebSocket handshake @driver.start # Initialize the protocol to get the page events send_command_and_wait_for_result("Page.enable") end |
Class Method Details
.html_to_pdf(html, params: {}) ⇒ Object
111 112 113 114 115 116 |
# File 'lib/palapala/renderer.rb', line 111 def self.html_to_pdf(html, params: {}) thread_local_instance.html_to_pdf(html, params: params) rescue StandardError reset # Reset the renderer on error, the websocket connection might be broken thread_local_instance.html_to_pdf(html, params: params) # Retry (once) end |
.ping ⇒ Object
118 119 120 |
# File 'lib/palapala/renderer.rb', line 118 def self.ping thread_local_instance.ping end |
.reset ⇒ Object
Reset the thread-local instance of the renderer
38 39 40 41 |
# File 'lib/palapala/renderer.rb', line 38 def self.reset puts "Clearing the thread local renderer" if Palapala.debug Thread.current[:renderer] = nil end |
.thread_local_instance ⇒ Object
Create a thread-local instance of the renderer
33 34 35 |
# File 'lib/palapala/renderer.rb', line 33 def self.thread_local_instance Thread.current[:renderer] ||= Renderer.new end |
.websocket_url ⇒ Object
Open a new tab in the remote chrome and return the WebSocket URL
128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/palapala/renderer.rb', line 128 def self.websocket_url uri = URI("#{Palapala.headless_chrome_url}/json/new") http = Net::HTTP.new(uri.host, uri.port) request = Net::HTTP::Put.new(uri) request["Content-Type"] = "application/json" response = http.request(request) tab_info = JSON.parse(response.body) websocket_url = tab_info["webSocketDebuggerUrl"] puts "WebSocket URL: #{websocket_url}" if Palapala.debug websocket_url end |
Instance Method Details
#close ⇒ Object
122 123 124 125 |
# File 'lib/palapala/renderer.rb', line 122 def close @driver.close @client.close end |
#current_id ⇒ Object
Get the current ID
56 |
# File 'lib/palapala/renderer.rb', line 56 def current_id = @id |
#html_to_pdf(html, params: {}) ⇒ Object
Convert HTML content to PDF See chromedevtools.github.io/devtools-protocol/tot/Page/#method-printToPDF
99 100 101 102 103 104 |
# File 'lib/palapala/renderer.rb', line 99 def html_to_pdf(html, params: {}) send_command_and_wait_for_event("Page.navigate", params: { url: data_url_for_html(html) }, event_name: "Page.frameStoppedLoading") result = send_command_and_wait_for_result("Page.printToPDF", params:) Base64.decode64(result["data"]) end |
#next_id ⇒ Object
Update the current ID to the next ID (increment by 1)
53 |
# File 'lib/palapala/renderer.rb', line 53 def next_id = @id = (@id || 0) + 1 |
#on_message(e) ⇒ Object
Callback to handle the incomming WebSocket messages
44 45 46 47 48 49 50 |
# File 'lib/palapala/renderer.rb', line 44 def (e) puts "Received: #{e.data[0..64]}" if Palapala.debug @response = JSON.parse(e.data) # Parse the JSON response if @response["error"] # Raise an error if the response contains an error raise "#{@response["error"]["message"]}: #{@response["error"]["data"]} (#{@response["error"]["code"]})" end end |
#ping ⇒ Object
106 107 108 109 |
# File 'lib/palapala/renderer.rb', line 106 def ping result = send_command_and_wait_for_result("Runtime.evaluate", params: { expression: "1 + 1" }) raise "Ping failed" unless result["result"]["value"] == 2 end |
#process_until(&block) ⇒ Object
Process the WebSocket messages until some state is true
59 60 61 62 63 64 65 |
# File 'lib/palapala/renderer.rb', line 59 def process_until(&block) loop do @driver.parse(@client.read) return if block.call return if @driver.state == :closed end end |
#send_and_wait(message) ⇒ Object
Method to send a message (text) and wait for a response
68 69 70 71 72 |
# File 'lib/palapala/renderer.rb', line 68 def send_and_wait(, &) puts "\nSending: #{}" if Palapala.debug @driver.text() process_until(&) end |
#send_command(method, params: {}, &block) ⇒ Object
Method to send a CDP command and wait for some state to be true
75 76 77 |
# File 'lib/palapala/renderer.rb', line 75 def send_command(method, params: {}, &block) send_and_wait(JSON.generate({ id: next_id, method:, params: }), &block) end |
#send_command_and_wait_for_event(method, event_name:, params: {}) ⇒ Object
Method to send a CDP command and wait for a specific method to be called
89 90 91 92 93 |
# File 'lib/palapala/renderer.rb', line 89 def send_command_and_wait_for_event(method, event_name:, params: {}) send_command(method, params:) do @response && @response["method"] == event_name end end |
#send_command_and_wait_for_result(method, params: {}) ⇒ Hash
Method to send a CDP command and wait for the matching event to get the result
81 82 83 84 85 86 |
# File 'lib/palapala/renderer.rb', line 81 def send_command_and_wait_for_result(method, params: {}) send_command(method, params:) do @response && @response["id"] == current_id end @response["result"] end |
#websocket_url ⇒ Object
25 26 27 28 29 30 |
# File 'lib/palapala/renderer.rb', line 25 def websocket_url self.class.websocket_url rescue Errno::ECONNREFUSED ChromeProcess.spawn_chrome # Spawn a new Chrome process self.class.websocket_url # Retry (once) end |