Zack Gao
Zack Gao

Reputation: 568

Using Ruby StringIO's gets method

If I run the following code:

require 'stringio'
chunked_data = StringIO.new("7\r\nHello, \r\n6\r\nworld!\r\n0\r\n")
data = ""
until chunked_data.eof?
  if chunk_head = chunked_data.gets("\r\n")
    print chunk_head
    if chunk = chunked_data.read(chunk_head.to_i)
      print chunk
      data << chunk
    end
  end
end

I get this output:

7
Hello, 
6
world!
0

According to the docs, the gets method takes a separator and returns the next line based on that separator. If I replace the \r\n in the string and separator with ., then the separator functionality seems to no longer work and I get this:

7.Hello, .6.world!.0.

What's going on?

Upvotes: 2

Views: 339

Answers (1)

Stefan
Stefan

Reputation: 114138

To see what's actually going on, let's replace print with a more explicit output:

require 'stringio'

def parse(chunked_data, separator)
  data = ""
  until chunked_data.eof?
    if chunk_head = chunked_data.gets(separator)
      puts "chunk_head: #{chunk_head.inspect}"
      if chunk = chunked_data.read(chunk_head.to_i)
        puts "     chunk: #{chunk.inspect}"
        data << chunk
      end
    end
  end
  data
end

result = parse(StringIO.new("7\r\nHello, \r\n6\r\nworld!\r\n0\r\n"), "\r\n")
puts "    result: #{result.inspect}"

Output:

chunk_head: "7\r\n"
     chunk: "Hello, "
chunk_head: "\r\n"
     chunk: ""
chunk_head: "6\r\n"
     chunk: "world!"
chunk_head: "\r\n"
     chunk: ""
chunk_head: "0\r\n"
     chunk: ""

Now with a .:

result = parse(StringIO.new("7.Hello, .6.world!.0."), ".")
puts "    result: #{result.inspect}"

Output:

chunk_head: "7."
     chunk: "Hello, "
chunk_head: "."
     chunk: ""
chunk_head: "6."
     chunk: "world!"
chunk_head: "."
     chunk: ""
chunk_head: "0."
     chunk: ""
    result: "Hello, world!"

As you can see, it works either way and the result is identical.

Although the result is correct, there seems to be a bug in your code: you don't read the separator after chunk. This can be fixed by adding a chunked_data.gets(separator) or chunked_data.read(separator.bytesize) after the if/end block:

def parse(chunked_data, separator)
  data = ""
  until chunked_data.eof?
    if chunk_head = chunked_data.gets(separator)
      puts "chunk_head: #{chunk_head.inspect}"
      if chunk = chunked_data.read(chunk_head.to_i)
        puts "     chunk: #{chunk.inspect}"
        data << chunk
      end
      chunked_data.read(separator.bytesize)
    end
  end
  data
end

result = parse(StringIO.new("7.Hello, .6.world!.0."), ".")
puts "    result: #{result.inspect}"

Output:

chunk_head: "7."
     chunk: "Hello, "
chunk_head: "6."
     chunk: "world!"
chunk_head: "0."
     chunk: ""
    result: "Hello, world!"

That looks better.

Upvotes: 2

Related Questions