rsolr 0.13.0.pre → 1.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,5 +1,7 @@
1
1
  =RSolr
2
2
 
3
+ Notice: This document is only for the the 1.0 (pre-release) in the master branch. The last stable gem release documentation can be found here: http://github.com/mwmitchell/rsolr/tree/v0.12.1
4
+
3
5
  A simple, extensible Ruby client for Apache Solr.
4
6
 
5
7
  == Installation:
@@ -11,23 +13,20 @@ A simple, extensible Ruby client for Apache Solr.
11
13
  require 'rsolr'
12
14
 
13
15
  # Direct connection
14
- solr = RSolr.connect :url=>'http://solrserver.com'
16
+ solr = RSolr.connect 'http://solrserver.com'
15
17
 
16
18
  # Connecting over a proxy server
17
- solr = RSolr.connect :url=>'http://solrserver.com', :proxy=>'http://user:pass@proxy.example.com:8080'
19
+ solr = RSolr.connect 'http://solrserver.com', :proxy=>'http://user:pass@proxy.example.com:8080'
18
20
 
19
21
  # send a request to /select
20
- response = solr.select :q=>'*:*'
22
+ response = solr.get 'select', :q=>'*:*'
21
23
 
22
24
  # send a request to a custom request handler; /catalog
23
- response = solr.request '/catalog', :q=>'*:*'
25
+ response = solr.get 'catalog', :q=>'*:*'
24
26
 
25
- # alternative to above:
26
- response = solr.catalog :q=>'*:*'
27
-
28
27
  == Querying
29
28
  Use the #select method to send requests to the /select handler:
30
- response = solr.select({
29
+ response = solr.get('select', {
31
30
  :q=>'washington',
32
31
  :start=>0,
33
32
  :rows=>10
@@ -35,25 +34,23 @@ Use the #select method to send requests to the /select handler:
35
34
 
36
35
  The params sent into the method are sent to Solr as-is. The one exception is if a value is an array. When an array is used, multiple parameters *with the same name* are generated for the Solr query. Example:
37
36
 
38
- solr.select :q=>'roses', :fq=>['red', 'violet']
37
+ solr.get 'select', :q=>'roses', :fq=>['red', 'violet']
39
38
 
40
39
  The above statement generates this Solr query:
41
40
 
42
- ?q=roses&fq=red&fq=violet
41
+ select?q=roses&fq=red&fq=violet
43
42
 
44
- Use the #request method for a custom request handler path:
45
- response = solr.request '/documents', :q=>'test'
46
-
47
- A shortcut for the above example use a method call instead:
48
- response = solr.documents :q=>'test'
43
+ There may be cases where the query string is too long for a GET request. RSolr solves this issue by providing a simple way to POST a query to Solr:
44
+ response = solr.post "select", nil, enormous_params_hash
49
45
 
46
+ nil is passed in as the query string data. The enormous_params_hash variable ends up serialized as a form-encoded query string, and the correct content-type headers are sent along to Solr.
50
47
 
51
48
  == Updating Solr
52
- Updating uses native Ruby structures. Hashes are used for single documents and arrays are used for a collection of documents (hashes). These structures get turned into simple XML "messages". Raw XML strings can also be used.
49
+ Updating us done using native Ruby objects. Hashes are used for single documents and arrays are used for a collection of documents (hashes). These objects get turned into simple XML "messages". Raw XML strings can also be used.
53
50
 
54
51
  Raw XML via #update
55
- solr.update '</commit>'
56
- solr.update '</optimize>'
52
+ solr.update '<commit/>'
53
+ solr.update '<optimize/>'
57
54
 
58
55
  Single document via #add
59
56
  solr.add :id=>1, :price=>1.00
@@ -91,22 +88,26 @@ Commit & optimize shortcuts
91
88
  The default response format is Ruby. When the :wt param is set to :ruby, the response is eval'd resulting in a Hash. You can get a raw response by setting the :wt to "ruby" - notice, the string -- not a symbol. RSolr will eval the Ruby string ONLY if the :wt value is :ruby. All other response formats are available as expected, :wt=>'xml' etc..
92
89
 
93
90
  ===Evaluated Ruby (default)
94
- solr.select(:wt=>:ruby) # notice :ruby is a Symbol
91
+ solr.get 'select', :wt=>:ruby # notice :ruby is a Symbol
95
92
  ===Raw Ruby
96
- solr.select(:wt=>'ruby') # notice 'ruby' is a String
93
+ solr.get 'select', :wt=>'ruby' # notice 'ruby' is a String
97
94
 
98
95
  ===XML:
99
- solr.select(:wt=>:xml)
96
+ solr.get 'select', :wt=>:xml
100
97
  ===JSON:
101
- solr.select(:wt=>:json)
98
+ solr.get 'select', :wt=>:json
102
99
 
103
- You can access the original request context (path, params, url etc.) by calling the #raw method:
104
- response = solr.select :q=>'*:*'
105
- response.raw[:status_code]
106
- response.raw[:body]
107
- response.raw[:url]
100
+ You can access the original request context (path, params, url etc.) by calling the #request method:
101
+ result = solr.get 'select', :q=>'*:*'
102
+ result.request[:uri]
103
+ result.request[:params]
104
+ etc..
108
105
 
109
- The raw is a hash that contains the generated params, url, path, post data, headers etc., very useful for debugging and testing.
106
+ Similarly, the object returned has a response object. This contains any headers that Solr returned, along with the raw response body:
107
+ result = solr.get 'select', :q=>'*:*'
108
+ result.response[:headers]
109
+ result.response[:status]
110
+ result.response[:body]
110
111
 
111
112
  ==Related Resources & Projects
112
113
  * {RSolr Google Group}[http://groups.google.com/group/rsolr] -- The RSolr discussion group
@@ -127,6 +128,16 @@ The raw is a hash that contains the generated params, url, path, post data, head
127
128
  (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
128
129
  * Send me a pull request. Bonus points for topic branches.
129
130
 
131
+ == Note on Patches/Pull Requests
132
+
133
+ * Fork the project.
134
+ * Make your feature addition or bug fix.
135
+ * Add tests for it. This is important so I don't break it in a
136
+ future version unintentionally.
137
+ * Commit, do not mess with rakefile, version, or history.
138
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
139
+ * Send me a pull request. Bonus points for topic branches.
140
+
130
141
  ==Contributors
131
142
  * Lorenzo Riccucci
132
143
  * Mike Perham
@@ -136,4 +147,12 @@ The raw is a hash that contains the generated params, url, path, post data, head
136
147
  * Fouad Mardini
137
148
  * Jeremy Hinegardner
138
149
  * Nathan Witmer
139
- * Craig Smith
150
+ * Craig Smith
151
+
152
+ ==Author
153
+
154
+ Matt Mitchell <mailto:goodieboy@gmail.com>
155
+
156
+ ==Copyright
157
+
158
+ Copyright (c) 2008-2010 mwmitchell. See LICENSE for details.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.13.0.pre
1
+ 1.0.0.beta
data/lib/rsolr/char.rb ADDED
@@ -0,0 +1,9 @@
1
+ # A module that contains (1) string related methods
2
+ module RSolr::Char
3
+
4
+ # backslash everything that isn't a word character
5
+ def escape value
6
+ value.gsub /(\W)/, '\\\\\1'
7
+ end
8
+
9
+ end
data/lib/rsolr/client.rb CHANGED
@@ -2,37 +2,31 @@ class RSolr::Client
2
2
 
3
3
  attr_reader :connection
4
4
 
5
- # "connection" is instance of:
6
- # RSolr::Adapter::HTTP
7
- # RSolr::Adapter::Direct (jRuby only)
8
- # or any other class that uses the connection "interface"
9
- def initialize(connection)
5
+ def initialize connection
10
6
  @connection = connection
11
7
  end
12
8
 
13
- # Send a request to a request handler using the method name.
14
- # Also proxies to the #paginate method if the method starts with "paginate_"
15
- def method_missing(method_name, *args, &blk)
16
- request("/#{method_name}", *args, &blk)
9
+ # GET request
10
+ def get path = '', params = nil, headers = nil
11
+ send_request :get, path, params, nil, headers
17
12
  end
18
13
 
19
- # sends data to the update handler
20
- # data can be a string of xml, or an object that returns xml from its #to_xml method
21
- def update(data, params={})
22
- request '/update', params, data
14
+ # essentially a GET, but no response body
15
+ def head path = '', params = nil, headers = nil
16
+ send_request :head, path, params, nil, headers
23
17
  end
24
18
 
25
- # send request solr
26
- # params is hash with valid solr request params (:q, :fl, :qf etc..)
27
- # if params[:wt] is not set, the default is :ruby
28
- # if :wt is something other than :ruby, the raw response body is used
29
- # otherwise, a simple Hash is returned
30
- # NOTE: to get raw ruby, use :wt=>'ruby' <- a string, not a symbol like :ruby
31
- #
32
- #
33
- def request(path, params={}, *extra)
34
- response = @connection.request(path, map_params(params), *extra)
35
- adapt_response(response)
19
+ # A path is required for a POST since, well...
20
+ # the / resource doesn't do anything with a POST.
21
+ # Also, Solr doesn't do headers with a POST
22
+ def post path, data = nil, params = nil, headers = nil
23
+ send_request :post, path, params, data, headers
24
+ end
25
+
26
+ # POST XML messages to /update with optional params
27
+ def update data, params = {}, headers = {}
28
+ headers['Content-Type'] ||= 'text/xml'
29
+ post 'update', data, params, headers
36
30
  end
37
31
 
38
32
  #
@@ -43,10 +37,10 @@ class RSolr::Client
43
37
  # solr.update([{:id=>1, :name=>'one'}, {:id=>2, :name=>'two'}])
44
38
  #
45
39
  def add(doc, params={}, &block)
46
- update message.add(doc, params, &block)
40
+ update xml.add(doc, params, &block)
47
41
  end
48
42
 
49
- # send "commit" message with options
43
+ # send "commit" xml with options
50
44
  #
51
45
  # Options recognized by solr
52
46
  #
@@ -58,10 +52,10 @@ class RSolr::Client
58
52
  # *NOTE* :expungeDeletes is Solr 1.4 only
59
53
  #
60
54
  def commit( options = {} )
61
- update message.commit( options )
55
+ update xml.commit( options )
62
56
  end
63
57
 
64
- # send "optimize" message with options.
58
+ # send "optimize" xml with options.
65
59
  #
66
60
  # Options recognized by solr
67
61
  #
@@ -73,61 +67,89 @@ class RSolr::Client
73
67
  # *NOTE* :expungeDeletes is Solr 1.4 only
74
68
  #
75
69
  def optimize( options = {} )
76
- update message.optimize( options )
70
+ update xml.optimize( options )
77
71
  end
78
72
 
79
73
  # send </rollback>
80
74
  # NOTE: solr 1.4 only
81
75
  def rollback
82
- update message.rollback
76
+ update xml.rollback
83
77
  end
84
78
 
85
79
  # Delete one or many documents by id
86
80
  # solr.delete_by_id 10
87
81
  # solr.delete_by_id([12, 41, 199])
88
82
  def delete_by_id(id)
89
- update message.delete_by_id(id)
83
+ update xml.delete_by_id(id)
90
84
  end
91
85
 
92
86
  # delete one or many documents by query
93
87
  # solr.delete_by_query 'available:0'
94
88
  # solr.delete_by_query ['quantity:0', 'manu:"FQ"']
95
89
  def delete_by_query(query)
96
- update message.delete_by_query(query)
90
+ update xml.delete_by_query(query)
97
91
  end
98
92
 
99
93
  # shortcut to RSolr::Message::Generator
100
- def message
101
- @message ||= RSolr::Message::Generator.new
94
+ def xml
95
+ @xml ||= RSolr::Xml::Generator.new
102
96
  end
103
97
 
104
- protected
98
+ def send_request method, path, params, data, headers
99
+ params = map_params params
100
+ uri, data, headers = build_request path, params, data, headers
101
+ request_context = {:connection=>connection, :method => method, :uri => uri, :data => data, :headers => headers, :params => params}
102
+ begin
103
+ response = data ? connection.send(method, uri, data, headers) : connection.send(method, uri, headers)
104
+ rescue
105
+ $!.extend(RSolr::Error::SolrContext).request = request_context
106
+ raise $!
107
+ end
108
+ raise "The connection adapter returned an unexpected object" unless response.is_a?(Hash)
109
+ raise RSolr::Error::Http.new request_context, response unless [200,302].include?(response[:status])
110
+ adapt_response request_context, response
111
+ end
105
112
 
106
- # sets default params etc.. - could be used as a mapping hook
107
- # type of request should be passed in here? -> map_params(:query, {})
108
- def map_params(params)
109
- params||={}
110
- {:wt=>:ruby}.merge(params)
113
+ def map_params params
114
+ params = params.nil? ? {} : params.dup
115
+ params[:wt] ||= :ruby
116
+ params
111
117
  end
112
-
113
- # "connection_response" must be a hash with the following keys:
114
- # :params - a sub hash of standard solr params
115
- # : body - the raw response body from the solr server
116
- # This method will evaluate the :body value if the params[:wt] == :ruby
117
- # otherwise, the body is returned
118
- # The return object has a special method attached called #raw
119
- # This method gives you access to the original response from the connection,
120
- # so you can access things like the actual :url sent to solr,
121
- # the raw :body, original :params and original :data
122
- def adapt_response(connection_response)
123
- data = connection_response[:body]
124
- # if the wt is :ruby, evaluate the ruby string response
125
- if connection_response[:params][:wt] == :ruby
126
- data = Kernel.eval(data)
118
+
119
+ def build_request path, params, data, headers
120
+ params ||= {}
121
+ headers ||= {}
122
+ request_uri = params.any? ? "#{path}?#{RSolr::Uri.params_to_solr params}" : path
123
+ if data
124
+ if data.is_a? Hash
125
+ data = RSolr::Uri.params_to_solr data
126
+ headers['Content-Type'] ||= 'application/x-www-form-urlencoded'
127
+ end
128
+ end
129
+ [request_uri, data, headers]
130
+ end
131
+
132
+ # This method will evaluate the :body value
133
+ # if the params[:uri].params[:wt] == :ruby
134
+ # ... otherwise, the body is returned as is.
135
+ # The return object has a special method attached called #context.
136
+ # This method gives you access to the original
137
+ # request and response from the connection.
138
+ # This method will raise an InvalidRubyResponse
139
+ # if the :wt => :ruby and the body
140
+ # couldn't be evaluated.
141
+ def adapt_response request, response
142
+ data = response[:body]
143
+ if request[:params][:wt] == :ruby
144
+ begin
145
+ data = Kernel.eval data.to_s
146
+ rescue SyntaxError
147
+ raise RSolr::Error::InvalidRubyResponse.new request, response
148
+ end
127
149
  end
128
- # attach a method called #raw that returns the original connection response value
129
- def data.raw; @raw end
130
- data.send(:instance_variable_set, '@raw', connection_response)
150
+ data.extend Module.new.instance_eval{attr_accessor :request, :response; self}
151
+ data.request = request
152
+ data.response = response
131
153
  data
132
154
  end
133
155
 
@@ -0,0 +1,122 @@
1
+ module RSolr::Error
2
+
3
+ module SolrContext
4
+
5
+ attr_accessor :request, :response
6
+
7
+ def to_s
8
+ m = "#{super.to_s}"
9
+
10
+ if response
11
+ m << " - #{response[:status]} #{Http::STATUS_CODES[response[:status].to_i]}"
12
+ details = parse_solr_error_response response[:body]
13
+ m << "Error: #{details}\n" if details
14
+ end
15
+
16
+ m << "\n" + self.backtrace[0..10].join("\n")
17
+ m << "\n\nSolr Request:"
18
+ m << "\n Method: #{request[:method].to_s.upcase}"
19
+ m << "\n Base URL: #{request[:connection].uri.to_s}"
20
+ m << "\n URL: #{request[:uri]}"
21
+ m << "\n Params: #{request[:params].inspect}"
22
+ m << "\n Data: #{request[:data].inspect}" if request[:data]
23
+ m << "\n Headers: #{request[:headers].inspect}"
24
+ if response
25
+ m << "\n\nSolr Response:"
26
+ m << "\n Code: #{response[:status]}"
27
+ m << "\n Headers: #{response[:headers].inspect}"
28
+ end
29
+ m
30
+ end
31
+
32
+ protected
33
+
34
+ def parse_solr_error_response body
35
+ begin
36
+ info = body.scan(/<pre>(.*)<\/pre>/mi)[0]
37
+ partial = info.to_s.split("\n")[0..10]
38
+ partial.join("\n").gsub("&gt;", ">").gsub("&lt;", "<")
39
+ rescue
40
+ nil
41
+ end
42
+ end
43
+
44
+ end
45
+
46
+ class Http < RuntimeError
47
+
48
+ include SolrContext
49
+
50
+ # ripped right from ActionPack
51
+ # Defines the standard HTTP status codes, by integer, with their
52
+ # corresponding default message texts.
53
+ # Source: http://www.iana.org/assignments/http-status-codes
54
+ STATUS_CODES = {
55
+ 100 => "Continue",
56
+ 101 => "Switching Protocols",
57
+ 102 => "Processing",
58
+
59
+ 200 => "OK",
60
+ 201 => "Created",
61
+ 202 => "Accepted",
62
+ 203 => "Non-Authoritative Information",
63
+ 204 => "No Content",
64
+ 205 => "Reset Content",
65
+ 206 => "Partial Content",
66
+ 207 => "Multi-Status",
67
+ 226 => "IM Used",
68
+
69
+ 300 => "Multiple Choices",
70
+ 301 => "Moved Permanently",
71
+ 302 => "Found",
72
+ 303 => "See Other",
73
+ 304 => "Not Modified",
74
+ 305 => "Use Proxy",
75
+ 307 => "Temporary Redirect",
76
+
77
+ 400 => "Bad Request",
78
+ 401 => "Unauthorized",
79
+ 402 => "Payment Required",
80
+ 403 => "Forbidden",
81
+ 404 => "Not Found",
82
+ 405 => "Method Not Allowed",
83
+ 406 => "Not Acceptable",
84
+ 407 => "Proxy Authentication Required",
85
+ 408 => "Request Timeout",
86
+ 409 => "Conflict",
87
+ 410 => "Gone",
88
+ 411 => "Length Required",
89
+ 412 => "Precondition Failed",
90
+ 413 => "Request Entity Too Large",
91
+ 414 => "Request-URI Too Long",
92
+ 415 => "Unsupported Media Type",
93
+ 416 => "Requested Range Not Satisfiable",
94
+ 417 => "Expectation Failed",
95
+ 422 => "Unprocessable Entity",
96
+ 423 => "Locked",
97
+ 424 => "Failed Dependency",
98
+ 426 => "Upgrade Required",
99
+
100
+ 500 => "Internal Server Error",
101
+ 501 => "Not Implemented",
102
+ 502 => "Bad Gateway",
103
+ 503 => "Service Unavailable",
104
+ 504 => "Gateway Timeout",
105
+ 505 => "HTTP Version Not Supported",
106
+ 507 => "Insufficient Storage",
107
+ 510 => "Not Extended"
108
+ }
109
+
110
+ def initialize request, response
111
+ @request, @response = request, response
112
+ end
113
+
114
+ end
115
+
116
+ # Thrown if the :wt is :ruby
117
+ # but the body wasn't succesfully parsed/evaluated
118
+ class InvalidRubyResponse < Http
119
+
120
+ end
121
+
122
+ end
data/lib/rsolr/http.rb ADDED
@@ -0,0 +1,106 @@
1
+ module RSolr
2
+
3
+ class Http
4
+
5
+ attr_reader :uri, :proxy, :options
6
+
7
+ def initialize base_url, options = {}
8
+ @options = options
9
+ @uri = base_url
10
+ @proxy = options[:proxy]
11
+ end
12
+
13
+ def base_uri
14
+ @proxy ? @proxy.request_uri : @uri.request_uri
15
+ end
16
+
17
+ def http
18
+ @http ||= (
19
+ http = if proxy
20
+ proxy_user, proxy_pass = proxy.userinfo.split(/:/) if proxy.userinfo
21
+ Net::HTTP.Proxy(proxy.host, proxy.port, proxy_user, proxy_pass).new uri.host, uri.port
22
+ else
23
+ Net::HTTP.new uri.host, uri.port
24
+ end
25
+
26
+ http.use_ssl = uri.port == 443 || uri.instance_of?(URI::HTTPS)
27
+
28
+ if options[:timeout] && options[:timeout].is_a?(Integer)
29
+ http.open_timeout = options[:timeout]
30
+ http.read_timeout = options[:timeout]
31
+ end
32
+
33
+ if options[:pem] && http.use_ssl?
34
+ http.cert = OpenSSL::X509::Certificate.new(options[:pem])
35
+ http.key = OpenSSL::PKey::RSA.new(options[:pem])
36
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
37
+ else
38
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
39
+ end
40
+
41
+ if options[:debug_output]
42
+ http.set_debug_output(options[:debug_output])
43
+ end
44
+
45
+ http
46
+ )
47
+ end
48
+
49
+ # send in path w/query
50
+ def get request_uri, headers = {}
51
+ req = setup_raw_request Net::HTTP::Get, request_uri, headers
52
+ perform_request http, req
53
+ end
54
+
55
+ # send in path w/query
56
+ def head request_uri, headers = {}
57
+ req = setup_raw_request Net::HTTP::Head, request_uri, headers
58
+ perform_request http, req
59
+ end
60
+
61
+ # send in path w/query
62
+ def post request_uri, data, headers = {}
63
+ req = setup_raw_request Net::HTTP::Post, request_uri, headers
64
+ req.body = data if data
65
+ perform_request http, req
66
+ end
67
+
68
+ def perform_request http, request
69
+ begin
70
+ response = http.request request
71
+ {:status => response.code.to_i, :headers => response.to_hash, :body => response.body}
72
+ rescue NoMethodError
73
+ $!.message == "undefined method `closed?' for nil:NilClass" ?
74
+ raise(Errno::ECONNREFUSED.new) :
75
+ raise($!)
76
+ end
77
+ end
78
+
79
+ def setup_raw_request http_method, request_uri, headers = {}
80
+ raw_request = http_method.new "#{base_uri}#{request_uri}"
81
+ raw_request.initialize_http_header headers
82
+ raw_request.basic_auth username, password if options[:basic_auth]
83
+ if options[:digest_auth]
84
+ res = http.head(request_uri, headers)
85
+ if res['www-authenticate'] != nil && res['www-authenticate'].length > 0
86
+ raw_request.digest_auth username, password, res
87
+ end
88
+ end
89
+ raw_request
90
+ end
91
+
92
+ def credentials
93
+ options[:basic_auth] || options[:digest_auth]
94
+ end
95
+
96
+ def username
97
+ credentials[:username]
98
+ end
99
+
100
+ def password
101
+ credentials[:password]
102
+ end
103
+
104
+ end
105
+
106
+ end
data/lib/rsolr/uri.rb ADDED
@@ -0,0 +1,55 @@
1
+ module RSolr::Uri
2
+
3
+ def self.create url
4
+ ::URI.parse url[-1] == ?/ ? url : "#{url}/"
5
+ end
6
+
7
+ # Returns a query string param pair as a string.
8
+ # Both key and value are escaped.
9
+ def build_param(k,v)
10
+ "#{escape_query_value(k)}=#{escape_query_value(v)}"
11
+ end
12
+
13
+ # Return the bytesize of String; uses String#size under Ruby 1.8 and
14
+ # String#bytesize under 1.9.
15
+ if ''.respond_to?(:bytesize)
16
+ def bytesize(string)
17
+ string.bytesize
18
+ end
19
+ else
20
+ def bytesize(string)
21
+ string.size
22
+ end
23
+ end
24
+
25
+ # Creates a Solr based query string.
26
+ # Keys that have arrays values are set multiple times:
27
+ # params_to_solr(:q => 'query', :fq => ['a', 'b'])
28
+ # is converted to:
29
+ # ?q=query&fq=a&fq=b
30
+ def params_to_solr(params)
31
+ mapped = params.map do |k, v|
32
+ next if v.to_s.empty?
33
+ if v.class == Array
34
+ params_to_solr(v.map { |x| [k, x] })
35
+ else
36
+ build_param k, v
37
+ end
38
+ end
39
+ mapped.compact.join("&")
40
+ end
41
+
42
+ # Performs URI escaping so that you can construct proper
43
+ # query strings faster. Use this rather than the cgi.rb
44
+ # version since it's faster.
45
+ # (Stolen from Rack).
46
+ def escape_query_value(s)
47
+ s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
48
+ #'%'+$1.unpack('H2'*$1.size).join('%').upcase
49
+ '%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
50
+ }.tr(' ', '+')
51
+ end
52
+
53
+ extend self
54
+
55
+ end