///|
/// header_framer returns a new Framer.
/// The messages are sent with HTTP content length and MIME type headers.
/// This is the format used by LSP and others.
pub fn header_framer() -> &Framer {
HeaderFramer::{ }
}
///|
pub struct HeaderFramer {}
///|
pub struct HeaderReader {
in_ : &@io.ByteReader
}
///|
pub struct HeaderWriter {
out : &@io.Writer
}
///|
pub impl Framer for HeaderFramer with fn reader(_self, in_) -> &Reader {
HeaderReader::{ in_, }
}
///|
pub impl Framer for HeaderFramer with fn writer(_self, out) -> &Writer {
HeaderWriter::{ out, }
}
///|
pub impl Reader for HeaderReader with fn read(self) -> (
Message?,
Int64,
@io.IOError?,
) {
let mut total = 0L
let mut length = 0
// read the header, stop on the first empty line
for ;; {
let buf = @io.Buffer::new()
let (n, err) = @io.copy_until(buf, self.in_, b'\n')
total += n
guard err is None else {
return (
None,
total,
Some(
@io.IOError("failed reading header line: \{err.unwrap().to_repr()}"),
),
)
}
let line = buf.to_string().trim()
// check we have a header line
if line == "" {
break
}
let colon = match line.find(":") {
Some(c) => c
None =>
return (
None,
total,
Some(@io.IOError("missing colon, invalid header line: '\{line}'")),
)
}
let name = line.to_owned().unsafe_substring(start=0, end=colon)
let value = line
.to_owned()
.unsafe_substring(start=colon + 1, end=line.length())
.trim()
// ignore unknown headers
if name.to_lower() == "content-length" {
length = @string.parse_int(value.to_owned(), base=10) catch {
_ =>
return (
None,
total,
Some(@io.IOError("failed parsing Content-Length: \{value}")),
)
}
if length <= 0 {
return (
None,
total,
Some(@io.IOError("invalid Content-Length: \{length}")),
)
}
}
}
if length == 0 {
return (None, total, Some(@io.IOError("missing Content-Length header")))
}
let buf = @io.Buffer::new(size_hint=length)
let (n, err) = @io.copy_size(buf, self.in_, length.to_int64())
total += n
guard err is None else {
return (
None,
total,
Some(@io.IOError("failed reading body: \{err.unwrap().to_repr()}")),
)
}
let msg : Message = @json.from_json(@json.parse(buf.to_string())) catch {
e => return (None, 0, Some(@io.IOError(e.to_string())))
}
(Some(msg), n, None)
}
///|
pub impl Writer for HeaderWriter with fn write(self, msg) -> (
Int64,
@io.IOError?,
) {
let s = msg.to_json().stringify()
let body = @io.Buffer::new()
let (body_total, err) = body.write_string(s)
guard err is None else { return (0, err) }
let hdr = @io.Buffer::new()
let (_, err) = hdr.write_string("Content-Length: \{body_total}\r\n\r\n")
guard err is None else { return (0, err) }
let (hdr_total, err) = self.out.write(hdr.to_slice())
guard err is None else { return (hdr_total.to_int64(), err) }
let (n, err) = self.out.write(body.to_slice())
guard err is None else { return (n.to_int64() + hdr_total.to_int64(), err) }
(n.to_int64() + hdr_total.to_int64(), err)
}