Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 10 additions & 16 deletions lib/protocol/grpc/body/readable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -117,26 +117,20 @@ def read_exactly(n)
def decompress(data)
case @encoding
when "gzip"
# Gzip format: zlib stream with gzip header (RFC 1952)
# Use MAX_WBITS + 32 to handle gzip header and CRC
inflater = Zlib::Inflate.new(Zlib::MAX_WBITS + 32)
result = inflater.inflate(data)
inflater.close
result
begin
Zlib.gunzip(data)
rescue => error
raise Error.new(Status::INTERNAL, "Failed to decompress message: #{error.message}")
end
when "deflate"
# Zlib format (RFC 1950) - default window bits handle zlib header
# This matches HTTP's "deflate" content-encoding
inflater = Zlib::Inflate.new
result = inflater.inflate(data)
inflater.close
result
begin
Zlib::Inflate.inflate(data)
rescue => error
raise Error.new(Status::INTERNAL, "Failed to decompress message: #{error.message}")
end
else
raise Error.new(Status::UNIMPLEMENTED, "Unsupported compression encoding: #{@encoding.inspect}")
end
rescue Error
raise
rescue StandardError => error
raise Error.new(Status::INTERNAL, "Failed to decompress message: #{error.message}")
end
end
end
Expand Down
24 changes: 11 additions & 13 deletions lib/protocol/grpc/body/writable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
require "protocol/http"
require "protocol/http/body/writable"
require "zlib"
require "stringio"

require_relative "../error"
require_relative "../status"
Expand Down Expand Up @@ -89,21 +88,20 @@ def write(message, compressed: nil)
def compress(data)
case @encoding
when "gzip"
# Use GzipWriter for proper gzip format (includes headers, CRC)
io = StringIO.new
gz = Zlib::GzipWriter.new(io, @level)
gz.write(data)
gz.close
io.string
begin
Zlib.gzip(data, level: @level)
rescue => error
raise Error.new(Status::INTERNAL, "Failed to compress message: #{error.message}")
end
when "deflate"
# Use zlib format (RFC 1950) for HTTP compatibility
# This matches HTTP's "deflate" content-encoding
Zlib::Deflate.deflate(data, @level)
begin
Zlib::Deflate.deflate(data, @level)
rescue => error
raise Error.new(Status::INTERNAL, "Failed to compress message: #{error.message}")
end
else
raise ArgumentError, "Unsupported compression encoding: #{@encoding.inspect}"
raise Error.new(Status::INTERNAL, "Unsupported compression encoding: #{@encoding.inspect}")
end
rescue StandardError => error
raise Error.new(Status::INTERNAL, "Failed to compress message: #{error.message}")
end
end
end
Expand Down
13 changes: 12 additions & 1 deletion test/protocol/grpc/body/readable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -227,12 +227,23 @@ def source_body.read
end
end

it "raises a gRPC error for invalid compressed data" do
it "raises a gRPC error for invalid gzip data" do
body = subject.new(source_body, encoding: "gzip")
write_data("invalid", compressed: true)

expect{body.read}.to raise_exception(Protocol::GRPC::Error) do |error|
expect(error.status_code).to be == Protocol::GRPC::Status::INTERNAL
expect(error.message).to be =~ /Failed to decompress message/
end
end

it "raises a gRPC error for invalid deflate data" do
body = subject.new(source_body, encoding: "deflate")
write_data("invalid", compressed: true)

expect{body.read}.to raise_exception(Protocol::GRPC::Error) do |error|
expect(error.status_code).to be == Protocol::GRPC::Status::INTERNAL
expect(error.message).to be =~ /Failed to decompress message/
end
end
end
Expand Down
27 changes: 25 additions & 2 deletions test/protocol/grpc/body/writable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,17 @@ def message.encode
expect(compressed).to be == 0
end

it "compresses messages using gzip" do
body = subject.new(encoding: "gzip")
message = message_class.new(value: "Hello")
body.write(message)
body.close_write

framed_data = body.join
expect(framed_data.getbyte(0)).to be == 1
expect(Zlib.gunzip(framed_data.byteslice(5..))).to be == message.to_proto
end

it "compresses messages using deflate" do
body = subject.new(encoding: "deflate")
message = message_class.new(value: "Hello")
Expand All @@ -234,11 +245,23 @@ def message.encode
end
end

it "raises a gRPC error when compression fails" do
body = subject.new(encoding: "deflate", level: 100)
it "raises a gRPC error when gzip compression fails" do
body = subject.new(encoding: "gzip")
expect(Zlib).to receive(:gzip).and_raise(Zlib::StreamError, "compression failed")

expect{body.write("Hello")}.to raise_exception(Protocol::GRPC::Error) do |error|
expect(error.status_code).to be == Protocol::GRPC::Status::INTERNAL
expect(error.message).to be == "Failed to compress message: compression failed"
end
end

it "raises a gRPC error when deflate compression fails" do
body = subject.new(encoding: "deflate")
expect(Zlib::Deflate).to receive(:deflate).and_raise(Zlib::StreamError, "compression failed")

expect{body.write("Hello")}.to raise_exception(Protocol::GRPC::Error) do |error|
expect(error.status_code).to be == Protocol::GRPC::Status::INTERNAL
expect(error.message).to be == "Failed to compress message: compression failed"
end
end
end
Expand Down