Class: SC::Rack::Builder

Inherits:
Object
  • Object
show all
Defined in:
lib/sproutcore/rack/builder.rb

Overview

A Rack application for serving dynamically-built SproutCore projects. Most of the time you will use this application as part of the sc-server command to dynamically build your SproutCore project while you develop it.

If you are deploying some Ruby-based infrastructure in your production environment, you could also use this application to dynamically build new versions of your SproutCore apps when you deploy them. This would allow you to potentially bypass the pre-deployment build step using sc-build.

While this model is supported by the Rack adaptor, it is generally recommended that you instead build you app without using this adaptor since the build step will help catch possible errors in your code before you go live with your project. Sometimes, however, dynamically building content is useful, and that is what this adaptor is for.

Using This Application

When you instantiate a builder, you must provide one or more projects that contain the resources you want to load. Each incoming request url will be mapped to an entriy in a project manifest. The entry is then built and the resulting file returned. Once a file has been built, it will not be rebuilt unless the source file it represents has changed.

In addition to dynamically building entries, the Builder can also forwards requests onto an SC::Rack::Proxy app to handle proxies requests.

Config Settings

This app respects several options that you can name in your config file (in addition to proxy configs), that can affect the app performance. Normally reasonable defaults for these settings are built into the SproutCore buildfile, but you may choose to override them if you are deploying into a production environment.

:reload_project::  If set to true, then the builder will reload the
  projects to look for changed files before servicing incoming 
  requests.  You will generally want this option while working in 
  debug mode, but you may want to disable it for production, since it
  can slow down performance.  

:use_cached_headers:: If set to true, then the builder will return
  static assets with an "Expires: <10-years>" header attached.  This
  will yield excellent performance in production systems but it may
  interfere with loading the most recent copies of files when in 
  development mode.

:combine_javascript:: If set, the generated html will reference a 
  combined version of the javascript for elgible targets.  This will
  yield better performance in production, but slows down load time in
  development mode.

:combine_stylesheets:: Ditto to combine_javascript

Constant Summary collapse

ONE_YEAR =

used to set expires header.

365 * 24 * 60 * 60

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(project) ⇒ Builder

When you create a new builder, pass in one or more projects you want the builder to monitor for changes.



76
77
78
79
# File 'lib/sproutcore/rack/builder.rb', line 76

def initialize(project)
  @project = project
  @last_reload_time = Time.now 
end

Instance Attribute Details

#projectObject (readonly)

Returns the value of attribute project.



151
152
153
# File 'lib/sproutcore/rack/builder.rb', line 151

def project
  @project
end

Instance Method Details

#call(env) ⇒ Object

Main entry point for this Rack application. Returns 404 if no matching entry could be found in the project.



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
# File 'lib/sproutcore/rack/builder.rb', line 83

def call(env)
  
  # define local variables so they will survive the mutext contexts 
  # below...
  ret = url = target = language = cacheable = manifest = entry = nil
  build_path = nil
  
  project_mutex.synchronize do 
    reload_project! # if needed

    # collect some standard info
    url = env['PATH_INFO']

    # look for a matching target
    target = target_for(url)
    ret = not_found("No matching target") if target.nil?

    # normalize url to resolve to entry & extract the language
    if ret.nil?
      url, language, cacheable = normalize_url(url, target)
      ret = not_found("Target requires language") if language.nil?
    end

    # lookup manifest
    if ret.nil?
      language = language.to_s.downcase.to_sym # normalize
      manifest = target.manifest_for(:language => language).build!

      # lookup entry by url
      unless entry = manifest.entries.find { |e| e.url == url }
        ret = not_found("No matching entry in target")
      end
    end

    if ret.nil?
      # Clean the entry so it will rebuild if we are serving an html 
      # file
      entry.clean! if [:html, :test].include?(entry.entry_type)
  
      # Now build entry and return a file object
      build_path = entry.build!.build_path
    end

    # Update last reload time.  This way if any other requests are
    # waiting, they won't rebuild their manifest.
    @last_reload_time = Time.now
  end

  return ret unless ret.nil?
  
  unless File.file?(build_path) && File.readable?(build_path)
    return not_found("File could not build (entry: #{entry.filename} - build_path: #{build_path}")
  end
  
  SC.logger.info "Serving #{target.target_name.to_s.sub(/^\//,'')}:#{entry.filename}"

  # define response headers
  file_size = File.size(build_path)
  headers = {
    "Last-Modified"  => File.mtime(build_path).httpdate,
    "Etag"           => File.mtime(build_path).to_i.to_s,
    "Content-Type"   => mime_type(build_path),
    "Content-Length" => file_size.to_s,
    "Expires"        => (cacheable ? (Time.now + ONE_YEAR) : Time.now).httpdate
  }
  [200, headers, File.open(build_path, 'rb')]
end