webmachine 0.4.2 → 1.0.0
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.
- data/README.md +70 -7
- data/Rakefile +19 -0
- data/examples/debugger.rb +32 -0
- data/examples/webrick.rb +6 -2
- data/lib/webmachine.rb +2 -0
- data/lib/webmachine/adapters/rack.rb +16 -3
- data/lib/webmachine/adapters/webrick.rb +7 -1
- data/lib/webmachine/application.rb +10 -10
- data/lib/webmachine/cookie.rb +168 -0
- data/lib/webmachine/decision/conneg.rb +1 -1
- data/lib/webmachine/decision/flow.rb +1 -1
- data/lib/webmachine/decision/fsm.rb +19 -12
- data/lib/webmachine/decision/helpers.rb +25 -1
- data/lib/webmachine/dispatcher.rb +34 -5
- data/lib/webmachine/dispatcher/route.rb +2 -0
- data/lib/webmachine/media_type.rb +3 -3
- data/lib/webmachine/request.rb +11 -0
- data/lib/webmachine/resource.rb +3 -1
- data/lib/webmachine/resource/authentication.rb +1 -1
- data/lib/webmachine/resource/callbacks.rb +16 -0
- data/lib/webmachine/resource/tracing.rb +20 -0
- data/lib/webmachine/response.rb +38 -8
- data/lib/webmachine/trace.rb +74 -0
- data/lib/webmachine/trace/fsm.rb +60 -0
- data/lib/webmachine/trace/pstore_trace_store.rb +39 -0
- data/lib/webmachine/trace/resource_proxy.rb +107 -0
- data/lib/webmachine/trace/static/http-headers-status-v3.png +0 -0
- data/lib/webmachine/trace/static/trace.erb +54 -0
- data/lib/webmachine/trace/static/tracelist.erb +14 -0
- data/lib/webmachine/trace/static/wmtrace.css +123 -0
- data/lib/webmachine/trace/static/wmtrace.js +725 -0
- data/lib/webmachine/trace/trace_resource.rb +129 -0
- data/lib/webmachine/version.rb +1 -1
- data/spec/spec_helper.rb +19 -0
- data/spec/webmachine/adapters/rack_spec.rb +77 -41
- data/spec/webmachine/configuration_spec.rb +1 -1
- data/spec/webmachine/cookie_spec.rb +99 -0
- data/spec/webmachine/decision/conneg_spec.rb +9 -8
- data/spec/webmachine/decision/flow_spec.rb +52 -4
- data/spec/webmachine/decision/helpers_spec.rb +36 -6
- data/spec/webmachine/dispatcher_spec.rb +1 -1
- data/spec/webmachine/headers_spec.rb +1 -1
- data/spec/webmachine/media_type_spec.rb +1 -1
- data/spec/webmachine/request_spec.rb +10 -0
- data/spec/webmachine/resource/authentication_spec.rb +3 -3
- data/spec/webmachine/response_spec.rb +45 -0
- data/spec/webmachine/trace/fsm_spec.rb +32 -0
- data/spec/webmachine/trace/resource_proxy_spec.rb +36 -0
- data/spec/webmachine/trace/trace_store_spec.rb +29 -0
- data/spec/webmachine/trace_spec.rb +17 -0
- data/webmachine.gemspec +2 -0
- metadata +130 -15
data/README.md
CHANGED
@@ -28,7 +28,7 @@ application for it!
|
|
28
28
|
```ruby
|
29
29
|
require 'webmachine'
|
30
30
|
# Require any of the files that contain your resources here
|
31
|
-
require 'my_resource'
|
31
|
+
require 'my_resource'
|
32
32
|
|
33
33
|
# Create an application which encompasses routes and configruation
|
34
34
|
MyApp = Webmachine::Application.new do |app|
|
@@ -63,7 +63,7 @@ class MyResource < Webmachine::Resource
|
|
63
63
|
def encodings_provided
|
64
64
|
{"gzip" => :encode_gzip, "identity" => :encode_identity}
|
65
65
|
end
|
66
|
-
|
66
|
+
|
67
67
|
def to_html
|
68
68
|
"<html><body>Hello, world!</body></html>"
|
69
69
|
end
|
@@ -85,7 +85,7 @@ object, `Webmachine.application` will return a global one.
|
|
85
85
|
```ruby
|
86
86
|
require 'webmachine'
|
87
87
|
require 'my_resource'
|
88
|
-
|
88
|
+
|
89
89
|
Webmachine.application.routes do
|
90
90
|
add ['*'], MyResource
|
91
91
|
end
|
@@ -95,11 +95,52 @@ Webmachine.application.configure do |config|
|
|
95
95
|
config.port = 3000
|
96
96
|
config.adapter = :Mongrel
|
97
97
|
end
|
98
|
-
|
98
|
+
|
99
99
|
# Start the server.
|
100
100
|
Webmachine.application.run
|
101
101
|
```
|
102
102
|
|
103
|
+
### Visual debugger
|
104
|
+
|
105
|
+
It can be hard to understand all of the decisions that Webmachine
|
106
|
+
makes when servicing a request to your resource, which is why we have
|
107
|
+
the "visual debugger". In development, you can turn on tracing of the
|
108
|
+
decision graph for a resource by implementing the `#trace?` callback
|
109
|
+
so that it returns true:
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
class MyTracedResource < Webmachine::Resource
|
113
|
+
def trace?
|
114
|
+
true
|
115
|
+
end
|
116
|
+
|
117
|
+
# The rest of your callbacks...
|
118
|
+
end
|
119
|
+
```
|
120
|
+
|
121
|
+
Then enable the visual debugger resource by adding a route to your
|
122
|
+
configuration:
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
Webmachine.application.routes do
|
126
|
+
# This can be any path as long as it ends with '*'
|
127
|
+
add ['trace', '*'], Webmachine::Trace::TraceResource
|
128
|
+
# The rest of your routes...
|
129
|
+
end
|
130
|
+
```
|
131
|
+
|
132
|
+
Now when you visit your traced resource, a trace of the request
|
133
|
+
process will be recorded in memory. Open your browser to `/trace` to
|
134
|
+
list the recorded traces and inspect the result. The response from your
|
135
|
+
traced resource will also include the `X-Webmachine-Trace-Id` that you
|
136
|
+
can use to lookup the trace. It might look something like this:
|
137
|
+
|
138
|
+

|
139
|
+
|
140
|
+
Refer to
|
141
|
+
[examples/debugger.rb](/seancribbs/webmachine-ruby/blob/master/examples/debugger.rb)
|
142
|
+
for an example of how to enable the debugger.
|
143
|
+
|
103
144
|
## Features
|
104
145
|
|
105
146
|
* Handles the hard parts of content negotiation, conditional
|
@@ -112,13 +153,12 @@ Webmachine.application.run
|
|
112
153
|
* Streaming/chunked response bodies are permitted as Enumerables,
|
113
154
|
Procs, or Fibers!
|
114
155
|
* Unlike the Erlang original, it does real Language negotiation.
|
156
|
+
* Includes the visual debugger so you can look through the decision
|
157
|
+
graph to determine how your resources are behaving.
|
115
158
|
|
116
159
|
## Problems/TODOs
|
117
160
|
|
118
161
|
* Command-line tools, and general polish.
|
119
|
-
* Tracing is exposed as an Array of decisions visited on the response
|
120
|
-
object. You should be able to turn this off and on, and visualize
|
121
|
-
the decisions on the sequence diagram.
|
122
162
|
|
123
163
|
## LICENSE
|
124
164
|
|
@@ -128,6 +168,29 @@ LICENSE for details.
|
|
128
168
|
|
129
169
|
## Changelog
|
130
170
|
|
171
|
+
### 1.0.0 July 7, 2012
|
172
|
+
|
173
|
+
1.0.0 is a major feature release that finally includes the visual
|
174
|
+
debugger, some nice cookie support, and some new extension
|
175
|
+
points. Added Peter Johanson and Armin Joellenbeck as
|
176
|
+
contributors. Thank you for your contributions!
|
177
|
+
|
178
|
+
* A cookie parsing and manipulation API was added.
|
179
|
+
* Conneg headers now accept any amount of whitespace around commas,
|
180
|
+
including none.
|
181
|
+
* `Callbacks#handle_exception` was added so that resources can handle
|
182
|
+
exceptions that they generate and produce more friendly responses.
|
183
|
+
* Chunked and non-chunked response bodies in the Rack adapter were
|
184
|
+
fixed.
|
185
|
+
* The WEBrick example was updated to use the new API.
|
186
|
+
* `Dispatcher` was refactored so that you can modify how resources
|
187
|
+
are initialized before dispatching occurs.
|
188
|
+
* `Route` now includes the `Translation` module so that exception
|
189
|
+
messages are properly rendered.
|
190
|
+
* The visual debugger was added (more details in the README).
|
191
|
+
* The `Content-Length` header will always be set inside Webmachine and
|
192
|
+
is no longer reliant on the adapter to set it.
|
193
|
+
|
131
194
|
### 0.4.2 March 22, 2012
|
132
195
|
|
133
196
|
0.4.2 is a bugfix release that corrects a few minor issues. Added Lars
|
data/Rakefile
CHANGED
@@ -32,6 +32,25 @@ task :release => :gem do
|
|
32
32
|
system "gem push pkg/#{gemspec.name}-#{gemspec.version}.gem"
|
33
33
|
end
|
34
34
|
|
35
|
+
desc "Cleans up white space in source files"
|
36
|
+
task :clean_whitespace do
|
37
|
+
no_file_cleaned = true
|
38
|
+
|
39
|
+
Dir["**/*.rb"].each do |file|
|
40
|
+
contents = File.read(file)
|
41
|
+
cleaned_contents = contents.gsub(/([ \t]+)$/, '')
|
42
|
+
unless cleaned_contents == contents
|
43
|
+
no_file_cleaned = false
|
44
|
+
puts " - Cleaned #{file}"
|
45
|
+
File.open(file, 'w') { |f| f.write(cleaned_contents) }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
if no_file_cleaned
|
50
|
+
puts "No files with trailing whitespace found"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
35
54
|
require 'rspec/core'
|
36
55
|
require 'rspec/core/rake_task'
|
37
56
|
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'webmachine'
|
2
|
+
require 'webmachine/trace'
|
3
|
+
|
4
|
+
class MyTracedResource < Webmachine::Resource
|
5
|
+
def trace?; true; end
|
6
|
+
|
7
|
+
def resource_exists?
|
8
|
+
case request.query['e']
|
9
|
+
when 'true'
|
10
|
+
true
|
11
|
+
when 'fail'
|
12
|
+
raise "BOOM"
|
13
|
+
else
|
14
|
+
false
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_html
|
19
|
+
"<html>You found me.</html>"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Webmachine::Trace.trace_store = :pstore, "./trace"
|
24
|
+
|
25
|
+
TraceExample = Webmachine::Application.new do |app|
|
26
|
+
app.routes do
|
27
|
+
add ['trace', '*'], Webmachine::Trace::TraceResource
|
28
|
+
add [], MyTracedResource
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
TraceExample.run
|
data/examples/webrick.rb
CHANGED
data/lib/webmachine.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'webmachine/configuration'
|
2
|
+
require 'webmachine/cookie'
|
2
3
|
require 'webmachine/headers'
|
3
4
|
require 'webmachine/request'
|
4
5
|
require 'webmachine/response'
|
@@ -10,6 +11,7 @@ require 'webmachine/adapters'
|
|
10
11
|
require 'webmachine/dispatcher'
|
11
12
|
require 'webmachine/application'
|
12
13
|
require 'webmachine/resource'
|
14
|
+
require 'webmachine/trace'
|
13
15
|
require 'webmachine/version'
|
14
16
|
|
15
17
|
# Webmachine is a toolkit for making well-behaved HTTP applications.
|
@@ -4,6 +4,7 @@ require 'webmachine/headers'
|
|
4
4
|
require 'webmachine/request'
|
5
5
|
require 'webmachine/response'
|
6
6
|
require 'webmachine/dispatcher'
|
7
|
+
require 'webmachine/chunked_body'
|
7
8
|
|
8
9
|
module Webmachine
|
9
10
|
module Adapters
|
@@ -58,10 +59,22 @@ module Webmachine
|
|
58
59
|
|
59
60
|
response.headers['Server'] = [Webmachine::SERVER_STRING, "Rack/#{::Rack.version}"].join(" ")
|
60
61
|
|
61
|
-
|
62
|
-
|
62
|
+
rack_status = response.code
|
63
|
+
rack_headers = response.headers.flattened("\n")
|
64
|
+
rack_body = case response.body
|
65
|
+
when String # Strings are enumerable in ruby 1.8
|
66
|
+
[response.body]
|
67
|
+
else
|
68
|
+
if response.body.respond_to?(:call)
|
69
|
+
Webmachine::ChunkedBody.new(Array(response.body.call))
|
70
|
+
elsif response.body.respond_to?(:each)
|
71
|
+
Webmachine::ChunkedBody.new(response.body)
|
72
|
+
else
|
73
|
+
[response.body.to_s]
|
74
|
+
end
|
75
|
+
end
|
63
76
|
|
64
|
-
[
|
77
|
+
[rack_status, rack_headers, rack_body]
|
65
78
|
end
|
66
79
|
|
67
80
|
# Wraps the Rack input so it can be treated like a String or
|
@@ -39,7 +39,13 @@ module Webmachine
|
|
39
39
|
response = Webmachine::Response.new
|
40
40
|
@dispatcher.dispatch(request, response)
|
41
41
|
wres.status = response.code.to_i
|
42
|
-
|
42
|
+
|
43
|
+
headers = response.headers.flattened.reject { |k,v| k == 'Set-Cookie' }
|
44
|
+
headers.each { |k,v| wres[k] = v }
|
45
|
+
|
46
|
+
cookies = [response.headers['Set-Cookie'] || []].flatten
|
47
|
+
cookies.each { |c| wres.cookies << c }
|
48
|
+
|
43
49
|
wres['Server'] = [Webmachine::SERVER_STRING, wres.config[:ServerSoftware]].join(" ")
|
44
50
|
case response.body
|
45
51
|
when String
|
@@ -4,19 +4,19 @@ require 'webmachine/dispatcher'
|
|
4
4
|
|
5
5
|
module Webmachine
|
6
6
|
# How to get your Webmachine app running:
|
7
|
-
#
|
7
|
+
#
|
8
8
|
# MyApp = Webmachine::Application.new do |app|
|
9
9
|
# app.routes do
|
10
10
|
# add ['*'], AssetResource
|
11
11
|
# end
|
12
|
-
#
|
12
|
+
#
|
13
13
|
# app.configure do |config|
|
14
14
|
# config.port = 8888
|
15
15
|
# end
|
16
16
|
# end
|
17
|
-
#
|
17
|
+
#
|
18
18
|
# MyApp.run
|
19
|
-
#
|
19
|
+
#
|
20
20
|
class Application
|
21
21
|
extend Forwardable
|
22
22
|
|
@@ -32,17 +32,17 @@ module Webmachine
|
|
32
32
|
#
|
33
33
|
# An instance of application contains Adapter configuration and
|
34
34
|
# a Dispatcher instance which can be configured with Routes.
|
35
|
-
#
|
35
|
+
#
|
36
36
|
# @param [Webmachine::Configuration] configuration
|
37
37
|
# a Webmachine::Configuration
|
38
|
-
#
|
38
|
+
#
|
39
39
|
# @yield [app]
|
40
40
|
# a block in which to configure this Application
|
41
41
|
# @yieldparam [Application]
|
42
42
|
# the Application instance being initialized
|
43
|
-
def initialize(configuration = Configuration.default)
|
43
|
+
def initialize(configuration = Configuration.default, dispatcher = Dispatcher.new)
|
44
44
|
@configuration = configuration
|
45
|
-
@dispatcher =
|
45
|
+
@dispatcher = dispatcher
|
46
46
|
|
47
47
|
yield self if block_given?
|
48
48
|
end
|
@@ -66,10 +66,10 @@ module Webmachine
|
|
66
66
|
|
67
67
|
# Evaluates the passed block in the context of {Webmachine::Dispatcher}
|
68
68
|
# for use in adding a number of routes at once.
|
69
|
-
#
|
69
|
+
#
|
70
70
|
# @return [Application, Array<Route>]
|
71
71
|
# self if configuring, or an Array of Routes otherwise
|
72
|
-
#
|
72
|
+
#
|
73
73
|
# @see Webmachine::Dispatcher#add_route
|
74
74
|
def routes(&block)
|
75
75
|
if block_given?
|
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module Webmachine
|
4
|
+
# An HTTP Cookie for a response, including optional attributes
|
5
|
+
class Cookie
|
6
|
+
# Parse a Cookie header, with any number of cookies, into a hash
|
7
|
+
# @param [String] the Cookie header
|
8
|
+
# @param [Boolean] whether to include duplicate cookie values in the
|
9
|
+
# response
|
10
|
+
# @return [Hash] cookie name/value pairs.
|
11
|
+
def self.parse(cstr, include_dups = false)
|
12
|
+
cookies = {}
|
13
|
+
(cstr || '').split(/\s*[;,]\s*/n).each { |c|
|
14
|
+
k,v = c.split(/\s*=\s*/, 2).map { |s| unescape(s) }
|
15
|
+
|
16
|
+
case cookies[k]
|
17
|
+
when nil
|
18
|
+
cookies[k] = v
|
19
|
+
when Array
|
20
|
+
cookies[k] << v
|
21
|
+
else
|
22
|
+
cookies[k] = [cookies[k], v] if include_dups
|
23
|
+
end
|
24
|
+
}
|
25
|
+
|
26
|
+
cookies
|
27
|
+
end
|
28
|
+
|
29
|
+
attr_reader :name, :value
|
30
|
+
|
31
|
+
# Allowed keys for the attributes parameter of
|
32
|
+
# {Webmachine::Cookie#initialize}
|
33
|
+
ALLOWED_ATTRIBUTES = [:secure, :httponly, :path, :domain,
|
34
|
+
:comment, :maxage, :expires, :version]
|
35
|
+
|
36
|
+
# If the cookie is HTTP only
|
37
|
+
def http_only?
|
38
|
+
@attributes[:httponly]
|
39
|
+
end
|
40
|
+
|
41
|
+
# If the cookie should be treated as a secure one by the client
|
42
|
+
def secure?
|
43
|
+
@attributes[:secure]
|
44
|
+
end
|
45
|
+
|
46
|
+
# The path for which the cookie is valid
|
47
|
+
def path
|
48
|
+
@attributes[:path]
|
49
|
+
end
|
50
|
+
|
51
|
+
# The domain for which the cookie is valid
|
52
|
+
def domain
|
53
|
+
@attributes[:domain]
|
54
|
+
end
|
55
|
+
|
56
|
+
# A comment allowing documentation on the intended use for the cookie
|
57
|
+
def comment
|
58
|
+
@attributes[:comment]
|
59
|
+
end
|
60
|
+
|
61
|
+
# Which version of the state management specification the cookie conforms
|
62
|
+
def version
|
63
|
+
@attributes[:version]
|
64
|
+
end
|
65
|
+
|
66
|
+
# The Max-Age, in seconds, for which the cookie is valid
|
67
|
+
def maxage
|
68
|
+
@attributes[:maxage]
|
69
|
+
end
|
70
|
+
|
71
|
+
# The expiration {DateTime} of the cookie
|
72
|
+
def expires
|
73
|
+
@attributes[:expires]
|
74
|
+
end
|
75
|
+
|
76
|
+
def initialize(name, value, attributes = {})
|
77
|
+
@name, @value, @attributes = name, value, attributes
|
78
|
+
end
|
79
|
+
|
80
|
+
# Convert to an RFC2109 valid cookie string
|
81
|
+
# @return [String] The RFC2109 valid cookie string
|
82
|
+
def to_s
|
83
|
+
attributes = ALLOWED_ATTRIBUTES.select { |a| @attributes[a] }.map do |a|
|
84
|
+
case a
|
85
|
+
when :httponly
|
86
|
+
"HttpOnly" if @attributes[a]
|
87
|
+
when :secure
|
88
|
+
"Secure" if @attributes[a]
|
89
|
+
when :maxage
|
90
|
+
"MaxAge=" + @attributes[a].to_s
|
91
|
+
when :expires
|
92
|
+
"Expires=" + rfc2822(@attributes[a])
|
93
|
+
when :comment
|
94
|
+
"Comment=" + escape(@attributes[a].to_s)
|
95
|
+
else
|
96
|
+
a.to_s.sub(/^\w/) { $&.capitalize } + "=" + @attributes[a].to_s
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
([escape(name) + "=" + escape(value)] + attributes).join("; ")
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def rfc2822(time)
|
106
|
+
wday = Time::RFC2822_DAY_NAME[time.wday]
|
107
|
+
mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
|
108
|
+
time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
|
109
|
+
end
|
110
|
+
|
111
|
+
if URI.respond_to?(:decode_www_form_component) and defined?(::Encoding)
|
112
|
+
# Escape a cookie
|
113
|
+
def escape(s)
|
114
|
+
URI.encode_www_form_component(s)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Unescape a cookie
|
118
|
+
# @private
|
119
|
+
def self.unescape(s, encoding = Encoding::UTF_8)
|
120
|
+
URI.decode_www_form_component(s, encoding)
|
121
|
+
end
|
122
|
+
else # We're on 1.8.7, or JRuby or Rubinius in 1.8 mode
|
123
|
+
# Copied and modified from 1.9.x URI
|
124
|
+
# @private
|
125
|
+
TBLENCWWWCOMP_ = {}
|
126
|
+
256.times do |i|
|
127
|
+
TBLENCWWWCOMP_[i.chr] = '%%%02X' % i
|
128
|
+
end
|
129
|
+
TBLENCWWWCOMP_[' '] = '+'
|
130
|
+
TBLENCWWWCOMP_.freeze
|
131
|
+
|
132
|
+
# @private
|
133
|
+
TBLDECWWWCOMP_ = {}
|
134
|
+
256.times do |i|
|
135
|
+
h, l = i>>4, i&15
|
136
|
+
TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr
|
137
|
+
TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr
|
138
|
+
TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr
|
139
|
+
TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr
|
140
|
+
end
|
141
|
+
TBLDECWWWCOMP_['+'] = ' '
|
142
|
+
TBLDECWWWCOMP_.freeze
|
143
|
+
|
144
|
+
# Decode given +str+ of URL-encoded form data.
|
145
|
+
#
|
146
|
+
# This decodes + to SP.
|
147
|
+
#
|
148
|
+
# @private
|
149
|
+
def self.unescape(str, enc=nil)
|
150
|
+
raise ArgumentError, "invalid %-encoding (#{str})" unless /\A(?:%\h\h|[^%]+)*\z/ =~ str
|
151
|
+
str.gsub(/\+|%\h\h/){|c| TBLDECWWWCOMP_[c] }
|
152
|
+
end
|
153
|
+
|
154
|
+
# Encode given +str+ to URL-encoded form data.
|
155
|
+
#
|
156
|
+
# This method doesn't convert *, -, ., 0-9, A-Z, _, a-z, but does convert SP
|
157
|
+
# (ASCII space) to + and converts others to %XX.
|
158
|
+
#
|
159
|
+
# This is an implementation of
|
160
|
+
# http://www.w3.org/TR/html5/forms.html#url-encoded-form-data
|
161
|
+
#
|
162
|
+
# @private
|
163
|
+
def escape(str)
|
164
|
+
str.to_s.gsub(/[^*\-.0-9A-Z_a-z]/){|c| TBLENCWWWCOMP_[c] }
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|