SKKサーバを作成する
#SKK #Rust
概要
Rustの学習のため、SKKサーバのサンプルを作成する
特定の見出し語に対する変換をサポートする
ぼく→emanon001
辞書ファイルの読み込みはサポートしない
AquaSKKで動けばOKとする
SKKサーバのプロトコル
AquaSKKのメモ
AquaSKKのソースコード
skkserv辞書
ソースコード
1(変換リクエスト)しか使用しない
skkservとのやりとりで使用するエンコーディング
リクエスト: EUC-JP
レスポンス: EUC-JP
プログラム辞書
プログラム辞書の変換候補を選んだ場合、ユーザー辞書に保存されない
現在日時のような動的な変換候補を返す場合は、この挙動の方が好ましい
skkservの場合はユーザー辞書に保存されるので、動的な変換候補とは相性がよくない
プログラム辞書用のサーバは指定できない。辞書の場所欄にアドレスを入力しても無視される
/2022nenのように数字を含む見出し語の変換を行うと、数字が#に置き換えられ、1#nen というリクエストが送信される
由来だと思う
12022nen は送信されない
1/#0年/\nとレスポンスを返すことで、2022年 という変換が可能かと思いきや、そのまま#0年と変換される
意図せずTCPストリームを閉じた場合にMacがフリーズする
skkservを停止するなり、AquaSKKの辞書設定からskkservを無効化すれば解消される(タイムラグがある)
Rustでのエンコーディングの変更
encoding crate を使用する
code:rust
fn decode_request(req: &u8) -> Result<String> {
EUC_JP
.decode(req, DecoderTrap::Strict) {
.map_err(|e| anyhow!(e))
}
fn encode_response(res: &str) -> Result<Vec<u8>> {
EUC_JP
.encode(&res, EncoderTrap::Strict)
.map_err(|e| anyhow!(e))
}
TCPサーバを立てる
code:server.rs
pub fn serve(config: ServerConfig) -> Result<()> {
let host = &config.host;
let listener = TcpListener::bind(&host)?;
debug!("bind {}", host);
loop {
let (stream, _) = listener.accept()?;
let config = config.clone();
// 複数クライアントからの接続をサポートするため、コネクション毎にスレッドを立ち上げる
thread::spawn(move || {
handler(stream, config).unwrap_or_else(|e| error!("{:?}", e));
});
}
}
fn handler(mut stream: TcpStream, config: ServerConfig) -> Result<()> {
debug!("Handling data from {}", stream.peer_addr()?);
let mut buffer = 0u8; 1024;
loop {
let nbytes = stream.read(&mut buffer)?;
if nbytes == 0 {
debug!("Connection closed.");
return Ok(());
}
// コマンドを実行
let s = decode_request(&buffer..nbytes)?;
debug!("request: '{}'", s);
let req = Request::from_str(&s)?;
debug!("parsed request: {:?}", req);
let res = match req {
Request::Disconnect => {
debug!("Connection closed.");
return Ok(());
}
Request::Convert(s) => command::convert(&s),
Request::Version => command::skkserv_version(),
Request::Host => command::skkserv_host(&config.host),
Request::Complete(s) => command::complete(&s),
};
debug!("response: {}", res);
let res = encode_response(&res)?;
stream.write_all(&res)?;
}
}
ぼく を emanon001 に変換する
code:command.rs
pub fn convert(src: &str) -> String {
let converters: Vec<Box<dyn SkkConverter>> = vec!Box::new(Emanon001Converter::new());
let candidates = converters
.into_iter()
.flat_map(|c| c.convert(src))
.flatten()
.collect::<Vec<String>>();
if candidates.is_empty() {
return "4\n".to_string();
} else {
// TODO: スラッシュが含まれている場合の対応
return format!("1/{}/\n", candidates.join("/"));
}
}
code:converter.rs
use regex::Regex;
pub type ConvertResult = Option<Vec<String>>;
pub trait SkkConverter {
fn convert(&self, src: &str) -> ConvertResult;
}
pub struct Emanon001Converter {}
impl Emanon001Converter {
pub fn new() -> Self {
Self {}
}
}
impl SkkConverter for Emanon001Converter {
fn convert(&self, src: &str) -> ConvertResult {
let re = Regex::new(r"\Aぼく\z").ok()?;
if re.is_match(src) {
Some(vec!"emanon001".into())
} else {
None
}
}
}
ソースコード全文