sproutcore 1.6.0.1-java → 1.7.1.beta-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +21 -0
- data/Gemfile +5 -0
- data/Rakefile +26 -13
- data/VERSION.yml +2 -2
- data/lib/Buildfile +43 -4
- data/lib/buildtasks/build.rake +10 -0
- data/lib/buildtasks/helpers/file_rule.rb +22 -0
- data/lib/buildtasks/helpers/file_rule_list.rb +137 -0
- data/lib/buildtasks/manifest.rake +133 -122
- data/lib/frameworks/sproutcore/CHANGELOG.md +69 -2
- data/lib/frameworks/sproutcore/apps/tests/english.lproj/strings.js +1 -0
- data/lib/frameworks/sproutcore/frameworks/bootstrap/system/browser.js +28 -22
- data/lib/frameworks/sproutcore/frameworks/core_foundation/controllers/array.js +9 -5
- data/lib/frameworks/sproutcore/frameworks/core_foundation/controllers/controller.js +1 -1
- data/lib/frameworks/sproutcore/frameworks/core_foundation/controls/button.js +18 -13
- data/lib/frameworks/sproutcore/frameworks/core_foundation/ext/handlebars/bind.js +5 -3
- data/lib/frameworks/sproutcore/frameworks/core_foundation/ext/handlebars/collection.js +2 -0
- data/lib/frameworks/sproutcore/frameworks/core_foundation/mixins/action_support.js +80 -0
- data/lib/frameworks/sproutcore/frameworks/core_foundation/mixins/template_helpers/text_field_support.js +84 -116
- data/lib/frameworks/sproutcore/frameworks/core_foundation/panes/pane.js +8 -5
- data/lib/frameworks/sproutcore/frameworks/core_foundation/system/event.js +157 -157
- data/lib/frameworks/sproutcore/frameworks/core_foundation/system/platform.js +5 -3
- data/lib/frameworks/sproutcore/frameworks/core_foundation/system/root_responder.js +6 -6
- data/lib/frameworks/sproutcore/frameworks/core_foundation/system/sparse_array.js +10 -7
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/mixins/action_support.js +106 -0
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/template/collection.js +18 -0
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/template/handlebars.js +71 -1
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/attribute_bindings_test.js +38 -0
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/class_name_bindings_test.js +47 -0
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layoutChildViews.js +18 -18
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layoutStyle.js +42 -10
- data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view.js +158 -1
- data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/keyboard.js +26 -1
- data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/layout_style.js +14 -8
- data/lib/frameworks/sproutcore/frameworks/datastore/models/record.js +15 -2
- data/lib/frameworks/sproutcore/frameworks/datastore/models/record_attribute.js +108 -108
- data/lib/frameworks/sproutcore/frameworks/datastore/system/query.js +1 -1
- data/lib/frameworks/sproutcore/frameworks/datastore/system/record_array.js +2 -4
- data/lib/frameworks/sproutcore/frameworks/datastore/tests/models/record/error_methods.js +2 -2
- data/lib/frameworks/sproutcore/frameworks/datastore/tests/models/single_attribute.js +26 -0
- data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/query/builders.js +7 -0
- data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/record_array/error_methods.js +1 -1
- data/lib/frameworks/sproutcore/frameworks/datetime/frameworks/core/system/datetime.js +4 -1
- data/lib/frameworks/sproutcore/frameworks/datetime/frameworks/core/tests/system/datetime.js +6 -0
- data/lib/frameworks/sproutcore/frameworks/desktop/panes/menu.js +26 -5
- data/lib/frameworks/sproutcore/frameworks/desktop/panes/picker.js +97 -96
- data/lib/frameworks/sproutcore/frameworks/desktop/system/drag.js +4 -3
- data/lib/frameworks/sproutcore/frameworks/desktop/tests/panes/menu/ui.js +17 -4
- data/lib/frameworks/sproutcore/frameworks/desktop/views/collection.js +7 -7
- data/lib/frameworks/sproutcore/frameworks/desktop/views/menu_item.js +7 -5
- data/lib/frameworks/sproutcore/frameworks/desktop/views/scroll.js +12 -3
- data/lib/frameworks/sproutcore/frameworks/desktop/views/web.js +23 -14
- data/lib/frameworks/sproutcore/frameworks/experimental/Buildfile +5 -1
- data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/menu/render_delegates/menu_scroller.js +28 -0
- data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/menu/tests/menu/scroll.js +235 -0
- data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/menu/views/menu/scroll.js +363 -0
- data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/menu/views/menu/scroller.js +250 -0
- data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/render_delegates/desktop_scroller.js +92 -0
- data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/render_delegates/native_scroll.js +25 -0
- data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/render_delegates/scroll.js +33 -0
- data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/render_delegates/touch_scroller.js +76 -0
- data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/tests/scroll/integration.js +50 -0
- data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/tests/scroll/methods.js +143 -0
- data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/tests/scroll/ui.js +258 -0
- data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/core_scroll.js +1164 -0
- data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/core_scroller.js +332 -0
- data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/desktop/scroll.js +236 -0
- data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/desktop/scroller.js +347 -0
- data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/scroll.js +15 -0
- data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/scroller.js +10 -0
- data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/touch/scroll.js +804 -0
- data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/touch/scroller.js +133 -0
- data/lib/frameworks/sproutcore/frameworks/foundation/resources/text_field.css +3 -3
- data/lib/frameworks/sproutcore/frameworks/foundation/validators/number.js +3 -1
- data/lib/frameworks/sproutcore/frameworks/foundation/views/text_field.js +3 -3
- data/lib/frameworks/sproutcore/frameworks/media/views/audio.js +2 -1
- data/lib/frameworks/sproutcore/frameworks/media/views/controls.js +2 -1
- data/lib/frameworks/sproutcore/frameworks/media/views/media_slider.js +2 -4
- data/lib/frameworks/sproutcore/frameworks/media/views/mini_controls.js +2 -4
- data/lib/frameworks/sproutcore/frameworks/media/views/simple_controls.js +2 -4
- data/lib/frameworks/sproutcore/frameworks/media/views/video.js +2 -2
- data/lib/frameworks/sproutcore/frameworks/routing/system/routes.js +29 -3
- data/lib/frameworks/sproutcore/frameworks/runtime/core.js +2 -2
- data/lib/frameworks/sproutcore/frameworks/runtime/debug/test_suites/array/replace.js +1 -1
- data/lib/frameworks/sproutcore/frameworks/runtime/private/property_chain.js +2 -1
- data/lib/frameworks/sproutcore/frameworks/runtime/system/binding.js +3 -3
- data/lib/frameworks/sproutcore/frameworks/runtime/system/index_set.js +2 -2
- data/lib/frameworks/sproutcore/frameworks/runtime/system/object.js +1 -1
- data/lib/frameworks/sproutcore/themes/ace/resources/collection/normal/list_item.css +2 -2
- data/lib/frameworks/sproutcore/themes/legacy_theme/english.lproj/segmented.css +1 -1
- data/lib/gen/app/templates/apps/@target_name@/Buildfile +3 -5
- data/lib/gen/app/templates/apps/@target_name@/resources/_theme.css +18 -0
- data/lib/gen/project/templates/@filename@/Buildfile +2 -2
- data/lib/sproutcore.rb +30 -5
- data/lib/sproutcore/builders.rb +1 -0
- data/lib/sproutcore/builders/chance_file.rb +9 -16
- data/lib/sproutcore/builders/html.rb +2 -1
- data/lib/sproutcore/builders/minify.rb +4 -35
- data/lib/sproutcore/builders/module.rb +38 -1
- data/lib/sproutcore/builders/split.rb +63 -0
- data/lib/sproutcore/builders/strings.rb +7 -1
- data/lib/sproutcore/helpers.rb +1 -1
- data/lib/sproutcore/helpers/css_split.rb +190 -0
- data/lib/sproutcore/helpers/entry_sorter.rb +2 -0
- data/lib/sproutcore/helpers/minifier.rb +40 -16
- data/lib/sproutcore/helpers/static_helper.rb +35 -17
- data/lib/sproutcore/models/manifest.rb +26 -0
- data/lib/sproutcore/models/target.rb +12 -1
- data/lib/sproutcore/rack.rb +1 -0
- data/lib/sproutcore/rack/proxy.rb +244 -225
- data/lib/sproutcore/rack/restrict_ip.rb +67 -0
- data/lib/sproutcore/rack/service.rb +8 -2
- data/lib/sproutcore/tools.rb +102 -46
- data/lib/sproutcore/tools/build.rb +91 -43
- data/lib/sproutcore/tools/gen.rb +2 -3
- data/lib/sproutcore/tools/manifest.rb +22 -16
- data/lib/sproutcore/tools/server.rb +21 -0
- data/spec/buildtasks/helpers/accept_list +22 -0
- data/spec/buildtasks/helpers/accept_list.rb +128 -0
- data/spec/buildtasks/helpers/list.json +11 -0
- data/spec/buildtasks/manifest/prepare_build_tasks/chance_2x_spec.rb +1 -39
- data/spec/buildtasks/manifest/prepare_build_tasks/chance_spec.rb +0 -38
- data/spec/buildtasks/manifest/prepare_build_tasks/combine_spec.rb +4 -4
- data/spec/buildtasks/manifest/prepare_build_tasks/module_spec.rb +2 -2
- data/spec/buildtasks/manifest/prepare_build_tasks/packed_2x_indirect_spec.rb +7 -16
- data/spec/buildtasks/manifest/prepare_build_tasks/packed_2x_spec.rb +7 -17
- data/spec/buildtasks/manifest/prepare_build_tasks/packed_spec.rb +11 -6
- data/spec/fixtures/builder_tests/Buildfile +2 -1
- data/spec/fixtures/builder_tests/apps/module_test/modules/required_module/core.js +0 -0
- data/spec/lib/builders/module_spec.rb +1 -1
- data/spec/spec_helper.rb +1 -0
- data/sproutcore.gemspec +4 -9
- data/vendor/chance/lib/chance.rb +25 -6
- data/vendor/chance/lib/chance/factory.rb +45 -0
- data/vendor/chance/lib/chance/instance.rb +173 -28
- data/vendor/chance/lib/chance/instance/data_url.rb +0 -29
- data/vendor/chance/lib/chance/instance/slicing.rb +57 -4
- data/vendor/chance/lib/chance/instance/spriting.rb +112 -21
- data/vendor/chance/lib/chance/parser.rb +80 -52
- data/vendor/sproutcore/SCCompiler.jar +0 -0
- data/vendor/sproutcore/lib/args4j-2.0.12.jar +0 -0
- data/vendor/sproutcore/lib/yuicompressor-2.4.2.jar +0 -0
- metadata +84 -25
@@ -80,26 +80,50 @@ module SC::Helpers
|
|
80
80
|
end
|
81
81
|
|
82
82
|
def minify(paths)
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
83
|
+
if not paths.kind_of?(Array)
|
84
|
+
paths = [paths]
|
85
|
+
end
|
86
|
+
|
87
|
+
# Split paths into HTML and JS
|
88
|
+
html_paths = paths.select {|p| p =~ /\.html$/}
|
89
|
+
js_paths = paths.select {|p| p =~ /\.js$/}
|
90
|
+
|
91
|
+
if html_paths.length > 0
|
92
|
+
command = %{java -jar "#{SC.html_jar}" "#{html_paths.join '" "'}" 2>&1}
|
93
|
+
puts "Executing #{command}"
|
94
|
+
output = `#{command}`
|
95
|
+
|
96
|
+
SC.logger.info output
|
97
|
+
if $?.exitstatus != 0
|
98
|
+
SC.logger.fatal(output)
|
99
|
+
SC.logger.fatal("!!!! Minifying failed. Please check that your JS code is valid.")
|
100
|
+
SC.logger.fatal("!!!! Failed compiling #{paths}")
|
101
|
+
|
102
|
+
exit(1)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
if js_paths.length > 0
|
107
|
+
js_paths.each {|p|
|
108
|
+
command = %{java -jar "#{SC.js_jar}" -o "#{p}" "#{p}" 2>&1}
|
109
|
+
puts "Executing #{command}"
|
110
|
+
output = `#{command}`
|
111
|
+
|
112
|
+
SC.logger.info output
|
113
|
+
if $?.exitstatus != 0
|
114
|
+
SC.logger.fatal(output)
|
115
|
+
SC.logger.fatal("!!!! Minifying failed. Please check that your JS code is valid.")
|
116
|
+
SC.logger.fatal("!!!! Failed compiling #{paths}")
|
117
|
+
|
118
|
+
exit(1)
|
119
|
+
end
|
120
|
+
}
|
90
121
|
end
|
91
122
|
|
92
|
-
|
93
|
-
|
123
|
+
# SCyui doesn't need a -o. It writes to the original path.
|
124
|
+
|
94
125
|
|
95
|
-
SC.logger.info output
|
96
|
-
if $?.exitstatus != 0
|
97
|
-
SC.logger.fatal(output)
|
98
|
-
SC.logger.fatal("!!!! Minifying failed. Please check that your JS code is valid.")
|
99
|
-
SC.logger.fatal("!!!! Failed compiling #{paths}")
|
100
126
|
|
101
|
-
exit(1)
|
102
|
-
end
|
103
127
|
end
|
104
128
|
|
105
129
|
end
|
@@ -75,22 +75,15 @@ module SC
|
|
75
75
|
combine_stylesheets = t.config[:combine_stylesheets]
|
76
76
|
|
77
77
|
combined_entries(t, opts, 'stylesheet.css', 'stylesheet-packed.css') do |cur_target, cur_entry|
|
78
|
-
|
79
78
|
# We have to figure out if we should use the 2x version.
|
80
79
|
# For this, we have to figure out the original name again...
|
81
80
|
if opts[:x2]
|
82
|
-
name = cur_entry.
|
81
|
+
name = cur_entry.filename
|
83
82
|
|
84
|
-
|
85
|
-
name = "stylesheet@2x.css"
|
86
|
-
elsif name == "stylesheet-packed"
|
87
|
-
name = "stylesheet@2x-packed.css"
|
88
|
-
else
|
89
|
-
raise "Unexpected entry name when collecting CSS: " + name
|
90
|
-
end
|
83
|
+
name = name.gsub /stylesheet/, "stylesheet@2x"
|
91
84
|
|
92
85
|
v = opts[:language] ? { :language => opts[:language] } : manifest.variation
|
93
|
-
x2_entry =
|
86
|
+
x2_entry = cur_target.manifest_for(v).entry_for name
|
94
87
|
|
95
88
|
cur_entry = x2_entry if x2_entry
|
96
89
|
end
|
@@ -190,11 +183,7 @@ module SC
|
|
190
183
|
ret << inline_javascript(resource_name)
|
191
184
|
end
|
192
185
|
end
|
193
|
-
|
194
|
-
url_prefix = $script_name ? %("#{$script_name}") : "null";
|
195
|
-
|
196
|
-
ret << %(<script type="text/javascript">SC.buildMode = "#{SC.build_mode}"; \n SC.urlPrefix = #{url_prefix}; </script>)
|
197
|
-
|
186
|
+
|
198
187
|
return ret * "\n"
|
199
188
|
end
|
200
189
|
|
@@ -413,6 +402,13 @@ module SC
|
|
413
402
|
|
414
403
|
# get the stylesheet or js entry for it...
|
415
404
|
entry = cur_manifest.entry_for packed_entry_name
|
405
|
+
|
406
|
+
if entry.nil?
|
407
|
+
# If it didn't find it, it may have been hidden by the IE hack for splitting CSS.
|
408
|
+
# In this case, we have to find it, but searching for any hidden file won't work
|
409
|
+
# because there could be more than one. So, we search based on
|
410
|
+
entry = cur_manifest.entry_for packed_entry_name, { :hidden => true, :is_split => true }
|
411
|
+
end
|
416
412
|
|
417
413
|
# It used to be like this:
|
418
414
|
# next if entry.nil? || !entry.composite? # no stylesheet or js
|
@@ -420,8 +416,30 @@ module SC
|
|
420
416
|
# a reason to check for composite entries, either. So, the composite
|
421
417
|
# check has been removed.
|
422
418
|
next if entry.nil? # no stylesheet or js
|
423
|
-
|
424
|
-
|
419
|
+
|
420
|
+
# HACK FOR IE. Yes, IE hacks are now in Ruby as well! Isn't that grand!
|
421
|
+
# Basically, IE does not allow more than 4096 selectors. The problem: it has
|
422
|
+
# a max of 4096 selectors per file!!!
|
423
|
+
#
|
424
|
+
# and the problem is, we don't know the number of selectors until AFTER CSS
|
425
|
+
# is built. So, we have to, at the last minute, find these other files.
|
426
|
+
# They are given entries, but these entries are supplied AFTER the manifest
|
427
|
+
# is technically finished. So, it is only now that we can go find them.
|
428
|
+
#
|
429
|
+
# In fact: it may not even be around yet. It won't until this CSS entry is built.
|
430
|
+
# We didn't always have to build the CSS entries before serving HTML but... now we do.
|
431
|
+
# Because IE sucks.
|
432
|
+
entry.build!
|
433
|
+
|
434
|
+
# The builder adds a :split_entries. :split_entries were added, we need to split
|
435
|
+
# CSS files.
|
436
|
+
if entry[:split_entries]
|
437
|
+
entry[:split_entries].each {|e|
|
438
|
+
yield(t, e)
|
439
|
+
}
|
440
|
+
else
|
441
|
+
yield(t, entry)
|
442
|
+
end
|
425
443
|
end
|
426
444
|
|
427
445
|
unpacked.each do |t|
|
@@ -93,6 +93,31 @@ module SC
|
|
93
93
|
end
|
94
94
|
|
95
95
|
def built?; @is_built; end
|
96
|
+
|
97
|
+
#
|
98
|
+
# Resets this manifest, and all same-variation manifests the target depends on.
|
99
|
+
# This is useful when building: it clears the entries and frees up memory so
|
100
|
+
# building doesn't take multiple gigabytes.
|
101
|
+
#
|
102
|
+
def reset!
|
103
|
+
return if @is_resetting
|
104
|
+
@is_resetting = true
|
105
|
+
|
106
|
+
targets = target.expand_required_targets({ :theme => true }) + [target]
|
107
|
+
entries = targets.map do |t|
|
108
|
+
t.manifest_for(variation).reset!
|
109
|
+
end
|
110
|
+
|
111
|
+
targets = target.modules({ :debug => false, :test => false, :theme => true }).each do |t|
|
112
|
+
t.manifest_for(variation).reset!
|
113
|
+
end
|
114
|
+
|
115
|
+
reset_entries!
|
116
|
+
|
117
|
+
@is_resetting = false
|
118
|
+
|
119
|
+
return self
|
120
|
+
end
|
96
121
|
|
97
122
|
# Resets the manifest entries. this is called before a build is
|
98
123
|
# performed. This will reset only the entries, none of the other props.
|
@@ -102,6 +127,7 @@ module SC
|
|
102
127
|
#
|
103
128
|
def reset_entries!
|
104
129
|
@is_built = false
|
130
|
+
self.delete(:entries)
|
105
131
|
@entries = EntryList.new
|
106
132
|
return self
|
107
133
|
end
|
@@ -157,6 +157,11 @@ module SC
|
|
157
157
|
# make sure we update for changes in theme and required (commonly changed):
|
158
158
|
key << config[:theme].to_s if config[:theme] and opts[:theme]
|
159
159
|
key << config[:required].to_s if config[:required]
|
160
|
+
if opts[:modules]
|
161
|
+
key << config[:inlined_modules] if config[:inlined_modules]
|
162
|
+
key << config[:prefetched_modules] if config[:prefetched_modules]
|
163
|
+
key << config[:deferred_modules] if config[:deferred_modules]
|
164
|
+
end
|
160
165
|
|
161
166
|
key = key.compact.join('.')
|
162
167
|
|
@@ -166,7 +171,7 @@ module SC
|
|
166
171
|
return ret unless ret.nil?
|
167
172
|
|
168
173
|
# else compute return value, respecting options
|
169
|
-
ret = [config[:required]
|
174
|
+
ret = [config[:required]]
|
170
175
|
if opts[:debug] && config[:debug_required]
|
171
176
|
ret << config[:debug_required]
|
172
177
|
end
|
@@ -190,6 +195,12 @@ module SC
|
|
190
195
|
end
|
191
196
|
end
|
192
197
|
|
198
|
+
if opts[:modules]
|
199
|
+
ret << config[:inlined_modules]
|
200
|
+
ret << config[:prefetched_modules]
|
201
|
+
ret << config[:deferred_modules]
|
202
|
+
end
|
203
|
+
|
193
204
|
ret = ret.flatten.compact.map do |n|
|
194
205
|
if (t = target_for(n)).nil?
|
195
206
|
SC.logger.warn "Could not find target #{n} that is required by #{self[:target_name]}"
|
data/lib/sproutcore/rack.rb
CHANGED
@@ -14,299 +14,318 @@ rescue LoadError => e
|
|
14
14
|
SC::HTTPS_ENABLED = false
|
15
15
|
end
|
16
16
|
|
17
|
-
|
18
|
-
require '
|
19
|
-
require '
|
20
|
-
|
17
|
+
begin
|
18
|
+
require 'eventmachine'
|
19
|
+
require 'em-http'
|
20
|
+
require 'thin'
|
21
|
+
|
22
|
+
SC::PROXY_ENABLED = true
|
23
|
+
rescue LoadError => e
|
24
|
+
SC::PROXY_ENABLED = false
|
25
|
+
end
|
21
26
|
|
22
|
-
|
27
|
+
if SC::PROXY_ENABLED
|
28
|
+
# There are cases where we cannot load the proxy and don't need to (build environment)
|
29
|
+
|
30
|
+
module SC
|
23
31
|
|
24
|
-
|
32
|
+
module Rack
|
25
33
|
|
26
|
-
|
27
|
-
|
34
|
+
class DeferrableBody
|
35
|
+
include EM::Deferrable
|
28
36
|
|
29
|
-
|
30
|
-
|
31
|
-
|
37
|
+
def initialize(options = {})
|
38
|
+
@options = options
|
39
|
+
end
|
32
40
|
|
33
|
-
|
34
|
-
|
35
|
-
|
41
|
+
def call(body)
|
42
|
+
body.each do |chunk|
|
43
|
+
@body_callback.call(prepare_chunk(chunk))
|
44
|
+
end
|
36
45
|
end
|
37
|
-
end
|
38
46
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
47
|
+
def prepare_chunk(chunk)
|
48
|
+
if chunked?
|
49
|
+
size = chunk.respond_to?(:bytesize) ? chunk.bytesize : chunk.length
|
50
|
+
"#{size.to_s(16)}\r\n#{chunk}\r\n"
|
51
|
+
else
|
52
|
+
# Thin doesn't like null bodies
|
53
|
+
chunk || ''
|
54
|
+
end
|
46
55
|
end
|
47
|
-
end
|
48
56
|
|
49
|
-
|
50
|
-
|
51
|
-
|
57
|
+
def each(&blk)
|
58
|
+
@body_callback = blk
|
59
|
+
end
|
52
60
|
|
53
|
-
|
61
|
+
private
|
54
62
|
|
55
|
-
|
56
|
-
|
63
|
+
def chunked?
|
64
|
+
@options[:chunked]
|
65
|
+
end
|
57
66
|
end
|
58
|
-
end
|
59
67
|
|
60
68
|
|
61
69
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
def initialize(project)
|
66
|
-
@project = project
|
67
|
-
@proxies = project.buildfile.proxies
|
68
|
-
end
|
69
|
-
|
70
|
-
def call(env)
|
71
|
-
path = env['PATH_INFO']
|
72
|
-
|
73
|
-
@proxies.each do |proxy, value|
|
74
|
-
# If the url matches a proxied url, handle it
|
75
|
-
if path.match(/^#{Regexp.escape(proxy.to_s)}/)
|
76
|
-
handle_proxy(value, proxy.to_s, env)
|
70
|
+
# Rack application proxies requests as needed for the given project.
|
71
|
+
class Proxy
|
77
72
|
|
78
|
-
|
79
|
-
|
80
|
-
|
73
|
+
def initialize(project)
|
74
|
+
@project = project
|
75
|
+
@proxies = project.buildfile.proxies
|
81
76
|
end
|
82
77
|
|
83
|
-
|
84
|
-
|
78
|
+
def call(env)
|
79
|
+
path = env['PATH_INFO']
|
85
80
|
|
86
|
-
|
87
|
-
|
88
|
-
|
81
|
+
@proxies.each do |proxy, value|
|
82
|
+
# If the url matches a proxied url, handle it
|
83
|
+
if path.match(/^#{Regexp.escape(proxy.to_s)}/)
|
84
|
+
handle_proxy(value, proxy.to_s, env)
|
89
85
|
|
90
|
-
|
86
|
+
# Don't block waiting for a response
|
87
|
+
throw :async
|
88
|
+
end
|
89
|
+
end
|
91
90
|
|
92
|
-
|
93
|
-
SC.logger << "~ WARNING: HTTPS is not supported on your system, using HTTP instead.\n"
|
94
|
-
SC.logger << " If you are using Ubuntu, you can run `apt-get install libopenssl-ruby`\n"
|
95
|
-
proxy[:secure] = false
|
91
|
+
return [404, {}, "not found"]
|
96
92
|
end
|
97
93
|
|
98
|
-
|
99
|
-
|
100
|
-
|
94
|
+
def chunked?(headers)
|
95
|
+
headers['Transfer-Encoding'] == "chunked"
|
96
|
+
end
|
101
97
|
|
102
|
-
|
103
|
-
protocol = proxy[:secure] ? 'https' : 'http'
|
98
|
+
def handle_proxy(proxy, proxy_url, env)
|
104
99
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
100
|
+
if proxy[:secure] && !SC::HTTPS_ENABLED
|
101
|
+
SC.logger << "~ WARNING: HTTPS is not supported on your system, using HTTP instead.\n"
|
102
|
+
SC.logger << " If you are using Ubuntu, you can run `apt-get install libopenssl-ruby`\n"
|
103
|
+
proxy[:secure] = false
|
104
|
+
end
|
109
105
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
url += '?' + params unless params.empty?
|
106
|
+
headers = request_headers(env, proxy) # ex. {"Host"=>"localhost:4020", "Connection"=>"...
|
107
|
+
path = env['PATH_INFO'] # ex. /contacts
|
108
|
+
params = env['QUERY_STRING'] # ex. since=yesterday&unread=true
|
114
109
|
|
115
|
-
|
116
|
-
|
117
|
-
req_body.rewind # May not be necessary but can't hurt
|
110
|
+
# Switch to https if proxy[:secure] configured
|
111
|
+
protocol = proxy[:secure] ? 'https' : 'http'
|
118
112
|
|
119
|
-
|
120
|
-
|
113
|
+
# Adjust the path if proxy[:url] configured
|
114
|
+
if proxy[:url]
|
115
|
+
path = path.sub(/^#{Regexp.escape proxy_url}/, proxy[:url])
|
116
|
+
end
|
121
117
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
request_options[:timeout] = proxy[:timeout] if proxy[:timeout]
|
127
|
-
request_options[:redirects] = 10 if proxy[:redirect] != false
|
128
|
-
|
129
|
-
EventMachine.run {
|
130
|
-
body = nil
|
131
|
-
conn = EM::HttpRequest.new(url)
|
132
|
-
chunked = false
|
133
|
-
headers = {}
|
134
|
-
method = env['REQUEST_METHOD'].upcase
|
135
|
-
status = 0
|
136
|
-
|
137
|
-
case method
|
138
|
-
when 'GET'
|
139
|
-
http = conn.get request_options
|
140
|
-
when 'POST'
|
141
|
-
http = conn.post request_options
|
142
|
-
when 'PUT'
|
143
|
-
http = conn.put request_options
|
144
|
-
when 'DELETE'
|
145
|
-
http = conn.delete request_options
|
146
|
-
else
|
147
|
-
http = conn.head request_options
|
148
|
-
end
|
118
|
+
# The endpoint URL
|
119
|
+
url = protocol + '://' + proxy[:to]
|
120
|
+
url += path unless path.empty?
|
121
|
+
url += '?' + params unless params.empty?
|
149
122
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
path = env['PATH_INFO']
|
154
|
-
url = http.last_effective_url
|
155
|
-
SC.logger << "~ PROXY FAILED: #{method} #{path} -> #{status} #{url}\n"
|
156
|
-
|
157
|
-
# If a body has been sent use it, otherwise respond with generic message
|
158
|
-
if !body
|
159
|
-
body = "Unable to proxy to #{url}. Received status: #{status}"
|
160
|
-
size = body.respond_to?(:bytesize) ? body.bytesize : body.length
|
161
|
-
headers = { 'Content-Length' => size.to_s }
|
162
|
-
body = [body]
|
163
|
-
end
|
123
|
+
if env['CONTENT_LENGTH'] || env['HTTP_TRANSFER_ENCODING']
|
124
|
+
req_body = env['rack.input']
|
125
|
+
req_body.rewind # May not be necessary but can't hurt
|
164
126
|
|
165
|
-
|
166
|
-
|
127
|
+
req_body = req_body.read
|
128
|
+
end
|
167
129
|
|
168
|
-
#
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
130
|
+
# Options for the request
|
131
|
+
request_options = {}
|
132
|
+
request_options[:head] = headers
|
133
|
+
request_options[:body] = req_body if !!req_body
|
134
|
+
request_options[:timeout] = proxy[:timeout] if proxy[:timeout]
|
135
|
+
request_options[:redirects] = 10 if proxy[:redirect] != false
|
136
|
+
request_options[:decoding] = false # don't decode gzipped content
|
137
|
+
|
138
|
+
EventMachine.run {
|
139
|
+
body = nil
|
140
|
+
conn = EM::HttpRequest.new(url)
|
141
|
+
chunked = false
|
142
|
+
headers = {}
|
143
|
+
method = env['REQUEST_METHOD'].upcase
|
144
|
+
status = 0
|
145
|
+
|
146
|
+
case method
|
147
|
+
when 'GET'
|
148
|
+
http = conn.get request_options
|
149
|
+
when 'POST'
|
150
|
+
http = conn.post request_options
|
151
|
+
when 'PUT'
|
152
|
+
http = conn.put request_options
|
153
|
+
when 'DELETE'
|
154
|
+
http = conn.delete request_options
|
155
|
+
else
|
156
|
+
http = conn.head request_options
|
183
157
|
end
|
184
158
|
|
185
|
-
|
159
|
+
# Received error
|
160
|
+
http.errback {
|
161
|
+
status = http.response_header.status
|
186
162
|
path = env['PATH_INFO']
|
187
163
|
url = http.last_effective_url
|
188
|
-
SC.logger << "~ PROXY:
|
189
|
-
end
|
190
|
-
}
|
164
|
+
SC.logger << "~ PROXY FAILED: #{method} #{path} -> #{status} #{url}\n"
|
191
165
|
|
192
|
-
|
193
|
-
|
194
|
-
|
166
|
+
# If a body has been sent use it, otherwise respond with generic message
|
167
|
+
if !body
|
168
|
+
body = "Unable to proxy to #{url}. Received status: #{status}"
|
169
|
+
size = body.respond_to?(:bytesize) ? body.bytesize : body.length
|
170
|
+
headers = { 'Content-Length' => size.to_s }
|
171
|
+
body = [body]
|
172
|
+
end
|
195
173
|
|
196
|
-
|
174
|
+
env['async.callback'].call [502, headers, body]
|
175
|
+
}
|
197
176
|
|
198
|
-
#
|
199
|
-
|
177
|
+
# Received response
|
178
|
+
http.callback {
|
200
179
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
elsif !headers['Location']
|
205
|
-
body = "Unable to proxy to #{url}. Received redirect with no Location."
|
180
|
+
# Too many redirects
|
181
|
+
if redirect? status
|
182
|
+
body = "Unable to proxy to #{url}. Too many redirects."
|
206
183
|
size = body.respond_to?(:bytesize) ? body.bytesize : body.length
|
207
184
|
headers = { 'Content-Length' => size.to_s }
|
208
185
|
|
209
|
-
|
186
|
+
env['async.callback'].call [502, headers, [body]]
|
187
|
+
else
|
188
|
+
# Terminate the deferred body (which may have been chunked)
|
189
|
+
if body
|
190
|
+
body.call ['']
|
191
|
+
body.succeed
|
192
|
+
end
|
193
|
+
|
194
|
+
# Log the initial path and the final url
|
195
|
+
path = env['PATH_INFO']
|
196
|
+
url = http.last_effective_url
|
197
|
+
SC.logger << "~ PROXY: #{method} #{path} -> #{status} #{url}\n"
|
198
|
+
end
|
199
|
+
}
|
200
|
+
|
201
|
+
# Received headers
|
202
|
+
http.headers { |hash|
|
203
|
+
status = http.response_header.status
|
204
|
+
|
205
|
+
headers = response_headers(hash)
|
206
|
+
|
207
|
+
# Don't respond on redirection, but fail out on bad redirects
|
208
|
+
if redirect? status
|
209
|
+
|
210
|
+
if status == 304
|
211
|
+
env["async.callback"].call [status, headers, ['']]
|
212
|
+
SC.logger << "~ PROXY: #{method} #{path} -> #{status} #{url}\n"
|
213
|
+
elsif !headers['Location']
|
214
|
+
body = "Unable to proxy to #{url}. Received redirect with no Location."
|
215
|
+
size = body.respond_to?(:bytesize) ? body.bytesize : body.length
|
216
|
+
headers = { 'Content-Length' => size.to_s }
|
217
|
+
|
218
|
+
http.close
|
219
|
+
end
|
220
|
+
|
221
|
+
else
|
222
|
+
# Stream the body right across in the format it was sent
|
223
|
+
chunked = chunked?(headers)
|
224
|
+
body = DeferrableBody.new({ :chunked => chunked })
|
225
|
+
|
226
|
+
# Start responding to the client immediately
|
227
|
+
env["async.callback"].call [status, headers, body]
|
210
228
|
end
|
229
|
+
}
|
211
230
|
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
231
|
+
# Received chunk of data
|
232
|
+
http.stream { |chunk|
|
233
|
+
# Ignore body of redirects
|
234
|
+
if !redirect? status
|
235
|
+
body.call [chunk]
|
236
|
+
end
|
237
|
+
}
|
216
238
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
239
|
+
# If the client disconnects early, make sure we close our other connection too
|
240
|
+
# TODO: this is waiting for changes not yet available in em-http
|
241
|
+
# Test with: curl http://0.0.0.0:4020/stream.twitter.com/1/statuses/sample.json -uTWITTER_USERNAME:TWITTER_PASSWORD
|
242
|
+
# env["async.close"].callback {
|
243
|
+
# conn.close
|
244
|
+
# }
|
221
245
|
|
222
|
-
# Received chunk of data
|
223
|
-
http.stream { |chunk|
|
224
|
-
# Ignore body of redirects
|
225
|
-
if !redirect? status
|
226
|
-
body.call [chunk]
|
227
|
-
end
|
228
246
|
}
|
229
|
-
|
230
|
-
end
|
247
|
+
end
|
231
248
|
|
232
|
-
|
233
|
-
|
234
|
-
|
249
|
+
def redirect?(status)
|
250
|
+
status >= 300 && status < 400
|
251
|
+
end
|
235
252
|
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
253
|
+
# collect headers...
|
254
|
+
def request_headers(env, proxy)
|
255
|
+
result = {}
|
256
|
+
env.each do |key, value|
|
257
|
+
next unless key =~ /^HTTP_/
|
241
258
|
|
242
|
-
|
243
|
-
|
244
|
-
|
259
|
+
key = headerize(key)
|
260
|
+
if !key.eql? "Version"
|
261
|
+
result[key] = value
|
262
|
+
end
|
245
263
|
end
|
246
|
-
end
|
247
264
|
|
248
|
-
|
249
|
-
|
265
|
+
# Rack documentation says CONTENT_TYPE and CONTENT_LENGTH aren't prefixed by HTTP_
|
266
|
+
result['Content-Type'] = env['CONTENT_TYPE'] if env['CONTENT_TYPE']
|
250
267
|
|
251
|
-
|
252
|
-
|
268
|
+
length = env['CONTENT_LENGTH']
|
269
|
+
result['Content-Length'] = length if length
|
253
270
|
|
254
|
-
|
255
|
-
|
256
|
-
|
271
|
+
# added 4/23/09 per Charles Jolley, corrects problem
|
272
|
+
# when making requests to virtual hosts
|
273
|
+
result['Host'] = proxy[:to]
|
257
274
|
|
258
|
-
|
259
|
-
|
275
|
+
result
|
276
|
+
end
|
260
277
|
|
261
|
-
|
262
|
-
|
263
|
-
|
278
|
+
# construct and display specific response headers
|
279
|
+
def response_headers(hash)
|
280
|
+
result = {}
|
264
281
|
|
265
|
-
|
266
|
-
|
282
|
+
hash.each do |key, value|
|
283
|
+
key = headerize(key)
|
267
284
|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
285
|
+
# Because Set-Cookie header can appear more the once in the response body,
|
286
|
+
# but Rack only accepts a hash of headers, we store it in a line break separated string
|
287
|
+
# for Ruby 1.9 and as an Array for Ruby 1.8
|
288
|
+
# See http://groups.google.com/group/rack-devel/browse_thread/thread/e8759b91a82c5a10/a8dbd4574fe97d69?#a8dbd4574fe97d69
|
289
|
+
if key.downcase == 'set-cookie'
|
290
|
+
cookies = []
|
274
291
|
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
292
|
+
case value
|
293
|
+
when Array then value.each { |c| cookies << strip_domain(c) }
|
294
|
+
when Hash then value.each { |_, c| cookies << strip_domain(c) }
|
295
|
+
else cookies << strip_domain(value)
|
296
|
+
end
|
280
297
|
|
281
|
-
|
282
|
-
|
298
|
+
# Remove nil values
|
299
|
+
result['Set-Cookie'] = [result['Set-Cookie'], cookies].compact
|
283
300
|
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
301
|
+
if Thin.ruby_18?
|
302
|
+
result['Set-Cookie'].flatten!
|
303
|
+
else
|
304
|
+
result['Set-Cookie'] = result['Set-Cookie'].join("\n")
|
305
|
+
end
|
288
306
|
end
|
307
|
+
|
308
|
+
SC.logger << " #{key}: #{value}\n"
|
309
|
+
result[key] = value
|
289
310
|
end
|
290
311
|
|
291
|
-
|
292
|
-
result[key] = value
|
312
|
+
::Rack::Utils::HeaderHash.new(result)
|
293
313
|
end
|
294
314
|
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
parts = str.gsub(/^HTTP_/, '').split('_')
|
301
|
-
parts.map! { |p| p.capitalize }.join('-')
|
302
|
-
end
|
315
|
+
# remove HTTP_, dasherize and titleize
|
316
|
+
def headerize(str)
|
317
|
+
parts = str.gsub(/^HTTP_/, '').split('_')
|
318
|
+
parts.map! { |p| p.capitalize }.join('-')
|
319
|
+
end
|
303
320
|
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
321
|
+
# Strip out the domain of passed in cookie. This technically may
|
322
|
+
# break certain scenarios where services try to set cross-domain
|
323
|
+
# cookies, but those services should not be doing that anyway...
|
324
|
+
def strip_domain(cookie)
|
325
|
+
cookie.to_s.gsub!(/domain=[^\;]+\;? ?/,'')
|
326
|
+
end
|
309
327
|
end
|
310
328
|
end
|
311
329
|
end
|
312
|
-
|
330
|
+
|
331
|
+
end
|