From 4e9144fba2a8c52c1555295dc01ae715d434fcc8 Mon Sep 17 00:00:00 2001 From: Tom Hughes Date: Sun, 21 Jan 2018 15:50:32 +0000 Subject: [PATCH 1/1] Add support for compressed request bodies --- config/initializers/compressed_requests.rb | 42 +++++ test/integration/compressed_requests_test.rb | 181 +++++++++++++++++++ 2 files changed, 223 insertions(+) create mode 100644 config/initializers/compressed_requests.rb create mode 100644 test/integration/compressed_requests_test.rb diff --git a/config/initializers/compressed_requests.rb b/config/initializers/compressed_requests.rb new file mode 100644 index 000000000..c6a84a103 --- /dev/null +++ b/config/initializers/compressed_requests.rb @@ -0,0 +1,42 @@ +module OpenStreetMap + class CompressedRequests + def initialize(app) + @app = app + end + + def method_handled?(env) + %w[POST PUT].include? env["REQUEST_METHOD"] + end + + def encoding_handled?(env) + %w[gzip deflate].include? env["HTTP_CONTENT_ENCODING"] + end + + def call(env) + if method_handled?(env) && encoding_handled?(env) + extracted = decode(env["rack.input"], env["HTTP_CONTENT_ENCODING"]) + + env.delete("HTTP_CONTENT_ENCODING") + env["CONTENT_LENGTH"] = extracted.bytesize + env["rack.input"] = StringIO.new(extracted) + end + + if env["HTTP_CONTENT_ENCODING"] + [415, {}, []] + else + @app.call(env) + end + end + + def decode(input, content_encoding) + input.rewind + + case content_encoding + when "gzip" then Zlib::GzipReader.new(input).read + when "deflate" then Zlib::Inflate.inflate(input.read) + end + end + end +end + +Rails.configuration.middleware.use OpenStreetMap::CompressedRequests diff --git a/test/integration/compressed_requests_test.rb b/test/integration/compressed_requests_test.rb new file mode 100644 index 000000000..0c66b306e --- /dev/null +++ b/test/integration/compressed_requests_test.rb @@ -0,0 +1,181 @@ +require "test_helper" + +class CompressedRequestsTest < ActionDispatch::IntegrationTest + def test_no_compression + user = create(:user) + changeset = create(:changeset, :user => user) + + node = create(:node) + way = create(:way) + relation = create(:relation) + other_relation = create(:relation) + # Create some tags, since we test that they are removed later + create(:node_tag, :node => node) + create(:way_tag, :way => way) + create(:relation_tag, :relation => relation) + + # simple diff to change a node, way and relation by removing + # their tags + diff = < + + + + + + + + + + + + + + +CHANGESET + + # upload it + post "/api/0.6/changeset/#{changeset.id}/upload", + :params => diff, + :headers => { + "HTTP_AUTHORIZATION" => format("Basic %s", Base64.encode64("#{user.display_name}:test")), + "HTTP_CONTENT_TYPE" => "application/xml" + } + assert_response :success, + "can't upload an uncompressed diff to changeset: #{@response.body}" + + # check that the changes made it into the database + assert_equal 0, Node.find(node.id).tags.size, "node #{node.id} should now have no tags" + assert_equal 0, Way.find(way.id).tags.size, "way #{way.id} should now have no tags" + assert_equal 0, Relation.find(relation.id).tags.size, "relation #{relation.id} should now have no tags" + end + + def test_gzip_compression + user = create(:user) + changeset = create(:changeset, :user => user) + + node = create(:node) + way = create(:way) + relation = create(:relation) + other_relation = create(:relation) + # Create some tags, since we test that they are removed later + create(:node_tag, :node => node) + create(:way_tag, :way => way) + create(:relation_tag, :relation => relation) + + # simple diff to change a node, way and relation by removing + # their tags + diff = < + + + + + + + + + + + + + + +CHANGESET + + # upload it + post "/api/0.6/changeset/#{changeset.id}/upload", + :params => gzip_content(diff), + :headers => { + "HTTP_AUTHORIZATION" => format("Basic %s", Base64.encode64("#{user.display_name}:test")), + "HTTP_CONTENT_ENCODING" => "gzip", + "HTTP_CONTENT_TYPE" => "application/xml" + } + assert_response :success, + "can't upload a gzip compressed diff to changeset: #{@response.body}" + + # check that the changes made it into the database + assert_equal 0, Node.find(node.id).tags.size, "node #{node.id} should now have no tags" + assert_equal 0, Way.find(way.id).tags.size, "way #{way.id} should now have no tags" + assert_equal 0, Relation.find(relation.id).tags.size, "relation #{relation.id} should now have no tags" + end + + def test_deflate_compression + user = create(:user) + changeset = create(:changeset, :user => user) + + node = create(:node) + way = create(:way) + relation = create(:relation) + other_relation = create(:relation) + # Create some tags, since we test that they are removed later + create(:node_tag, :node => node) + create(:way_tag, :way => way) + create(:relation_tag, :relation => relation) + + # simple diff to change a node, way and relation by removing + # their tags + diff = < + + + + + + + + + + + + + + +CHANGESET + + # upload it + post "/api/0.6/changeset/#{changeset.id}/upload", + :params => deflate_content(diff), + :headers => { + "HTTP_AUTHORIZATION" => format("Basic %s", Base64.encode64("#{user.display_name}:test")), + "HTTP_CONTENT_ENCODING" => "deflate", + "HTTP_CONTENT_TYPE" => "application/xml" + } + assert_response :success, + "can't upload a deflate compressed diff to changeset: #{@response.body}" + + # check that the changes made it into the database + assert_equal 0, Node.find(node.id).tags.size, "node #{node.id} should now have no tags" + assert_equal 0, Way.find(way.id).tags.size, "way #{way.id} should now have no tags" + assert_equal 0, Relation.find(relation.id).tags.size, "relation #{relation.id} should now have no tags" + end + + def test_invalid_compression + user = create(:user) + changeset = create(:changeset, :user => user) + + # upload it + post "/api/0.6/changeset/#{changeset.id}/upload", + :params => "", + :headers => { + "HTTP_AUTHORIZATION" => format("Basic %s", Base64.encode64("#{user.display_name}:test")), + "HTTP_CONTENT_ENCODING" => "unknown", + "HTTP_CONTENT_TYPE" => "application/xml" + } + assert_response :unsupported_media_type + end + + private + + def gzip_content(uncompressed) + compressed = StringIO.new + gz = Zlib::GzipWriter.new(compressed) + gz.write(uncompressed) + gz.close + compressed.string + end + + def deflate_content(uncompressed) + Zlib::Deflate.deflate(uncompressed) + end +end -- 2.43.2