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