class Pinion::Server

Attributes

mount_point[R]

Public Class Methods

new(mount_point) click to toggle source
# File lib/pinion/server.rb, line 13
def initialize(mount_point)
  @mount_point = mount_point
end

Public Instance Methods

asset_inline(path) click to toggle source
# File lib/pinion/server.rb, line 128
def asset_inline(path) Asset[path].contents end
asset_url(path) click to toggle source

Helper methods for an application to generate urls (with fingerprints in production)

# File lib/pinion/server.rb, line 112
def asset_url(path)
  path.sub!(%r[^(#{@mount_point})?/?], "")
  mounted_path = "#{@mount_point}/#{path}"

  return mounted_path unless Pinion.environment == "production"

  # Add on a checksum tag in production
  asset = Asset[path]
  raise "Error: no such asset available: #{path}" unless asset
  mounted_path, dot, extension = mounted_path.rpartition(".")
  return mounted_path if dot.empty?
  "#{mounted_path}-#{asset.checksum}.#{extension}"
end
bundle_url(name) click to toggle source

Return the bundle url. In production, the single bundled result is produced; otherwise, each individual asset_url is returned.

# File lib/pinion/server.rb, line 148
def bundle_url(name)
  bundle = Bundle[name]
  raise "No such bundle: #{name}" unless bundle
  return bundle.paths.map { |p| asset_url(p) } unless Pinion.environment == "production"
  ["#{@mount_point}/#{bundle.name}-#{bundle.checksum}.#{bundle.extension}"]
end
call(env) click to toggle source

Boilerplate mostly stolen from sprockets github.com/sstephenson/sprockets/blob/master/lib/sprockets/server.rb

# File lib/pinion/server.rb, line 45
def call(env)
  # Avoid modifying the session state, don't set cookies, etc
  env["rack.session.options"] ||= {}
  env["rack.session.options"].merge! :defer => true, :skip => true

  root = env["SCRIPT_NAME"]
  path = Rack::Utils.unescape(env["PATH_INFO"].to_s).sub(%r[^/], "")

  if path.include? ".."
    return [403, { "Content-Type" => "text/plain", "Content-Length" => "9" }, ["Forbidden"]]
  end

  # Pull out the md5sum if it's part of the given path
  # e.g. foo/bar-a95c53a7a0f5f492a74499e70578d150.js -> a95c53a7a0f5f492a74499e70578d150
  checksum_tag = bundle_name = nil
  matches = path.match /([^\/]+)-([\da-f]{32})\..+$/
  if matches && matches.size == 3
    bundle_name = matches[1]
    checksum_tag = matches[2]
    path.sub!("-#{checksum_tag}", "")
  end

  # First see if there is a bundle with this name
  asset = Bundle[bundle_name]
  if asset
    # If the checksum doesn't match the bundle, we want to return a 404.
    asset = nil unless asset.checksum == checksum_tag
  else
    # If there is no bundle with this name, find another type of Asset with the name instead.
    asset = Asset[path]
  end

  if asset
    # If the ETag matches, give a 304
    return [304, {}, []] if env["HTTP_IF_NONE_MATCH"] == %Q["#{asset.checksum}"]

    if Pinion.environment == "production"
      # In production, if there is a checksum, cache for a year (because if the asset changes with a
      # redeploy, the checksum will change). If there is no checksum, cache for 10 minutes (this way at
      # worst we only serve 10 minute stale assets, and caches in front of Pinion will be able to help
      # out).
      cache_policy = checksum_tag ? "max-age=31536000" : "max-age=600"
    else
      # Don't cache in dev.
      cache_policy = "must-revalidate"
    end
    headers = {
      "Content-Type" => asset.content_type,
      "Content-Length" => asset.length.to_s,
      "ETag" => %Q["#{asset.checksum}"],
      "Cache-Control" => "public, #{cache_policy}",
      "Last-Modified" => asset.mtime.httpdate,
    }
    body = env["REQUEST_METHOD"] == "HEAD" ? [] : asset
    [200, headers, body]
  else
    [404, { "Content-Type" => "text/plain", "Content-Length" => "9" }, ["Not found"]]
  end
rescue Exception => e
  # TODO: logging
  STDERR.puts "Error compiling #{path}:"
  STDERR.puts "#{e.class.name}: #{e.message}"
  # TODO: render nice custom errors in the browser
  raise
end
convert(from_and_to, &block) click to toggle source
# File lib/pinion/server.rb, line 17
def convert(from_and_to, &block)
  unless from_and_to.is_a?(Hash) && from_and_to.size == 1
    raise Error, "Unexpected argument to convert: #{from_and_to.inspect}"
  end
  from, to = from_and_to.to_a[0]
  unless [from, to].all? { |s| s.is_a? Symbol }
    raise Error, "Expecting symbols in this hash #{from_and_to.inspect}"
  end
  if block_given?
    # Save new conversion type (this might overwrite an implicit or previously defined conversion)
    Conversion.create(from_and_to) do
      render { |file_contents| block.call(file_contents) }
    end
  else
    unless Conversion[from_and_to]
      raise Error, "No implicit conversion for #{from_and_to.inspect}. Must provide a conversion block."
    end
  end
end
create_bundle(name, bundle_name, paths) click to toggle source

Create a bundle of assets. Each asset must convert to the same final type. ‘name` is an identifier for this bundle; `bundle_name` is the # identifier for your bundle type (e.g. `:concatenate_and_uglify_js`); and `paths` are all the asset paths. In development, no bundles will be created (but the list of discrete assets will be saved for use in a subsequent `bundle_url` call).

# File lib/pinion/server.rb, line 136
def create_bundle(name, bundle_name, paths)
  if Bundle[name]
    raise "Error: there is already a bundle called #{name} with different component files. Each " <<
          "bundle must have a unique name."
  end

  normalized_paths = paths.map { |path| path.sub(%r[^(#{@mount_point})?/?], "") }
  Bundle.create(name, bundle_name, normalized_paths)
end
css_bundle(name) click to toggle source
# File lib/pinion/server.rb, line 155
def css_bundle(name) bundle_url(name).map { |path| css_wrapper(path) }.join end
css_inline(path) click to toggle source
# File lib/pinion/server.rb, line 129
def css_inline(path) %Q{<style type="text/css">#{asset_inline(path)}</style>} end
css_url(path) click to toggle source
# File lib/pinion/server.rb, line 125
def css_url(path) css_wrapper(asset_url(path)) end
js_bundle(name) click to toggle source
# File lib/pinion/server.rb, line 154
def js_bundle(name) bundle_url(name).map { |path| js_wrapper(path) }.join end
js_inline(path) click to toggle source
# File lib/pinion/server.rb, line 130
def js_inline(path) %Q{<script>#{asset_inline(path)}</script>} end
js_url(path) click to toggle source
# File lib/pinion/server.rb, line 126
def js_url(path) js_wrapper(asset_url(path)) end
watch(path) click to toggle source
# File lib/pinion/server.rb, line 37
def watch(path)
  raise Error, "#{path} is not a directory." unless File.directory? path
  Asset.watch_path(path)
  Conversion.add_watch_directory path
end

Private Instance Methods

css_wrapper(inner) click to toggle source
# File lib/pinion/server.rb, line 162
def css_wrapper(inner) %Q{<link type="text/css" rel="stylesheet" href="#{inner}" />} end
js_wrapper(inner) click to toggle source

Need type=“text/javascript” for IE compatibility. The content-type will be set correctly as “application/javascript” in the response.

# File lib/pinion/server.rb, line 161
def js_wrapper(inner) %Q{<script type="text/javascript" src="#{inner}"></script>} end
with_content_length(response) click to toggle source
# File lib/pinion/server.rb, line 164
def with_content_length(response)
  status, headers, body = response
  [status, headers.merge({ "Content-Length" => Rack::Utils.bytesize(body).to_s }), body]
end