summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/puppet/defaults.rb8
-rw-r--r--lib/puppet/indirector/rest.rb12
-rw-r--r--lib/puppet/network/http/compression.rb112
-rwxr-xr-xlib/puppet/type/file/content.rb8
4 files changed, 132 insertions, 8 deletions
diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb
index 2e3888298..b0995c2a9 100644
--- a/lib/puppet/defaults.rb
+++ b/lib/puppet/defaults.rb
@@ -621,7 +621,13 @@ module Puppet
:graph => [false, "Whether to create dot graph files for the different
configuration graphs. These dot files can be interpreted by tools
like OmniGraffle or dot (which is part of ImageMagick)."],
- :graphdir => ["$statedir/graphs", "Where to store dot-outputted graphs."]
+ :graphdir => ["$statedir/graphs", "Where to store dot-outputted graphs."],
+ :http_compression => [false, "Allow http compression in REST communication with the master.
+ This setting might improve performance for puppetd -> puppetmasterd communications over slow WANs.
+ Your puppetmaster needs to support compression (usually by activating some settings in a reverse-proxy
+ in front of the puppetmaster, which rules out webrick).
+ It is harmless to activate this settings if your master doesn't support
+ compression, but if it supports it, this setting might reduce performance on high-speed LANs."]
)
# Plugin information.
diff --git a/lib/puppet/indirector/rest.rb b/lib/puppet/indirector/rest.rb
index 4fd385919..f9b11c16f 100644
--- a/lib/puppet/indirector/rest.rb
+++ b/lib/puppet/indirector/rest.rb
@@ -3,10 +3,12 @@ require 'uri'
require 'puppet/network/http_pool'
require 'puppet/network/http/api/v1'
+require 'puppet/network/http/compression'
# Access objects via REST
class Puppet::Indirector::REST < Puppet::Indirector::Terminus
include Puppet::Network::HTTP::API::V1
+ include Puppet::Network::HTTP::Compression.module
class << self
attr_reader :server_setting, :port_setting
@@ -43,22 +45,24 @@ class Puppet::Indirector::REST < Puppet::Indirector::Terminus
content_type = response['content-type'].gsub(/\s*;.*$/,'') # strip any appended charset
+ body = uncompress_body(response)
+
# Convert the response to a deserialized object.
if multiple
- model.convert_from_multiple(content_type, response.body)
+ model.convert_from_multiple(content_type, body)
else
- model.convert_from(content_type, response.body)
+ model.convert_from(content_type, body)
end
else
# Raise the http error if we didn't get a 'success' of some kind.
- message = "Error %s on SERVER: %s" % [response.code, (response.body||'').empty? ? response.message : response.body]
+ message = "Error %s on SERVER: %s" % [response.code, (response.body||'').empty? ? response.message : uncompress_body(response)]
raise Net::HTTPError.new(message, response)
end
end
# Provide appropriate headers.
def headers
- {"Accept" => model.supported_formats.join(", ")}
+ add_accept_encoding({"Accept" => model.supported_formats.join(", ")})
end
def network(request)
diff --git a/lib/puppet/network/http/compression.rb b/lib/puppet/network/http/compression.rb
new file mode 100644
index 000000000..722ecfe24
--- /dev/null
+++ b/lib/puppet/network/http/compression.rb
@@ -0,0 +1,112 @@
+require 'puppet/network/http'
+
+module Puppet::Network::HTTP::Compression
+
+ # this module function allows to use the right underlying
+ # methods depending on zlib presence
+ def module
+ return Active if Puppet.features.zlib?
+ return None
+ end
+ module_function :module
+
+ module Active
+ require 'zlib'
+ require 'stringio'
+
+ # return an uncompressed body if the response has been
+ # compressed
+ def uncompress_body(response)
+ case response['content-encoding']
+ when 'gzip'
+ return Zlib::GzipReader.new(StringIO.new(response.body)).read
+ when 'deflate'
+ return Zlib::Inflate.new().inflate(response.body)
+ when nil, 'identity'
+ return response.body
+ else
+ raise Net::HTTPError.new("Unknown content encoding - #{response['content-encoding']}", response)
+ end
+ end
+
+ def uncompress(response)
+ raise Net::HTTPError.new("No block passed") unless block_given?
+
+ case response['content-encoding']
+ when 'gzip','deflate'
+ uncompressor = ZlibAdapter.new
+ when nil, 'identity'
+ uncompressor = IdentityAdapter.new
+ else
+ raise Net::HTTPError.new("Unknown content encoding - #{response['content-encoding']}", response)
+ end
+
+ yield uncompressor
+
+ uncompressor.close
+ end
+
+ def add_accept_encoding(headers={})
+ headers['accept-encoding'] = 'gzip; q=1.0, deflate; q=1.0; identity' if Puppet.settings[:http_compression]
+ headers
+ end
+
+ # This adapters knows how to uncompress both 'zlib' stream (the deflate algorithm from Content-Encoding)
+ # and GZip streams.
+ class ZlibAdapter
+ def initialize
+ # Create an inflater that knows to parse GZip streams and zlib streams.
+ # This uses a property of the C Zlib library, documented as follow:
+ # windowBits can also be greater than 15 for optional gzip decoding. Add
+ # 32 to windowBits to enable zlib and gzip decoding with automatic header
+ # detection, or add 16 to decode only the gzip format (the zlib format will
+ # return a Z_DATA_ERROR). If a gzip stream is being decoded, strm->adler is
+ # a crc32 instead of an adler32.
+ @uncompressor = Zlib::Inflate.new(15 + 32)
+ @first = true
+ end
+
+ def uncompress(chunk)
+ out = @uncompressor.inflate(chunk)
+ @first = false
+ return out
+ rescue Zlib::DataError => z
+ # it can happen that we receive a raw deflate stream
+ # which might make our inflate throw a data error.
+ # in this case, we try with a verbatim (no header)
+ # deflater.
+ @uncompressor = Zlib::Inflate.new
+ retry if @first
+ raise
+ end
+
+ def close
+ @uncompressor.finish
+ @uncompressor.close
+ end
+ end
+ end
+
+ module None
+ def uncompress_body(response)
+ response.body
+ end
+
+ def add_accept_encoding(headers)
+ headers
+ end
+
+ def uncompress(response)
+ yield IdentityAdapter.new
+ end
+ end
+
+ class IdentityAdapter
+ def uncompress(chunk)
+ chunk
+ end
+
+ def close
+ end
+ end
+end
diff --git a/lib/puppet/type/file/content.rb b/lib/puppet/type/file/content.rb
index 1817d5646..ff139f208 100755
--- a/lib/puppet/type/file/content.rb
+++ b/lib/puppet/type/file/content.rb
@@ -3,12 +3,14 @@ require 'uri'
require 'puppet/util/checksums'
require 'puppet/network/http/api/v1'
+require 'puppet/network/http/compression'
module Puppet
Puppet::Type.type(:file).newproperty(:content) do
include Puppet::Util::Diff
include Puppet::Util::Checksums
include Puppet::Network::HTTP::API::V1
+ include Puppet::Network::HTTP::Compression.module
attr_reader :actual_content
@@ -159,13 +161,13 @@ module Puppet
else
request = Puppet::Indirector::Request.new(:file_content, :find, source_or_content.full_path)
connection = Puppet::Network::HttpPool.http_instance(source_or_content.server, source_or_content.port)
- connection.request_get(indirection2uri(request), {"Accept" => "raw"}) do |response|
+ connection.request_get(indirection2uri(request), add_accept_encoding({"Accept" => "raw"})) do |response|
case response.code
when "404"; nil
- when /^2/; response.read_body { |chunk| yield chunk }
+ when /^2/; uncompress(response) { |uncompressor| response.read_body { |chunk| yield uncompressor.uncompress(chunk) } }
else
# Raise the http error if we didn't get a 'success' of some kind.
- message = "Error %s on SERVER: %s" % [response.code, (response.body||'').empty? ? response.message : response.body]
+ message = "Error %s on SERVER: %s" % [response.code, (response.body||'').empty? ? response.message : uncompress_body(response)]
raise Net::HTTPError.new(message, response)
end
end