@@ -5,38 +5,40 @@ module Rack
5
5
class ReverseProxy
6
6
def initialize ( app = nil , &b )
7
7
@app = app || lambda { [ 404 , [ ] , [ ] ] }
8
- @paths = { }
9
- @opts = { :preserve_host => false }
8
+ @matchers = [ ]
9
+ @global_options = { :preserve_host => false , :matching => :all }
10
10
instance_eval &b if block_given?
11
11
end
12
12
13
13
def call ( env )
14
14
rackreq = Rack ::Request . new ( env )
15
- matcher , url = get_matcher_and_url rackreq . fullpath
15
+ matcher = get_matcher rackreq . fullpath
16
16
return @app . call ( env ) if matcher . nil?
17
17
18
- uri = get_uri ( url , matcher , rackreq . fullpath )
18
+ uri = matcher . get_uri ( rackreq . fullpath , env )
19
+ all_opts = @global_options . dup . merge ( matcher . options )
19
20
headers = Rack ::Utils ::HeaderHash . new
20
21
env . each { |key , value |
21
22
if key =~ /HTTP_(.*)/
22
23
headers [ $1] = value
23
24
end
24
25
}
25
- headers [ 'HOST' ] = uri . host if @opts [ :preserve_host ]
26
+ headers [ 'HOST' ] = uri . host if all_opts [ :preserve_host ]
26
27
27
28
session = Net ::HTTP . new ( uri . host , uri . port )
28
29
session . use_ssl = ( uri . scheme == 'https' )
29
30
session . verify_mode = OpenSSL ::SSL ::VERIFY_NONE
31
+ session . read_timeout = all_opts [ :timeout ] if all_opts [ :timeout ]
30
32
session . start { |http |
31
33
m = rackreq . request_method
32
34
case m
33
35
when "GET" , "HEAD" , "DELETE" , "OPTIONS" , "TRACE"
34
36
req = Net ::HTTP . const_get ( m . capitalize ) . new ( uri . request_uri , headers )
35
- req . basic_auth @opts [ :username ] , @opts [ :password ] if @opts [ :username ] and @opts [ :password ]
37
+ req . basic_auth all_opts [ :username ] , all_opts [ :password ] if all_opts [ :username ] and all_opts [ :password ]
36
38
when "PUT" , "POST"
37
39
req = Net ::HTTP . const_get ( m . capitalize ) . new ( uri . request_uri , headers )
38
- req . basic_auth @opts [ :username ] , @opts [ :password ] if @opts [ :username ] and @opts [ :password ]
39
- req . content_length = rackreq . body . length
40
+ req . basic_auth all_opts [ :username ] , all_opts [ :password ] if all_opts [ :username ] and all_opts [ :password ]
41
+ req . content_length = rackreq . body . size
40
42
req . body_stream = rackreq . body
41
43
else
42
44
raise "method not supported: #{ m } "
@@ -55,17 +57,17 @@ def call(env)
55
57
56
58
private
57
59
58
- def get_matcher_and_url path
59
- matches = @paths . select do |matcher , url |
60
- match_path ( path , matcher )
60
+ def get_matcher path
61
+ matches = @matchers . select do |matcher |
62
+ matcher . match? ( path )
61
63
end
62
64
63
65
if matches . length < 1
64
66
nil
65
- elsif matches . length > 1
67
+ elsif matches . length > 1 && @global_options [ :matching ] != :first
66
68
raise AmbiguousProxyMatch . new ( path , matches )
67
69
else
68
- matches . first . map { | a | a . dup }
70
+ matches . first
69
71
end
70
72
end
71
73
@@ -79,27 +81,14 @@ def create_response_headers http_response
79
81
response_headers
80
82
end
81
83
82
- def match_path ( path , matcher )
83
- if matcher . is_a? ( Regexp )
84
- path . match ( matcher )
85
- else
86
- path . match ( /^#{ matcher . to_s } / )
87
- end
88
- end
89
84
90
- def get_uri ( url , matcher , path )
91
- if url =~/\$ \d /
92
- match_path ( path , matcher ) . to_a . each_with_index { |m , i | url . gsub! ( "$#{ i . to_s } " , m ) }
93
- URI ( url )
94
- else
95
- URI . join ( url , path )
96
- end
85
+ def reverse_proxy_options ( options )
86
+ @global_options = options
97
87
end
98
88
99
89
def reverse_proxy matcher , url , opts = { }
100
- raise GenericProxyURI . new ( url ) if matcher . is_a? ( String ) && URI ( url ) . class == URI ::Generic
101
- @paths . merge! ( matcher => url )
102
- @opts . merge! ( opts )
90
+ raise GenericProxyURI . new ( url ) if matcher . is_a? ( String ) && url . is_a? ( String ) && URI ( url ) . class == URI ::Generic
91
+ @matchers << ReverseProxyMatcher . new ( matcher , url , opts )
103
92
end
104
93
end
105
94
@@ -129,8 +118,41 @@ def to_s
129
118
private
130
119
131
120
def formatted_matches
132
- matches . map { |m | %Q(" #{ m [ 0 ] . to_s } " => " #{ m [ 1 ] } ") } . join ( ', ' )
121
+ matches . map { |matcher | matcher . to_s } . join ( ', ' )
133
122
end
134
123
end
135
124
125
+ class ReverseProxyMatcher
126
+ def initialize ( matching , url , options )
127
+ @matching = matching
128
+ @url = url
129
+ @options = options
130
+ @matching_regexp = matching . kind_of? ( Regexp ) ? matching : /^#{ matching . to_s } /
131
+ end
132
+
133
+ attr_reader :matching , :matching_regexp , :url , :options
134
+
135
+ def match? ( path )
136
+ match_path ( path ) ? true : false
137
+ end
138
+
139
+ def get_uri ( path , env )
140
+ _url = ( url . respond_to? ( :call ) ? url . call ( env ) : url )
141
+ if _url =~/\$ \d /
142
+ match_path ( path ) . to_a . each_with_index { |m , i | _url . gsub! ( "$#{ i . to_s } " , m ) }
143
+ URI ( _url )
144
+ else
145
+ URI . join ( _url , path )
146
+ end
147
+ end
148
+ def to_s
149
+ %Q("#{ matching . to_s } " => "#{ url } ")
150
+ end
151
+ private
152
+ def match_path ( path )
153
+ path . match ( matching_regexp )
154
+ end
155
+
156
+
157
+ end
136
158
end
0 commit comments