fluther 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2009 Plataforma Tecnologia. http://blog.plataformatec.com.br
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1,104 @@
1
+ h1. Fluther Ruby Client
2
+
3
+ h2. Introduction
4
+
5
+ This gem provides an interface to the "Fluther discussion service":http://www.fluther.com/. It is implemented as a piece of Rack
6
+ middleware which handles proxying requests to Fluther and returning the response so that it can be included in your web application.
7
+ While it should be usable with any Rack-based application, these docs assume you are embedding Fluther in a Rails 2.x application.
8
+
9
+ Requirements:
10
+ * Rack
11
+ * Warden
12
+ * Thin (optional)
13
+
14
+ h2. Installation
15
+
16
+ * Add the @fluther@ gem to your application (i.e. in @Gemfile@ or @environment.rb@).
17
+
18
+ * Create an initializer (e.g. @config/initializers/fluther.rb@) that inserts the Fluther proxy after the Warden module,
19
+ mounting it on the appropriate path:
20
+
21
+ <pre>
22
+ Rails.configuration.after_initialize do
23
+ Rails.configuration.middleware.insert_after Warden::Manager, Fluther::Proxy, '/qna'
24
+ end
25
+ </pre>
26
+
27
+ * In the environment or initializer, add the Fluther configuration:
28
+
29
+ <pre>
30
+ class Fluther::Config
31
+ # hostname of the Fluther server for this environment (provided by Fluther)
32
+ fluther_host 'fstage.fluther.com'
33
+
34
+ # federated API key (provided by Fluther)
35
+ app_key '2b6a0009c414c53e3d4fa8f8c3134d59'
36
+
37
+ # mapping of attributes in the User model to the Fluther user
38
+ user_fields :id => :id, :name => :name, :email => :email # (defaults)
39
+ end
40
+ </pre>
41
+
42
+
43
+ h2. Rails Integration
44
+
45
+ The proxy provides three Rack variables that include the Fluther response: @fluther.header@, @fluther.title@, and
46
+ @fluther.response@. The first two (may) contain HTML blocks which should be inserted into the page @<head>@ and @<title>@ blocks,
47
+ respectively, and the third is the HTML for the Fluther widget itself.
48
+
49
+ To integrate the response into your application, you should add an action which is routed from the same path as the Fluther proxy.
50
+ For this example, we assume the controller is @MainController@, the action is @fluther@, and as above, it is mounted at @/qna@.
51
+ Also, we assume that the application layout includes @yield(:head)@ in the @<head>@ block:
52
+
53
+ <pre>
54
+ # config/routes.rb
55
+ map.fluther '/qna/*_', :controller => 'main', :action => 'fluther'
56
+ </pre>
57
+
58
+ <pre>
59
+ # app/views/main/fluther.html.erb
60
+ <%
61
+ if (header = request.env['fluther.header']).present?
62
+ content_for :head, header
63
+ end
64
+ if (title = request.env['fluther.title']).present?
65
+ content_for :head, content_tag(:title, title)
66
+ end
67
+ %>
68
+ <%= request.env['fluther.response'] -%>
69
+ </pre>
70
+
71
+ You should now be able to start your application, navigate to http://localhost:300/qna and see the Fluther page.
72
+
73
+
74
+ h2. Thin Integration
75
+
76
+ The Fluther gem uses "EventMachine":http://github.com/igrigorik/em-http-request to perform the HTTP request. When running under a
77
+ non-EventMachine web server, the proxy request will be performed synchronously. However, when running under
78
+ the "Thin":http://code.macournoyer.com/thin/ web server, the proxy can take advantage of the presense of EventMachine to perform the
79
+ request asynchronously, i.e. without blocking the web server process. This does require some additional setup:
80
+
81
+ * Add the @async-rack@ gem to your application.
82
+ * Add the following Rails code (v2 specific) to the block in the initializer you created above:
83
+
84
+ <pre>
85
+ ...
86
+ Rails.configuration.threadsafe!
87
+ Rails.configuration.dependency_loading = true
88
+ class AsyncRack::Lock # kludge to disable error on async response in Rack::Lock
89
+ def async_callback(result)
90
+ super
91
+ end
92
+ end
93
+ </pre>
94
+
95
+ * To run your application: @script/server thin@
96
+
97
+
98
+ h2. Credits
99
+
100
+ Special thanks to Andrew and Ben at Fluther for all their help with integration and testing.
101
+
102
+ * Author: "Steve Sloan":mailto:steve@conceivian.com
103
+ * Copyright: (c) 2010 Conceivian Corporation
104
+ * License: MIT
data/lib/fluther.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'fluther/config'
2
+ require 'fluther/proxy'
3
+
4
+ module Fluther
5
+ ClientVersion = '1.0.2'.freeze
6
+ end
@@ -0,0 +1,21 @@
1
+ module Fluther
2
+ class Config
3
+ def self.fluther_host( *args )
4
+ @@fluther_host = nil unless defined? @@fluther_host
5
+ return @@fluther_host if args.empty?
6
+ @@fluther_host = args.first
7
+ end
8
+
9
+ def self.app_key( *args )
10
+ @@app_key = nil unless defined? @@app_key
11
+ return @@app_key if args.empty?
12
+ @@app_key = args.first
13
+ end
14
+
15
+ def self.user_fields( *args )
16
+ @@user_fields = { :id => :id, :name => :name, :email => :email } unless defined? @@user_fields
17
+ return @@user_fields if args.empty?
18
+ @@user_fields.update args.first
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,110 @@
1
+ require 'rack/request'
2
+ require 'em-http-request'
3
+
4
+ module Fluther
5
+
6
+ class Proxy
7
+ def initialize( app, prefix )
8
+ @app, @prefix = app, prefix
9
+ @prefix = "/#{@prefix}" unless @prefix.starts_with?('/')
10
+ end
11
+
12
+ def call( env )
13
+ return @app.call( env ) unless env['PATH_INFO'] =~ %r{^#{@prefix}}
14
+ @in_req = Rack::Request.new env
15
+
16
+ @user = {}
17
+ if user = @in_req.env['warden'].authenticate rescue nil
18
+ @user = Hash[ Fluther::Config.user_fields.map { |dest, src| [dest, user.send(src).to_s] } ]
19
+ end
20
+
21
+ exec_request
22
+ end
23
+
24
+ protected
25
+ def build_request
26
+ params = @in_req.params.dup.update(
27
+ :fed_key => Fluther::Config.app_key,
28
+ )
29
+ params[:fed_sessionid] = @in_req.cookies['fed_sessionid'] if @in_req.cookies['fed_sessionid']
30
+ if @user.present?
31
+ params[:fed_uid] = @user[:id].to_s
32
+ params[:fed_username] = @user[:name].to_s
33
+ params[:fed_email] = @user[:email].to_s
34
+ end
35
+
36
+ options = {
37
+ :redirects => 0,
38
+ :timeout => 10,
39
+ :head => {
40
+ 'User-Agent' => "Fluther Federated Client #{Fluther::ClientVersion} (Ruby)",
41
+ 'X-Forwarded-For' => @in_req.env['REMOTE_ADDR'],
42
+ 'X-Forwarded-Host' => @in_req.env['HTTP_HOST'],
43
+ }
44
+ }
45
+ options[:head]['X-Requested-With'] = @in_req.env['HTTP_X_REQUESTED_WITH'] if @in_req.env['HTTP_X_REQUESTED_WITH']
46
+ options[@in_req.post? ? :body : :query] = params
47
+
48
+ path = @in_req.path.sub( %r{^#{@prefix}}, '' )
49
+ path = '/' + path unless path.starts_with?('/')
50
+ url = "#{@in_req.scheme}://#{Fluther::Config.fluther_host}#{path}"
51
+
52
+ Rails.logger.debug @in_req.request_method
53
+ Rails.logger.debug url
54
+ Rails.logger.debug options
55
+
56
+ EventMachine::HttpRequest.new( url ).send( @in_req.request_method.downcase.to_sym, options )
57
+ end
58
+
59
+ def exec_request
60
+ result = nil
61
+ em_running = EM.reactor_running?
62
+ EM.run do
63
+ fluther = build_request
64
+ fluther.callback do
65
+ result = handle_response fluther
66
+
67
+ if em_running
68
+ # async
69
+ @in_req.env['async.callback'].call result
70
+ else
71
+ # sync
72
+ EM.stop
73
+ end
74
+ end
75
+ end
76
+
77
+ if em_running
78
+ # async
79
+ throw :async
80
+ else
81
+ # sync
82
+ result
83
+ end
84
+ end
85
+
86
+ def handle_response( fluther )
87
+ Rails.logger.debug fluther.response_header.status
88
+ Rails.logger.debug fluther.response_header
89
+
90
+ type_header = fluther.response_header['CONTENT_TYPE']
91
+ content_type = type_header.split(';')[0] || 'text/html'
92
+
93
+ result = if [301, 302].include?( fluther.response_header.status )
94
+ [ fluther.response_header.status, {'Location' => fluther.response_header['LOCATION']}, ['Redirecting'] ]
95
+
96
+ elsif @in_req.xhr? || (content_type != 'text/html')
97
+ [ fluther.response_header.status, {'Content-Type' => type_header}, [fluther.response] ]
98
+
99
+ else
100
+ fluther.response.html_safe if fluther.response.respond_to?(:html_safe)
101
+ @in_req.env['fluther.response'] = fluther.response
102
+ @in_req.env['fluther.title'] = fluther.response_header['FLUTHER_TITLE'] if fluther.response_header['FLUTHER_TITLE']
103
+ @in_req.env['fluther.header'] = fluther.response_header['FLUTHER_HEADER'] if fluther.response_header['FLUTHER_HEADER']
104
+ @app.call @in_req.env
105
+ end
106
+ result
107
+ end
108
+ end
109
+
110
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluther
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 0
8
+ - 0
9
+ version: 1.0.0
10
+ platform: ruby
11
+ authors:
12
+ - Steve Sloan
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-08-10 00:00:00 -07:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: em-http-request
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :runtime
32
+ version_requirements: *id001
33
+ description: Ruby interface to the Fluther discussion system
34
+ email: steve@conceivian.com
35
+ executables: []
36
+
37
+ extensions: []
38
+
39
+ extra_rdoc_files:
40
+ - README.textile
41
+ files:
42
+ - MIT-LICENSE
43
+ - README.textile
44
+ - lib/fluther.rb
45
+ - lib/fluther/config.rb
46
+ - lib/fluther/proxy.rb
47
+ has_rdoc: true
48
+ homepage: http://github.com/conceivian/fluther
49
+ licenses: []
50
+
51
+ post_install_message:
52
+ rdoc_options:
53
+ - --charset=UTF-8
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ segments:
62
+ - 0
63
+ version: "0"
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ segments:
70
+ - 0
71
+ version: "0"
72
+ requirements: []
73
+
74
+ rubyforge_project:
75
+ rubygems_version: 1.3.7
76
+ signing_key:
77
+ specification_version: 3
78
+ summary: Ruby interface to the Fluther discussion system
79
+ test_files: []
80
+