Class: SC::Rack::Builder

Inherits:
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.



162
163
164
# File 'lib/sproutcore/rack/builder.rb', line 162

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
150
151
152
153
154
155
156
157
158
159
160
# 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
    did_reload = reload_project! # if needed

    # set SCRIPT_NAME to correctly set namespaces
    $script_name = env["SCRIPT_NAME"]

    # collect some standard info
    url = env['PATH_INFO']
    url = '/sproutcore/welcome' if url == '/'

    #designer mode?
    $design_mode = ((/designMode=YES/ =~ env['QUERY_STRING']) != nil) ? true : false

    # 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?
      build_path = entry[:build_path]
      if [:html, :test].include?(entry[:entry_type])
        #if did_reload || !File.exist?(build_path)
        #always clean html files...
        SC.profile("PROFILE_BUILD") do
          entry.clean!.build!
        end
      else
        entry.build!
      end

    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, target.config[:mime_types]),
    "Content-Length" => file_size.to_s,
    "Expires"        => (cacheable ? (Time.now + ONE_YEAR) : Time.now).httpdate
  }
  [200, headers, File.open(build_path, 'rb')]
end