sproutcore 1.6.0.1-java → 1.7.1.beta-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (143) hide show
  1. data/CHANGELOG +21 -0
  2. data/Gemfile +5 -0
  3. data/Rakefile +26 -13
  4. data/VERSION.yml +2 -2
  5. data/lib/Buildfile +43 -4
  6. data/lib/buildtasks/build.rake +10 -0
  7. data/lib/buildtasks/helpers/file_rule.rb +22 -0
  8. data/lib/buildtasks/helpers/file_rule_list.rb +137 -0
  9. data/lib/buildtasks/manifest.rake +133 -122
  10. data/lib/frameworks/sproutcore/CHANGELOG.md +69 -2
  11. data/lib/frameworks/sproutcore/apps/tests/english.lproj/strings.js +1 -0
  12. data/lib/frameworks/sproutcore/frameworks/bootstrap/system/browser.js +28 -22
  13. data/lib/frameworks/sproutcore/frameworks/core_foundation/controllers/array.js +9 -5
  14. data/lib/frameworks/sproutcore/frameworks/core_foundation/controllers/controller.js +1 -1
  15. data/lib/frameworks/sproutcore/frameworks/core_foundation/controls/button.js +18 -13
  16. data/lib/frameworks/sproutcore/frameworks/core_foundation/ext/handlebars/bind.js +5 -3
  17. data/lib/frameworks/sproutcore/frameworks/core_foundation/ext/handlebars/collection.js +2 -0
  18. data/lib/frameworks/sproutcore/frameworks/core_foundation/mixins/action_support.js +80 -0
  19. data/lib/frameworks/sproutcore/frameworks/core_foundation/mixins/template_helpers/text_field_support.js +84 -116
  20. data/lib/frameworks/sproutcore/frameworks/core_foundation/panes/pane.js +8 -5
  21. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/event.js +157 -157
  22. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/platform.js +5 -3
  23. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/root_responder.js +6 -6
  24. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/sparse_array.js +10 -7
  25. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/mixins/action_support.js +106 -0
  26. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/template/collection.js +18 -0
  27. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/template/handlebars.js +71 -1
  28. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/attribute_bindings_test.js +38 -0
  29. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/class_name_bindings_test.js +47 -0
  30. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layoutChildViews.js +18 -18
  31. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layoutStyle.js +42 -10
  32. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view.js +158 -1
  33. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/keyboard.js +26 -1
  34. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/layout_style.js +14 -8
  35. data/lib/frameworks/sproutcore/frameworks/datastore/models/record.js +15 -2
  36. data/lib/frameworks/sproutcore/frameworks/datastore/models/record_attribute.js +108 -108
  37. data/lib/frameworks/sproutcore/frameworks/datastore/system/query.js +1 -1
  38. data/lib/frameworks/sproutcore/frameworks/datastore/system/record_array.js +2 -4
  39. data/lib/frameworks/sproutcore/frameworks/datastore/tests/models/record/error_methods.js +2 -2
  40. data/lib/frameworks/sproutcore/frameworks/datastore/tests/models/single_attribute.js +26 -0
  41. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/query/builders.js +7 -0
  42. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/record_array/error_methods.js +1 -1
  43. data/lib/frameworks/sproutcore/frameworks/datetime/frameworks/core/system/datetime.js +4 -1
  44. data/lib/frameworks/sproutcore/frameworks/datetime/frameworks/core/tests/system/datetime.js +6 -0
  45. data/lib/frameworks/sproutcore/frameworks/desktop/panes/menu.js +26 -5
  46. data/lib/frameworks/sproutcore/frameworks/desktop/panes/picker.js +97 -96
  47. data/lib/frameworks/sproutcore/frameworks/desktop/system/drag.js +4 -3
  48. data/lib/frameworks/sproutcore/frameworks/desktop/tests/panes/menu/ui.js +17 -4
  49. data/lib/frameworks/sproutcore/frameworks/desktop/views/collection.js +7 -7
  50. data/lib/frameworks/sproutcore/frameworks/desktop/views/menu_item.js +7 -5
  51. data/lib/frameworks/sproutcore/frameworks/desktop/views/scroll.js +12 -3
  52. data/lib/frameworks/sproutcore/frameworks/desktop/views/web.js +23 -14
  53. data/lib/frameworks/sproutcore/frameworks/experimental/Buildfile +5 -1
  54. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/menu/render_delegates/menu_scroller.js +28 -0
  55. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/menu/tests/menu/scroll.js +235 -0
  56. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/menu/views/menu/scroll.js +363 -0
  57. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/menu/views/menu/scroller.js +250 -0
  58. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/render_delegates/desktop_scroller.js +92 -0
  59. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/render_delegates/native_scroll.js +25 -0
  60. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/render_delegates/scroll.js +33 -0
  61. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/render_delegates/touch_scroller.js +76 -0
  62. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/tests/scroll/integration.js +50 -0
  63. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/tests/scroll/methods.js +143 -0
  64. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/tests/scroll/ui.js +258 -0
  65. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/core_scroll.js +1164 -0
  66. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/core_scroller.js +332 -0
  67. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/desktop/scroll.js +236 -0
  68. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/desktop/scroller.js +347 -0
  69. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/scroll.js +15 -0
  70. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/scroller.js +10 -0
  71. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/touch/scroll.js +804 -0
  72. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/touch/scroller.js +133 -0
  73. data/lib/frameworks/sproutcore/frameworks/foundation/resources/text_field.css +3 -3
  74. data/lib/frameworks/sproutcore/frameworks/foundation/validators/number.js +3 -1
  75. data/lib/frameworks/sproutcore/frameworks/foundation/views/text_field.js +3 -3
  76. data/lib/frameworks/sproutcore/frameworks/media/views/audio.js +2 -1
  77. data/lib/frameworks/sproutcore/frameworks/media/views/controls.js +2 -1
  78. data/lib/frameworks/sproutcore/frameworks/media/views/media_slider.js +2 -4
  79. data/lib/frameworks/sproutcore/frameworks/media/views/mini_controls.js +2 -4
  80. data/lib/frameworks/sproutcore/frameworks/media/views/simple_controls.js +2 -4
  81. data/lib/frameworks/sproutcore/frameworks/media/views/video.js +2 -2
  82. data/lib/frameworks/sproutcore/frameworks/routing/system/routes.js +29 -3
  83. data/lib/frameworks/sproutcore/frameworks/runtime/core.js +2 -2
  84. data/lib/frameworks/sproutcore/frameworks/runtime/debug/test_suites/array/replace.js +1 -1
  85. data/lib/frameworks/sproutcore/frameworks/runtime/private/property_chain.js +2 -1
  86. data/lib/frameworks/sproutcore/frameworks/runtime/system/binding.js +3 -3
  87. data/lib/frameworks/sproutcore/frameworks/runtime/system/index_set.js +2 -2
  88. data/lib/frameworks/sproutcore/frameworks/runtime/system/object.js +1 -1
  89. data/lib/frameworks/sproutcore/themes/ace/resources/collection/normal/list_item.css +2 -2
  90. data/lib/frameworks/sproutcore/themes/legacy_theme/english.lproj/segmented.css +1 -1
  91. data/lib/gen/app/templates/apps/@target_name@/Buildfile +3 -5
  92. data/lib/gen/app/templates/apps/@target_name@/resources/_theme.css +18 -0
  93. data/lib/gen/project/templates/@filename@/Buildfile +2 -2
  94. data/lib/sproutcore.rb +30 -5
  95. data/lib/sproutcore/builders.rb +1 -0
  96. data/lib/sproutcore/builders/chance_file.rb +9 -16
  97. data/lib/sproutcore/builders/html.rb +2 -1
  98. data/lib/sproutcore/builders/minify.rb +4 -35
  99. data/lib/sproutcore/builders/module.rb +38 -1
  100. data/lib/sproutcore/builders/split.rb +63 -0
  101. data/lib/sproutcore/builders/strings.rb +7 -1
  102. data/lib/sproutcore/helpers.rb +1 -1
  103. data/lib/sproutcore/helpers/css_split.rb +190 -0
  104. data/lib/sproutcore/helpers/entry_sorter.rb +2 -0
  105. data/lib/sproutcore/helpers/minifier.rb +40 -16
  106. data/lib/sproutcore/helpers/static_helper.rb +35 -17
  107. data/lib/sproutcore/models/manifest.rb +26 -0
  108. data/lib/sproutcore/models/target.rb +12 -1
  109. data/lib/sproutcore/rack.rb +1 -0
  110. data/lib/sproutcore/rack/proxy.rb +244 -225
  111. data/lib/sproutcore/rack/restrict_ip.rb +67 -0
  112. data/lib/sproutcore/rack/service.rb +8 -2
  113. data/lib/sproutcore/tools.rb +102 -46
  114. data/lib/sproutcore/tools/build.rb +91 -43
  115. data/lib/sproutcore/tools/gen.rb +2 -3
  116. data/lib/sproutcore/tools/manifest.rb +22 -16
  117. data/lib/sproutcore/tools/server.rb +21 -0
  118. data/spec/buildtasks/helpers/accept_list +22 -0
  119. data/spec/buildtasks/helpers/accept_list.rb +128 -0
  120. data/spec/buildtasks/helpers/list.json +11 -0
  121. data/spec/buildtasks/manifest/prepare_build_tasks/chance_2x_spec.rb +1 -39
  122. data/spec/buildtasks/manifest/prepare_build_tasks/chance_spec.rb +0 -38
  123. data/spec/buildtasks/manifest/prepare_build_tasks/combine_spec.rb +4 -4
  124. data/spec/buildtasks/manifest/prepare_build_tasks/module_spec.rb +2 -2
  125. data/spec/buildtasks/manifest/prepare_build_tasks/packed_2x_indirect_spec.rb +7 -16
  126. data/spec/buildtasks/manifest/prepare_build_tasks/packed_2x_spec.rb +7 -17
  127. data/spec/buildtasks/manifest/prepare_build_tasks/packed_spec.rb +11 -6
  128. data/spec/fixtures/builder_tests/Buildfile +2 -1
  129. data/spec/fixtures/builder_tests/apps/module_test/modules/required_module/core.js +0 -0
  130. data/spec/lib/builders/module_spec.rb +1 -1
  131. data/spec/spec_helper.rb +1 -0
  132. data/sproutcore.gemspec +4 -9
  133. data/vendor/chance/lib/chance.rb +25 -6
  134. data/vendor/chance/lib/chance/factory.rb +45 -0
  135. data/vendor/chance/lib/chance/instance.rb +173 -28
  136. data/vendor/chance/lib/chance/instance/data_url.rb +0 -29
  137. data/vendor/chance/lib/chance/instance/slicing.rb +57 -4
  138. data/vendor/chance/lib/chance/instance/spriting.rb +112 -21
  139. data/vendor/chance/lib/chance/parser.rb +80 -52
  140. data/vendor/sproutcore/SCCompiler.jar +0 -0
  141. data/vendor/sproutcore/lib/args4j-2.0.12.jar +0 -0
  142. data/vendor/sproutcore/lib/yuicompressor-2.4.2.jar +0 -0
  143. metadata +84 -25
@@ -72,6 +72,8 @@ module SC
72
72
  [1, name]
73
73
  when /lproj\/strings.js$/
74
74
  [-2, name]
75
+ when /lproj\/layout.js$/
76
+ [-2, name]
75
77
  else
76
78
  [-1, name]
77
79
  end
@@ -80,26 +80,50 @@ module SC::Helpers
80
80
  end
81
81
 
82
82
  def minify(paths)
83
- paths = paths.first if paths.is_a?(Array) && paths.length == 1
84
-
85
- if paths.is_a?(Array)
86
- paths = paths.map{|p| '"'+p+'"'}.join(' ')
87
- outfile = '".js$:.js"'
88
- else
89
- paths = outfile = '"'+paths+'"'
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
- command = %{java -Xmx256m -jar "#{SC.yui_jar}" -o #{outfile} #{paths} 2>&1}
93
- output = `#{command}`
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.extensionless_filename
81
+ name = cur_entry.filename
83
82
 
84
- if name == "stylesheet"
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 = t.manifest_for(v).entry_for name
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
- yield(t, entry)
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], config[:inlined_modules]]
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]}"
@@ -2,5 +2,6 @@ require "sproutcore/rack/builder"
2
2
  require "sproutcore/rack/dev"
3
3
  require "sproutcore/rack/filesystem"
4
4
  require "sproutcore/rack/proxy"
5
+ require 'sproutcore/rack/restrict_ip'
5
6
  require "sproutcore/rack/service"
6
7
  require "sproutcore/rack/test_runner"
@@ -14,299 +14,318 @@ rescue LoadError => e
14
14
  SC::HTTPS_ENABLED = false
15
15
  end
16
16
 
17
- require 'eventmachine'
18
- require 'em-http'
19
- require 'thin'
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
- module SC
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
- module Rack
32
+ module Rack
25
33
 
26
- class DeferrableBody
27
- include EM::Deferrable
34
+ class DeferrableBody
35
+ include EM::Deferrable
28
36
 
29
- def initialize(options = {})
30
- @options = options
31
- end
37
+ def initialize(options = {})
38
+ @options = options
39
+ end
32
40
 
33
- def call(body)
34
- body.each do |chunk|
35
- @body_callback.call(prepare_chunk(chunk))
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
- def prepare_chunk(chunk)
40
- if chunked?
41
- size = chunk.respond_to?(:bytesize) ? chunk.bytesize : chunk.length
42
- "#{size.to_s(16)}\r\n#{chunk}\r\n"
43
- else
44
- # Thin doesn't like null bodies
45
- chunk || ''
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
- def each(&blk)
50
- @body_callback = blk
51
- end
57
+ def each(&blk)
58
+ @body_callback = blk
59
+ end
52
60
 
53
- private
61
+ private
54
62
 
55
- def chunked?
56
- @options[:chunked]
63
+ def chunked?
64
+ @options[:chunked]
65
+ end
57
66
  end
58
- end
59
67
 
60
68
 
61
69
 
62
- # Rack application proxies requests as needed for the given project.
63
- class Proxy
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
- # Don't block waiting for a response
79
- throw :async
80
- end
73
+ def initialize(project)
74
+ @project = project
75
+ @proxies = project.buildfile.proxies
81
76
  end
82
77
 
83
- return [404, {}, "not found"]
84
- end
78
+ def call(env)
79
+ path = env['PATH_INFO']
85
80
 
86
- def chunked?(headers)
87
- headers['Transfer-Encoding'] == "chunked"
88
- end
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
- def handle_proxy(proxy, proxy_url, env)
86
+ # Don't block waiting for a response
87
+ throw :async
88
+ end
89
+ end
91
90
 
92
- if proxy[:secure] && !SC::HTTPS_ENABLED
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
- headers = request_headers(env, proxy) # ex. {"Host"=>"localhost:4020", "Connection"=>"...
99
- path = env['PATH_INFO'] # ex. /contacts
100
- params = env['QUERY_STRING'] # ex. since=yesterday&unread=true
94
+ def chunked?(headers)
95
+ headers['Transfer-Encoding'] == "chunked"
96
+ end
101
97
 
102
- # Switch to https if proxy[:secure] configured
103
- protocol = proxy[:secure] ? 'https' : 'http'
98
+ def handle_proxy(proxy, proxy_url, env)
104
99
 
105
- # Adjust the path if proxy[:url] configured
106
- if proxy[:url]
107
- path = path.sub(/^#{Regexp.escape proxy_url}/, proxy[:url])
108
- end
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
- # The endpoint URL
111
- url = protocol + '://' + proxy[:to]
112
- url += path unless path.empty?
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
- if env['CONTENT_LENGTH'] || env['HTTP_TRANSFER_ENCODING']
116
- req_body = env['rack.input']
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
- req_body = req_body.read
120
- end
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
- # Options for the request
123
- request_options = {}
124
- request_options[:head] = headers
125
- request_options[:body] = req_body if !!req_body
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
- # Received error
151
- http.errback {
152
- status = http.response_header.status
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
- env['async.callback'].call [502, headers, body]
166
- }
127
+ req_body = req_body.read
128
+ end
167
129
 
168
- # Received response
169
- http.callback {
170
-
171
- # Too many redirects
172
- if redirect? status
173
- body = "Unable to proxy to #{url}. Too many redirects."
174
- size = body.respond_to?(:bytesize) ? body.bytesize : body.length
175
- headers = { 'Content-Length' => size.to_s }
176
-
177
- env['async.callback'].call [502, headers, [body]]
178
- else
179
- # Terminate the deferred body (which may have been chunked)
180
- if body
181
- body.call ['']
182
- body.succeed
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
- # Log the initial path and the final url
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: #{method} #{path} -> #{status} #{url}\n"
189
- end
190
- }
164
+ SC.logger << "~ PROXY FAILED: #{method} #{path} -> #{status} #{url}\n"
191
165
 
192
- # Received headers
193
- http.headers { |hash|
194
- status = http.response_header.status
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
- headers = response_headers(hash)
174
+ env['async.callback'].call [502, headers, body]
175
+ }
197
176
 
198
- # Don't respond on redirection, but fail out on bad redirects
199
- if redirect? status
177
+ # Received response
178
+ http.callback {
200
179
 
201
- if status == 304
202
- env["async.callback"].call [status, headers, ['']]
203
- SC.logger << "~ PROXY: #{method} #{path} -> #{status} #{url}\n"
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
- http.close
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
- else
213
- # Stream the body right across in the format it was sent
214
- chunked = chunked?(headers)
215
- body = DeferrableBody.new({ :chunked => chunked })
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
- # Start responding to the client immediately
218
- env["async.callback"].call [status, headers, body]
219
- end
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
- def redirect?(status)
233
- status >= 300 && status < 400
234
- end
249
+ def redirect?(status)
250
+ status >= 300 && status < 400
251
+ end
235
252
 
236
- # collect headers...
237
- def request_headers(env, proxy)
238
- result = {}
239
- env.each do |key, value|
240
- next unless key =~ /^HTTP_/
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
- key = headerize(key)
243
- if !key.eql? "Version"
244
- result[key] = value
259
+ key = headerize(key)
260
+ if !key.eql? "Version"
261
+ result[key] = value
262
+ end
245
263
  end
246
- end
247
264
 
248
- # Rack documentation says CONTENT_TYPE and CONTENT_LENGTH aren't prefixed by HTTP_
249
- result['Content-Type'] = env['CONTENT_TYPE'] if env['CONTENT_TYPE']
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
- length = env['CONTENT_LENGTH']
252
- result['Content-Length'] = length if length
268
+ length = env['CONTENT_LENGTH']
269
+ result['Content-Length'] = length if length
253
270
 
254
- # added 4/23/09 per Charles Jolley, corrects problem
255
- # when making requests to virtual hosts
256
- result['Host'] = proxy[:to]
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
- result
259
- end
275
+ result
276
+ end
260
277
 
261
- # construct and display specific response headers
262
- def response_headers(hash)
263
- result = {}
278
+ # construct and display specific response headers
279
+ def response_headers(hash)
280
+ result = {}
264
281
 
265
- hash.each do |key, value|
266
- key = headerize(key)
282
+ hash.each do |key, value|
283
+ key = headerize(key)
267
284
 
268
- # Because Set-Cookie header can appear more the once in the response body,
269
- # but Rack only accepts a hash of headers, we store it in a line break separated string
270
- # for Ruby 1.9 and as an Array for Ruby 1.8
271
- # See http://groups.google.com/group/rack-devel/browse_thread/thread/e8759b91a82c5a10/a8dbd4574fe97d69?#a8dbd4574fe97d69
272
- if key.downcase == 'set-cookie'
273
- cookies = []
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
- case value
276
- when Array then value.each { |c| cookies << strip_domain(c) }
277
- when Hash then value.each { |_, c| cookies << strip_domain(c) }
278
- else cookies << strip_domain(value)
279
- end
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
- # Remove nil values
282
- result['Set-Cookie'] = [result['Set-Cookie'], cookies].compact
298
+ # Remove nil values
299
+ result['Set-Cookie'] = [result['Set-Cookie'], cookies].compact
283
300
 
284
- if Thin.ruby_18?
285
- result['Set-Cookie'].flatten!
286
- else
287
- result['Set-Cookie'] = result['Set-Cookie'].join("\n")
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
- SC.logger << " #{key}: #{value}\n"
292
- result[key] = value
312
+ ::Rack::Utils::HeaderHash.new(result)
293
313
  end
294
314
 
295
- ::Rack::Utils::HeaderHash.new(result)
296
- end
297
-
298
- # remove HTTP_, dasherize and titleize
299
- def headerize(str)
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
- # Strip out the domain of passed in cookie. This technically may
305
- # break certain scenarios where services try to set cross-domain
306
- # cookies, but those services should not be doing that anyway...
307
- def strip_domain(cookie)
308
- cookie.to_s.gsub!(/domain=[^\;]+\;? ?/,'')
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
- end
330
+
331
+ end