class Wunderbar::CGI

Public Class Methods

call(scope) click to toggle source
# File lib/wunderbar/cgi-methods.rb, line 115
def self.call(scope)
  new.call(scope)
end
headers(headers) click to toggle source

map Ruby CGI headers to Rack headers

# File lib/wunderbar/cgi-methods.rb, line 200
def self.headers(headers)
  result = headers.dup
  type = result.delete('type') || 'text/html'
  charset = result.delete('charset')
  type = "#{type}; charset=#{charset}" if charset
  result['Content-Type'] ||= type
  result
end

Public Instance Methods

call(scope) click to toggle source
# File lib/wunderbar/cgi-methods.rb, line 119
def call(scope)
  # asset support for Rack
  request = (scope.respond_to? :request) ? scope.request : nil
  if request and request.path =~ %r{/assets/\w[-.\w]+}
    path = '.' + scope.request.path
    headers = {'type' => 'text/plain'}
    headers['type'] = 'application/javascript' if path =~ /\.js$/
    out?(scope, headers) { File.read path if File.exist? path }
    return
  end

  env = scope.env
  accept    = env['HTTP_ACCEPT'].to_s
  path_info = env['PATH_INFO'].to_s

  # implied request types
  text = Wunderbar::Options::TEXT || (accept =~ /plain/ && accept !~ /html/)
  xhr_json = Wunderbar::Options::XHR_JSON || (accept =~ /json/)
  xhr_json ||= !text && env['HTTP_X_REQUESTED_WITH'].to_s=='XMLHttpRequest'
  @xhtml = (accept =~ /xhtml/ or accept == '')
  @pdf   = (accept =~ /pdf/)

  # parse json arguments
  if xhr_json and request and request.respond_to? :body
    if env['CONTENT_TYPE'] =~ %r{^application/json(;.*)?$}
      scope.params.merge! JSON.parse(scope.request.body.read)
    end
  end

  # overrides via the command line
  xhtml_override = ARGV.include?('--xhtml')
  html_override  = ARGV.include?('--html')
  @pdf           ||= ARGV.include?('--pdf')

  # overrides via the uri query parameter
  # xhr_json       ||= (path_info.end_with? '.json')
  text           ||= (path_info.end_with? '.text')
  @pdf           ||= (path_info.end_with? '.pdf')
  xhtml_override ||= (path_info.end_with? '.xhtml')
  html_override  ||= (path_info.end_with? '.html')

  # disable conneg if only one handler is provided
  if Wunderbar.queue.length == 1
    htype = Wunderbar.queue.first.first
    xhr_json = (htype == :json)
    text     = (htype == :text)
  end

  Wunderbar.queue.each do |type, args, block|
    case type
    when :html, :xhtml
      unless xhr_json or text
        if type == :html
          @xhtml = false unless xhtml_override
        else
          @xhtml = false if html_override
        end

        self.html(scope, *args, &block)
        return
      end
    when :json
      if xhr_json
        self.json(scope, *args, &block)
        return
      end
    when :text
      if text
        self.text(scope, *args, &block)
        return
      end
    when Proc
      unless xhr_json or text
        instance_exec scope, args, block, &type
        return
      end
    end
  end
end
html(scope, *args, &block) click to toggle source

produce html/xhtml

# File lib/wunderbar/cgi-methods.rb, line 89
def html(scope, *args, &block)
  headers = { 'type' => 'text/html', 'charset' => 'UTF-8' }
  headers['type'] = 'application/xhtml+xml' if @xhtml

  x = HtmlMarkup.new(scope)

  begin
    if @pdf
      x._.pdf = true if @pdf
      headers = { 'type' => 'application/pdf' }
      output = html2pdf {x.html(*args, &block)}
    else
      output = x.html(*args, &block)
    end
  rescue ::Exception => exception
    headers['status'] = Wunderbar::ServerError.text
    x.clear!
    output = x.html(*args) do
      _h1 'Internal Server Error'
      _exception exception
    end
  end

  out?(scope, headers) { output }
end
html2pdf(input=nil, &block) click to toggle source
# File lib/wunderbar/cgi-methods.rb, line 53
def html2pdf(input=nil, &block)
  require 'thread'
  require 'open3'
  require 'stringio'

  display=":#{rand(999)+1}"
  pid = fork do
    # close open files
    STDIN.reopen '/dev/null'
    STDOUT.reopen '/dev/null', 'a'
    STDERR.reopen STDOUT

    Process.setsid
    Wunderbar.error Process.exec("Xvfb #{display}")
    Process.exit
  end
  Process.detach(pid)

  ENV['DISPLAY']=display
  input ||= block.call
  output = StringIO.new

  Open3.popen3('wkhtmltopdf - -') do |pin, pout, perr|
    [
      Thread.new { pin.write input; pin.close },
      Thread.new { IO.copy_stream(pout, output) },
      Thread.new { perr.readpartial(4096) until perr.eof? }
    ].map(&:join)
  end

  output.string
ensure
  Process.kill 'INT', pid rescue nil
end
json(scope, &block) click to toggle source

produce json

# File lib/wunderbar/cgi-methods.rb, line 7
def json(scope, &block)
  headers = { 'type' => 'application/json', 'charset' => 'UTF-8',
    'Cache-Control' => 'no-cache' }
  builder = JsonBuilder.new(scope)
  output = builder.encode(&block)
  headers['status'] = "404 Not Found" if output == {}
rescue Exception => exception
  headers['status'] = Wunderbar::ServerError.text
  builder._! Hash.new unless builder.target? Hash
  builder._exception exception
ensure
  out?(scope, headers) { builder.target! }
end
out?(scope, headers, &block) click to toggle source

Conditionally provide output, based on ETAG

# File lib/wunderbar/cgi-methods.rb, line 35
def out?(scope, headers, &block)
  content = block.call
  etag = Digest::MD5.hexdigest(content)

  if scope.env['HTTP_IF_NONE_MATCH'] == etag.inspect
    headers['Date'] = ::CGI.rfc1123_date(Time.now)
    scope.out headers.merge('status' => '304 Not Modified') do
      ''
    end
  else
    scope.out headers.merge('Etag' => etag.inspect) do
      content
    end
  end
rescue Exception => exception
  Wunderbar.fatal exception.inspect
end
polymer_element(scope, *args, &block) click to toggle source
# File lib/wunderbar/polymer.rb, line 24
def polymer_element(scope, *args, &block)
  headers = { 'type' => 'text/html', 'charset' => 'UTF-8' }
  x = HtmlMarkup.new(scope)

  begin
     element = x._polymer_element *args do
      x.instance_eval(&block)
    end
    output = element.serialize.join("\n") + "\n"
  rescue ::Exception => exception
    headers['status'] = Wunderbar::ServerError.text
    x.clear!
    output = x.html(*args) do
      _h1 'Internal Server Error'
      _exception exception
    end
  end

  out?(scope, headers) { output }
end
ruby2js(scope, *args, &block) click to toggle source
# File lib/wunderbar/script.rb, line 58
def ruby2js(scope, *args, &block)
  headers = { 'type' => 'application/javascript', 'charset' => 'UTF-8' }

  begin
    output = Ruby2JS.convert(block) + "\n"
  rescue Parser::SyntaxError => exception
    headers['status'] = Wunderbar::ServerError.text
    location = exception.diagnostic.location
    output = "// Syntax Error: line #{location.line}, " +
      "column: #{location.column}\n#{exception}\n"
  rescue ::Exception => exception
    headers['status'] = Wunderbar::ServerError.text
    output = "// Internal Server Error: #{exception}\n"
    exception.backtrace.each do |frame| 
      next if CALLERS_TO_IGNORE.any? {|re| frame =~ re}
      output += "  #{frame}\n"
    end
  end

  out?(scope, headers) { output }
end
text(scope, &block) click to toggle source

produce text

# File lib/wunderbar/cgi-methods.rb, line 22
def text(scope, &block)
  headers = {'type' => 'text/plain', 'charset' => 'UTF-8'}
  builder = TextBuilder.new(scope)
  output = builder.encode(&block)
  headers['status'] = "404 Not Found" if output == ''
rescue Exception => exception
  headers['status'] = Wunderbar::ServerError.text
  builder._exception exception
ensure
  out?(scope, headers) { builder.target! }
end