Class: Scarpe::Components::AssetServer
- Inherits:
-
Object
- Object
- Scarpe::Components::AssetServer
- Includes:
- Base64, Shoes::Log
- Defined in:
- scarpe-components/lib/scarpe/components/asset_server.rb
Defined Under Namespace
Classes: FileServlet
Constant Summary collapse
- URL_TYPES =
[:auto, :asset, :data]
Constants included from Shoes::Log
Shoes::Log::DEFAULT_COMPONENT, Shoes::Log::DEFAULT_DEBUG_LOG_CONFIG, Shoes::Log::DEFAULT_LOG_CONFIG
Instance Attribute Summary collapse
-
#dir ⇒ Object
readonly
Returns the value of attribute dir.
-
#port ⇒ Object
readonly
Returns the value of attribute port.
-
#server_started ⇒ Object
readonly
Returns the value of attribute server_started.
Instance Method Summary collapse
-
#asset_url(url, url_type: :auto) ⇒ Object
Get an asset URL for the given url or filename.
- #find_open_port ⇒ Object
-
#initialize(port: 0, app_dir:, never_start_server: false, connect_timeout: 5) ⇒ AssetServer
constructor
Port 0 will auto-assign a free port.
- #kill_server ⇒ Object
- #port_is_responding?(port, timeout: 0.1) ⇒ Boolean
- #relative_path_from_to(from, to) ⇒ Object
- #retry_port(port, timeout: 2.0) ⇒ Object
- #start_server_thread ⇒ Object
- #start_webrick ⇒ Object
Methods included from Shoes::Log
configure_logger, #log_init, logger
Methods included from Base64
#encode_file_to_base64, #mime_type_for_filename, #valid_url?
Constructor Details
#initialize(port: 0, app_dir:, never_start_server: false, connect_timeout: 5) ⇒ AssetServer
Port 0 will auto-assign a free port
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
# File 'scarpe-components/lib/scarpe/components/asset_server.rb', line 17 def initialize(port: 0, app_dir:, never_start_server: false, connect_timeout: 5) log_init("AssetServer") require "scarpe/components/base64" @server_started = false @server_thread = nil @port = port != 0 ? port : find_open_port @app_dir = File. app_dir @components_dir = File. "#{__dir__}/../../.." @connect_timeout = connect_timeout @never_start_server = never_start_server # For now, always use 16kb as the cutoff for preferring to serve a file with the asset server @auto_asset_url_size = 16 * 1024 # Make sure the child process is dead at_exit do kill_server end end |
Instance Attribute Details
#dir ⇒ Object (readonly)
Returns the value of attribute dir.
12 13 14 |
# File 'scarpe-components/lib/scarpe/components/asset_server.rb', line 12 def dir @dir end |
#port ⇒ Object (readonly)
Returns the value of attribute port.
10 11 12 |
# File 'scarpe-components/lib/scarpe/components/asset_server.rb', line 10 def port @port end |
#server_started ⇒ Object (readonly)
Returns the value of attribute server_started.
11 12 13 |
# File 'scarpe-components/lib/scarpe/components/asset_server.rb', line 11 def server_started @server_started end |
Instance Method Details
#asset_url(url, url_type: :auto) ⇒ Object
Get an asset URL for the given url or filename. The asset server can return a data URL, which encodes the entire file into the URL. It can return an asset server URL, which will serve the file via a local webrick server (@see AssetServer).
If url_type is auto, asset_url will return a data URL or asset server URL depending on file size and whether it's local to the asset server. Remote URLs will always be returned verbatim.
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 |
# File 'scarpe-components/lib/scarpe/components/asset_server.rb', line 52 def asset_url(url, url_type: :auto) unless URL_TYPES.include?(url_type) raise ArgumentError, "The url_type arg must be one of #{URL_TYPES.inspect}!" end if valid_url?(url) # This is already not local, use it directly return url end # Invalid URLs are assumed to be file paths. url = File. url file_size = File.size(url) # Calculate the app-relative path to the file. If it's not outside the app # dir, great, use that. If it *is* outside the app dir, see if the # scarpe-components dir is better (e.g. for Tiranti Bootstrap CSS assets.) relative_app_path = relative_path_from_to(@app_dir, url) relative_path = relative_app_path if relative_app_path.start_with?("../") relative_comp_path = relative_path_from_to(@components_dir, url) relative_path = relative_comp_path unless relative_comp_path.start_with?("../") end # If url_type is :auto, we will use a data URL for small files and files that # would be outside the asset server's directory. Data URLs are less efficient # for large files, but we'll try to always serve *something* if we can. if url_type == :data || (url_type == :auto && file_size < @auto_asset_url_size) || (url_type == :auto && relative_path.start_with?("../")) # The MIME media type for this file file_type = mime_type_for_filename(url) # Up to 16kb per file, inline it directly to avoid an extra HTTP request return "data:#{file_type};base64,#{encode_file_to_base64(url)}" end # Start the server if we're returning an asset-server URL unless @server_started || @never_start_server start_server_thread end if relative_path.start_with?("../") raise Scarpe::OperationNotAllowedError, "Large asset is outside of application directory and asset URL was requested: #{url.inspect}" end if relative_path == relative_app_path "http://127.0.0.1:#{@port}/app/#{relative_path}" else "http://127.0.0.1:#{@port}/comp/#{relative_path}" end end |
#find_open_port ⇒ Object
105 106 107 108 109 110 111 |
# File 'scarpe-components/lib/scarpe/components/asset_server.rb', line 105 def find_open_port require "socket" s = TCPServer.new('127.0.0.1', port) port = s.addr[1] s.close port end |
#kill_server ⇒ Object
167 168 169 170 171 172 173 174 |
# File 'scarpe-components/lib/scarpe/components/asset_server.rb', line 167 def kill_server return unless @server_started && @server_thread @server.shutdown @server_thread.join if @server_thread.alive? @server_started = false @server_thread = nil end |
#port_is_responding?(port, timeout: 0.1) ⇒ Boolean
113 114 115 |
# File 'scarpe-components/lib/scarpe/components/asset_server.rb', line 113 def port_is_responding?(port, timeout: 0.1) Socket.tcp("127.0.0.1", port, connect_timeout: timeout) { true } rescue false end |
#relative_path_from_to(from, to) ⇒ Object
128 129 130 131 |
# File 'scarpe-components/lib/scarpe/components/asset_server.rb', line 128 def relative_path_from_to(from, to) require 'pathname' Pathname.new(to).relative_path_from(Pathname.new from).to_s end |
#retry_port(port, timeout: 2.0) ⇒ Object
117 118 119 120 121 122 123 124 125 126 |
# File 'scarpe-components/lib/scarpe/components/asset_server.rb', line 117 def retry_port(port, timeout: 2.0) t = Time.now loop do resp = port_is_responding?(port) return true if resp return false if Time.now - t > timeout sleep 0.1 end end |
#start_server_thread ⇒ Object
133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'scarpe-components/lib/scarpe/components/asset_server.rb', line 133 def start_server_thread return if @server_started @server_thread = Thread.new do start_webrick end @server_started = true # Give the asset server a couple of seconds to respond retry_port(@port, timeout: @connect_timeout) unless port_is_responding?(@port, timeout: 0.1) @log.warn "Asset server port doesn't seem to be responding after #{@connect_timeout} seconds!" end end |
#start_webrick ⇒ Object
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'scarpe-components/lib/scarpe/components/asset_server.rb', line 148 def start_webrick require "tempfile" log = WEBrick::Log.new Tempfile.new("scarpe_asset_server_log") access_log = [ [Tempfile.new("scarpe_asset_server_access_log"), WEBrick::AccessLog::COMBINED_LOG_FORMAT] ] @server = WEBrick::HTTPServer.new(Port: @port, DocumentRoot: @app_dir, Logger: log, AccessLog: access_log) @server.mount('/app', FileServlet, { Type: :app, Prefix: "/app", DocumentRoot: @app_dir }) @server.mount('/comp', FileServlet, { Type: :scarpe_components, Prefix: "/comp", DocumentRoot: @components_dir }) @server.start # Set up a signal trap to gracefully shut down the server on interrupt (e.g., Ctrl+C) trap('INT') do @server.shutdown end end |