Class: Ibex::LSP::Transport
- Inherits:
-
Object
- Object
- Ibex::LSP::Transport
- Defined in:
- lib/ibex/lsp/transport.rb,
sig/ibex/lsp/transport.rbs
Overview
Reads and writes bounded LSP JSON-RPC messages over Content-Length framing.
Constant Summary collapse
- HEADER_SEPARATOR =
"\r\n\r\n"
Instance Method Summary collapse
- #content_length(header) ⇒ Integer
-
#initialize(input, output) ⇒ Transport
constructor
A new instance of Transport.
- #read_bytes(length) ⇒ String
-
#read_chunk(length) ⇒ String?
IO#read(length) may wait for the entire length on a live pipe.
- #read_header ⇒ String?
- #read_message ⇒ Hash[String, untyped]?
- #write_message(message) ⇒ void
Constructor Details
#initialize(input, output) ⇒ Transport
Returns a new instance of Transport.
10 11 12 13 14 |
# File 'lib/ibex/lsp/transport.rb', line 10 def initialize(input, output) @input = input @output = output @buffer = String.new(encoding: Encoding::BINARY) end |
Instance Method Details
#content_length(header) ⇒ Integer
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/ibex/lsp/transport.rb', line 73 def content_length(header) values = header.split("\r\n").filter_map do |line| name, value = line.split(":", 2) raise ProtocolError.new("malformed JSON-RPC header", code: -32_700, fatal: true) unless value value.strip if name&.casecmp?("Content-Length") end unless values.one? && values.fetch(0).match?(/\A\d+\z/) raise ProtocolError.new("exactly one numeric Content-Length header is required", code: -32_700, fatal: true) end length = Integer(values.fetch(0), 10) if length > Limits::MAX_MESSAGE_BYTES raise ProtocolError.new("JSON-RPC message exceeds #{Limits::MAX_MESSAGE_BYTES} bytes", code: -32_700, fatal: true) end length end |
#read_bytes(length) ⇒ String
94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/ibex/lsp/transport.rb', line 94 def read_bytes(length) while @buffer.bytesize < length chunk = read_chunk([4096, length - @buffer.bytesize].min) raise ProtocolError.new("truncated JSON-RPC body", code: -32_700, fatal: true) unless chunk @buffer << chunk.b end body = @buffer.byteslice(0, length) || "" @buffer = @buffer.byteslice(length, @buffer.bytesize) || +"" body.force_encoding(Encoding::UTF_8) end |
#read_chunk(length) ⇒ String?
IO#read(length) may wait for the entire length on a live pipe. readpartial lets the stdio server process the bytes that are already available.
109 110 111 112 113 114 115 |
# File 'lib/ibex/lsp/transport.rb', line 109 def read_chunk(length) return @input.read(length) unless @input.respond_to?(:readpartial) @input.readpartial(length) rescue EOFError nil end |
#read_header ⇒ String?
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/ibex/lsp/transport.rb', line 46 def read_header loop do index = @buffer.index(HEADER_SEPARATOR) if index if index > Limits::MAX_HEADER_BYTES raise ProtocolError.new("JSON-RPC header exceeds #{Limits::MAX_HEADER_BYTES} bytes", code: -32_700, fatal: true) end header = @buffer.byteslice(0, index) || "" @buffer = @buffer.byteslice(index + HEADER_SEPARATOR.bytesize, @buffer.bytesize) || +"" return header end if @buffer.bytesize > Limits::MAX_HEADER_BYTES raise ProtocolError.new("JSON-RPC header exceeds #{Limits::MAX_HEADER_BYTES} bytes", code: -32_700, fatal: true) end chunk = read_chunk(4096) return nil if chunk.nil? && @buffer.empty? raise ProtocolError.new("truncated JSON-RPC header", code: -32_700, fatal: true) unless chunk @buffer << chunk.b end end |
#read_message ⇒ Hash[String, untyped]?
17 18 19 20 21 22 23 24 25 26 27 28 29 |
# File 'lib/ibex/lsp/transport.rb', line 17 def header = read_header return unless header length = content_length(header) body = read_bytes(length) parsed = JSON.parse(body) raise ProtocolError.new("JSON-RPC message must be an object", code: -32_600) unless parsed.is_a?(Hash) parsed rescue JSON::ParserError => e raise ProtocolError.new("malformed JSON: #{e.}", code: -32_700) end |
#write_message(message) ⇒ void
This method returns an undefined value.
32 33 34 35 36 37 38 39 40 41 |
# File 'lib/ibex/lsp/transport.rb', line 32 def () body = JSON.generate() if body.bytesize > Limits::MAX_OUTPUT_BYTES raise ProtocolError.new("JSON-RPC output exceeds #{Limits::MAX_OUTPUT_BYTES} bytes", code: -32_603) end @output.write("Content-Length: #{body.bytesize}\r\n\r\n") @output.write(body) @output.flush if @output.respond_to?(:flush) end |