Class: Protocol::HTTY::Stream

Inherits:
Object
  • Object
show all
Defined in:
lib/protocol/htty/stream.rb

Overview

Transport an opaque byte stream after the HTTY bootstrap handshake.

Constant Summary collapse

ESC =
"\e"
DCS =
"#{ESC}P"
ST =
"#{ESC}\\"
BOOTSTRAP_PREFIX =
"+H"
RAW_MODE =
"raw"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(input, output) ⇒ Stream

Create a stream on top of raw byte-preserving endpoints.



40
41
42
43
44
# File 'lib/protocol/htty/stream.rb', line 40

def initialize(input, output)
	@input = input
	@output = output
	@local_closed = false
end

Instance Attribute Details

#inputObject (readonly)

Returns the value of attribute input.



46
47
48
# File 'lib/protocol/htty/stream.rb', line 46

def input
  @input
end

#outputObject (readonly)

Returns the value of attribute output.



47
48
49
# File 'lib/protocol/htty/stream.rb', line 47

def output
  @output
end

Class Method Details

.open(input, output, bootstrap: nil, mode: RAW_MODE) ⇒ Object



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/protocol/htty/stream.rb', line 16

def self.open(input, output, bootstrap: nil, mode: RAW_MODE)
	stream = self.new(input, output)
	
	# Disable buffering:
	input.sync = true
	output.sync = true
	
	case bootstrap
	when :write
		stream.write_bootstrap(mode)
	when :read
		actual_mode = stream.read_bootstrap
		
		unless actual_mode == mode
			raise ProtocolError, "Expected HTTY bootstrap mode #{mode.inspect}, got #{actual_mode.inspect}"
		end
	end
	
	return stream
end

Instance Method Details

#close_write(error = nil) ⇒ Object Also known as: close

Close the local write side of this stream abstraction. HTTY does not define a close packet, and closing this object does not close the underlying terminal IO.



101
102
103
104
105
106
# File 'lib/protocol/htty/stream.rb', line 101

def close_write(error = nil)
	unless @local_closed
		@local_closed = true
		@output.flush
	end
end

#closed?Boolean

Check whether the local side of the transport is closed.

Returns:

  • (Boolean)


112
113
114
# File 'lib/protocol/htty/stream.rb', line 112

def closed?
	@local_closed
end

#flushObject

Flush any buffered output through the underlying stream.



94
95
96
# File 'lib/protocol/htty/stream.rb', line 94

def flush
	@output.flush
end

#ioObject

Return the underlying output stream.



50
51
52
# File 'lib/protocol/htty/stream.rb', line 50

def io
	@output
end

#read(length = nil) ⇒ Object

Read application bytes from the HTTY transport. The HTTP/2 framer always requests exact byte counts (header size, then payload length), so we delegate directly to the underlying input.



76
77
78
# File 'lib/protocol/htty/stream.rb', line 76

def read(length = nil)
	@input.read(length)
end

#read_bootstrapObject



59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/protocol/htty/stream.rb', line 59

def read_bootstrap
	while payload = read_payload
		next unless payload.start_with?(BOOTSTRAP_PREFIX)
		mode = payload.delete_prefix(BOOTSTRAP_PREFIX)
		
		unless mode == RAW_MODE
			raise ProtocolError, "Unsupported HTTY bootstrap mode: #{mode.inspect}"
		end
		
		return mode
	end
	
	return nil
end

#readable?Boolean

Check whether the remote side may still provide more data.

Returns:

  • (Boolean)


118
119
120
# File 'lib/protocol/htty/stream.rb', line 118

def readable?
	!(@input.respond_to?(:closed?) && @input.closed?)
end

#write(data, flush: false) ⇒ Object

Write application bytes after bootstrap.

Raises:

  • (IOError)


83
84
85
86
87
88
89
90
# File 'lib/protocol/htty/stream.rb', line 83

def write(data, flush: false)
	raise IOError, "HTTY stream is closed for writing!" if @local_closed
	
	@output.write(data.to_s.b)
	@output.flush if flush
	
	return self
end

#write_bootstrap(mode = RAW_MODE) ⇒ Object



54
55
56
57
# File 'lib/protocol/htty/stream.rb', line 54

def write_bootstrap(mode = RAW_MODE)
	@output.write("#{DCS}#{BOOTSTRAP_PREFIX}#{mode}#{ST}")
	@output.flush
end