Rack::Utils and CGI escape and unescape performance boost
As performance boosts are about speed, we’ll start with the benchmarks. Here is a run of spec/bench.rb, from the url_escape source tree.
Escape
| - | user | system | total | real |
|---|---|---|---|---|
| URLEscape::escape | 0.200000 | 0.000000 | 0.200000 | ( 0.196100) |
| CGI::escape | 3.830000 | 0.010000 | 3.840000 | ( 3.828438) |
| Rack::Utils::escape | 3.880000 | 0.010000 | 3.890000 | ( 3.880745) |
Unescape
| - | user | system | total | real |
|---|---|---|---|---|
| URLEscape::unescape | 0.090000 | 0.000000 | 0.090000 | ( 0.089190) |
| CGI::unescape | 2.820000 | 0.000000 | 2.820000 | ( 2.816234) |
| Rack::Utils::unescape | 3.140000 | 0.000000 | 3.140000 | ( 3.137291) |
URLEscape provides these two methods as a C extension, suitable for
use on ruby 1.8.6-8 and 1.9.1+; tested on linux, XP, and Vista.
The jruby version uses the java stdlib’s java.net.URLEncoder and
URLDecoder. We only see a 200-700% increase with this change, and would like to improve
on those numbers.
Why?
Josh Susser initially noticed the ability to overload #escape and #unescape while testing a client application. At the same time, we had just come across a bottleneck when regression testing FXC (a web app which serves configuration information to the FreeSWITCH softswitch) where requests were being delayed in our rack middleware, which parses the POST data sent by FreeSWITCH and routes requests to the ramaze application for processing. The delay was noticeable under loads of only 50 req/second; where rack became the bottleneck, not ramaze, the db, or any other factor. Adding the above library (on linux, with ruby 1.9.1) removed the delay in rack, pushing the work back to to the web app (or database) where it’s free to be as slow as it must. Optimally we’d like to perform at a speed equal to the database, making it the final bottleneck in a dynamic application.
Installation and Usage
To use URLEscape standalone
Install with one of the following methods:
- gem install url_escape
- get the tarball from RubyForge
- get the source from GitHub and rake install in the source top-level.
Then simply require “url_escape” and you have access to URLEscape.escape(string) and URLEscape.unescape(string)
To use URLEscape’s escape/unescape in place of CGI or Rack::Utils versions
gem install rackfastescape
or
gem install cgifastescape
This will install url_escape if it’s not already installed, as well.
You can optionally install rackfastescape or cgifastescape from rubyforge’s tarball or the github source (rake install as with urlescape). If you use tarball or source rake install, *you will have to manually install urlescape first.*
Once installed, simply use
require "rack_fast_escape"to replace Rack::Utils version, or
require "cgi_fast_escape"to replace the CGI version.
What else?
The ability of large posts to slow down a web application cannot be removed by just speeding up the POST parser. In order to alleviate the risk of such large POSTs being used to deny a service, firewall or web server throttling or limiting is a more reliable protection to enable. Here are a few examples:
Lighttpd: http://lighttpd.net
- Offers mod_evasive which limits connections per ip, as well as the ability to limit the data rate per connection.
Nginx: http://nginx.org
- Flexible limiting system, per vhost, per user, per connection.
Netfilter/QoS (linux): http://l7-filter.sourceforge.net/
Allow classifiation of HTTP packets so iptables/tc or whatever utility you’d like can have the info it needs about the HTTP protocol to make limiting/dropping/queueing decisions
Others: Apache, Squid, Litespeed, many others will have various methods of limiting size and frequency of requests.
Side Note
When speccing these libraries, a few implementation differences came to light which we’ll highlight here.
- Rack::Utils and CGI both throw errors on a mixed ASCII and Unicode string in ruby 1.9.1 and above
- Java’s URLDecoder and URLEncoder do not escape or unescape mixed ASCII/Unicode properly.
URLEscape (the C version) handles these cases properly, though we don’t expect you’d see them much in proper requests.
Thanks
To Evan Phoenix, Josh Susser, Trey Dempsey, Jayson Vaughn, Michael Fellinger, Kevin Berry, and all the other contributors of ideas and support who made this product a reality.
License
Nothing to fear, it’s MIT
More FreeSWITCH ruby love - FXC on the way 1
With FSR well under way and being used both internally and in the wild for FreeSWITCH application development, the next logical step will be a Configurator for FreeSWITCH itself. FreeSWITCH has long lacked the standard Administrator/User configuration available via web or GUI, FXC is intended to allow web interfaces for configuration to be built easily with any Rack application utilizing the FreeSWITCH xml_curl interface. The first pass of FXC will include some Rack middleware which turns FreeSWITCH requests (to /) into easy to organize routes such as /directory/register/internal/1000, /dialplan/public/8885551212, /configuration/acl.conf. The goal is to eliminate the gruntwork of routing by POST variables, exposing clean Rack-app routes up the stack (for ramaze, sinatra, etc). The following is an example with ramaze.
middleware.rb
module FXC
module Rack
class Middleware
def initialize(app)
@app = app
end
def call(env)
r = ::Rack::Request.new(env)
return @app.call(env) unless r.params["section"]
path = r.params["section"] + "/"
path << case path
when "dialplan/"
dp_req(env, r)
when "directory/"
dir_req(env, r)
when "configuration/"
conf_req(env, r)
end
env["PATH_INFO"] << (env["PATH_INFO"].match(%r{/$}) ? path : "/#{path}")
@app.call(env)
end
private
def dp_req(env, r)
s = [r.params["Caller-Context"]]
s << r.params["Caller-Destination-Number"]
s.join("/")
end
def dir_req(env, r)
s = []
if r.params["purpose"]
s << r.params["purpose"].gsub("-","_")
s << r.params["sip_profile"]
elsif r.params["action"] and r.params["action"] == "sip_auth"
s << "register"
s << r.params["sip_profile"]
s << r.params["sip_auth_username"]
elsif r.params["user"]
s << "voicemail"
s << r.params["sip_profile"]
s << r.params["user"]
end
s.join("/")
end
def conf_req(env, r)
s = []
if r.params["key_name"] == "name"
s << r.params["key_value"]
end
s.join("/")
end
end
end
end
controller/dialplan.rb
# Copyright (c) 2008-2009 The Rubyists, LLC (effortless systems) <rubyists@rubyists.com>
# Distributed under the terms of the MIT license.
# The full text can be found in the LICENSE file included with this software
#
module FXC
class Dialplan < Controller
map '/dialplan'
layout :dialplan
def index(*args)
Ramaze::Log.info("Got unhandled dialplan request: " + request.inspect)
not_found
end
def default(number)
Ramaze::Log.info("got default dialplan request for #{number}")
not_found
end
def public(number)
@did = FXC::Did.first(:number.like /#{number.sub(/^1/,'1?')}/)
if @did
@user = @did.user
@targets = @did.targets
Ramaze::Log.info("Routing #{@did.number} to #{@user.dialstring}")
render_view(:index)
else
Ramaze::Log.info("Got public dialplan request for #{number}, but no DID matches: ")
not_found
end
end
end
end
In the FXC::Dialplan controller, each method represents a FreeSWICH dialplan context. Undefined contexts (in this ramaze controller) will fallthrough to the index method and be logged.
Finally, the FreeSWITCH configuration to send requests to above app becomes a single line/single url
conf/autoloadconfigs/xmlcurl.conf.xml
<configuration name="xml_curl.conf" description="cURL XML Gateway">
<bindings>
<binding name="fxc">
<param name="gateway-url" value="http://127.0.0.1:9292/" bindings="configuration|directory|dialplan"/>
</binding>
</bindings>
</configuration>
Next step is completing all the methods available for each binding type (configuration, dialplan, directory). Once complete FreeSWITCH web config on Rack should follow rapidly. That will be the “Look Ma, No XML” FXC release (TBA).
Ramaze 2009.04 (Innate/Ramaze) Released!
With the release of Rack 1.0, the release of the refactored Ramaze which utilizes Innate is now available. This is a major change from Legacy Ramaze, though attention was paid to as much backwards compatibility as possible. For a complete reference to the new Ramaze, see the Ramaze Book. I'd like to thank manveru and the rest of the Ramaze team for all the work leading up to this release, and look forward to upgrading the few apps I don't already have running on innate-ed ramaze. A small bit about this release:
- the ramaze command - most old syntax is supported, but a newer syntax is going to be standard going forward. For example,
ramaze start myapp.pid -DWould start ramaze, using myapp.pid to store the pid information, as a (-D) daemon on the default port (7000).ramaze statusWould give the status of a running instance of a daemon.ramaze -hto see the new usage options. - config.ru - has replaced start.ru for the rackup usage (also used by ramaze start|stop) This is one critical file needed if you plan on porting an old ramaze app to innate-ed ramaze.
- controllers - are now instances of Innate::Node, and can be grouped in an application namespace. This allows for easy sharing of controller code among applications. A bit of this power is described in the Ramaze Book.
- Helpers - Some helpers have been renamed, notably form_helper to sequel_form see the Helpers section of the book for a list of all the supported helper names and usages
These only touch on the surface of the new changes, an official release document is upcoming.