How to use http compression with Savon

Doing a lot of SOAP requests using Savon in Rails I wondered, if it is possible to reduce the size of the SOAP responses using http compression (gzip or deflate). Short answer, it is. Long answer, you have to know how to enable it and there a multiple ways to do the trick.

The following is based on Savon 1.0, so if you are using Savon >= 2, things may have changed.

First some background information. In version 0.7.9 Savon added support for gzip compression, so I first tried this.

Savon::Client.new "http://mydomain/myService?wsdl", :gzip => true

Unfortunately, that doesn’t work and Savon complains about wrong argument number. Digging into Savons code it showed, that you can only pass a block as the second parameter. But what do put in there ?

Savon internally uses HTTPI to abstract several Ruby http clients. When you want to mess with http in Savon, you have to mess with HTTPI. Now back to the question what do put in Savons Client block to enable http compression ? The answer is, from inside the block, you can access ‘http’, which is in fact an instance of HTTPI::Request.

HTTPI::Request provides some methods to set and alter the requests http headers. That means, setting http header options for a Savon Client would look like this

Savon::Client.new "http://mydomain/myService?wsdl" do
  http.headers = { 'Accept-Encoding' => 'gzip, deflate' }
end

Of cause, you can set other headers this way, too. It’s just a hash.

HTTPI::Request offers a shortcut method for setting the http header to indicate http compression. It’s called ‘gzip’. So our code from above code also be written like this.

Savon::Client.new "http://mydomain/myService?wsdl" do
  http.gzip
end

Ok, we are done. Quit simple if you know where to put it 🙂

Last but not least you could also enable compression ‘per request’ using Savons soap request hook. Savon offers exactly one hook called :soap_request. To let the documentation speak, it acts like an around filter wrapping the POST request executed to call a SOAP service.

The benefit of interfering the request using this hook is, that you can enable http compression ‘globally’ for all instances of Savon::Client.

Savon.configure do |config|
  config.hooks.define(:enable_compression, :soap_request) do |callback, request|
    # we have to use request.http instead of http
    request.http.gzip
    # and trigger to actual request on our own
    response = callback.call
  end
end

Using http compression the size of my SOAP responses could be noticeably reduced. In fact some compressed responses are only 1/10 of their original size. So this is a very cheap option to save bandwidth and maybe speed up request processing.

How to use http compression with Savon

Include files from git submodules when building a ruby gem

Today I ran into the following situation. I wanted to build a gem with bundler which has some vendor assets included as git submodules. My directory structure was something like that

.git/
app/*
lib/*
vendor/assets/javascripts/es5-shim (submodule)
vendor/assets/javascripts/pathjs (submodule)

When doing

rake build

the files from the submodules where not included in the gem, because the gemspec specifies the files as follows.

gem.files = `git ls-files`.split($\)

Unfortunately, git ls-files does not list files from submodules and that’s why, these files are not included in the gem.

I solved this by utilizing git submodules’s foreach statement in combination with some ruby string manipulation.

The resulting gemspec looks like this.


# -*- encoding: utf-8 -*-
require File.expand_path('../lib/example/version', __FILE__)
Gem::Specification.new do |gem|
gem.authors = ["John Doe"]
gem.email = ["john_doe@example.org"]
gem.description = %q{Write a gem description}
gem.summary = %q{Write a gem summary}
gem.homepage = ""
gem.files = `git ls-files`.split($\)
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
gem.name = "example"
gem.require_paths = ["lib"]
gem.version = Example::VERSION
# get an array of submodule dirs by executing 'pwd' inside each submodule
`git submodule –quiet foreach pwd`.split($\).each do |submodule_path|
# for each submodule, change working directory to that submodule
Dir.chdir(submodule_path) do
# issue git ls-files in submodule's directory
submodule_files = `git ls-files`.split($\)
# prepend the submodule path to create absolute file paths
submodule_files_fullpaths = submodule_files.map do |filename|
"#{submodule_path}/#{filename}"
end
# remove leading path parts to get paths relative to the gem's root dir
# (this assumes, that the gemspec resides in the gem's root dir)
submodule_files_paths = submodule_files_fullpaths.map do |filename|
filename.gsub "#{File.dirname(__FILE__)}/", ""
end
# add relative paths to gem.files
gem.files += submodule_files_paths
end
end
end

view raw

example.gemspec

hosted with ❤ by GitHub

Matt Connolly suggested a shorter version of the gemspec. Have a look at his comment.

Include files from git submodules when building a ruby gem