webmachine 0.4.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
![preview calls at decision](http://seancribbs-skitch.s3.amazonaws.com/Webmachine_Trace_2156885920-20120625-100153.png)
|
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
|