使用Rust读取Neovim标准输入时无法获取完整JSON-RPC消息的问题
问题分析
你当前的问题出在按行读取stdin的方式不符合JSON-RPC的消息格式。Neovim发送的JSON-RPC消息结构是:
Content-Length: [字节数]\r\n
\r\n
[对应字节数的JSON内容]
lines()方法会按换行符分割内容,无法处理这种基于长度的消息协议:
- 读取到
Content-Length行后,下一行是空行 - 后续的JSON内容可能包含换行,或者需要一次性读取指定字节数的完整内容,而不是逐行拆分
解决方案
修改读取逻辑,按照JSON-RPC的协议规范来读取:
- 解析
Content-Length头获取消息体长度 - 跳过头部和消息体之间的空行
- 一次性读取对应长度的字节作为完整JSON消息
修改后的main.rs代码:
use std::io::{self, BufRead, BufReader, Read}; use std::str::FromStr; fn main() -> io::Result<()> { let stdin = io::stdin(); let mut reader = BufReader::new(stdin.lock()); loop { // 读取Content-Length头 let mut header_line = String::new(); match reader.read_line(&mut header_line) { Ok(0) => break, // stdin关闭,退出循环 Ok(_) => {}, Err(e) => { logger::print_logs(format!("读取头行失败: {}", e), None)?; continue; } } // 解析Content-Length值 let content_length = match header_line.strip_prefix("Content-Length: ") { Some(len_str) => { let len_str = len_str.trim(); usize::from_str(len_str).map_err(|e| { io::Error::new(io::ErrorKind::InvalidData, format!("解析Content-Length失败: {}", e)) }) } None => { logger::print_logs(format!("无效的头行: {}", header_line), None)?; continue; } }; let content_length = match content_length { Ok(val) => val, Err(e) => { logger::print_logs(e.to_string(), None)?; continue; } }; logger::print_logs(format!("Content-Length: {}", content_length), None)?; // 跳过空行(头和消息体之间的分隔) let mut empty_line = String::new(); if let Err(e) = reader.read_line(&mut empty_line) { logger::print_logs(format!("读取空行失败: {}", e), None)?; continue; } // 读取指定长度的JSON消息体 let mut json_buffer = vec![0; content_length]; match reader.read_exact(&mut json_buffer) { Ok(_) => { let json_str = String::from_utf8_lossy(&json_buffer); logger::print_logs("完整JSON消息:".to_string(), None)?; logger::print_logs(json_str.to_string(), None)?; } Err(e) => { logger::print_logs(format!("读取JSON消息失败: {}", e), None)?; continue; } } } Ok(()) }
额外优化建议
你的logger.rs每次写入日志都先读取整个文件再重新写入,效率较低且存在并发风险(单线程下虽能运行,但不推荐),可以改为追加模式:
use std::fs::{self, File, OpenOptions}; use std::io::Write; const LOGS_FOLDER: &str = "./logs"; const DEFAULT_LOGS_FILE: &str = "lsp_logs.txt"; pub fn print_logs(message: String, file_name: Option<&str>) -> std::io::Result<()> { fs::create_dir_all(LOGS_FOLDER)?; let file_name = file_name.unwrap_or(DEFAULT_LOGS_FILE); let file_location = format!("{}/{}", LOGS_FOLDER, file_name); // 使用追加模式打开文件,无需先读取原有内容 let mut file = OpenOptions::new() .create(true) .append(true) .open(file_location)?; writeln!(file, "{}", message)?; Ok(()) }
内容的提问来源于stack exchange,提问作者Cristi
相关产品推荐
相关产品推荐

