diff --git a/Cargo.lock b/Cargo.lock index a01351fbd..7fc7cbc41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,6 +33,7 @@ dependencies = [ "num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "parity-hash-fetch 1.6.0", + "parity-ipfs 1.6.0", "parity-reactor 0.1.0", "parity-rpc-client 1.4.0", "parity-updater 1.6.0", @@ -104,6 +105,11 @@ dependencies = [ "syntex_syntax 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "base-x" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "base32" version = "0.3.1" @@ -189,6 +195,16 @@ name = "cfg-if" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "cid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "multibase 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "multihash 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "varmint 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "clippy" version = "0.0.103" @@ -422,6 +438,7 @@ dependencies = [ "base32 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcore 1.6.0", "ethcore-devtools 1.6.0", "ethcore-rpc 1.6.0", "ethcore-util 1.6.0", @@ -1306,6 +1323,23 @@ dependencies = [ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "multibase" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "base-x 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "multihash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ring 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tiny-keccak 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "nanomsg" version = "0.5.1" @@ -1562,6 +1596,18 @@ dependencies = [ "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parity-ipfs" +version = "1.6.0" +dependencies = [ + "cid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcore 1.6.0", + "ethcore-util 1.6.0", + "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", + "multihash 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rlp 0.1.0", +] + [[package]] name = "parity-reactor" version = "0.1.0" @@ -1817,6 +1863,15 @@ dependencies = [ "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ring" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "untrusted 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rlp" version = "0.1.0" @@ -2326,6 +2381,11 @@ name = "unicode-xid" version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "untrusted" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "url" version = "1.2.0" @@ -2353,6 +2413,11 @@ name = "utf8-ranges" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "varmint" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "vecio" version = "0.1.0" @@ -2450,6 +2515,7 @@ dependencies = [ "checksum app_dirs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b7d1c0d48a81bbb13043847f957971f4d87c81542d80ece5e84ba3cba4058fd4" "checksum arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "16e3bdb2f54b3ace0285975d59a97cf8ed3855294b2b6bc651fcf22a9c352975" "checksum aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "07d344974f0a155f091948aa389fb1b912d3a58414fbdb9c8d446d193ee3496a" +"checksum base-x 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2f59103b47307f76e03bef1633aec7fa9e29bfb5aa6daf5a334f94233c71f6c1" "checksum base32 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1b9605ba46d61df0410d8ac686b0007add8172eba90e8e909c347856fe794d8c" "checksum bigint 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2311bcd71b281e142a095311c22509f0d6bcd87b3000d7dbaa810929b9d6f6ae" "checksum bit-set 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e6e1e6fb1c9e3d6fcdec57216a74eaa03e41f52a22f13a16438251d8e88b89da" @@ -2464,6 +2530,7 @@ dependencies = [ "checksum bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c129aff112dcc562970abb69e2508b40850dd24c274761bb50fb8a0067ba6c27" "checksum bytes 0.4.0-dev (git+https://github.com/carllerche/bytes)" = "" "checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c" +"checksum cid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0ad0fdcfbfdfa789a0cf941dd19f7f1d3a377522f6e4c2a760d246ac56b4780" "checksum clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)" = "5b4fabf979ddf6419a313c1c0ada4a5b95cfd2049c56e8418d622d27b4b6ff32" "checksum clippy_lints 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)" = "ce96ec05bfe018a0d5d43da115e54850ea2217981ff0f2e462780ab9d594651a" "checksum cookie 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90266f45846f14a1e986c77d1e9c2626b8c342ed806fe60241ec38cc8697b245" @@ -2531,6 +2598,8 @@ dependencies = [ "checksum mio 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "410a1a0ff76f5a226f1e4e3ff1756128e65cd30166e39c3892283e2ac09d5b67" "checksum miow 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d5bfc6782530ac8ace97af10a540054a37126b63b0702ddaaa243b73b5745b9a" "checksum msdos_time 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c04b68cc63a8480fb2550343695f7be72effdec953a9d4508161c3e69041c7d8" +"checksum multibase 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b9c35dac080fd6e16a99924c8dfdef0af89d797dd851adab25feaffacf7850d6" +"checksum multihash 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "755d5a39bee3faaf649437e873beab334990221b2faf1f2e56ca10a9e4600235" "checksum nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)" = "" "checksum nanomsg-sys 0.5.0 (git+https://github.com/ethcore/nanomsg.rs.git)" = "" "checksum native-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aa4e52995154bb6f0b41e4379a279482c9387c1632e3798ba4e511ef8c54ee09" @@ -2581,6 +2650,7 @@ dependencies = [ "checksum regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)" = "b4329b8928a284580a1c63ec9d846b12f6d3472317243ff7077aff11f23f2b29" "checksum regex-syntax 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "841591b1e05609a643e3b4d0045fce04f701daba7151ddcd3ad47b080693d5a9" "checksum reqwest 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3bef9ed8fdfcc30947d6b774938dc0c3f369a474efe440df2c7f278180b2d2e6" +"checksum ring 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "87ac4fce2ee4bb10dd106788e90fdfa4c5a7f3f9f6aae29824db77dc57e2767d" "checksum rocksdb 0.4.5 (git+https://github.com/ethcore/rust-rocksdb)" = "" "checksum rocksdb-sys 0.3.0 (git+https://github.com/ethcore/rust-rocksdb)" = "" "checksum rotor 0.6.3 (git+https://github.com/ethcore/rotor)" = "" @@ -2642,9 +2712,11 @@ dependencies = [ "checksum unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "26643a2f83bac55f1976fb716c10234485f9202dcd65cfbdf9da49867b271172" "checksum unicode-xid 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "36dff09cafb4ec7c8cf0023eb0b686cb6ce65499116a12201c9e11840ca01beb" "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" +"checksum untrusted 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "193df64312e3515fd983ded55ad5bcaa7647a035804828ed757e832ce6029ef3" "checksum url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "afe9ec54bc4db14bc8744b7fed060d785ac756791450959b2248443319d5b119" "checksum user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ef4711d107b21b410a3a974b1204d9accc8b10dad75d8324b5d755de1617d47" "checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" +"checksum varmint 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5211976e8f86adc9920dd7621777bf8974c7812e48eb2aeb97fb1c26cd55ae84" "checksum vecio 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0795a11576d29ae80525a3fda315bf7b534f8feb9d34101e5fe63fb95bb2fd24" "checksum vergen 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "56b639f935488eb40f06d17c3e3bcc3054f6f75d264e187b1107c8d1cba8d31c" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" diff --git a/Cargo.toml b/Cargo.toml index f304c917b..520a6c4c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ rlp = { path = "util/rlp" } rpc-cli = { path = "rpc_cli" } parity-rpc-client = { path = "rpc_client" } parity-hash-fetch = { path = "hash-fetch" } +parity-ipfs = { path = "ipfs" } parity-updater = { path = "updater" } parity-reactor = { path = "util/reactor" } ethcore-dapps = { path = "dapps", optional = true } diff --git a/ipfs/Cargo.toml b/ipfs/Cargo.toml new file mode 100644 index 000000000..46a1dd3aa --- /dev/null +++ b/ipfs/Cargo.toml @@ -0,0 +1,14 @@ +[package] +description = "Parity IPFS crate" +name = "parity-ipfs" +version = "1.6.0" +license = "GPL-3.0" +authors = ["Parity Technologies "] + +[dependencies] +ethcore = { path = "../ethcore" } +ethcore-util = { path = "../util" } +rlp = { path = "../util/rlp" } +hyper = { default-features = false, git = "https://github.com/ethcore/hyper" } +cid = "~0.2.0" +multihash = "~0.5.0" diff --git a/ipfs/src/error.rs b/ipfs/src/error.rs new file mode 100644 index 000000000..6513c6da0 --- /dev/null +++ b/ipfs/src/error.rs @@ -0,0 +1,86 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +use {multihash, cid, hyper}; +use handler::Out; + +pub type Result = ::std::result::Result; + +/// IPFS server error +#[derive(Debug)] +pub enum ServerError { + /// Wrapped `std::io::Error` + IoError(::std::io::Error), + /// Other `hyper` error + Other(hyper::error::Error), +} + +pub enum Error { + CidParsingFailed, + UnsupportedHash, + UnsupportedCid, + BlockNotFound, + TransactionNotFound, + StateRootNotFound, +} + +impl From for Out { + fn from(err: Error) -> Out { + use self::Error::*; + + match err { + UnsupportedHash => Out::Bad("Hash must be Keccak-256"), + UnsupportedCid => Out::Bad("CID codec not supported"), + CidParsingFailed => Out::Bad("CID parsing failed"), + BlockNotFound => Out::NotFound("Block not found"), + TransactionNotFound => Out::NotFound("Transaction not found"), + StateRootNotFound => Out::NotFound("State root not found"), + } + } +} + +impl From for Error { + fn from(_: cid::Error) -> Error { + Error::CidParsingFailed + } +} + +impl From for Error { + fn from(_: multihash::Error) -> Error { + Error::CidParsingFailed + } +} + +impl From<::std::io::Error> for ServerError { + fn from(err: ::std::io::Error) -> ServerError { + ServerError::IoError(err) + } +} + +impl From for ServerError { + fn from(err: hyper::error::Error) -> ServerError { + ServerError::Other(err) + } +} + +impl From for String { + fn from(err: ServerError) -> String { + match err { + ServerError::IoError(err) => err.to_string(), + ServerError::Other(err) => err.to_string(), + } + } +} diff --git a/ipfs/src/handler.rs b/ipfs/src/handler.rs new file mode 100644 index 000000000..e8a8549d6 --- /dev/null +++ b/ipfs/src/handler.rs @@ -0,0 +1,129 @@ +use {rlp, multihash}; +use error::{Error, Result}; +use cid::{ToCid, Codec}; + +use std::sync::Arc; +use std::ops::Deref; +use multihash::Hash; +use hyper::Next; +use util::{Bytes, H256}; +use ethcore::client::{BlockId, TransactionId, BlockChainClient}; + +type Reason = &'static str; + +pub enum Out { + OctetStream(Bytes), + NotFound(Reason), + Bad(Reason), +} + +pub struct IpfsHandler { + client: Arc, + out: Out, +} + +impl IpfsHandler { + pub fn new(client: Arc) -> Self { + IpfsHandler { + client: client, + out: Out::NotFound("Route not found") + } + } + + pub fn out(&self) -> &Out { + &self.out + } + + pub fn route(&mut self, path: &str, query: Option<&str>) -> Next { + let result = match path { + "/api/v0/block/get" => self.route_cid(query), + _ => return Next::write(), + }; + + match result { + Ok(_) => Next::write(), + Err(err) => { + self.out = err.into(); + + Next::write() + } + } + } + + fn route_cid(&mut self, query: Option<&str>) -> Result<()> { + let query = query.unwrap_or(""); + + let cid = get_param(&query, "arg").ok_or(Error::CidParsingFailed)?.to_cid()?; + + let mh = multihash::decode(&cid.hash)?; + + if mh.alg != Hash::Keccak256 { return Err(Error::UnsupportedHash); } + + let hash: H256 = mh.digest.into(); + + match cid.codec { + Codec::EthereumBlock => self.get_block(hash), + Codec::EthereumBlockList => self.get_block_list(hash), + Codec::EthereumTx => self.get_transaction(hash), + Codec::EthereumStateTrie => self.get_state_trie(hash), + _ => return Err(Error::UnsupportedCid), + } + } + + fn get_block(&mut self, hash: H256) -> Result<()> { + let block_id = BlockId::Hash(hash); + let block = self.client.block_header(block_id).ok_or(Error::BlockNotFound)?; + + self.out = Out::OctetStream(block.into_inner()); + + Ok(()) + } + + fn get_block_list(&mut self, hash: H256) -> Result<()> { + let ommers = self.client.find_uncles(&hash).ok_or(Error::BlockNotFound)?; + + self.out = Out::OctetStream(rlp::encode(&ommers).to_vec()); + + Ok(()) + } + + fn get_transaction(&mut self, hash: H256) -> Result<()> { + let tx_id = TransactionId::Hash(hash); + let tx = self.client.transaction(tx_id).ok_or(Error::TransactionNotFound)?; + + self.out = Out::OctetStream(rlp::encode(tx.deref()).to_vec()); + + Ok(()) + } + + fn get_state_trie(&mut self, hash: H256) -> Result<()> { + let data = self.client.state_data(&hash).ok_or(Error::StateRootNotFound)?; + + self.out = Out::OctetStream(data); + + Ok(()) + } +} + +/// Get a query parameter's value by name. +pub fn get_param<'a>(query: &'a str, name: &str) -> Option<&'a str> { + query.split('&') + .find(|part| part.starts_with(name) && part[name.len()..].starts_with("=")) + .map(|part| &part[name.len() + 1..]) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get_param() { + let query = "foo=100&bar=200&qux=300"; + + assert_eq!(get_param(query, "foo"), Some("100")); + assert_eq!(get_param(query, "bar"), Some("200")); + assert_eq!(get_param(query, "qux"), Some("300")); + assert_eq!(get_param(query, "bar="), None); + assert_eq!(get_param(query, "200"), None); + } +} diff --git a/ipfs/src/lib.rs b/ipfs/src/lib.rs new file mode 100644 index 000000000..341d80ab8 --- /dev/null +++ b/ipfs/src/lib.rs @@ -0,0 +1,123 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +extern crate hyper; +extern crate multihash; +extern crate cid; + +extern crate rlp; +extern crate ethcore; +extern crate ethcore_util as util; + +mod error; +mod handler; + +use std::sync::Arc; +use error::ServerError; +use handler::{IpfsHandler, Out}; +use hyper::server::{Listening, Handler, Request, Response}; +use hyper::net::HttpStream; +use hyper::header::{ContentLength, ContentType}; +use hyper::{Next, Encoder, Decoder, Method, RequestUri, StatusCode}; +use ethcore::client::BlockChainClient; + +impl Handler for IpfsHandler { + fn on_request(&mut self, req: Request) -> Next { + if *req.method() != Method::Get { + return Next::write(); + } + + let (path, query) = match *req.uri() { + RequestUri::AbsolutePath { ref path, ref query } => (path, query.as_ref().map(AsRef::as_ref)), + _ => return Next::write(), + }; + + self.route(path, query) + } + + fn on_request_readable(&mut self, _decoder: &mut Decoder) -> Next { + Next::write() + } + + fn on_response(&mut self, res: &mut Response) -> Next { + use Out::*; + + match *self.out() { + OctetStream(ref bytes) => { + let headers = res.headers_mut(); + + headers.set(ContentLength(bytes.len() as u64)); + headers.set(ContentType("application/octet-stream".parse() + .expect("Static content type; qed"))); + + Next::write() + }, + NotFound(reason) => { + res.set_status(StatusCode::NotFound); + + res.headers_mut().set(ContentLength(reason.len() as u64)); + res.headers_mut().set(ContentType("text/plain".parse() + .expect("Static content type; qed"))); + + Next::write() + }, + Bad(reason) => { + res.set_status(StatusCode::BadRequest); + + res.headers_mut().set(ContentLength(reason.len() as u64)); + res.headers_mut().set(ContentType("text/plain".parse() + .expect("Static content type; qed"))); + + Next::write() + } + } + } + + fn on_response_writable(&mut self, transport: &mut Encoder) -> Next { + use Out::*; + + match *self.out() { + OctetStream(ref bytes) => { + // Nothing to do here + let _ = transport.write(&bytes); + + Next::end() + }, + NotFound(reason) | Bad(reason) => { + // Nothing to do here + let _ = transport.write(reason.as_bytes()); + + Next::end() + } + } + } +} + +pub fn start_server(client: Arc) -> Result { + let addr = "0.0.0.0:5001".parse().expect("can't fail on static input; qed"); + + hyper::Server::http(&addr)? + .handle(move |_| IpfsHandler::new(client.clone())) + .map(|(listening, srv)| { + + ::std::thread::spawn(move || { + srv.run(); + }); + + listening + }) + .map_err(Into::into) +} diff --git a/parity/main.rs b/parity/main.rs index 63d59d5fa..199dff4b4 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -56,6 +56,7 @@ extern crate ethcore_signer; extern crate ethcore_util as util; extern crate ethsync; extern crate parity_hash_fetch as hash_fetch; +extern crate parity_ipfs as ipfs; extern crate parity_reactor; extern crate parity_updater as updater; extern crate rpc_cli; diff --git a/parity/run.rs b/parity/run.rs index 56cd26ea0..728505068 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -47,6 +47,7 @@ use dir::Directories; use cache::CacheConfig; use user_defaults::UserDefaults; use dapps; +use ipfs; use signer; use modules; use rpc_apis; @@ -420,6 +421,10 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R }; let signer_server = signer::start(cmd.signer_conf.clone(), signer_deps)?; + + // the ipfs server + let ipfs_server = ipfs::start_server(client.clone())?; + // the informant let informant = Arc::new(Informant::new( service.client(), @@ -476,7 +481,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R let restart = wait_for_exit(panic_handler, Some(updater), can_restart); // drop this stuff as soon as exit detected. - drop((http_server, ipc_server, dapps_server, signer_server, event_loop)); + drop((http_server, ipc_server, dapps_server, signer_server, ipfs_server, event_loop)); info!("Finishing work, please wait...");