mirror of
https://github.com/OMGeeky/tarpc.git
synced 2026-02-23 15:49:54 +01:00
Compare commits
320 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7dbfe07c97 | ||
|
|
8bc01a993b | ||
|
|
e2728d84f3 | ||
|
|
85a8f8fce7 | ||
|
|
9267daa409 | ||
|
|
a01ecd3314 | ||
|
|
dd662beb9b | ||
|
|
5857de5164 | ||
|
|
8cb6ff89cc | ||
|
|
a441fcb771 | ||
|
|
f0ad99b900 | ||
|
|
5add81b5f3 | ||
|
|
15080b2889 | ||
|
|
8598d4beaf | ||
|
|
40faf25d99 | ||
|
|
c8be9b690b | ||
|
|
f4018a431e | ||
|
|
79aee18d17 | ||
|
|
f9ff2c4e50 | ||
|
|
073bc25e18 | ||
|
|
d0d65c413a | ||
|
|
e59116fb48 | ||
|
|
daa96a69a2 | ||
|
|
85ae614983 | ||
|
|
e17549a1f7 | ||
|
|
0b8a845ec1 | ||
|
|
2b8f3db1fd | ||
|
|
44792347b1 | ||
|
|
f7c371930f | ||
|
|
3eec8fe1dd | ||
|
|
9c973eb80b | ||
|
|
37763f25e4 | ||
|
|
515ab90299 | ||
|
|
9987eae290 | ||
|
|
22545c653c | ||
|
|
8b9847e347 | ||
|
|
49bc4d0bce | ||
|
|
0780af9e05 | ||
|
|
bc16ffd2d0 | ||
|
|
c7831e8aa6 | ||
|
|
8faba59d66 | ||
|
|
bceaea1206 | ||
|
|
fea8d5eb1d | ||
|
|
db9c23058d | ||
|
|
77638b388d | ||
|
|
eac6b64aeb | ||
|
|
7ae107cf2b | ||
|
|
12efcae80c | ||
|
|
5fbd45ea49 | ||
|
|
e651838f19 | ||
|
|
cdb44090bd | ||
|
|
fc2edc89af | ||
|
|
30eed41c40 | ||
|
|
517477129c | ||
|
|
1325d1a2d7 | ||
|
|
15f83961e9 | ||
|
|
4fc37fe707 | ||
|
|
8ee6ce0307 | ||
|
|
07f9d5a34d | ||
|
|
a0a11f8704 | ||
|
|
6673869721 | ||
|
|
63caacf0c1 | ||
|
|
2c09a35705 | ||
|
|
6bf4d171c1 | ||
|
|
0f52b08426 | ||
|
|
acdf03c8ca | ||
|
|
b2f69faa13 | ||
|
|
2749d33f88 | ||
|
|
338c91d393 | ||
|
|
fa2df184e9 | ||
|
|
fe4eab38f1 | ||
|
|
06beec3e5a | ||
|
|
d39a382012 | ||
|
|
efcc914e65 | ||
|
|
a1072c8c06 | ||
|
|
ed90f4ecea | ||
|
|
b5bf696017 | ||
|
|
fe20c8af14 | ||
|
|
2ffa1138dd | ||
|
|
9d552e48a4 | ||
|
|
fafe569ebc | ||
|
|
c286c596bd | ||
|
|
348111a423 | ||
|
|
984c1c29c5 | ||
|
|
cc1290636d | ||
|
|
41683eee1d | ||
|
|
6c2239d6f2 | ||
|
|
3196fd91ff | ||
|
|
45fa4c7bf1 | ||
|
|
c7c18cbaaa | ||
|
|
1c0cf2a67f | ||
|
|
15a3900f3d | ||
|
|
f0ecd7008d | ||
|
|
3567202aa3 | ||
|
|
802ee838ca | ||
|
|
c976ca710a | ||
|
|
9d2d69b4f4 | ||
|
|
558dda28ad | ||
|
|
4a4ffab611 | ||
|
|
cabbbb2a0b | ||
|
|
3dc1b6381d | ||
|
|
c96ab77dcf | ||
|
|
865712f36e | ||
|
|
a8e5bc45a1 | ||
|
|
95c57a4b2d | ||
|
|
ab3e73812c | ||
|
|
d34ca2acda | ||
|
|
9e92666932 | ||
|
|
a6b25dc268 | ||
|
|
77e12f56cc | ||
|
|
05c6be192d | ||
|
|
568484f14f | ||
|
|
918b6b3b75 | ||
|
|
d5854fd049 | ||
|
|
9646d92cae | ||
|
|
a660ed7f1a | ||
|
|
3eb2292841 | ||
|
|
5a525d9fb7 | ||
|
|
e4ef0881e6 | ||
|
|
91e9ad3001 | ||
|
|
b3c187cdac | ||
|
|
ffea090726 | ||
|
|
e8f942f463 | ||
|
|
5e9527e583 | ||
|
|
48452c04a6 | ||
|
|
626254e836 | ||
|
|
3719564efc | ||
|
|
ef41d4349c | ||
|
|
200407a4c9 | ||
|
|
388c4be69a | ||
|
|
77653282b6 | ||
|
|
5dbfa99d0b | ||
|
|
18cbbe5b15 | ||
|
|
e22210bfd8 | ||
|
|
d242bdbb82 | ||
|
|
bdd6737914 | ||
|
|
f2bf1adf8b | ||
|
|
be156f4d6b | ||
|
|
35f8aefb30 | ||
|
|
8a29aa29b2 | ||
|
|
b5750365ca | ||
|
|
f6b1660092 | ||
|
|
5c17ffacae | ||
|
|
13e56481bb | ||
|
|
608be5372b | ||
|
|
583f2cd92f | ||
|
|
ef8deb2059 | ||
|
|
c437d66603 | ||
|
|
662a627e4e | ||
|
|
d47a931f9f | ||
|
|
a30e929b63 | ||
|
|
b638f45d27 | ||
|
|
68ba7505ac | ||
|
|
3afcfe6274 | ||
|
|
85fbe411e6 | ||
|
|
aaaaf942d6 | ||
|
|
29b6425fb5 | ||
|
|
539776eb27 | ||
|
|
61e7d9aab8 | ||
|
|
59988c6ee1 | ||
|
|
a1de4c1b05 | ||
|
|
b6e9d61286 | ||
|
|
67ad2b90be | ||
|
|
3506397150 | ||
|
|
db665ebb60 | ||
|
|
cff8782e18 | ||
|
|
531dc20d66 | ||
|
|
d8d240ec12 | ||
|
|
eb49c30fdb | ||
|
|
b661ff0175 | ||
|
|
b880d65f44 | ||
|
|
451b99b92a | ||
|
|
99b13ae6fc | ||
|
|
5bace01f2b | ||
|
|
4a63064cbd | ||
|
|
3eb57d4009 | ||
|
|
14c97b61f9 | ||
|
|
20d1a019ae | ||
|
|
8c0181633d | ||
|
|
987e445935 | ||
|
|
1c318182c4 | ||
|
|
ac7e2eedb2 | ||
|
|
6d1fbab73c | ||
|
|
e8902c21a2 | ||
|
|
be5f55c5f6 | ||
|
|
54017839d1 | ||
|
|
b5f2fe5f4f | ||
|
|
437997b2d1 | ||
|
|
b5e472b374 | ||
|
|
f76030ecd7 | ||
|
|
b7952d3f47 | ||
|
|
8d561d21b2 | ||
|
|
8968b1fbd2 | ||
|
|
ab6e829ddd | ||
|
|
246c09c876 | ||
|
|
e7cfbc1085 | ||
|
|
7aabfb3c14 | ||
|
|
64ca851209 | ||
|
|
1738864d32 | ||
|
|
683617674c | ||
|
|
3c4932f55a | ||
|
|
3cf8e440f7 | ||
|
|
2e02f33fc4 | ||
|
|
d8472dcd1c | ||
|
|
2c5846621f | ||
|
|
6a6157948a | ||
|
|
1c18a3c4fe | ||
|
|
e8ec295e85 | ||
|
|
44eec09418 | ||
|
|
fe116a1b6b | ||
|
|
ec4fa8636b | ||
|
|
2d58340d16 | ||
|
|
801f09e9e6 | ||
|
|
6ce3a3d943 | ||
|
|
4d636d2882 | ||
|
|
3693c95a67 | ||
|
|
43a2df4a13 | ||
|
|
166f1523d6 | ||
|
|
1cc8cbcdc3 | ||
|
|
9dafc704e9 | ||
|
|
32e0b0d7f8 | ||
|
|
b87c52758e | ||
|
|
9235e12904 | ||
|
|
265fe56fa6 | ||
|
|
7b5b29a9c3 | ||
|
|
709f4ab1ac | ||
|
|
bbfb4325d2 | ||
|
|
f33cb3bd53 | ||
|
|
6a6832cfbc | ||
|
|
b0495ebc00 | ||
|
|
aec1574824 | ||
|
|
5d27d34bd3 | ||
|
|
fe978f2c56 | ||
|
|
44f472c65c | ||
|
|
e995acd4c9 | ||
|
|
e8fcf0e4de | ||
|
|
9dcd38c012 | ||
|
|
5ac4b710a5 | ||
|
|
2eb0b2cc83 | ||
|
|
72a9f8f70d | ||
|
|
8e5a44b423 | ||
|
|
714541a7a4 | ||
|
|
a1f529f794 | ||
|
|
a8766a9200 | ||
|
|
ef96c87226 | ||
|
|
3543b34f2b | ||
|
|
6273ebefa7 | ||
|
|
9827f75459 | ||
|
|
c398e2389b | ||
|
|
03dc512e25 | ||
|
|
8307c708a3 | ||
|
|
774411c636 | ||
|
|
d5b2f23f74 | ||
|
|
396aec3c2f | ||
|
|
28c6c333e5 | ||
|
|
2d1a77ec10 | ||
|
|
a0e6147482 | ||
|
|
fcdb0d9375 | ||
|
|
4c1d15f8ea | ||
|
|
ece1cc60b9 | ||
|
|
7d8a508379 | ||
|
|
9193357d60 | ||
|
|
b777e0bbf7 | ||
|
|
04624f054d | ||
|
|
f870f832a9 | ||
|
|
dc347021d4 | ||
|
|
5973e54f62 | ||
|
|
e5e5c5975c | ||
|
|
6bb3691a30 | ||
|
|
e2f1511fb3 | ||
|
|
99ba380825 | ||
|
|
39235343d6 | ||
|
|
f3afd080f3 | ||
|
|
043d0a1c21 | ||
|
|
be4caeebe8 | ||
|
|
06a2cab31c | ||
|
|
934c51f4ab | ||
|
|
cc8a8e76b0 | ||
|
|
b9ba10b8a4 | ||
|
|
1ee1f9274a | ||
|
|
7f354be850 | ||
|
|
c9a63c2a5a | ||
|
|
ee1143c709 | ||
|
|
4ed127b39e | ||
|
|
66cd136c6a | ||
|
|
58cbe6f4ea | ||
|
|
250a7fd7b9 | ||
|
|
a44fd808d9 | ||
|
|
65c4d83c88 | ||
|
|
00692fe9a3 | ||
|
|
0968760ef7 | ||
|
|
75b2c00b54 | ||
|
|
ffee124526 | ||
|
|
06a03697c4 | ||
|
|
a675551a31 | ||
|
|
d0e9693263 | ||
|
|
6d23174219 | ||
|
|
a06b583334 | ||
|
|
937e9c2c43 | ||
|
|
54883d6354 | ||
|
|
86b1470832 | ||
|
|
82762583be | ||
|
|
3462451256 | ||
|
|
17d800b8a8 | ||
|
|
403eba201b | ||
|
|
f2328d200e | ||
|
|
51e6bac2dc | ||
|
|
f3fcbbb8d2 | ||
|
|
05acb97f04 | ||
|
|
07c052a1c1 | ||
|
|
34cf0c8172 | ||
|
|
7b196400b8 | ||
|
|
1f30bb9ba6 | ||
|
|
e2756edd72 | ||
|
|
8957d2dac3 | ||
|
|
21e5734ef7 | ||
|
|
dddeca19a1 | ||
|
|
a9b86280b5 | ||
|
|
7dae99d7b5 | ||
|
|
9dd3d55744 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,5 @@
|
||||
target
|
||||
Cargo.lock
|
||||
.cargo
|
||||
*.swp
|
||||
*.bk
|
||||
|
||||
24
.travis.yml
24
.travis.yml
@@ -2,10 +2,18 @@ language: rust
|
||||
sudo: false
|
||||
|
||||
rust:
|
||||
- stable
|
||||
- beta
|
||||
- nightly
|
||||
|
||||
os:
|
||||
- linux
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libcurl4-openssl-dev
|
||||
- libelf-dev
|
||||
- libdw-dev
|
||||
|
||||
before_script:
|
||||
- |
|
||||
pip install 'travis-cargo<0.2' --user &&
|
||||
@@ -13,8 +21,14 @@ before_script:
|
||||
|
||||
script:
|
||||
- |
|
||||
(cd tarpc && cargo build) &&
|
||||
(cd tarpc && cargo test)
|
||||
travis-cargo build -- --features tls && travis-cargo test -- --features tls && travis-cargo bench -- --features tls &&
|
||||
rustdoc --test README.md -L target/debug/deps -L target/debug &&
|
||||
travis-cargo build && travis-cargo test && travis-cargo bench
|
||||
|
||||
after_success:
|
||||
- (cd tarpc && travis-cargo coveralls --no-sudo)
|
||||
- travis-cargo coveralls --no-sudo
|
||||
|
||||
env:
|
||||
global:
|
||||
# override the default `--features unstable` used for the nightly branch
|
||||
- TRAVIS_CARGO_NIGHTLY_FEATURE=""
|
||||
|
||||
54
Cargo.toml
Normal file
54
Cargo.toml
Normal file
@@ -0,0 +1,54 @@
|
||||
[package]
|
||||
name = "tarpc"
|
||||
version = "0.7.3"
|
||||
authors = ["Adam Wright <adam.austin.wright@gmail.com>", "Tim Kuehn <timothy.j.kuehn@gmail.com>"]
|
||||
license = "MIT"
|
||||
documentation = "https://docs.rs/tarpc"
|
||||
homepage = "https://github.com/google/tarpc"
|
||||
repository = "https://github.com/google/tarpc"
|
||||
keywords = ["rpc", "network", "server", "api", "tls"]
|
||||
categories = ["asynchronous", "network-programming"]
|
||||
readme = "README.md"
|
||||
description = "An RPC framework for Rust with a focus on ease of use."
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "google/tarpc" }
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.0.0-alpha6"
|
||||
byteorder = "1.0"
|
||||
bytes = "0.4"
|
||||
cfg-if = "0.1.0"
|
||||
futures = "0.1.11"
|
||||
lazy_static = "0.2"
|
||||
log = "0.3"
|
||||
net2 = "0.2"
|
||||
num_cpus = "1.0"
|
||||
serde = "0.9"
|
||||
serde_derive = "0.9"
|
||||
tarpc-plugins = { path = "src/plugins", version = "0.1.1" }
|
||||
thread-pool = "0.1.1"
|
||||
tokio-core = "0.1.6"
|
||||
tokio-io = "0.1"
|
||||
tokio-proto = "0.1.1"
|
||||
tokio-service = "0.1"
|
||||
|
||||
# Optional dependencies
|
||||
native-tls = { version = "0.1.1", optional = true }
|
||||
tokio-tls = { version = "0.1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
chrono = "0.3"
|
||||
env_logger = "0.3"
|
||||
futures-cpupool = "0.1"
|
||||
clap = "2.0"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dev-dependencies]
|
||||
security-framework = "0.1"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
tls = ["tokio-tls", "native-tls"]
|
||||
unstable = ["serde/unstable"]
|
||||
|
||||
[workspace]
|
||||
304
README.md
304
README.md
@@ -1,57 +1,303 @@
|
||||
## tarpc: Tim & Adam's RPC lib
|
||||
[](https://travis-ci.org/google/tarpc)
|
||||
[](https://coveralls.io/github/google/tarpc?branch=master)
|
||||
[](LICENSE.txt)
|
||||
[](LICENSE)
|
||||
[](https://crates.io/crates/tarpc)
|
||||
[](https://gitter.im/tarpc/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
*Disclaimer*: This is not an official Google product.
|
||||
|
||||
tarpc is an RPC framework for rust with a focus on ease of use. Defining and implementing an echo-like server can be done in just a few lines of code:
|
||||
tarpc is an RPC framework for rust with a focus on ease of use. Defining a
|
||||
service can be done in just a few lines of code, and most of the boilerplate of
|
||||
writing a server is taken care of for you.
|
||||
|
||||
[Documentation](https://docs.rs/tarpc)
|
||||
|
||||
## What is an RPC framework?
|
||||
"RPC" stands for "Remote Procedure Call," a function call where the work of
|
||||
producing the return value is being done somewhere else. When an rpc function is
|
||||
invoked, behind the scenes the function contacts some other process somewhere
|
||||
and asks them to evaluate the function instead. The original function then
|
||||
returns the value produced by the other process.
|
||||
|
||||
RPC frameworks are a fundamental building block of most microservices-oriented
|
||||
architectures. Two well-known ones are [gRPC](http://www.grpc.io) and
|
||||
[Cap'n Proto](https://capnproto.org/).
|
||||
|
||||
tarpc differentiates itself from other RPC frameworks by defining the schema in code,
|
||||
rather than in a separate language such as .proto. This means there's no separate compilation
|
||||
process, and no cognitive context switching between different languages. Additionally, it
|
||||
works with the community-backed library serde: any serde-serializable type can be used as
|
||||
arguments to tarpc fns.
|
||||
|
||||
## Usage
|
||||
**NB**: *this example is for master. Are you looking for other
|
||||
[versions](https://docs.rs/tarpc)?*
|
||||
|
||||
Add to your `Cargo.toml` dependencies:
|
||||
|
||||
```toml
|
||||
tarpc = "0.7.2"
|
||||
tarpc-plugins = "0.1.1"
|
||||
```
|
||||
|
||||
## Example: Sync
|
||||
|
||||
tarpc has two APIs: `sync` for blocking code and `future` for asynchronous
|
||||
code. Here's how to use the sync api.
|
||||
|
||||
```rust
|
||||
#![feature(plugin)]
|
||||
#![plugin(tarpc_plugins)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate tarpc;
|
||||
|
||||
mod hello_service {
|
||||
service! {
|
||||
rpc hello(name: String) -> String;
|
||||
}
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use tarpc::sync::{client, server};
|
||||
use tarpc::sync::client::ClientExt;
|
||||
use tarpc::util::{FirstSocketAddr, Never};
|
||||
|
||||
service! {
|
||||
rpc hello(name: String) -> String;
|
||||
}
|
||||
|
||||
struct HelloService;
|
||||
impl hello_service::Service for HelloService {
|
||||
fn hello(&self, name: String) -> String {
|
||||
format!("Hello, {}!", s)
|
||||
#[derive(Clone)]
|
||||
struct HelloServer;
|
||||
|
||||
impl SyncService for HelloServer {
|
||||
fn hello(&self, name: String) -> Result<String, Never> {
|
||||
Ok(format!("Hello, {}!", name))
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let server_handle = hello_service::serve("0.0.0.0:0", HelloService, None).unwrap();
|
||||
let client = hello_service::Client::new(server_handle.local_addr(), None).unwrap();
|
||||
assert_eq!("Hello, Mom!".into(), client.hello("Mom".into()).unwrap());
|
||||
drop(client);
|
||||
server_handle.shutdown();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
thread::spawn(move || {
|
||||
let mut handle = HelloServer.listen("localhost:0", server::Options::default())
|
||||
.unwrap();
|
||||
tx.send(handle.addr()).unwrap();
|
||||
handle.run();
|
||||
});
|
||||
let client = SyncClient::connect(rx.recv().unwrap(), client::Options::default()).unwrap();
|
||||
println!("{}", client.hello("Mom".to_string()).unwrap());
|
||||
}
|
||||
```
|
||||
|
||||
The `service!` macro expands to a collection of items that collectively form an rpc service. In the
|
||||
above example, the macro is called within the `hello_service` module. This module will contain a
|
||||
`Client` type, a `Service` trait, and a `serve` function. `serve` can be used to start a server
|
||||
listening on a tcp port. A `Client` can connect to such a service. Any type implementing the
|
||||
`Service` trait can be passed to `serve`. These generated types are specific to the echo service,
|
||||
and make it easy and ergonomic to write servers without dealing with sockets or serialization
|
||||
directly. See the tarpc_examples package for more sophisticated examples.
|
||||
The `service!` macro expands to a collection of items that form an
|
||||
rpc service. In the above example, the macro is called within the
|
||||
`hello_service` module. This module will contain `SyncClient`, `AsyncClient`,
|
||||
and `FutureClient` types, and `SyncService` and `AsyncService` traits. There is
|
||||
also a `ServiceExt` trait that provides starter `fn`s for services, with an
|
||||
umbrella impl for all services. These generated types make it easy and
|
||||
ergonomic to write servers without dealing with sockets or serialization
|
||||
directly. Simply implement one of the generated traits, and you're off to the
|
||||
races! See the `tarpc_examples` package for more examples.
|
||||
|
||||
## Example: Futures
|
||||
|
||||
Here's the same service, implemented using futures.
|
||||
|
||||
```rust
|
||||
#![feature(plugin)]
|
||||
#![plugin(tarpc_plugins)]
|
||||
|
||||
extern crate futures;
|
||||
#[macro_use]
|
||||
extern crate tarpc;
|
||||
extern crate tokio_core;
|
||||
|
||||
use futures::Future;
|
||||
use tarpc::future::{client, server};
|
||||
use tarpc::future::client::ClientExt;
|
||||
use tarpc::util::{FirstSocketAddr, Never};
|
||||
use tokio_core::reactor;
|
||||
|
||||
service! {
|
||||
rpc hello(name: String) -> String;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct HelloServer;
|
||||
|
||||
impl FutureService for HelloServer {
|
||||
type HelloFut = Result<String, Never>;
|
||||
|
||||
fn hello(&self, name: String) -> Self::HelloFut {
|
||||
Ok(format!("Hello, {}!", name))
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut reactor = reactor::Core::new().unwrap();
|
||||
let (handle, server) = HelloServer.listen("localhost:10000".first_socket_addr(),
|
||||
&reactor.handle(),
|
||||
server::Options::default())
|
||||
.unwrap();
|
||||
reactor.handle().spawn(server);
|
||||
let options = client::Options::default().handle(reactor.handle());
|
||||
reactor.run(FutureClient::connect(handle.addr(), options)
|
||||
.map_err(tarpc::Error::from)
|
||||
.and_then(|client| client.hello("Mom".to_string()))
|
||||
.map(|resp| println!("{}", resp)))
|
||||
.unwrap();
|
||||
}
|
||||
```
|
||||
|
||||
## Example: Futures + TLS
|
||||
|
||||
By default, tarpc internally uses a [`TcpStream`] for communication between your clients and
|
||||
servers. However, TCP by itself has no encryption. As a result, your communication will be sent in
|
||||
the clear. If you want your RPC communications to be encrypted, you can choose to use [TLS]. TLS
|
||||
operates as an encryption layer on top of TCP. When using TLS, your communication will occur over a
|
||||
[`TlsStream<TcpStream>`]. You can add the ability to make TLS clients and servers by adding `tarpc`
|
||||
with the `tls` feature flag enabled.
|
||||
|
||||
When using TLS, some additional information is required. You will need to make [`TlsAcceptor`] and
|
||||
`client::tls::Context` structs; `client::tls::Context` requires a [`TlsConnector`]. The
|
||||
[`TlsAcceptor`] and [`TlsConnector`] types are defined in the [native-tls]. tarpc re-exports
|
||||
external TLS-related types in its `native_tls` module (`tarpc::native_tls`).
|
||||
|
||||
[TLS]: https://en.wikipedia.org/wiki/Transport_Layer_Security
|
||||
[`TcpStream`]: https://docs.rs/tokio-core/0.1/tokio_core/net/struct.TcpStream.html
|
||||
[`TlsStream<TcpStream>`]: https://docs.rs/native-tls/0.1/native_tls/struct.TlsStream.html
|
||||
[`TlsAcceptor`]: https://docs.rs/native-tls/0.1/native_tls/struct.TlsAcceptor.html
|
||||
[`TlsConnector`]: https://docs.rs/native-tls/0.1/native_tls/struct.TlsConnector.html
|
||||
[native-tls]: https://github.com/sfackler/rust-native-tls
|
||||
|
||||
Both TLS streams and TCP streams are supported in the same binary when the `tls` feature is enabled.
|
||||
However, if you are working with both stream types, ensure that you use the TLS clients with TLS
|
||||
servers and TCP clients with TCP servers.
|
||||
|
||||
```rust,no_run
|
||||
#![feature(plugin)]
|
||||
#![plugin(tarpc_plugins)]
|
||||
|
||||
extern crate futures;
|
||||
#[macro_use]
|
||||
extern crate tarpc;
|
||||
extern crate tokio_core;
|
||||
|
||||
use futures::Future;
|
||||
use tarpc::future::{client, server};
|
||||
use tarpc::future::client::ClientExt;
|
||||
use tarpc::tls;
|
||||
use tarpc::util::{FirstSocketAddr, Never};
|
||||
use tokio_core::reactor;
|
||||
use tarpc::native_tls::{Pkcs12, TlsAcceptor};
|
||||
|
||||
service! {
|
||||
rpc hello(name: String) -> String;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct HelloServer;
|
||||
|
||||
impl FutureService for HelloServer {
|
||||
type HelloFut = Result<String, Never>;
|
||||
|
||||
fn hello(&self, name: String) -> Self::HelloFut {
|
||||
Ok(format!("Hello, {}!", name))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_acceptor() -> TlsAcceptor {
|
||||
let buf = include_bytes!("test/identity.p12");
|
||||
let pkcs12 = Pkcs12::from_der(buf, "password").unwrap();
|
||||
TlsAcceptor::builder(pkcs12).unwrap().build().unwrap()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut reactor = reactor::Core::new().unwrap();
|
||||
let acceptor = get_acceptor();
|
||||
let (handle, server) = HelloServer.listen("localhost:10000".first_socket_addr(),
|
||||
&reactor.handle(),
|
||||
server::Options::default().tls(acceptor)).unwrap();
|
||||
reactor.handle().spawn(server);
|
||||
let options = client::Options::default()
|
||||
.handle(reactor.handle())
|
||||
.tls(tls::client::Context::new("foobar.com").unwrap());
|
||||
reactor.run(FutureClient::connect(handle.addr(), options)
|
||||
.map_err(tarpc::Error::from)
|
||||
.and_then(|client| client.hello("Mom".to_string()))
|
||||
.map(|resp| println!("{}", resp)))
|
||||
.unwrap();
|
||||
}
|
||||
```
|
||||
|
||||
## Tips
|
||||
|
||||
### Sync vs Futures
|
||||
|
||||
A single `service!` invocation generates code for both synchronous and future-based applications.
|
||||
It's up to the user whether they want to implement the sync API or the futures API. The sync API has
|
||||
the simplest programming model, at the cost of some overhead - each RPC is handled in its own
|
||||
thread. The futures API is based on tokio and can run on any tokio-compatible executor. This mean a
|
||||
service that implements the futures API for a tarpc service can run on a single thread, avoiding
|
||||
context switches and the memory overhead of having a thread per RPC.
|
||||
|
||||
### Errors
|
||||
|
||||
All generated tarpc RPC methods return either `tarpc::Result<T, E>` or something like `Future<T,
|
||||
E>`. The error type defaults to `tarpc::util::Never` (a wrapper for `!` which implements
|
||||
`std::error::Error`) if no error type is explicitly specified in the `service!` macro invocation. An
|
||||
error type can be specified like so:
|
||||
|
||||
```rust,ignore
|
||||
use tarpc::util::Message;
|
||||
|
||||
service! {
|
||||
rpc hello(name: String) -> String | Message
|
||||
}
|
||||
```
|
||||
|
||||
`tarpc::util::Message` is just a wrapper around string that implements `std::error::Error` provided
|
||||
for service implementations that don't require complex error handling. The pipe is used as syntax
|
||||
for specifying the error type in a way that's agnostic of whether the service implementation is
|
||||
synchronous or future-based. Note that in the simpler examples in the readme, no pipe is used, and
|
||||
the macro automatically chooses `tarpc::util::Never` as the error type.
|
||||
|
||||
The above declaration would produce the following synchronous service trait:
|
||||
|
||||
```rust,ignore
|
||||
trait SyncService {
|
||||
fn hello(&self, name: String) -> Result<String, Message>;
|
||||
}
|
||||
```
|
||||
|
||||
and the following future-based trait:
|
||||
|
||||
```rust,ignore
|
||||
trait FutureService {
|
||||
type HelloFut: IntoFuture<String, Message>;
|
||||
|
||||
fn hello(&mut self, name: String) -> Self::HelloFut;
|
||||
}
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Use `cargo doc` as you normally would to see the documentation created for all
|
||||
items expanded by a `service!` invocation.
|
||||
|
||||
## Additional Features
|
||||
- Concurrent requests from a single client.
|
||||
- Attributes can be specified on rpc methods. These will be included on both the `Service` trait
|
||||
methods as well as on the `Client`'s stub methods.
|
||||
- Just like regular fns, the return type can be left off when it's `-> ()`.
|
||||
- Arg-less rpc's are also allowed.
|
||||
|
||||
## Planned Improvements (actively being worked on)
|
||||
- Concurrent requests from a single client.
|
||||
- Compatible with tokio services.
|
||||
- Run any number of clients and services on a single event loop.
|
||||
- Any type that `impl`s `serde`'s `Serialize` and `Deserialize` can be used in
|
||||
rpc signatures.
|
||||
- Attributes can be specified on rpc methods. These will be included on both the
|
||||
services' trait methods as well as on the clients' stub methods.
|
||||
|
||||
## Gaps/Potential Improvements (not necessarily actively being worked on)
|
||||
|
||||
- Configurable server rate limiting.
|
||||
- Automatic client retries with exponential backoff when server is busy.
|
||||
- Load balancing
|
||||
- Service discovery
|
||||
- Automatically reconnect on the client side when the connection cuts out.
|
||||
- Support asynchronous server implementations (currently thread per connection).
|
||||
- Support generic serialization protocols.
|
||||
|
||||
## Contributing
|
||||
|
||||
67
RELEASES.md
Normal file
67
RELEASES.md
Normal file
@@ -0,0 +1,67 @@
|
||||
## 0.7.3 (2017-04-26)
|
||||
|
||||
This release removes the `Sync` bound on RPC args for both sync and future
|
||||
clients. No breaking changes.
|
||||
|
||||
## 0.7.2 (2017-04-22)
|
||||
|
||||
## Breaking Changes
|
||||
This release updates tarpc-plugins to work with rustc master. Thus, older
|
||||
versions of rustc are no longer supported. We chose a minor version bump
|
||||
because it is still source-compatible with existing code using tarpc.
|
||||
|
||||
## 0.7.1 (2017-03-31)
|
||||
|
||||
This release was purely doc fixes. No breaking changes.
|
||||
|
||||
## 0.7 (2017-03-31)
|
||||
|
||||
## Breaking Changes
|
||||
This release is a complete overhaul to build tarpc on top of the tokio stack.
|
||||
It's safe to assume that everything broke with this release.
|
||||
|
||||
Two traits are now generated by the macro, `FutureService` and `SyncService`.
|
||||
`SyncService` is the successor to the original `Service` trait. It uses a configurable
|
||||
thread pool to serve requests. `FutureService`, as the name implies, uses futures
|
||||
to serve requests and is single-threaded by default.
|
||||
|
||||
The easiest way to upgrade from a 0.6 service impl is to `impl SyncService for MyService`.
|
||||
For more complete information, see the readme and the examples directory.
|
||||
|
||||
## 0.6 (2016-08-07)
|
||||
|
||||
### Breaking Changes
|
||||
* Updated serde to 0.8. Requires dependents to update as well.
|
||||
|
||||
## 0.5 (2016-04-24)
|
||||
|
||||
### Breaking Changes
|
||||
0.5 adds support for arbitrary transports via the
|
||||
[`Transport`](tarpc/src/transport/mod.rs#L7) trait.
|
||||
Out of the box tarpc provides implementations for:
|
||||
|
||||
* Tcp, for types `impl`ing `ToSocketAddrs`.
|
||||
* Unix sockets via the `UnixTransport` type.
|
||||
|
||||
This was a breaking change: `handler.local_addr()` was renamed
|
||||
`handler.dialer()`.
|
||||
|
||||
## 0.4 (2016-04-02)
|
||||
|
||||
### Breaking Changes
|
||||
* Updated to the latest version of serde, 0.7.0. Because tarpc exposes serde in
|
||||
its API, this forces downstream code to update to the latest version of
|
||||
serde, as well.
|
||||
|
||||
## 0.3 (2016-02-20)
|
||||
|
||||
### Breaking Changes
|
||||
* The timeout arg to `serve` was replaced with a `Config` struct, which
|
||||
currently only contains one field, but will be expanded in the future
|
||||
to allow configuring serialization protocol, and other things.
|
||||
* `serve` was changed to be a default method on the generated `Service` traits,
|
||||
and it was renamed `spawn_with_config`. A second `default fn` was added:
|
||||
`spawn`, which takes no `Config` arg.
|
||||
|
||||
### Other Changes
|
||||
* Expanded items will no longer generate unused warnings.
|
||||
54
benches/latency.rs
Normal file
54
benches/latency.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
#![feature(plugin, test)]
|
||||
#![plugin(tarpc_plugins)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate tarpc;
|
||||
#[cfg(test)]
|
||||
extern crate test;
|
||||
extern crate env_logger;
|
||||
extern crate futures;
|
||||
extern crate tokio_core;
|
||||
|
||||
use tarpc::future::{client, server};
|
||||
use tarpc::future::client::ClientExt;
|
||||
use tarpc::util::{FirstSocketAddr, Never};
|
||||
#[cfg(test)]
|
||||
use test::Bencher;
|
||||
use tokio_core::reactor;
|
||||
|
||||
service! {
|
||||
rpc ack();
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Server;
|
||||
|
||||
impl FutureService for Server {
|
||||
type AckFut = futures::Finished<(), Never>;
|
||||
fn ack(&self) -> Self::AckFut {
|
||||
futures::finished(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[bench]
|
||||
fn latency(bencher: &mut Bencher) {
|
||||
let _ = env_logger::init();
|
||||
let mut reactor = reactor::Core::new().unwrap();
|
||||
let (handle, server) = Server
|
||||
.listen("localhost:0".first_socket_addr(),
|
||||
&reactor.handle(),
|
||||
server::Options::default())
|
||||
.unwrap();
|
||||
reactor.handle().spawn(server);
|
||||
let client = FutureClient::connect(handle.addr(),
|
||||
client::Options::default().handle(reactor.handle()));
|
||||
let client = reactor.run(client).unwrap();
|
||||
|
||||
bencher.iter(|| reactor.run(client.ack()).unwrap());
|
||||
}
|
||||
1
clippy.toml
Normal file
1
clippy.toml
Normal file
@@ -0,0 +1 @@
|
||||
doc-valid-idents = ["gRPC"]
|
||||
195
examples/concurrency.rs
Normal file
195
examples/concurrency.rs
Normal file
@@ -0,0 +1,195 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
#![feature(inclusive_range_syntax, conservative_impl_trait, plugin, never_type)]
|
||||
#![plugin(tarpc_plugins)]
|
||||
|
||||
extern crate chrono;
|
||||
extern crate clap;
|
||||
extern crate env_logger;
|
||||
extern crate futures;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate tarpc;
|
||||
extern crate tokio_core;
|
||||
extern crate futures_cpupool;
|
||||
|
||||
use clap::{Arg, App};
|
||||
use futures::{Future, Stream};
|
||||
use futures_cpupool::{CpuFuture, CpuPool};
|
||||
use std::{cmp, thread};
|
||||
use std::sync::{Arc, mpsc};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::time::{Duration, Instant};
|
||||
use tarpc::future::{client, server};
|
||||
use tarpc::future::client::ClientExt;
|
||||
use tarpc::util::{FirstSocketAddr, Never};
|
||||
use tokio_core::reactor;
|
||||
|
||||
service! {
|
||||
rpc read(size: u32) -> serde::bytes::ByteBuf;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Server {
|
||||
pool: CpuPool,
|
||||
request_count: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
fn new() -> Self {
|
||||
Server {
|
||||
pool: CpuPool::new_num_cpus(),
|
||||
request_count: Arc::new(AtomicUsize::new(1)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FutureService for Server {
|
||||
type ReadFut = CpuFuture<serde::bytes::ByteBuf, Never>;
|
||||
|
||||
fn read(&self, size: u32) -> Self::ReadFut {
|
||||
let request_number = self.request_count.fetch_add(1, Ordering::SeqCst);
|
||||
debug!("Server received read({}) no. {}", size, request_number);
|
||||
self.pool
|
||||
.spawn(futures::lazy(move || {
|
||||
let mut vec = Vec::with_capacity(size as usize);
|
||||
for i in 0..size {
|
||||
vec.push(((i % 2) << 8) as u8);
|
||||
}
|
||||
debug!("Server sending response no. {}", request_number);
|
||||
Ok(vec.into())
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
const CHUNK_SIZE: u32 = 1 << 10;
|
||||
|
||||
trait Microseconds {
|
||||
fn microseconds(&self) -> i64;
|
||||
}
|
||||
|
||||
impl Microseconds for Duration {
|
||||
fn microseconds(&self) -> i64 {
|
||||
chrono::Duration::from_std(*self)
|
||||
.unwrap()
|
||||
.num_microseconds()
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Stats {
|
||||
sum: Duration,
|
||||
count: u64,
|
||||
min: Option<Duration>,
|
||||
max: Option<Duration>,
|
||||
}
|
||||
|
||||
/// Spawns a `reactor::Core` running forever on a new thread.
|
||||
fn spawn_core() -> reactor::Remote {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
thread::spawn(move || {
|
||||
let mut core = reactor::Core::new().unwrap();
|
||||
tx.send(core.handle().remote().clone()).unwrap();
|
||||
|
||||
// Run forever
|
||||
core.run(futures::empty::<(), !>()).unwrap();
|
||||
});
|
||||
rx.recv().unwrap()
|
||||
}
|
||||
|
||||
fn run_once(clients: Vec<FutureClient>,
|
||||
concurrency: u32)
|
||||
-> impl Future<Item = (), Error = ()> + 'static {
|
||||
let start = Instant::now();
|
||||
futures::stream::futures_unordered((0..concurrency as usize)
|
||||
.zip(clients.iter().enumerate().cycle())
|
||||
.map(|(iteration, (client_idx, client))| {
|
||||
let start = Instant::now();
|
||||
debug!("Client {} reading (iteration {})...", client_idx, iteration);
|
||||
client
|
||||
.read(CHUNK_SIZE)
|
||||
.map(move |_| (client_idx, iteration, start))
|
||||
}))
|
||||
.map(|(client_idx, iteration, start)| {
|
||||
let elapsed = start.elapsed();
|
||||
debug!("Client {} received reply (iteration {}).",
|
||||
client_idx,
|
||||
iteration);
|
||||
elapsed
|
||||
})
|
||||
.map_err(|e| panic!(e))
|
||||
.fold(Stats::default(), move |mut stats, elapsed| {
|
||||
stats.sum += elapsed;
|
||||
stats.count += 1;
|
||||
stats.min = Some(cmp::min(stats.min.unwrap_or(elapsed), elapsed));
|
||||
stats.max = Some(cmp::max(stats.max.unwrap_or(elapsed), elapsed));
|
||||
Ok(stats)
|
||||
})
|
||||
.map(move |stats| {
|
||||
info!("{} requests => Mean={}µs, Min={}µs, Max={}µs, Total={}µs",
|
||||
stats.count,
|
||||
stats.sum.microseconds() as f64 / stats.count as f64,
|
||||
stats.min.unwrap().microseconds(),
|
||||
stats.max.unwrap().microseconds(),
|
||||
start.elapsed().microseconds());
|
||||
})
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let _ = env_logger::init();
|
||||
let matches = App::new("Tarpc Concurrency")
|
||||
.about("Demonstrates making concurrent requests to a tarpc service.")
|
||||
.arg(Arg::with_name("concurrency")
|
||||
.short("c")
|
||||
.long("concurrency")
|
||||
.value_name("LEVEL")
|
||||
.help("Sets a custom concurrency level")
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name("clients")
|
||||
.short("n")
|
||||
.long("num_clients")
|
||||
.value_name("AMOUNT")
|
||||
.help("How many clients to distribute requests between")
|
||||
.takes_value(true))
|
||||
.get_matches();
|
||||
let concurrency = matches
|
||||
.value_of("concurrency")
|
||||
.map(&str::parse)
|
||||
.map(Result::unwrap)
|
||||
.unwrap_or(10);
|
||||
let num_clients = matches
|
||||
.value_of("clients")
|
||||
.map(&str::parse)
|
||||
.map(Result::unwrap)
|
||||
.unwrap_or(4);
|
||||
|
||||
let mut reactor = reactor::Core::new().unwrap();
|
||||
let (handle, server) = Server::new()
|
||||
.listen("localhost:0".first_socket_addr(),
|
||||
&reactor.handle(),
|
||||
server::Options::default())
|
||||
.unwrap();
|
||||
reactor.handle().spawn(server);
|
||||
info!("Server listening on {}.", handle.addr());
|
||||
|
||||
let clients = (0..num_clients)
|
||||
// Spin up a couple threads to drive the clients.
|
||||
.map(|i| (i, spawn_core()))
|
||||
.map(|(i, remote)| {
|
||||
info!("Client {} connecting...", i);
|
||||
FutureClient::connect(handle.addr(), client::Options::default().remote(remote))
|
||||
.map_err(|e| panic!(e))
|
||||
});
|
||||
|
||||
let run = futures::collect(clients).and_then(|clients| run_once(clients, concurrency));
|
||||
|
||||
info!("Starting...");
|
||||
|
||||
reactor.run(run).unwrap();
|
||||
}
|
||||
149
examples/pubsub.rs
Normal file
149
examples/pubsub.rs
Normal file
@@ -0,0 +1,149 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
#![feature(plugin)]
|
||||
#![plugin(tarpc_plugins)]
|
||||
|
||||
extern crate env_logger;
|
||||
extern crate futures;
|
||||
#[macro_use]
|
||||
extern crate tarpc;
|
||||
extern crate tokio_core;
|
||||
|
||||
use futures::{Future, future};
|
||||
use publisher::FutureServiceExt as PublisherExt;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
use std::rc::Rc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use subscriber::FutureServiceExt as SubscriberExt;
|
||||
use tarpc::future::{client, server};
|
||||
use tarpc::future::client::ClientExt;
|
||||
use tarpc::util::{FirstSocketAddr, Message, Never};
|
||||
use tokio_core::reactor;
|
||||
|
||||
pub mod subscriber {
|
||||
service! {
|
||||
rpc receive(message: String);
|
||||
}
|
||||
}
|
||||
|
||||
pub mod publisher {
|
||||
use std::net::SocketAddr;
|
||||
use tarpc::util::Message;
|
||||
|
||||
service! {
|
||||
rpc broadcast(message: String);
|
||||
rpc subscribe(id: u32, address: SocketAddr) | Message;
|
||||
rpc unsubscribe(id: u32);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Subscriber {
|
||||
id: u32,
|
||||
}
|
||||
|
||||
impl subscriber::FutureService for Subscriber {
|
||||
type ReceiveFut = Result<(), Never>;
|
||||
|
||||
fn receive(&self, message: String) -> Self::ReceiveFut {
|
||||
println!("{} received message: {}", self.id, message);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Subscriber {
|
||||
fn listen(id: u32, handle: &reactor::Handle, options: server::Options) -> server::Handle {
|
||||
let (server_handle, server) = Subscriber { id: id }
|
||||
.listen("localhost:0".first_socket_addr(), handle, options)
|
||||
.unwrap();
|
||||
handle.spawn(server);
|
||||
server_handle
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Publisher {
|
||||
clients: Rc<RefCell<HashMap<u32, subscriber::FutureClient>>>,
|
||||
}
|
||||
|
||||
impl Publisher {
|
||||
fn new() -> Publisher {
|
||||
Publisher { clients: Rc::new(RefCell::new(HashMap::new())) }
|
||||
}
|
||||
}
|
||||
|
||||
impl publisher::FutureService for Publisher {
|
||||
type BroadcastFut = Box<Future<Item = (), Error = Never>>;
|
||||
|
||||
fn broadcast(&self, message: String) -> Self::BroadcastFut {
|
||||
let acks = self.clients
|
||||
.borrow()
|
||||
.values()
|
||||
.map(move |client| client.receive(message.clone())
|
||||
// Ignore failing subscribers. In a real pubsub,
|
||||
// you'd want to continually retry until subscribers
|
||||
// ack.
|
||||
.then(|_| Ok(())))
|
||||
// Collect to a vec to end the borrow on `self.clients`.
|
||||
.collect::<Vec<_>>();
|
||||
Box::new(future::join_all(acks).map(|_| ()))
|
||||
}
|
||||
|
||||
type SubscribeFut = Box<Future<Item = (), Error = Message>>;
|
||||
|
||||
fn subscribe(&self, id: u32, address: SocketAddr) -> Self::SubscribeFut {
|
||||
let clients = self.clients.clone();
|
||||
Box::new(subscriber::FutureClient::connect(address, client::Options::default())
|
||||
.map(move |subscriber| {
|
||||
println!("Subscribing {}.", id);
|
||||
clients.borrow_mut().insert(id, subscriber);
|
||||
()
|
||||
})
|
||||
.map_err(|e| e.to_string().into()))
|
||||
}
|
||||
|
||||
type UnsubscribeFut = Box<Future<Item = (), Error = Never>>;
|
||||
|
||||
fn unsubscribe(&self, id: u32) -> Self::UnsubscribeFut {
|
||||
println!("Unsubscribing {}", id);
|
||||
self.clients.borrow_mut().remove(&id).unwrap();
|
||||
futures::finished(()).boxed()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let _ = env_logger::init();
|
||||
let mut reactor = reactor::Core::new().unwrap();
|
||||
let (publisher_handle, server) = Publisher::new()
|
||||
.listen("localhost:0".first_socket_addr(),
|
||||
&reactor.handle(),
|
||||
server::Options::default())
|
||||
.unwrap();
|
||||
reactor.handle().spawn(server);
|
||||
|
||||
let subscriber1 = Subscriber::listen(0, &reactor.handle(), server::Options::default());
|
||||
let subscriber2 = Subscriber::listen(1, &reactor.handle(), server::Options::default());
|
||||
|
||||
let publisher = reactor
|
||||
.run(publisher::FutureClient::connect(publisher_handle.addr(), client::Options::default()))
|
||||
.unwrap();
|
||||
reactor
|
||||
.run(publisher
|
||||
.subscribe(0, subscriber1.addr())
|
||||
.and_then(|_| publisher.subscribe(1, subscriber2.addr()))
|
||||
.map_err(|e| panic!(e))
|
||||
.and_then(|_| {
|
||||
println!("Broadcasting...");
|
||||
publisher.broadcast("hello to all".to_string())
|
||||
})
|
||||
.and_then(|_| publisher.unsubscribe(1))
|
||||
.and_then(|_| publisher.broadcast("hi again".to_string())))
|
||||
.unwrap();
|
||||
thread::sleep(Duration::from_millis(300));
|
||||
}
|
||||
67
examples/readme_errors.rs
Normal file
67
examples/readme_errors.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
#![feature(plugin)]
|
||||
#![plugin(tarpc_plugins)]
|
||||
|
||||
extern crate futures;
|
||||
#[macro_use]
|
||||
extern crate tarpc;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate tokio_core;
|
||||
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use tarpc::sync::{client, server};
|
||||
use tarpc::sync::client::ClientExt;
|
||||
|
||||
service! {
|
||||
rpc hello(name: String) -> String | NoNameGiven;
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct NoNameGiven;
|
||||
|
||||
impl fmt::Display for NoNameGiven {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.description())
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for NoNameGiven {
|
||||
fn description(&self) -> &str {
|
||||
r#"The empty String, "", is not a valid argument to rpc `hello`."#
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct HelloServer;
|
||||
|
||||
impl SyncService for HelloServer {
|
||||
fn hello(&self, name: String) -> Result<String, NoNameGiven> {
|
||||
if name == "" {
|
||||
Err(NoNameGiven)
|
||||
} else {
|
||||
Ok(format!("Hello, {}!", name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
thread::spawn(move || {
|
||||
let handle = HelloServer
|
||||
.listen("localhost:10000", server::Options::default())
|
||||
.unwrap();
|
||||
tx.send(handle.addr()).unwrap();
|
||||
handle.run();
|
||||
});
|
||||
let client = SyncClient::connect(rx.recv().unwrap(), client::Options::default()).unwrap();
|
||||
println!("{}", client.hello("Mom".to_string()).unwrap());
|
||||
println!("{}", client.hello("".to_string()).unwrap_err());
|
||||
}
|
||||
51
examples/readme_futures.rs
Normal file
51
examples/readme_futures.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
#![feature(plugin)]
|
||||
#![plugin(tarpc_plugins)]
|
||||
|
||||
extern crate futures;
|
||||
#[macro_use]
|
||||
extern crate tarpc;
|
||||
extern crate tokio_core;
|
||||
|
||||
use futures::Future;
|
||||
use tarpc::future::{client, server};
|
||||
use tarpc::future::client::ClientExt;
|
||||
use tarpc::util::{FirstSocketAddr, Never};
|
||||
use tokio_core::reactor;
|
||||
|
||||
service! {
|
||||
rpc hello(name: String) -> String;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct HelloServer;
|
||||
|
||||
impl FutureService for HelloServer {
|
||||
type HelloFut = Result<String, Never>;
|
||||
|
||||
fn hello(&self, name: String) -> Self::HelloFut {
|
||||
Ok(format!("Hello, {}!", name))
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut reactor = reactor::Core::new().unwrap();
|
||||
let (handle, server) = HelloServer
|
||||
.listen("localhost:10000".first_socket_addr(),
|
||||
&reactor.handle(),
|
||||
server::Options::default())
|
||||
.unwrap();
|
||||
reactor.handle().spawn(server);
|
||||
|
||||
let options = client::Options::default().handle(reactor.handle());
|
||||
reactor
|
||||
.run(FutureClient::connect(handle.addr(), options)
|
||||
.map_err(tarpc::Error::from)
|
||||
.and_then(|client| client.hello("Mom".to_string()))
|
||||
.map(|resp| println!("{}", resp)))
|
||||
.unwrap();
|
||||
}
|
||||
47
examples/readme_sync.rs
Normal file
47
examples/readme_sync.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
// required by `FutureClient` (not used directly in this example)
|
||||
#![feature(plugin)]
|
||||
#![plugin(tarpc_plugins)]
|
||||
|
||||
extern crate futures;
|
||||
#[macro_use]
|
||||
extern crate tarpc;
|
||||
extern crate tokio_core;
|
||||
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use tarpc::sync::{client, server};
|
||||
use tarpc::sync::client::ClientExt;
|
||||
use tarpc::util::Never;
|
||||
|
||||
service! {
|
||||
rpc hello(name: String) -> String;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct HelloServer;
|
||||
|
||||
impl SyncService for HelloServer {
|
||||
fn hello(&self, name: String) -> Result<String, Never> {
|
||||
Ok(format!("Hello from thread {}, {}!",
|
||||
thread::current().name().unwrap(),
|
||||
name))
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
thread::spawn(move || {
|
||||
let handle = HelloServer
|
||||
.listen("localhost:0", server::Options::default())
|
||||
.unwrap();
|
||||
tx.send(handle.addr()).unwrap();
|
||||
handle.run();
|
||||
});
|
||||
let client = SyncClient::connect(rx.recv().unwrap(), client::Options::default()).unwrap();
|
||||
println!("{}", client.hello("Mom".to_string()).unwrap());
|
||||
}
|
||||
105
examples/server_calling_server.rs
Normal file
105
examples/server_calling_server.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
#![feature(plugin)]
|
||||
#![plugin(tarpc_plugins)]
|
||||
|
||||
extern crate env_logger;
|
||||
#[macro_use]
|
||||
extern crate tarpc;
|
||||
extern crate futures;
|
||||
extern crate tokio_core;
|
||||
|
||||
use add::{FutureService as AddFutureService, FutureServiceExt as AddExt};
|
||||
use double::{FutureService as DoubleFutureService, FutureServiceExt as DoubleExt};
|
||||
use futures::{BoxFuture, Future, Stream};
|
||||
use tarpc::future::{client, server};
|
||||
use tarpc::future::client::ClientExt as Fc;
|
||||
use tarpc::util::{FirstSocketAddr, Message, Never};
|
||||
use tokio_core::reactor;
|
||||
|
||||
pub mod add {
|
||||
service! {
|
||||
/// Add two ints together.
|
||||
rpc add(x: i32, y: i32) -> i32;
|
||||
}
|
||||
}
|
||||
|
||||
pub mod double {
|
||||
use tarpc::util::Message;
|
||||
|
||||
service! {
|
||||
/// 2 * x
|
||||
rpc double(x: i32) -> i32 | Message;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AddServer;
|
||||
|
||||
impl AddFutureService for AddServer {
|
||||
type AddFut = Result<i32, Never>;
|
||||
|
||||
fn add(&self, x: i32, y: i32) -> Self::AddFut {
|
||||
Ok(x + y)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct DoubleServer {
|
||||
client: add::FutureClient,
|
||||
}
|
||||
|
||||
impl DoubleServer {
|
||||
fn new(client: add::FutureClient) -> Self {
|
||||
DoubleServer { client: client }
|
||||
}
|
||||
}
|
||||
|
||||
impl DoubleFutureService for DoubleServer {
|
||||
type DoubleFut = BoxFuture<i32, Message>;
|
||||
|
||||
fn double(&self, x: i32) -> Self::DoubleFut {
|
||||
self.client
|
||||
.add(x, x)
|
||||
.map_err(|e| e.to_string().into())
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let _ = env_logger::init();
|
||||
let mut reactor = reactor::Core::new().unwrap();
|
||||
let (add, server) = AddServer
|
||||
.listen("localhost:0".first_socket_addr(),
|
||||
&reactor.handle(),
|
||||
server::Options::default())
|
||||
.unwrap();
|
||||
reactor.handle().spawn(server);
|
||||
|
||||
let options = client::Options::default().handle(reactor.handle());
|
||||
let add_client = reactor
|
||||
.run(add::FutureClient::connect(add.addr(), options))
|
||||
.unwrap();
|
||||
|
||||
let (double, server) = DoubleServer::new(add_client)
|
||||
.listen("localhost:0".first_socket_addr(),
|
||||
&reactor.handle(),
|
||||
server::Options::default())
|
||||
.unwrap();
|
||||
reactor.handle().spawn(server);
|
||||
|
||||
let double_client = reactor
|
||||
.run(double::FutureClient::connect(double.addr(), client::Options::default()))
|
||||
.unwrap();
|
||||
reactor
|
||||
.run(futures::stream::futures_unordered((0..5).map(|i| double_client.double(i)))
|
||||
.map_err(|e| println!("{}", e))
|
||||
.for_each(|i| {
|
||||
println!("{:?}", i);
|
||||
Ok(())
|
||||
}))
|
||||
.unwrap();
|
||||
}
|
||||
96
examples/sync_server_calling_server.rs
Normal file
96
examples/sync_server_calling_server.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
#![feature(plugin)]
|
||||
#![plugin(tarpc_plugins)]
|
||||
|
||||
extern crate env_logger;
|
||||
#[macro_use]
|
||||
extern crate tarpc;
|
||||
extern crate futures;
|
||||
extern crate tokio_core;
|
||||
|
||||
use add::{SyncService as AddSyncService, SyncServiceExt as AddExt};
|
||||
use double::{SyncService as DoubleSyncService, SyncServiceExt as DoubleExt};
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use tarpc::sync::{client, server};
|
||||
use tarpc::sync::client::ClientExt as Fc;
|
||||
use tarpc::util::{FirstSocketAddr, Message, Never};
|
||||
|
||||
pub mod add {
|
||||
service! {
|
||||
/// Add two ints together.
|
||||
rpc add(x: i32, y: i32) -> i32;
|
||||
}
|
||||
}
|
||||
|
||||
pub mod double {
|
||||
use tarpc::util::Message;
|
||||
|
||||
service! {
|
||||
/// 2 * x
|
||||
rpc double(x: i32) -> i32 | Message;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AddServer;
|
||||
|
||||
impl AddSyncService for AddServer {
|
||||
fn add(&self, x: i32, y: i32) -> Result<i32, Never> {
|
||||
Ok(x + y)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct DoubleServer {
|
||||
client: add::SyncClient,
|
||||
}
|
||||
|
||||
impl DoubleServer {
|
||||
fn new(client: add::SyncClient) -> Self {
|
||||
DoubleServer { client: client }
|
||||
}
|
||||
}
|
||||
|
||||
impl DoubleSyncService for DoubleServer {
|
||||
fn double(&self, x: i32) -> Result<i32, Message> {
|
||||
self.client.add(x, x).map_err(|e| e.to_string().into())
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let _ = env_logger::init();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
thread::spawn(move || {
|
||||
let handle = AddServer
|
||||
.listen("localhost:0".first_socket_addr(),
|
||||
server::Options::default())
|
||||
.unwrap();
|
||||
tx.send(handle.addr()).unwrap();
|
||||
handle.run();
|
||||
});
|
||||
|
||||
|
||||
let add = rx.recv().unwrap();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
thread::spawn(move || {
|
||||
let add_client = add::SyncClient::connect(add, client::Options::default()).unwrap();
|
||||
let handle = DoubleServer::new(add_client)
|
||||
.listen("localhost:0".first_socket_addr(),
|
||||
server::Options::default())
|
||||
.unwrap();
|
||||
tx.send(handle.addr()).unwrap();
|
||||
handle.run();
|
||||
});
|
||||
let double = rx.recv().unwrap();
|
||||
|
||||
let double_client = double::SyncClient::connect(double, client::Options::default()).unwrap();
|
||||
for i in 0..5 {
|
||||
let doubled = double_client.double(i).unwrap();
|
||||
println!("{:?}", doubled);
|
||||
}
|
||||
}
|
||||
115
examples/throughput.rs
Normal file
115
examples/throughput.rs
Normal file
@@ -0,0 +1,115 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
#![feature(plugin)]
|
||||
#![plugin(tarpc_plugins)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate tarpc;
|
||||
extern crate env_logger;
|
||||
extern crate futures;
|
||||
extern crate serde;
|
||||
extern crate tokio_core;
|
||||
|
||||
use std::io::{Read, Write, stdout};
|
||||
use std::net;
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use std::time;
|
||||
use tarpc::future::server;
|
||||
use tarpc::sync::client::{self, ClientExt};
|
||||
use tarpc::util::{FirstSocketAddr, Never};
|
||||
use tokio_core::reactor;
|
||||
|
||||
lazy_static! {
|
||||
static ref BUF: Arc<serde::bytes::ByteBuf> = Arc::new(gen_vec(CHUNK_SIZE as usize).into());
|
||||
}
|
||||
|
||||
fn gen_vec(size: usize) -> Vec<u8> {
|
||||
let mut vec: Vec<u8> = Vec::with_capacity(size);
|
||||
for i in 0..size {
|
||||
vec.push(((i % 2) << 8) as u8);
|
||||
}
|
||||
vec
|
||||
}
|
||||
|
||||
service! {
|
||||
rpc read() -> Arc<serde::bytes::ByteBuf>;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Server;
|
||||
|
||||
impl FutureService for Server {
|
||||
type ReadFut = Result<Arc<serde::bytes::ByteBuf>, Never>;
|
||||
|
||||
fn read(&self) -> Self::ReadFut {
|
||||
Ok(BUF.clone())
|
||||
}
|
||||
}
|
||||
|
||||
const CHUNK_SIZE: u32 = 1 << 19;
|
||||
|
||||
fn bench_tarpc(target: u64) {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
thread::spawn(move || {
|
||||
let mut reactor = reactor::Core::new().unwrap();
|
||||
let (addr, server) = Server
|
||||
.listen("localhost:0".first_socket_addr(),
|
||||
&reactor.handle(),
|
||||
server::Options::default())
|
||||
.unwrap();
|
||||
tx.send(addr).unwrap();
|
||||
reactor.run(server).unwrap();
|
||||
});
|
||||
let client = SyncClient::connect(rx.recv().unwrap().addr(), client::Options::default())
|
||||
.unwrap();
|
||||
let start = time::Instant::now();
|
||||
let mut nread = 0;
|
||||
while nread < target {
|
||||
nread += client.read().unwrap().len() as u64;
|
||||
print!(".");
|
||||
stdout().flush().unwrap();
|
||||
}
|
||||
println!("done");
|
||||
let duration = time::Instant::now() - start;
|
||||
println!("TARPC: {}MB/s",
|
||||
(target as f64 / (1024f64 * 1024f64)) /
|
||||
(duration.as_secs() as f64 + duration.subsec_nanos() as f64 / 10E9));
|
||||
}
|
||||
|
||||
fn bench_tcp(target: u64) {
|
||||
let l = net::TcpListener::bind("localhost:0").unwrap();
|
||||
let addr = l.local_addr().unwrap();
|
||||
thread::spawn(move || {
|
||||
let (mut stream, _) = l.accept().unwrap();
|
||||
while let Ok(_) = stream.write_all(&*BUF) {}
|
||||
});
|
||||
let mut stream = net::TcpStream::connect(&addr).unwrap();
|
||||
let mut buf = vec![0; CHUNK_SIZE as usize];
|
||||
let start = time::Instant::now();
|
||||
let mut nread = 0;
|
||||
while nread < target {
|
||||
stream.read_exact(&mut buf[..]).unwrap();
|
||||
nread += CHUNK_SIZE as u64;
|
||||
print!(".");
|
||||
stdout().flush().unwrap();
|
||||
}
|
||||
println!("done");
|
||||
let duration = time::Instant::now() - start;
|
||||
println!("TCP: {}MB/s",
|
||||
(target as f64 / (1024f64 * 1024f64)) /
|
||||
(duration.as_secs() as f64 + duration.subsec_nanos() as f64 / 10E9));
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let _ = env_logger::init();
|
||||
let _ = *BUF; // To non-lazily initialize it.
|
||||
bench_tcp(256 << 20);
|
||||
bench_tarpc(256 << 20);
|
||||
}
|
||||
109
examples/two_clients.rs
Normal file
109
examples/two_clients.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
#![feature(plugin)]
|
||||
#![plugin(tarpc_plugins)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[macro_use]
|
||||
extern crate tarpc;
|
||||
extern crate bincode;
|
||||
extern crate env_logger;
|
||||
extern crate futures;
|
||||
extern crate tokio_core;
|
||||
|
||||
use bar::FutureServiceExt as BarExt;
|
||||
use baz::FutureServiceExt as BazExt;
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use tarpc::future::server;
|
||||
use tarpc::sync::client;
|
||||
use tarpc::sync::client::ClientExt;
|
||||
use tarpc::util::{FirstSocketAddr, Never};
|
||||
use tokio_core::reactor;
|
||||
|
||||
mod bar {
|
||||
service! {
|
||||
rpc bar(i: i32) -> i32;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Bar;
|
||||
impl bar::FutureService for Bar {
|
||||
type BarFut = Result<i32, Never>;
|
||||
|
||||
fn bar(&self, i: i32) -> Self::BarFut {
|
||||
Ok(i)
|
||||
}
|
||||
}
|
||||
|
||||
mod baz {
|
||||
service! {
|
||||
rpc baz(s: String) -> String;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Baz;
|
||||
impl baz::FutureService for Baz {
|
||||
type BazFut = Result<String, Never>;
|
||||
|
||||
fn baz(&self, s: String) -> Self::BazFut {
|
||||
Ok(format!("Hello, {}!", s))
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! pos {
|
||||
() => (concat!(file!(), ":", line!()))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let _ = env_logger::init();
|
||||
let bar_client = {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
thread::spawn(move || {
|
||||
let mut reactor = reactor::Core::new().unwrap();
|
||||
let (handle, server) = Bar.listen("localhost:0".first_socket_addr(),
|
||||
&reactor.handle(),
|
||||
server::Options::default())
|
||||
.unwrap();
|
||||
tx.send(handle).unwrap();
|
||||
reactor.run(server).unwrap();
|
||||
});
|
||||
let handle = rx.recv().unwrap();
|
||||
bar::SyncClient::connect(handle.addr(), client::Options::default()).unwrap()
|
||||
};
|
||||
|
||||
let baz_client = {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
thread::spawn(move || {
|
||||
let mut reactor = reactor::Core::new().unwrap();
|
||||
let (handle, server) = Baz.listen("localhost:0".first_socket_addr(),
|
||||
&reactor.handle(),
|
||||
server::Options::default())
|
||||
.unwrap();
|
||||
tx.send(handle).unwrap();
|
||||
reactor.run(server).unwrap();
|
||||
});
|
||||
let handle = rx.recv().unwrap();
|
||||
baz::SyncClient::connect(handle.addr(), client::Options::default()).unwrap()
|
||||
};
|
||||
|
||||
|
||||
info!("Result: {:?}", bar_client.bar(17));
|
||||
|
||||
let total = 20;
|
||||
for i in 1..(total + 1) {
|
||||
if i % 2 == 0 {
|
||||
info!("Result 1: {:?}", bar_client.bar(i));
|
||||
} else {
|
||||
info!("Result 2: {:?}", baz_client.baz(i.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
info!("Done.");
|
||||
}
|
||||
123
hooks/pre-commit
123
hooks/pre-commit
@@ -1,18 +1,41 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright 2016 Google Inc. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
# This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to verify what is about to be committed.
|
||||
# Called by "git commit" with no arguments. The hook should
|
||||
# exit with non-zero status after issuing an appropriate message if
|
||||
# it wants to stop the commit.
|
||||
# Pre-commit hook for the tarpc repository. To use this hook, copy it to .git/hooks in your
|
||||
# repository root.
|
||||
#
|
||||
# To enable this hook, rename this file to "pre-commit".
|
||||
# This precommit checks the following:
|
||||
# 1. All filenames are ascii
|
||||
# 2. There is no bad whitespace
|
||||
# 3. rustfmt is installed
|
||||
# 4. rustfmt is a noop on files that are in the index
|
||||
#
|
||||
# Options:
|
||||
#
|
||||
# - TARPC_SKIP_RUSTFMT, default = 0
|
||||
#
|
||||
# Set this to 1 to skip running rustfmt
|
||||
#
|
||||
# Note that these options are most useful for testing the hooks themselves. Use git commit
|
||||
# --no-verify to skip the pre-commit hook altogether.
|
||||
|
||||
if git rev-parse --verify HEAD >/dev/null 2>&1
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
PREFIX="${GREEN}[PRECOMMIT]${NC}"
|
||||
FAILURE="${RED}FAILED${NC}"
|
||||
WARNING="${RED}[WARNING]${NC}"
|
||||
SKIPPED="${YELLOW}SKIPPED${NC}"
|
||||
SUCCESS="${GREEN}ok${NC}"
|
||||
|
||||
if git rev-parse --verify HEAD &>/dev/null
|
||||
then
|
||||
against=HEAD
|
||||
else
|
||||
@@ -20,35 +43,71 @@ else
|
||||
against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
|
||||
fi
|
||||
|
||||
# If you want to allow non-ASCII filenames set this variable to true.
|
||||
allownonascii=$(git config --bool hooks.allownonascii)
|
||||
FAILED=0
|
||||
|
||||
# Redirect output to stderr.
|
||||
exec 1>&2
|
||||
|
||||
# Cross platform projects tend to avoid non-ASCII filenames; prevent
|
||||
# them from being added to the repository. We exploit the fact that the
|
||||
# printable range starts at the space character and ends with tilde.
|
||||
if [ "$allownonascii" != "true" ] &&
|
||||
# Note that the use of brackets around a tr range is ok here, (it's
|
||||
# even required, for portability to Solaris 10's /usr/bin/tr), since
|
||||
# the square bracket bytes happen to fall in the designated range.
|
||||
test $(git diff --cached --name-only --diff-filter=A -z $against |
|
||||
LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
|
||||
printf "${PREFIX} Checking that all filenames are ascii ... "
|
||||
# Note that the use of brackets around a tr range is ok here, (it's
|
||||
# even required, for portability to Solaris 10's /usr/bin/tr), since
|
||||
# the square bracket bytes happen to fall in the designated range.
|
||||
if test $(git diff --cached --name-only --diff-filter=A -z $against | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
|
||||
then
|
||||
cat <<\EOF
|
||||
Error: Attempt to add a non-ASCII file name.
|
||||
FAILED=1
|
||||
printf "${FAILURE}\n"
|
||||
else
|
||||
printf "${SUCCESS}\n"
|
||||
fi
|
||||
|
||||
This can cause problems if you want to work with people on other platforms.
|
||||
printf "${PREFIX} Checking for bad whitespace ... "
|
||||
git diff-index --check --cached $against -- &>/dev/null
|
||||
if [ "$?" != 0 ]; then
|
||||
FAILED=1
|
||||
printf "${FAILURE}\n"
|
||||
else
|
||||
printf "${SUCCESS}\n"
|
||||
fi
|
||||
|
||||
To be portable it is advisable to rename the file.
|
||||
|
||||
If you know what you are doing you can disable this check using:
|
||||
|
||||
git config hooks.allownonascii true
|
||||
EOF
|
||||
printf "${PREFIX} Checking for rustfmt ... "
|
||||
command -v rustfmt &>/dev/null
|
||||
if [ $? == 0 ]; then
|
||||
printf "${SUCCESS}\n"
|
||||
else
|
||||
printf "${FAILURE}\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# If there are whitespace errors, print the offending file names and fail.
|
||||
exec git diff-index --check --cached $against --
|
||||
printf "${PREFIX} Checking for shasum ... "
|
||||
command -v shasum &>/dev/null
|
||||
if [ $? == 0 ]; then
|
||||
printf "${SUCCESS}\n"
|
||||
else
|
||||
printf "${FAILURE}\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Just check that running rustfmt doesn't do anything to the file. I do this instead of
|
||||
# modifying the file because I don't want to mess with the developer's index, which may
|
||||
# not only contain discrete files.
|
||||
printf "${PREFIX} Checking formatting ... "
|
||||
FMTRESULT=0
|
||||
diff=""
|
||||
for file in $(git diff --name-only --cached);
|
||||
do
|
||||
if [ ${file: -3} == ".rs" ]; then
|
||||
diff="$diff$(rustfmt --skip-children --write-mode=diff $file)"
|
||||
fi
|
||||
done
|
||||
if grep --quiet "^Diff at line" <<< "$diff"; then
|
||||
FMTRESULT=1
|
||||
fi
|
||||
|
||||
if [ "${TARPC_SKIP_RUSTFMT}" == 1 ]; then
|
||||
printf "${SKIPPED}\n"$?
|
||||
elif [ ${FMTRESULT} != 0 ]; then
|
||||
FAILED=1
|
||||
printf "${FAILURE}\n"
|
||||
echo "$diff" | sed 's/Using rustfmt config file.*$/d/'
|
||||
else
|
||||
printf "${SUCCESS}\n"
|
||||
fi
|
||||
|
||||
exit ${FAILED}
|
||||
|
||||
110
hooks/pre-push
110
hooks/pre-push
@@ -1,39 +1,101 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright 2016 Google Inc. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
# This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
#!/bin/sh
|
||||
# Pre-push hook for the tarpc repository. To use this hook, copy it to .git/hooks in your repository
|
||||
# root.
|
||||
#
|
||||
# This hook runs tests to make sure only working code is being pushed. If present, rustup is used
|
||||
# to build and test the code on the appropriate toolchains. The working copy must not contain
|
||||
# uncommitted changes, since the script currently just runs cargo build/test in the working copy.
|
||||
#
|
||||
# Options:
|
||||
#
|
||||
# - TARPC_ALLOW_DIRTY, default = 0
|
||||
#
|
||||
# Setting this variable to 1 will run tests even though there are code changes in the working
|
||||
# copy. Set to 0 by default, since the intent is to test the code that's being pushed, not changes
|
||||
# still in the working copy.
|
||||
#
|
||||
# Note that these options are most useful for testing the hooks themselves. Use git push --no-verify
|
||||
# to skip the pre-push hook altogether.
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
PREFIX="${GREEN}[PREPUSH]${NC}"
|
||||
FAILURE="${RED}FAILED${NC}"
|
||||
WARNING="${YELLOW}[WARNING]${NC}"
|
||||
SKIPPED="${YELLOW}SKIPPED${NC}"
|
||||
SUCCESS="${GREEN}ok${NC}"
|
||||
|
||||
printf "${PREFIX} Clean working copy ... "
|
||||
git diff --exit-code &>/dev/null
|
||||
if [ "$?" != 0 ];
|
||||
then
|
||||
echo ${RED}ERROR${NC} You have uncommitted changes please commit or stash them before pushing so that I can run tests!
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf "${YELLOW}[PRESUBMIT]${NC} Running tests ... "
|
||||
|
||||
TEST_RESULT=0
|
||||
cargo test --manifest-path tarpc/Cargo.toml &>/dev/null
|
||||
if [ "$?" != "0" ];
|
||||
then
|
||||
printf "${RED}FAILED${NC}"
|
||||
TEST_RESULT=1
|
||||
if [ "$?" == 0 ]; then
|
||||
printf "${SUCCESS}\n"
|
||||
else
|
||||
printf "${GREEN}ok${NC}"
|
||||
fi
|
||||
printf "\n"
|
||||
|
||||
RESULT=0
|
||||
if [ "$TEST_RESULT" == "1" ];
|
||||
then
|
||||
RESULT=1
|
||||
if [ "${TARPC_ALLOW_DIRTY}" == "1" ]
|
||||
then
|
||||
printf "${SKIPPED}\n"
|
||||
else
|
||||
printf "${FAILURE}\n"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
exit $RESULT
|
||||
PREPUSH_RESULT=0
|
||||
|
||||
try_run() {
|
||||
TEXT=$1
|
||||
shift
|
||||
printf "${PREFIX} ${TEXT}"
|
||||
OUTPUT=$($@ 2>&1)
|
||||
if [ "$?" != "0" ]; then
|
||||
printf "${FAILURE}, output shown below\n"
|
||||
printf "\n\n"
|
||||
printf "$OUTPUT"
|
||||
printf "\n\n"
|
||||
PREPUSH_RESULT=1
|
||||
return 1
|
||||
else
|
||||
printf "${SUCCESS}\n"
|
||||
fi
|
||||
}
|
||||
|
||||
TOOLCHAIN_RESULT=0
|
||||
check_toolchain() {
|
||||
printf "${PREFIX} Checking for $1 toolchain ... "
|
||||
if [[ $(rustup toolchain list) =~ $1 ]]; then
|
||||
printf "${SUCCESS}\n"
|
||||
else
|
||||
TOOLCHAIN_RESULT=1
|
||||
PREPUSH_RESULT=1
|
||||
printf "${FAILURE}\n"
|
||||
fi
|
||||
}
|
||||
|
||||
printf "${PREFIX} Checking for rustup or current toolchain directive... "
|
||||
command -v rustup &>/dev/null
|
||||
if [ "$?" == 0 ]; then
|
||||
printf "${SUCCESS}\n"
|
||||
|
||||
check_toolchain nightly
|
||||
if [ ${TOOLCHAIN_RESULT} == 1 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
try_run "Building ... " cargo build --color=always
|
||||
try_run "Testing ... " cargo test --color=always
|
||||
try_run "Benching ... " cargo bench --color=always
|
||||
|
||||
try_run "Building with tls ... " cargo build --color=always --features tls
|
||||
try_run "Testing with tls ... " cargo test --color=always --features tls
|
||||
try_run "Benching with tls ... " cargo bench --color=always --features tls
|
||||
fi
|
||||
|
||||
exit $PREPUSH_RESULT
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
ideal_width = 100
|
||||
reorder_imports = true
|
||||
92
src/errors.rs
Normal file
92
src/errors.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{fmt, io};
|
||||
use std::error::Error as StdError;
|
||||
|
||||
/// All errors that can occur during the use of tarpc.
|
||||
#[derive(Debug)]
|
||||
pub enum Error<E> {
|
||||
/// Any IO error.
|
||||
Io(io::Error),
|
||||
/// Error deserializing the server response.
|
||||
///
|
||||
/// Typically this indicates a faulty implementation of `serde::Serialize` or
|
||||
/// `serde::Deserialize`.
|
||||
ResponseDeserialize(::bincode::Error),
|
||||
/// Error deserializing the client request.
|
||||
///
|
||||
/// Typically this indicates a faulty implementation of `serde::Serialize` or
|
||||
/// `serde::Deserialize`.
|
||||
RequestDeserialize(String),
|
||||
/// The server was unable to reply to the rpc for some reason.
|
||||
///
|
||||
/// This is a service-specific error. Its type is individually specified in the
|
||||
/// `service!` macro for each rpc.
|
||||
App(E),
|
||||
}
|
||||
|
||||
impl<E: StdError + Deserialize + Serialize + Send + 'static> fmt::Display for Error<E> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Error::ResponseDeserialize(ref e) => write!(f, r#"{}: "{}""#, self.description(), e),
|
||||
Error::RequestDeserialize(ref e) => write!(f, r#"{}: "{}""#, self.description(), e),
|
||||
Error::App(ref e) => fmt::Display::fmt(e, f),
|
||||
Error::Io(ref e) => fmt::Display::fmt(e, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: StdError + Deserialize + Serialize + Send + 'static> StdError for Error<E> {
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
Error::ResponseDeserialize(_) => "The client failed to deserialize the response.",
|
||||
Error::RequestDeserialize(_) => "The server failed to deserialize the request.",
|
||||
Error::App(ref e) => e.description(),
|
||||
Error::Io(ref e) => e.description(),
|
||||
}
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&StdError> {
|
||||
match *self {
|
||||
Error::ResponseDeserialize(ref e) => e.cause(),
|
||||
Error::RequestDeserialize(_) |
|
||||
Error::App(_) => None,
|
||||
Error::Io(ref e) => e.cause(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> From<io::Error> for Error<E> {
|
||||
fn from(err: io::Error) -> Self {
|
||||
Error::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> From<WireError<E>> for Error<E> {
|
||||
fn from(err: WireError<E>) -> Self {
|
||||
match err {
|
||||
WireError::RequestDeserialize(s) => Error::RequestDeserialize(s),
|
||||
WireError::App(e) => Error::App(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A serializable, server-supplied error.
|
||||
#[doc(hidden)]
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub enum WireError<E> {
|
||||
/// Server-side error in deserializing the client request.
|
||||
RequestDeserialize(String),
|
||||
/// The server was unable to reply to the rpc for some reason.
|
||||
App(E),
|
||||
}
|
||||
|
||||
/// Convert `native_tls::Error` to `std::io::Error`
|
||||
#[cfg(feature = "tls")]
|
||||
pub fn native_to_io(e: ::native_tls::Error) -> io::Error {
|
||||
io::Error::new(io::ErrorKind::Other, e)
|
||||
}
|
||||
263
src/future/client.rs
Normal file
263
src/future/client.rs
Normal file
@@ -0,0 +1,263 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
use {REMOTE, bincode};
|
||||
use future::server::Response;
|
||||
use futures::{self, Future, future};
|
||||
use protocol::Proto;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use stream_type::StreamType;
|
||||
use tokio_core::net::TcpStream;
|
||||
use tokio_core::reactor;
|
||||
use tokio_proto::BindClient as ProtoBindClient;
|
||||
use tokio_proto::multiplex::ClientService;
|
||||
use tokio_service::Service;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "tls")] {
|
||||
use errors::native_to_io;
|
||||
use tls::client::Context;
|
||||
use tokio_tls::TlsConnectorExt;
|
||||
} else {}
|
||||
}
|
||||
|
||||
/// Additional options to configure how the client connects and operates.
|
||||
#[derive(Debug)]
|
||||
pub struct Options {
|
||||
/// Max packet size in bytes.
|
||||
max_payload_size: u64,
|
||||
reactor: Option<Reactor>,
|
||||
#[cfg(feature = "tls")]
|
||||
tls_ctx: Option<Context>,
|
||||
}
|
||||
|
||||
impl Default for Options {
|
||||
#[cfg(feature = "tls")]
|
||||
fn default() -> Self {
|
||||
Options {
|
||||
max_payload_size: 2 << 20,
|
||||
reactor: None,
|
||||
tls_ctx: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tls"))]
|
||||
fn default() -> Self {
|
||||
Options {
|
||||
max_payload_size: 2 << 20,
|
||||
reactor: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Options {
|
||||
/// Set the max payload size in bytes. The default is 2 << 20 (2 MiB).
|
||||
pub fn max_payload_size(mut self, bytes: u64) -> Self {
|
||||
self.max_payload_size = bytes;
|
||||
self
|
||||
}
|
||||
|
||||
/// Drive using the given reactor handle.
|
||||
pub fn handle(mut self, handle: reactor::Handle) -> Self {
|
||||
self.reactor = Some(Reactor::Handle(handle));
|
||||
self
|
||||
}
|
||||
|
||||
/// Drive using the given reactor remote.
|
||||
pub fn remote(mut self, remote: reactor::Remote) -> Self {
|
||||
self.reactor = Some(Reactor::Remote(remote));
|
||||
self
|
||||
}
|
||||
|
||||
/// Connect using the given `Context`
|
||||
#[cfg(feature = "tls")]
|
||||
pub fn tls(mut self, tls_ctx: Context) -> Self {
|
||||
self.tls_ctx = Some(tls_ctx);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
enum Reactor {
|
||||
Handle(reactor::Handle),
|
||||
Remote(reactor::Remote),
|
||||
}
|
||||
|
||||
impl fmt::Debug for Reactor {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
const HANDLE: &'static &'static str = &"Reactor::Handle";
|
||||
const HANDLE_INNER: &'static &'static str = &"Handle { .. }";
|
||||
const REMOTE: &'static &'static str = &"Reactor::Remote";
|
||||
const REMOTE_INNER: &'static &'static str = &"Remote { .. }";
|
||||
|
||||
match *self {
|
||||
Reactor::Handle(_) => f.debug_tuple(HANDLE).field(HANDLE_INNER).finish(),
|
||||
Reactor::Remote(_) => f.debug_tuple(REMOTE).field(REMOTE_INNER).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[doc(hidden)]
|
||||
pub struct Client<Req, Resp, E>
|
||||
where Req: Serialize + 'static,
|
||||
Resp: Deserialize + 'static,
|
||||
E: Deserialize + 'static
|
||||
{
|
||||
inner: ClientService<StreamType, Proto<Req, Response<Resp, E>>>,
|
||||
}
|
||||
|
||||
impl<Req, Resp, E> Clone for Client<Req, Resp, E>
|
||||
where Req: Serialize + 'static,
|
||||
Resp: Deserialize + 'static,
|
||||
E: Deserialize + 'static
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Client { inner: self.inner.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<Req, Resp, E> Service for Client<Req, Resp, E>
|
||||
where Req: Serialize + Send + 'static,
|
||||
Resp: Deserialize + Send + 'static,
|
||||
E: Deserialize + Send + 'static
|
||||
{
|
||||
type Request = Req;
|
||||
type Response = Resp;
|
||||
type Error = ::Error<E>;
|
||||
type Future = ResponseFuture<Req, Resp, E>;
|
||||
|
||||
fn call(&self, request: Self::Request) -> Self::Future {
|
||||
fn identity<T>(t: T) -> T {
|
||||
t
|
||||
}
|
||||
self.inner
|
||||
.call(request)
|
||||
.map(Self::map_err as _)
|
||||
.map_err(::Error::from as _)
|
||||
.and_then(identity as _)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Req, Resp, E> Client<Req, Resp, E>
|
||||
where Req: Serialize + 'static,
|
||||
Resp: Deserialize + 'static,
|
||||
E: Deserialize + 'static
|
||||
{
|
||||
fn bind(handle: &reactor::Handle, tcp: StreamType, max_payload_size: u64) -> Self
|
||||
where Req: Serialize + Send + 'static,
|
||||
Resp: Deserialize + Send + 'static,
|
||||
E: Deserialize + Send + 'static
|
||||
{
|
||||
let inner = Proto::new(max_payload_size).bind_client(&handle, tcp);
|
||||
Client { inner }
|
||||
}
|
||||
|
||||
fn map_err(resp: WireResponse<Resp, E>) -> Result<Resp, ::Error<E>> {
|
||||
resp.map(|r| r.map_err(::Error::from))
|
||||
.map_err(::Error::ResponseDeserialize)
|
||||
.and_then(|r| r)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Req, Resp, E> fmt::Debug for Client<Req, Resp, E>
|
||||
where Req: Serialize + 'static,
|
||||
Resp: Deserialize + 'static,
|
||||
E: Deserialize + 'static
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
write!(f, "Client {{ .. }}")
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension methods for clients.
|
||||
pub trait ClientExt: Sized {
|
||||
/// The type of the future returned when calling `connect`.
|
||||
type ConnectFut: Future<Item = Self, Error = io::Error>;
|
||||
|
||||
/// Connects to a server located at the given address, using the given options.
|
||||
fn connect(addr: SocketAddr, options: Options) -> Self::ConnectFut;
|
||||
}
|
||||
|
||||
/// A future that resolves to a `Client` or an `io::Error`.
|
||||
pub type ConnectFuture<Req, Resp, E> =
|
||||
futures::Flatten<futures::MapErr<futures::Oneshot<io::Result<Client<Req, Resp, E>>>,
|
||||
fn(futures::Canceled) -> io::Error>>;
|
||||
|
||||
impl<Req, Resp, E> ClientExt for Client<Req, Resp, E>
|
||||
where Req: Serialize + Send + 'static,
|
||||
Resp: Deserialize + Send + 'static,
|
||||
E: Deserialize + Send + 'static
|
||||
{
|
||||
type ConnectFut = ConnectFuture<Req, Resp, E>;
|
||||
|
||||
fn connect(addr: SocketAddr, options: Options) -> Self::ConnectFut {
|
||||
// we need to do this for tls because we need to avoid moving the entire `Options`
|
||||
// struct into the `setup` closure, since `Reactor` is not `Send`.
|
||||
#[cfg(feature = "tls")]
|
||||
let mut options = options;
|
||||
#[cfg(feature = "tls")]
|
||||
let tls_ctx = options.tls_ctx.take();
|
||||
|
||||
let max_payload_size = options.max_payload_size;
|
||||
|
||||
let connect = move |handle: &reactor::Handle| {
|
||||
let handle2 = handle.clone();
|
||||
TcpStream::connect(&addr, handle)
|
||||
.and_then(move |socket| {
|
||||
// TODO(https://github.com/tokio-rs/tokio-proto/issues/132): move this into the
|
||||
// ServerProto impl
|
||||
#[cfg(feature = "tls")]
|
||||
match tls_ctx {
|
||||
Some(tls_ctx) => {
|
||||
future::Either::A(tls_ctx
|
||||
.tls_connector
|
||||
.connect_async(&tls_ctx.domain, socket)
|
||||
.map(StreamType::Tls)
|
||||
.map_err(native_to_io))
|
||||
}
|
||||
None => future::Either::B(future::ok(StreamType::Tcp(socket))),
|
||||
}
|
||||
#[cfg(not(feature = "tls"))]
|
||||
future::ok(StreamType::Tcp(socket))
|
||||
})
|
||||
.map(move |tcp| Client::bind(&handle2, tcp, max_payload_size))
|
||||
};
|
||||
let (tx, rx) = futures::oneshot();
|
||||
let setup = move |handle: &reactor::Handle| {
|
||||
connect(handle).then(move |result| {
|
||||
// If send fails it means the client no longer cared about connecting.
|
||||
let _ = tx.send(result);
|
||||
Ok(())
|
||||
})
|
||||
};
|
||||
|
||||
match options.reactor {
|
||||
Some(Reactor::Handle(handle)) => {
|
||||
handle.spawn(setup(&handle));
|
||||
}
|
||||
Some(Reactor::Remote(remote)) => {
|
||||
remote.spawn(setup);
|
||||
}
|
||||
None => {
|
||||
REMOTE.spawn(setup);
|
||||
}
|
||||
}
|
||||
fn panic(canceled: futures::Canceled) -> io::Error {
|
||||
unreachable!(canceled)
|
||||
}
|
||||
rx.map_err(panic as _).flatten()
|
||||
}
|
||||
}
|
||||
|
||||
type ResponseFuture<Req, Resp, E> =
|
||||
futures::AndThen<futures::MapErr<
|
||||
futures::Map<<ClientService<StreamType, Proto<Req, Response<Resp, E>>> as Service>::Future,
|
||||
fn(WireResponse<Resp, E>) -> Result<Resp, ::Error<E>>>,
|
||||
fn(io::Error) -> ::Error<E>>,
|
||||
Result<Resp, ::Error<E>>,
|
||||
fn(Result<Resp, ::Error<E>>) -> Result<Resp, ::Error<E>>>;
|
||||
|
||||
type WireResponse<R, E> = Result<Response<R, E>, bincode::Error>;
|
||||
4
src/future/mod.rs
Normal file
4
src/future/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
/// Provides the base client stubs used by the service macro.
|
||||
pub mod client;
|
||||
/// Provides the base server boilerplate used by service implementations.
|
||||
pub mod server;
|
||||
76
src/future/server/connection.rs
Normal file
76
src/future/server/connection.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
use futures::unsync;
|
||||
use std::io;
|
||||
use tokio_service::{NewService, Service};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Action {
|
||||
Increment,
|
||||
Decrement,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Tracker {
|
||||
pub tx: unsync::mpsc::UnboundedSender<Action>,
|
||||
}
|
||||
|
||||
impl Tracker {
|
||||
pub fn pair() -> (Self, unsync::mpsc::UnboundedReceiver<Action>) {
|
||||
let (tx, rx) = unsync::mpsc::unbounded();
|
||||
(Self { tx }, rx)
|
||||
}
|
||||
|
||||
pub fn increment(&self) {
|
||||
let _ = self.tx.send(Action::Increment);
|
||||
}
|
||||
|
||||
pub fn decrement(&self) {
|
||||
debug!("Closing connection");
|
||||
let _ = self.tx.send(Action::Decrement);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TrackingService<S> {
|
||||
pub service: S,
|
||||
pub tracker: Tracker,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TrackingNewService<S> {
|
||||
pub new_service: S,
|
||||
pub connection_tracker: Tracker,
|
||||
}
|
||||
|
||||
impl<S: Service> Service for TrackingService<S> {
|
||||
type Request = S::Request;
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Future = S::Future;
|
||||
|
||||
fn call(&self, req: Self::Request) -> Self::Future {
|
||||
trace!("Calling service.");
|
||||
self.service.call(req)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Drop for TrackingService<S> {
|
||||
fn drop(&mut self) {
|
||||
debug!("Dropping ConnnectionTrackingService.");
|
||||
self.tracker.decrement();
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: NewService> NewService for TrackingNewService<S> {
|
||||
type Request = S::Request;
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Instance = TrackingService<S::Instance>;
|
||||
|
||||
fn new_service(&self) -> io::Result<Self::Instance> {
|
||||
self.connection_tracker.increment();
|
||||
Ok(TrackingService {
|
||||
service: self.new_service.new_service()?,
|
||||
tracker: self.connection_tracker.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
454
src/future/server/mod.rs
Normal file
454
src/future/server/mod.rs
Normal file
@@ -0,0 +1,454 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
use {bincode, net2};
|
||||
use errors::WireError;
|
||||
use futures::{Async, Future, Poll, Stream, future as futures};
|
||||
use protocol::Proto;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use stream_type::StreamType;
|
||||
use tokio_core::net::{Incoming, TcpListener, TcpStream};
|
||||
use tokio_core::reactor;
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
use tokio_proto::BindServer;
|
||||
use tokio_service::NewService;
|
||||
|
||||
mod connection;
|
||||
mod shutdown;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "tls")] {
|
||||
use native_tls::{self, TlsAcceptor};
|
||||
use tokio_tls::{AcceptAsync, TlsAcceptorExt, TlsStream};
|
||||
use errors::native_to_io;
|
||||
} else {}
|
||||
}
|
||||
|
||||
pub use self::shutdown::{Shutdown, ShutdownFuture};
|
||||
|
||||
/// A handle to a bound server.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Handle {
|
||||
addr: SocketAddr,
|
||||
shutdown: Shutdown,
|
||||
}
|
||||
|
||||
impl Handle {
|
||||
/// Returns a hook for shutting down the server.
|
||||
pub fn shutdown(&self) -> &Shutdown {
|
||||
&self.shutdown
|
||||
}
|
||||
|
||||
/// The socket address the server is bound to.
|
||||
pub fn addr(&self) -> SocketAddr {
|
||||
self.addr
|
||||
}
|
||||
}
|
||||
|
||||
enum Acceptor {
|
||||
Tcp,
|
||||
#[cfg(feature = "tls")]
|
||||
Tls(TlsAcceptor),
|
||||
}
|
||||
|
||||
struct Accept {
|
||||
#[cfg(feature = "tls")]
|
||||
inner: futures::Either<futures::MapErr<futures::Map<AcceptAsync<TcpStream>,
|
||||
fn(TlsStream<TcpStream>) -> StreamType>,
|
||||
fn(native_tls::Error) -> io::Error>,
|
||||
futures::FutureResult<StreamType, io::Error>>,
|
||||
#[cfg(not(feature = "tls"))]
|
||||
inner: futures::FutureResult<StreamType, io::Error>,
|
||||
}
|
||||
|
||||
impl Future for Accept {
|
||||
type Item = StreamType;
|
||||
type Error = io::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
self.inner.poll()
|
||||
}
|
||||
}
|
||||
|
||||
impl Acceptor {
|
||||
// TODO(https://github.com/tokio-rs/tokio-proto/issues/132): move this into the ServerProto impl
|
||||
#[cfg(feature = "tls")]
|
||||
fn accept(&self, socket: TcpStream) -> Accept {
|
||||
Accept {
|
||||
inner: match *self {
|
||||
Acceptor::Tls(ref tls_acceptor) => {
|
||||
futures::Either::A(tls_acceptor
|
||||
.accept_async(socket)
|
||||
.map(StreamType::Tls as _)
|
||||
.map_err(native_to_io))
|
||||
}
|
||||
Acceptor::Tcp => futures::Either::B(futures::ok(StreamType::Tcp(socket))),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tls"))]
|
||||
fn accept(&self, socket: TcpStream) -> Accept {
|
||||
Accept { inner: futures::ok(StreamType::Tcp(socket)) }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
impl From<Options> for Acceptor {
|
||||
fn from(options: Options) -> Self {
|
||||
match options.tls_acceptor {
|
||||
Some(tls_acceptor) => Acceptor::Tls(tls_acceptor),
|
||||
None => Acceptor::Tcp,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tls"))]
|
||||
impl From<Options> for Acceptor {
|
||||
fn from(_: Options) -> Self {
|
||||
Acceptor::Tcp
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Acceptor {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::Acceptor::*;
|
||||
#[cfg(feature = "tls")]
|
||||
const TLS: &'static &'static str = &"TlsAcceptor { .. }";
|
||||
|
||||
match *self {
|
||||
Tcp => fmt.debug_tuple("Acceptor::Tcp").finish(),
|
||||
#[cfg(feature = "tls")]
|
||||
Tls(_) => fmt.debug_tuple("Acceptlr::Tls").field(TLS).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Accept {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt.debug_struct("Accept").finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct AcceptStream<S> {
|
||||
stream: S,
|
||||
acceptor: Acceptor,
|
||||
future: Option<Accept>,
|
||||
}
|
||||
|
||||
impl<S> Stream for AcceptStream<S>
|
||||
where S: Stream<Item = (TcpStream, SocketAddr), Error = io::Error>
|
||||
{
|
||||
type Item = <Accept as Future>::Item;
|
||||
type Error = io::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, io::Error> {
|
||||
if self.future.is_none() {
|
||||
let stream = match try_ready!(self.stream.poll()) {
|
||||
None => return Ok(Async::Ready(None)),
|
||||
Some((stream, _)) => stream,
|
||||
};
|
||||
self.future = Some(self.acceptor.accept(stream));
|
||||
}
|
||||
assert!(self.future.is_some());
|
||||
match self.future.as_mut().unwrap().poll() {
|
||||
Ok(Async::Ready(e)) => {
|
||||
self.future = None;
|
||||
Ok(Async::Ready(Some(e)))
|
||||
}
|
||||
Err(e) => {
|
||||
self.future = None;
|
||||
Err(e)
|
||||
}
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional options to configure how the server operates.
|
||||
pub struct Options {
|
||||
/// Max packet size in bytes.
|
||||
max_payload_size: u64,
|
||||
#[cfg(feature = "tls")]
|
||||
tls_acceptor: Option<TlsAcceptor>,
|
||||
}
|
||||
|
||||
impl Default for Options {
|
||||
#[cfg(not(feature = "tls"))]
|
||||
fn default() -> Self {
|
||||
Options { max_payload_size: 2 << 20 }
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
fn default() -> Self {
|
||||
Options {
|
||||
max_payload_size: 2 << 20,
|
||||
tls_acceptor: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Options {
|
||||
/// Set the max payload size in bytes. The default is 2 << 20 (2 MiB).
|
||||
pub fn max_payload_size(mut self, bytes: u64) -> Self {
|
||||
self.max_payload_size = bytes;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `TlsAcceptor`
|
||||
#[cfg(feature = "tls")]
|
||||
pub fn tls(mut self, tls_acceptor: TlsAcceptor) -> Self {
|
||||
self.tls_acceptor = Some(tls_acceptor);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Options {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
#[cfg(feature = "tls")]
|
||||
const SOME: &'static &'static str = &"Some(_)";
|
||||
#[cfg(feature = "tls")]
|
||||
const NONE: &'static &'static str = &"None";
|
||||
|
||||
let mut debug_struct = fmt.debug_struct("Options");
|
||||
#[cfg(feature = "tls")]
|
||||
debug_struct.field("tls_acceptor",
|
||||
if self.tls_acceptor.is_some() {
|
||||
SOME
|
||||
} else {
|
||||
NONE
|
||||
});
|
||||
debug_struct.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// A message from server to client.
|
||||
#[doc(hidden)]
|
||||
pub type Response<T, E> = Result<T, WireError<E>>;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn listen<S, Req, Resp, E>(new_service: S,
|
||||
addr: SocketAddr,
|
||||
handle: &reactor::Handle,
|
||||
options: Options)
|
||||
-> io::Result<(Handle, Listen<S, Req, Resp, E>)>
|
||||
where S: NewService<Request = Result<Req, bincode::Error>,
|
||||
Response = Response<Resp, E>,
|
||||
Error = io::Error> + 'static,
|
||||
Req: Deserialize + 'static,
|
||||
Resp: Serialize + 'static,
|
||||
E: Serialize + 'static
|
||||
{
|
||||
let (addr, shutdown, server) = listen_with(new_service,
|
||||
addr,
|
||||
handle,
|
||||
options.max_payload_size,
|
||||
Acceptor::from(options))?;
|
||||
Ok((Handle {
|
||||
addr: addr,
|
||||
shutdown: shutdown,
|
||||
},
|
||||
server))
|
||||
}
|
||||
|
||||
/// Spawns a service that binds to the given address using the given handle.
|
||||
fn listen_with<S, Req, Resp, E>(new_service: S,
|
||||
addr: SocketAddr,
|
||||
handle: &reactor::Handle,
|
||||
max_payload_size: u64,
|
||||
acceptor: Acceptor)
|
||||
-> io::Result<(SocketAddr, Shutdown, Listen<S, Req, Resp, E>)>
|
||||
where S: NewService<Request = Result<Req, bincode::Error>,
|
||||
Response = Response<Resp, E>,
|
||||
Error = io::Error> + 'static,
|
||||
Req: Deserialize + 'static,
|
||||
Resp: Serialize + 'static,
|
||||
E: Serialize + 'static
|
||||
{
|
||||
let listener = listener(&addr, handle)?;
|
||||
let addr = listener.local_addr()?;
|
||||
debug!("Listening on {}.", addr);
|
||||
|
||||
let handle = handle.clone();
|
||||
let (connection_tracker, shutdown, shutdown_future) = shutdown::Watcher::triple();
|
||||
let server = BindStream {
|
||||
handle: handle,
|
||||
new_service: connection::TrackingNewService {
|
||||
connection_tracker: connection_tracker,
|
||||
new_service: new_service,
|
||||
},
|
||||
stream: AcceptStream {
|
||||
stream: listener.incoming(),
|
||||
acceptor: acceptor,
|
||||
future: None,
|
||||
},
|
||||
max_payload_size: max_payload_size,
|
||||
};
|
||||
|
||||
let server = AlwaysOkUnit(server.select(shutdown_future));
|
||||
Ok((addr, shutdown, Listen { inner: server }))
|
||||
}
|
||||
|
||||
fn listener(addr: &SocketAddr, handle: &reactor::Handle) -> io::Result<TcpListener> {
|
||||
const PENDING_CONNECTION_BACKLOG: i32 = 1024;
|
||||
|
||||
let builder = match *addr {
|
||||
SocketAddr::V4(_) => net2::TcpBuilder::new_v4(),
|
||||
SocketAddr::V6(_) => net2::TcpBuilder::new_v6(),
|
||||
}?;
|
||||
configure_tcp(&builder)?;
|
||||
builder.reuse_address(true)?;
|
||||
builder
|
||||
.bind(addr)?
|
||||
.listen(PENDING_CONNECTION_BACKLOG)
|
||||
.and_then(|l| TcpListener::from_listener(l, addr, handle))
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn configure_tcp(tcp: &net2::TcpBuilder) -> io::Result<()> {
|
||||
use net2::unix::UnixTcpBuilderExt;
|
||||
tcp.reuse_port(true)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn configure_tcp(_tcp: &net2::TcpBuilder) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct BindStream<S, St> {
|
||||
handle: reactor::Handle,
|
||||
new_service: connection::TrackingNewService<S>,
|
||||
stream: St,
|
||||
max_payload_size: u64,
|
||||
}
|
||||
|
||||
impl<S, St> fmt::Debug for BindStream<S, St>
|
||||
where S: fmt::Debug,
|
||||
St: fmt::Debug
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
const HANDLE: &'static &'static str = &"Handle { .. }";
|
||||
f.debug_struct("BindStream")
|
||||
.field("handle", HANDLE)
|
||||
.field("new_service", &self.new_service)
|
||||
.field("stream", &self.stream)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Req, Resp, E, I, St> BindStream<S, St>
|
||||
where S: NewService<Request = Result<Req, bincode::Error>,
|
||||
Response = Response<Resp, E>,
|
||||
Error = io::Error> + 'static,
|
||||
Req: Deserialize + 'static,
|
||||
Resp: Serialize + 'static,
|
||||
E: Serialize + 'static,
|
||||
I: AsyncRead + AsyncWrite + 'static,
|
||||
St: Stream<Item = I, Error = io::Error>
|
||||
{
|
||||
fn bind_each(&mut self) -> Poll<(), io::Error> {
|
||||
loop {
|
||||
match try!(self.stream.poll()) {
|
||||
Async::Ready(Some(socket)) => {
|
||||
Proto::new(self.max_payload_size).bind_server(&self.handle,
|
||||
socket,
|
||||
self.new_service.new_service()?);
|
||||
}
|
||||
Async::Ready(None) => return Ok(Async::Ready(())),
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Req, Resp, E, I, St> Future for BindStream<S, St>
|
||||
where S: NewService<Request = Result<Req, bincode::Error>,
|
||||
Response = Response<Resp, E>,
|
||||
Error = io::Error> + 'static,
|
||||
Req: Deserialize + 'static,
|
||||
Resp: Serialize + 'static,
|
||||
E: Serialize + 'static,
|
||||
I: AsyncRead + AsyncWrite + 'static,
|
||||
St: Stream<Item = I, Error = io::Error>
|
||||
{
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self.bind_each() {
|
||||
Ok(Async::Ready(())) => Ok(Async::Ready(())),
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Err(e) => {
|
||||
error!("While processing incoming connections: {}", e);
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The future representing a running server.
|
||||
#[doc(hidden)]
|
||||
pub struct Listen<S, Req, Resp, E>
|
||||
where S: NewService<Request = Result<Req, bincode::Error>,
|
||||
Response = Response<Resp, E>,
|
||||
Error = io::Error> + 'static,
|
||||
Req: Deserialize + 'static,
|
||||
Resp: Serialize + 'static,
|
||||
E: Serialize + 'static
|
||||
{
|
||||
inner: AlwaysOkUnit<futures::Select<BindStream<S, AcceptStream<Incoming>>, shutdown::Watcher>>,
|
||||
}
|
||||
|
||||
impl<S, Req, Resp, E> Future for Listen<S, Req, Resp, E>
|
||||
where S: NewService<Request = Result<Req, bincode::Error>,
|
||||
Response = Response<Resp, E>,
|
||||
Error = io::Error> + 'static,
|
||||
Req: Deserialize + 'static,
|
||||
Resp: Serialize + 'static,
|
||||
E: Serialize + 'static
|
||||
{
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<(), ()> {
|
||||
self.inner.poll()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Req, Resp, E> fmt::Debug for Listen<S, Req, Resp, E>
|
||||
where S: NewService<Request = Result<Req, bincode::Error>,
|
||||
Response = Response<Resp, E>,
|
||||
Error = io::Error> + 'static,
|
||||
Req: Deserialize + 'static,
|
||||
Resp: Serialize + 'static,
|
||||
E: Serialize + 'static
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
f.debug_struct("Listen").finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct AlwaysOkUnit<F>(F);
|
||||
|
||||
impl<F> Future for AlwaysOkUnit<F>
|
||||
where F: Future
|
||||
{
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<(), ()> {
|
||||
match self.0.poll() {
|
||||
Ok(Async::Ready(_)) |
|
||||
Err(_) => Ok(Async::Ready(())),
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
}
|
||||
180
src/future/server/shutdown.rs
Normal file
180
src/future/server/shutdown.rs
Normal file
@@ -0,0 +1,180 @@
|
||||
|
||||
|
||||
use super::{AlwaysOkUnit, connection};
|
||||
use futures::{Async, Future, Poll, Stream, future as futures, stream};
|
||||
use futures::sync::{mpsc, oneshot};
|
||||
use futures::unsync;
|
||||
|
||||
/// A hook to shut down a running server.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Shutdown {
|
||||
tx: mpsc::UnboundedSender<oneshot::Sender<()>>,
|
||||
}
|
||||
|
||||
/// A future that resolves when server shutdown completes.
|
||||
#[derive(Debug)]
|
||||
pub struct ShutdownFuture {
|
||||
inner: futures::Either<futures::FutureResult<(), ()>, AlwaysOkUnit<oneshot::Receiver<()>>>,
|
||||
}
|
||||
|
||||
impl Future for ShutdownFuture {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<(), ()> {
|
||||
self.inner.poll()
|
||||
}
|
||||
}
|
||||
|
||||
impl Shutdown {
|
||||
/// Initiates an orderly server shutdown.
|
||||
///
|
||||
/// First, the server enters lameduck mode, in which
|
||||
/// existing connections are honored but no new connections are accepted. Then, once all
|
||||
/// connections are closed, it initates total shutdown.
|
||||
///
|
||||
/// The returned future resolves when the server is completely shut down.
|
||||
pub fn shutdown(&self) -> ShutdownFuture {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let inner = if let Err(_) = self.tx.send(tx) {
|
||||
trace!("Server already initiated shutdown.");
|
||||
futures::Either::A(futures::ok(()))
|
||||
} else {
|
||||
futures::Either::B(AlwaysOkUnit(rx))
|
||||
};
|
||||
ShutdownFuture { inner: inner }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Watcher {
|
||||
shutdown_rx: stream::Take<mpsc::UnboundedReceiver<oneshot::Sender<()>>>,
|
||||
connections: unsync::mpsc::UnboundedReceiver<connection::Action>,
|
||||
queued_error: Option<()>,
|
||||
shutdown: Option<oneshot::Sender<()>>,
|
||||
done: bool,
|
||||
num_connections: u64,
|
||||
}
|
||||
|
||||
impl Watcher {
|
||||
pub fn triple() -> (connection::Tracker, Shutdown, Self) {
|
||||
let (connection_tx, connections) = connection::Tracker::pair();
|
||||
let (shutdown_tx, shutdown_rx) = mpsc::unbounded();
|
||||
(connection_tx,
|
||||
Shutdown { tx: shutdown_tx },
|
||||
Watcher {
|
||||
shutdown_rx: shutdown_rx.take(1),
|
||||
connections: connections,
|
||||
queued_error: None,
|
||||
shutdown: None,
|
||||
done: false,
|
||||
num_connections: 0,
|
||||
})
|
||||
}
|
||||
|
||||
fn process_connection(&mut self, action: connection::Action) {
|
||||
match action {
|
||||
connection::Action::Increment => self.num_connections += 1,
|
||||
connection::Action::Decrement => self.num_connections -= 1,
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_shutdown_requests(&mut self) -> Poll<Option<()>, ()> {
|
||||
Ok(Async::Ready(match try_ready!(self.shutdown_rx.poll()) {
|
||||
Some(tx) => {
|
||||
debug!("Received shutdown request.");
|
||||
self.shutdown = Some(tx);
|
||||
Some(())
|
||||
}
|
||||
None => None,
|
||||
}))
|
||||
}
|
||||
|
||||
fn poll_connections(&mut self) -> Poll<Option<()>, ()> {
|
||||
Ok(Async::Ready(match try_ready!(self.connections.poll()) {
|
||||
Some(action) => {
|
||||
self.process_connection(action);
|
||||
Some(())
|
||||
}
|
||||
None => None,
|
||||
}))
|
||||
}
|
||||
|
||||
fn poll_shutdown_requests_and_connections(&mut self) -> Poll<Option<()>, ()> {
|
||||
if let Some(e) = self.queued_error.take() {
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
match try!(self.poll_shutdown_requests()) {
|
||||
Async::NotReady => {
|
||||
match try_ready!(self.poll_connections()) {
|
||||
Some(()) => Ok(Async::Ready(Some(()))),
|
||||
None => Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
Async::Ready(None) => {
|
||||
match try_ready!(self.poll_connections()) {
|
||||
Some(()) => Ok(Async::Ready(Some(()))),
|
||||
None => Ok(Async::Ready(None)),
|
||||
}
|
||||
}
|
||||
Async::Ready(Some(())) => {
|
||||
match self.poll_connections() {
|
||||
Err(e) => {
|
||||
self.queued_error = Some(e);
|
||||
Ok(Async::Ready(Some(())))
|
||||
}
|
||||
Ok(Async::NotReady) | Ok(Async::Ready(None)) | Ok(Async::Ready(Some(()))) => {
|
||||
Ok(Async::Ready(Some(())))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn should_continue(&mut self) -> bool {
|
||||
match self.shutdown.take() {
|
||||
Some(shutdown) => {
|
||||
debug!("Lameduck mode: {} open connections", self.num_connections);
|
||||
if self.num_connections == 0 {
|
||||
debug!("Shutting down.");
|
||||
// Not required for the shutdown future to be waited on, so this
|
||||
// can fail (which is fine).
|
||||
let _ = shutdown.send(());
|
||||
false
|
||||
} else {
|
||||
self.shutdown = Some(shutdown);
|
||||
true
|
||||
}
|
||||
}
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn process_request(&mut self) -> Poll<Option<()>, ()> {
|
||||
if self.done {
|
||||
return Ok(Async::Ready(None));
|
||||
}
|
||||
if self.should_continue() {
|
||||
self.poll_shutdown_requests_and_connections()
|
||||
} else {
|
||||
self.done = true;
|
||||
Ok(Async::Ready(None))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for Watcher {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<(), ()> {
|
||||
loop {
|
||||
match try!(self.process_request()) {
|
||||
Async::Ready(Some(())) => continue,
|
||||
Async::Ready(None) => return Ok(Async::Ready(())),
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
211
src/lib.rs
Normal file
211
src/lib.rs
Normal file
@@ -0,0 +1,211 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
//! tarpc is an RPC framework for rust with a focus on ease of use. Defining a
|
||||
//! service can be done in just a few lines of code, and most of the boilerplate of
|
||||
//! writing a server is taken care of for you.
|
||||
//!
|
||||
//! ## What is an RPC framework?
|
||||
//! "RPC" stands for "Remote Procedure Call," a function call where the work of
|
||||
//! producing the return value is being done somewhere else. When an rpc function is
|
||||
//! invoked, behind the scenes the function contacts some other process somewhere
|
||||
//! and asks them to evaluate the function instead. The original function then
|
||||
//! returns the value produced by the other process.
|
||||
//!
|
||||
//! RPC frameworks are a fundamental building block of most microservices-oriented
|
||||
//! architectures. Two well-known ones are [gRPC](http://www.grpc.io) and
|
||||
//! [Cap'n Proto](https://capnproto.org/).
|
||||
//!
|
||||
//! tarpc differentiates itself from other RPC frameworks by defining the schema in code,
|
||||
//! rather than in a separate language such as .proto. This means there's no separate compilation
|
||||
//! process, and no cognitive context switching between different languages. Additionally, it
|
||||
//! works with the community-backed library serde: any serde-serializable type can be used as
|
||||
//! arguments to tarpc fns.
|
||||
//!
|
||||
//! Example usage:
|
||||
//!
|
||||
//! ```
|
||||
//! #![feature(plugin)]
|
||||
//! #![plugin(tarpc_plugins)]
|
||||
//!
|
||||
//! #[macro_use]
|
||||
//! extern crate tarpc;
|
||||
//! extern crate tokio_core;
|
||||
//!
|
||||
//! use tarpc::sync::{client, server};
|
||||
//! use tarpc::sync::client::ClientExt;
|
||||
//! use tarpc::util::Never;
|
||||
//! use tokio_core::reactor;
|
||||
//! use std::sync::mpsc;
|
||||
//! use std::thread;
|
||||
//!
|
||||
//! service! {
|
||||
//! rpc hello(name: String) -> String;
|
||||
//! }
|
||||
//!
|
||||
//! #[derive(Clone)]
|
||||
//! struct HelloServer;
|
||||
//!
|
||||
//! impl SyncService for HelloServer {
|
||||
//! fn hello(&self, name: String) -> Result<String, Never> {
|
||||
//! Ok(format!("Hello, {}!", name))
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let (tx, rx) = mpsc::channel();
|
||||
//! thread::spawn(move || {
|
||||
//! let mut handle = HelloServer.listen("localhost:10000",
|
||||
//! server::Options::default()).unwrap();
|
||||
//! tx.send(handle.addr()).unwrap();
|
||||
//! handle.run();
|
||||
//! });
|
||||
//! let addr = rx.recv().unwrap();
|
||||
//! let client = SyncClient::connect(addr, client::Options::default()).unwrap();
|
||||
//! println!("{}", client.hello("Mom".to_string()).unwrap());
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Example usage with TLS:
|
||||
//!
|
||||
//! ```no-run
|
||||
//! #![feature(plugin)]
|
||||
//! #![plugin(tarpc_plugins)]
|
||||
//!
|
||||
//! #[macro_use]
|
||||
//! extern crate tarpc;
|
||||
//!
|
||||
//! use tarpc::sync::{client, server};
|
||||
//! use tarpc::sync::client::ClientExt;
|
||||
//! use tarpc::tls;
|
||||
//! use tarpc::util::Never;
|
||||
//! use tarpc::native_tls::{TlsAcceptor, Pkcs12};
|
||||
//!
|
||||
//! service! {
|
||||
//! rpc hello(name: String) -> String;
|
||||
//! }
|
||||
//!
|
||||
//! #[derive(Clone)]
|
||||
//! struct HelloServer;
|
||||
//!
|
||||
//! impl SyncService for HelloServer {
|
||||
//! fn hello(&self, name: String) -> Result<String, Never> {
|
||||
//! Ok(format!("Hello, {}!", name))
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! fn get_acceptor() -> TlsAcceptor {
|
||||
//! let buf = include_bytes!("test/identity.p12");
|
||||
//! let pkcs12 = Pkcs12::from_der(buf, "password").unwrap();
|
||||
//! TlsAcceptor::builder(pkcs12).unwrap().build().unwrap()
|
||||
//! }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let addr = "localhost:10000";
|
||||
//! let acceptor = get_acceptor();
|
||||
//! let _server = HelloServer.listen(addr, server::Options::default().tls(acceptor));
|
||||
//! let client = SyncClient::connect(addr,
|
||||
//! client::Options::default()
|
||||
//! .tls(tls::client::Context::new("foobar.com").unwrap()))
|
||||
//! .unwrap();
|
||||
//! println!("{}", client.hello("Mom".to_string()).unwrap());
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
#![deny(missing_docs, missing_debug_implementations)]
|
||||
#![feature(never_type)]
|
||||
#![cfg_attr(test, feature(plugin))]
|
||||
#![cfg_attr(test, plugin(tarpc_plugins))]
|
||||
|
||||
extern crate byteorder;
|
||||
extern crate bytes;
|
||||
#[macro_use]
|
||||
extern crate cfg_if;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate net2;
|
||||
extern crate num_cpus;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate thread_pool;
|
||||
extern crate tokio_io;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub extern crate bincode;
|
||||
#[doc(hidden)]
|
||||
#[macro_use]
|
||||
pub extern crate futures;
|
||||
#[doc(hidden)]
|
||||
pub extern crate serde;
|
||||
#[doc(hidden)]
|
||||
pub extern crate tokio_core;
|
||||
#[doc(hidden)]
|
||||
pub extern crate tokio_proto;
|
||||
#[doc(hidden)]
|
||||
pub extern crate tokio_service;
|
||||
|
||||
pub use errors::Error;
|
||||
#[doc(hidden)]
|
||||
pub use errors::WireError;
|
||||
|
||||
/// Provides some utility error types, as well as a trait for spawning futures on the default event
|
||||
/// loop.
|
||||
pub mod util;
|
||||
|
||||
/// Provides the macro used for constructing rpc services and client stubs.
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
/// Synchronous version of the tarpc API
|
||||
pub mod sync;
|
||||
/// Futures-based version of the tarpc API.
|
||||
pub mod future;
|
||||
/// TLS-specific functionality.
|
||||
#[cfg(feature = "tls")]
|
||||
pub mod tls;
|
||||
/// Provides implementations of `ClientProto` and `ServerProto` that implement the tarpc protocol.
|
||||
/// The tarpc protocol is a length-delimited, bincode-serialized payload.
|
||||
mod protocol;
|
||||
/// Provides a few different error types.
|
||||
mod errors;
|
||||
/// Provides an abstraction over TLS and TCP streams.
|
||||
mod stream_type;
|
||||
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use tokio_core::reactor;
|
||||
|
||||
lazy_static! {
|
||||
/// The `Remote` for the default reactor core.
|
||||
static ref REMOTE: reactor::Remote = {
|
||||
spawn_core()
|
||||
};
|
||||
}
|
||||
|
||||
/// Spawns a `reactor::Core` running forever on a new thread.
|
||||
fn spawn_core() -> reactor::Remote {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
thread::spawn(move || {
|
||||
let mut core = reactor::Core::new().unwrap();
|
||||
tx.send(core.handle().remote().clone()).unwrap();
|
||||
|
||||
// Run forever
|
||||
core.run(futures::empty::<(), !>()).unwrap();
|
||||
});
|
||||
rx.recv().unwrap()
|
||||
}
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "tls")] {
|
||||
extern crate tokio_tls;
|
||||
extern crate native_tls as native_tls_inner;
|
||||
|
||||
/// Re-exported TLS-related types from the `native_tls` crate.
|
||||
pub mod native_tls {
|
||||
pub use native_tls_inner::{Error, Pkcs12, TlsAcceptor, TlsConnector};
|
||||
}
|
||||
} else {}
|
||||
}
|
||||
1332
src/macros.rs
Normal file
1332
src/macros.rs
Normal file
File diff suppressed because it is too large
Load Diff
21
src/plugins/Cargo.toml
Normal file
21
src/plugins/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "tarpc-plugins"
|
||||
version = "0.1.1"
|
||||
authors = ["Adam Wright <adam.austin.wright@gmail.com>", "Tim Kuehn <timothy.j.kuehn@gmail.com>"]
|
||||
license = "MIT"
|
||||
documentation = "https://docs.rs/tarpc"
|
||||
homepage = "https://github.com/google/tarpc"
|
||||
repository = "https://github.com/google/tarpc"
|
||||
keywords = ["rpc", "network", "server", "api", "tls"]
|
||||
categories = ["asynchronous", "network-programming"]
|
||||
readme = "../../README.md"
|
||||
description = "Plugins for tarpc, an RPC framework for Rust with a focus on ease of use."
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "google/tarpc" }
|
||||
|
||||
[dependencies]
|
||||
itertools = "0.5"
|
||||
|
||||
[lib]
|
||||
plugin = true
|
||||
198
src/plugins/src/lib.rs
Normal file
198
src/plugins/src/lib.rs
Normal file
@@ -0,0 +1,198 @@
|
||||
#![feature(plugin_registrar, rustc_private)]
|
||||
|
||||
extern crate itertools;
|
||||
extern crate rustc_plugin;
|
||||
extern crate syntax;
|
||||
|
||||
use itertools::Itertools;
|
||||
use rustc_plugin::Registry;
|
||||
use syntax::ast::{self, Ident, TraitRef, Ty, TyKind};
|
||||
use syntax::ext::base::{ExtCtxt, MacResult, DummyResult, MacEager};
|
||||
use syntax::ext::quote::rt::Span;
|
||||
use syntax::parse::{self, token, str_lit, PResult};
|
||||
use syntax::parse::parser::{Parser, PathStyle};
|
||||
use syntax::symbol::Symbol;
|
||||
use syntax::ptr::P;
|
||||
use syntax::tokenstream::{TokenTree, TokenStream};
|
||||
use syntax::util::small_vector::SmallVector;
|
||||
|
||||
fn snake_to_camel(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) -> Box<MacResult + 'static> {
|
||||
let mut parser = parse::new_parser_from_tts(cx.parse_sess(), tts.into());
|
||||
// The `expand_expr` method is called so that any macro calls in the
|
||||
// parsed expression are expanded.
|
||||
|
||||
let mut item = match parser.parse_trait_item(&mut false) {
|
||||
Ok(s) => s,
|
||||
Err(mut diagnostic) => {
|
||||
diagnostic.emit();
|
||||
return DummyResult::any(sp);
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(mut diagnostic) = parser.expect(&token::Eof) {
|
||||
diagnostic.emit();
|
||||
return DummyResult::any(sp);
|
||||
}
|
||||
|
||||
let old_ident = convert(&mut item.ident);
|
||||
|
||||
// As far as I know, it's not possible in macro_rules! to reference an $ident in a doc string,
|
||||
// so this is the hacky workaround.
|
||||
//
|
||||
// This code looks intimidating, but it's just iterating through the trait item's attributes
|
||||
// copying non-doc attributes, and modifying doc attributes such that replacing any {} in the
|
||||
// doc string instead holds the original, snake_case ident.
|
||||
let attrs: Vec<_> = item.attrs
|
||||
.drain(..)
|
||||
.map(|mut attr| {
|
||||
if !attr.is_sugared_doc {
|
||||
return attr;
|
||||
}
|
||||
|
||||
// Getting at the underlying doc comment is surprisingly painful.
|
||||
// The call-chain goes something like:
|
||||
//
|
||||
// - https://github.com/rust-lang/rust/blob/9c15de4fd59bee290848b5443c7e194fd5afb02c/src/libsyntax/attr.rs#L283
|
||||
// - https://github.com/rust-lang/rust/blob/9c15de4fd59bee290848b5443c7e194fd5afb02c/src/libsyntax/attr.rs#L1067
|
||||
// - https://github.com/rust-lang/rust/blob/9c15de4fd59bee290848b5443c7e194fd5afb02c/src/libsyntax/attr.rs#L1196
|
||||
// - https://github.com/rust-lang/rust/blob/9c15de4fd59bee290848b5443c7e194fd5afb02c/src/libsyntax/parse/mod.rs#L399
|
||||
// - https://github.com/rust-lang/rust/blob/9c15de4fd59bee290848b5443c7e194fd5afb02c/src/libsyntax/parse/mod.rs#L268
|
||||
//
|
||||
// Note that a docstring (i.e., something with is_sugared_doc) *always* has exactly two
|
||||
// tokens: an Eq followed by a Literal, where the Literal contains a Str_. We therefore
|
||||
// match against that, modifying the inner Str with our modified Symbol.
|
||||
let mut tokens = attr.tokens.clone().into_trees();
|
||||
if let Some(tt @ TokenTree::Token(_, token::Eq)) = tokens.next() {
|
||||
let mut docstr = tokens.next().expect("Docstrings must have literal docstring");
|
||||
if let TokenTree::Token(_, token::Literal(token::Str_(ref mut doc), _)) = docstr {
|
||||
*doc = Symbol::intern(&str_lit(&doc.as_str()).replace("{}", &old_ident));
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
attr.tokens = TokenStream::concat(vec![tt.into(), docstr.into()]);
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
attr
|
||||
})
|
||||
.collect();
|
||||
item.attrs.extend(attrs.into_iter());
|
||||
|
||||
MacEager::trait_items(SmallVector::one(item))
|
||||
}
|
||||
|
||||
fn impl_snake_to_camel(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) -> Box<MacResult + 'static> {
|
||||
let mut parser = parse::new_parser_from_tts(cx.parse_sess(), tts.into());
|
||||
// The `expand_expr` method is called so that any macro calls in the
|
||||
// parsed expression are expanded.
|
||||
|
||||
let mut item = match parser.parse_impl_item(&mut false) {
|
||||
Ok(s) => s,
|
||||
Err(mut diagnostic) => {
|
||||
diagnostic.emit();
|
||||
return DummyResult::any(sp);
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(mut diagnostic) = parser.expect(&token::Eof) {
|
||||
diagnostic.emit();
|
||||
return DummyResult::any(sp);
|
||||
}
|
||||
|
||||
convert(&mut item.ident);
|
||||
MacEager::impl_items(SmallVector::one(item))
|
||||
}
|
||||
|
||||
fn ty_snake_to_camel(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) -> Box<MacResult + 'static> {
|
||||
let mut parser = parse::new_parser_from_tts(cx.parse_sess(), tts.into());
|
||||
// The `expand_expr` method is called so that any macro calls in the
|
||||
// parsed expression are expanded.
|
||||
|
||||
let mut path = match parser.parse_path(PathStyle::Type) {
|
||||
Ok(s) => s,
|
||||
Err(mut diagnostic) => {
|
||||
diagnostic.emit();
|
||||
return DummyResult::any(sp);
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(mut diagnostic) = parser.expect(&token::Eof) {
|
||||
diagnostic.emit();
|
||||
return DummyResult::any(sp);
|
||||
}
|
||||
|
||||
// Only capitalize the final segment
|
||||
convert(&mut path.segments
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.identifier);
|
||||
MacEager::ty(P(Ty {
|
||||
id: ast::DUMMY_NODE_ID,
|
||||
node: TyKind::Path(None, path),
|
||||
span: sp,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Converts an ident in-place to CamelCase and returns the previous ident.
|
||||
fn convert(ident: &mut Ident) -> String {
|
||||
let ident_str = ident.to_string();
|
||||
let mut camel_ty = String::new();
|
||||
|
||||
{
|
||||
// Find the first non-underscore and add it capitalized.
|
||||
let mut chars = ident_str.chars();
|
||||
|
||||
// Find the first non-underscore char, uppercase it, and append it.
|
||||
// Guaranteed to succeed because all idents must have at least one non-underscore char.
|
||||
camel_ty.extend(chars.find(|&c| c != '_').unwrap().to_uppercase());
|
||||
|
||||
// When we find an underscore, we remove it and capitalize the next char. To do this,
|
||||
// we need to ensure the next char is not another underscore.
|
||||
let mut chars = chars.coalesce(|c1, c2| {
|
||||
if c1 == '_' && c2 == '_' {
|
||||
Ok(c1)
|
||||
} else {
|
||||
Err((c1, c2))
|
||||
}
|
||||
});
|
||||
|
||||
while let Some(c) = chars.next() {
|
||||
if c != '_' {
|
||||
camel_ty.push(c);
|
||||
} else {
|
||||
if let Some(c) = chars.next() {
|
||||
camel_ty.extend(c.to_uppercase());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The Fut suffix is hardcoded right now; this macro isn't really meant to be general-purpose.
|
||||
camel_ty.push_str("Fut");
|
||||
|
||||
*ident = Ident::with_empty_ctxt(Symbol::intern(&camel_ty));
|
||||
ident_str
|
||||
}
|
||||
|
||||
trait ParseTraitRef {
|
||||
fn parse_trait_ref(&mut self) -> PResult<TraitRef>;
|
||||
}
|
||||
|
||||
impl<'a> ParseTraitRef for Parser<'a> {
|
||||
/// Parse a::B<String,i32>
|
||||
fn parse_trait_ref(&mut self) -> PResult<TraitRef> {
|
||||
Ok(TraitRef {
|
||||
path: self.parse_path(PathStyle::Type)?,
|
||||
ref_id: ast::DUMMY_NODE_ID,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[plugin_registrar]
|
||||
#[doc(hidden)]
|
||||
pub fn plugin_registrar(reg: &mut Registry) {
|
||||
reg.register_macro("snake_to_camel", snake_to_camel);
|
||||
reg.register_macro("impl_snake_to_camel", impl_snake_to_camel);
|
||||
reg.register_macro("ty_snake_to_camel", ty_snake_to_camel);
|
||||
}
|
||||
222
src/protocol.rs
Normal file
222
src/protocol.rs
Normal file
@@ -0,0 +1,222 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
use bincode::{self, Infinite};
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use bytes::BytesMut;
|
||||
use bytes::buf::BufMut;
|
||||
use serde;
|
||||
use std::io::{self, Cursor};
|
||||
use std::marker::PhantomData;
|
||||
use std::mem;
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
use tokio_io::codec::{Encoder, Decoder, Framed};
|
||||
use tokio_proto::multiplex::{ClientProto, ServerProto};
|
||||
use tokio_proto::streaming::multiplex::RequestId;
|
||||
|
||||
// `Encode` is the type that `Codec` encodes. `Decode` is the type it decodes.
|
||||
#[derive(Debug)]
|
||||
pub struct Codec<Encode, Decode> {
|
||||
max_payload_size: u64,
|
||||
state: CodecState,
|
||||
_phantom_data: PhantomData<(Encode, Decode)>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum CodecState {
|
||||
Id,
|
||||
Len { id: u64 },
|
||||
Payload { id: u64, len: u64 },
|
||||
}
|
||||
|
||||
impl<Encode, Decode> Codec<Encode, Decode> {
|
||||
fn new(max_payload_size: u64) -> Self {
|
||||
Codec {
|
||||
max_payload_size: max_payload_size,
|
||||
state: CodecState::Id,
|
||||
_phantom_data: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn too_big(payload_size: u64, max_payload_size: u64) -> io::Error {
|
||||
warn!("Not sending too-big packet of size {} (max is {})",
|
||||
payload_size,
|
||||
max_payload_size);
|
||||
io::Error::new(io::ErrorKind::InvalidData,
|
||||
format!("Maximum payload size is {} bytes but got a payload of {}",
|
||||
max_payload_size,
|
||||
payload_size))
|
||||
}
|
||||
|
||||
impl<Encode, Decode> Encoder for Codec<Encode, Decode>
|
||||
where Encode: serde::Serialize,
|
||||
Decode: serde::Deserialize
|
||||
{
|
||||
type Item = (RequestId, Encode);
|
||||
type Error = io::Error;
|
||||
|
||||
fn encode(&mut self, (id, message): Self::Item, buf: &mut BytesMut) -> io::Result<()> {
|
||||
let payload_size = bincode::serialized_size(&message);
|
||||
if payload_size > self.max_payload_size {
|
||||
return Err(too_big(payload_size, self.max_payload_size));
|
||||
}
|
||||
let message_size = 2 * mem::size_of::<u64>() + payload_size as usize;
|
||||
buf.reserve(message_size);
|
||||
buf.put_u64::<BigEndian>(id);
|
||||
trace!("Encoded request id = {} as {:?}", id, buf);
|
||||
buf.put_u64::<BigEndian>(payload_size);
|
||||
bincode::serialize_into(&mut buf.writer(), &message, Infinite)
|
||||
.map_err(|serialize_err| io::Error::new(io::ErrorKind::Other, serialize_err))?;
|
||||
trace!("Encoded buffer: {:?}", buf);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Encode, Decode> Decoder for Codec<Encode, Decode>
|
||||
where Decode: serde::Deserialize
|
||||
{
|
||||
type Item = (RequestId, Result<Decode, bincode::Error>);
|
||||
type Error = io::Error;
|
||||
|
||||
fn decode(&mut self, buf: &mut BytesMut) -> io::Result<Option<Self::Item>> {
|
||||
use self::CodecState::*;
|
||||
trace!("Codec::decode: {:?}", buf);
|
||||
|
||||
loop {
|
||||
match self.state {
|
||||
Id if buf.len() < mem::size_of::<u64>() => {
|
||||
trace!("--> Buf len is {}; waiting for 8 to parse id.", buf.len());
|
||||
return Ok(None);
|
||||
}
|
||||
Id => {
|
||||
let mut id_buf = buf.split_to(mem::size_of::<u64>());
|
||||
let id = Cursor::new(&mut id_buf).read_u64::<BigEndian>()?;
|
||||
trace!("--> Parsed id = {} from {:?}", id, id_buf);
|
||||
self.state = Len { id: id };
|
||||
}
|
||||
Len { .. } if buf.len() < mem::size_of::<u64>() => {
|
||||
trace!("--> Buf len is {}; waiting for 8 to parse packet length.",
|
||||
buf.len());
|
||||
return Ok(None);
|
||||
}
|
||||
Len { id } => {
|
||||
let len_buf = buf.split_to(mem::size_of::<u64>());
|
||||
let len = Cursor::new(len_buf).read_u64::<BigEndian>()?;
|
||||
trace!("--> Parsed payload length = {}, remaining buffer length = {}",
|
||||
len,
|
||||
buf.len());
|
||||
if len > self.max_payload_size {
|
||||
return Err(too_big(len, self.max_payload_size));
|
||||
}
|
||||
self.state = Payload { id: id, len: len };
|
||||
}
|
||||
Payload { len, .. } if buf.len() < len as usize => {
|
||||
trace!("--> Buf len is {}; waiting for {} to parse payload.",
|
||||
buf.len(),
|
||||
len);
|
||||
return Ok(None);
|
||||
}
|
||||
Payload { id, len } => {
|
||||
let payload = buf.split_to(len as usize);
|
||||
let result = bincode::deserialize_from(&mut Cursor::new(payload), Infinite);
|
||||
// Reset the state machine because, either way, we're done processing this
|
||||
// message.
|
||||
self.state = Id;
|
||||
|
||||
return Ok(Some((id, result)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the `multiplex::ServerProto` trait.
|
||||
#[derive(Debug)]
|
||||
pub struct Proto<Encode, Decode> {
|
||||
max_payload_size: u64,
|
||||
_phantom_data: PhantomData<(Encode, Decode)>,
|
||||
}
|
||||
|
||||
impl<Encode, Decode> Proto<Encode, Decode> {
|
||||
/// Returns a new `Proto`.
|
||||
pub fn new(max_payload_size: u64) -> Self {
|
||||
Proto {
|
||||
max_payload_size: max_payload_size,
|
||||
_phantom_data: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Encode, Decode> ServerProto<T> for Proto<Encode, Decode>
|
||||
where T: AsyncRead + AsyncWrite + 'static,
|
||||
Encode: serde::Serialize + 'static,
|
||||
Decode: serde::Deserialize + 'static
|
||||
{
|
||||
type Response = Encode;
|
||||
type Request = Result<Decode, bincode::Error>;
|
||||
type Transport = Framed<T, Codec<Encode, Decode>>;
|
||||
type BindTransport = Result<Self::Transport, io::Error>;
|
||||
|
||||
fn bind_transport(&self, io: T) -> Self::BindTransport {
|
||||
Ok(io.framed(Codec::new(self.max_payload_size)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Encode, Decode> ClientProto<T> for Proto<Encode, Decode>
|
||||
where T: AsyncRead + AsyncWrite + 'static,
|
||||
Encode: serde::Serialize + 'static,
|
||||
Decode: serde::Deserialize + 'static
|
||||
{
|
||||
type Response = Result<Decode, bincode::Error>;
|
||||
type Request = Encode;
|
||||
type Transport = Framed<T, Codec<Encode, Decode>>;
|
||||
type BindTransport = Result<Self::Transport, io::Error>;
|
||||
|
||||
fn bind_transport(&self, io: T) -> Self::BindTransport {
|
||||
Ok(io.framed(Codec::new(self.max_payload_size)))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize() {
|
||||
const MSG: (u64, (char, char, char)) = (4, ('a', 'b', 'c'));
|
||||
let mut buf = BytesMut::with_capacity(10);
|
||||
|
||||
// Serialize twice to check for idempotence.
|
||||
for _ in 0..2 {
|
||||
let mut codec: Codec<(char, char, char), (char, char, char)> = Codec::new(2_000_000);
|
||||
codec.encode(MSG, &mut buf).unwrap();
|
||||
let actual: Result<Option<(u64, Result<(char, char, char), bincode::Error>)>,
|
||||
io::Error> = codec.decode(&mut buf);
|
||||
|
||||
match actual {
|
||||
Ok(Some((id, ref v))) if id == MSG.0 && *v.as_ref().unwrap() == MSG.1 => {}
|
||||
bad => panic!("Expected {:?}, but got {:?}", Some(MSG), bad),
|
||||
}
|
||||
|
||||
assert!(buf.is_empty(), "Expected empty buf but got {:?}", buf);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_big() {
|
||||
let mut codec: Codec<Vec<u8>, Vec<u8>> = Codec::new(24);
|
||||
|
||||
let mut buf = BytesMut::with_capacity(40);
|
||||
assert_eq!(codec
|
||||
.encode((0, vec![0; 24]), &mut buf)
|
||||
.err()
|
||||
.unwrap()
|
||||
.kind(),
|
||||
io::ErrorKind::InvalidData);
|
||||
|
||||
// Header
|
||||
buf.put_slice(&mut [0u8; 8]);
|
||||
// Len
|
||||
buf.put_slice(&mut [0u8, 0, 0, 0, 0, 0, 0, 25]);
|
||||
assert_eq!(codec.decode(&mut buf).err().unwrap().kind(),
|
||||
io::ErrorKind::InvalidData);
|
||||
}
|
||||
94
src/stream_type.rs
Normal file
94
src/stream_type.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
use bytes::{Buf, BufMut};
|
||||
use futures::Poll;
|
||||
use std::io;
|
||||
use tokio_core::net::TcpStream;
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
#[cfg(feature = "tls")]
|
||||
use tokio_tls::TlsStream;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum StreamType {
|
||||
Tcp(TcpStream),
|
||||
#[cfg(feature = "tls")]
|
||||
Tls(TlsStream<TcpStream>),
|
||||
}
|
||||
|
||||
impl From<TcpStream> for StreamType {
|
||||
fn from(stream: TcpStream) -> Self {
|
||||
StreamType::Tcp(stream)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
impl From<TlsStream<TcpStream>> for StreamType {
|
||||
fn from(stream: TlsStream<TcpStream>) -> Self {
|
||||
StreamType::Tls(stream)
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Read for StreamType {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
match *self {
|
||||
StreamType::Tcp(ref mut stream) => stream.read(buf),
|
||||
#[cfg(feature = "tls")]
|
||||
StreamType::Tls(ref mut stream) => stream.read(buf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Write for StreamType {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
match *self {
|
||||
StreamType::Tcp(ref mut stream) => stream.write(buf),
|
||||
#[cfg(feature = "tls")]
|
||||
StreamType::Tls(ref mut stream) => stream.write(buf),
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
match *self {
|
||||
StreamType::Tcp(ref mut stream) => stream.flush(),
|
||||
#[cfg(feature = "tls")]
|
||||
StreamType::Tls(ref mut stream) => stream.flush(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for StreamType {
|
||||
// By overriding this fn, `StreamType` is obliged to never read the uninitialized buffer.
|
||||
// Most sane implementations would never have a reason to, and `StreamType` does not, so
|
||||
// this is safe.
|
||||
unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool {
|
||||
match *self {
|
||||
StreamType::Tcp(ref stream) => stream.prepare_uninitialized_buffer(buf),
|
||||
#[cfg(feature = "tls")]
|
||||
StreamType::Tls(ref stream) => stream.prepare_uninitialized_buffer(buf),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_buf<B: BufMut>(&mut self, buf: &mut B) -> Poll<usize, io::Error> {
|
||||
match *self {
|
||||
StreamType::Tcp(ref mut stream) => stream.read_buf(buf),
|
||||
#[cfg(feature = "tls")]
|
||||
StreamType::Tls(ref mut stream) => stream.read_buf(buf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for StreamType {
|
||||
fn shutdown(&mut self) -> Poll<(), io::Error> {
|
||||
match *self {
|
||||
StreamType::Tcp(ref mut stream) => stream.shutdown(),
|
||||
#[cfg(feature = "tls")]
|
||||
StreamType::Tls(ref mut stream) => stream.shutdown(),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_buf<B: Buf>(&mut self, buf: &mut B) -> Poll<usize, io::Error> {
|
||||
match *self {
|
||||
StreamType::Tcp(ref mut stream) => stream.write_buf(buf),
|
||||
#[cfg(feature = "tls")]
|
||||
StreamType::Tls(ref mut stream) => stream.write_buf(buf),
|
||||
}
|
||||
}
|
||||
}
|
||||
242
src/sync/client.rs
Normal file
242
src/sync/client.rs
Normal file
@@ -0,0 +1,242 @@
|
||||
use future::client::{Client as FutureClient, ClientExt as FutureClientExt,
|
||||
Options as FutureOptions};
|
||||
/// Exposes a trait for connecting synchronously to servers.
|
||||
use futures::{Future, Stream};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::net::{SocketAddr, ToSocketAddrs};
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
#[cfg(feature = "tls")]
|
||||
use tls::client::Context;
|
||||
use tokio_core::reactor;
|
||||
use tokio_proto::util::client_proxy::{ClientProxy, Receiver, pair};
|
||||
use tokio_service::Service;
|
||||
use util::FirstSocketAddr;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct Client<Req, Resp, E> {
|
||||
proxy: ClientProxy<Req, Resp, ::Error<E>>,
|
||||
}
|
||||
|
||||
impl<Req, Resp, E> Clone for Client<Req, Resp, E> {
|
||||
fn clone(&self) -> Self {
|
||||
Client { proxy: self.proxy.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<Req, Resp, E> fmt::Debug for Client<Req, Resp, E> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
const PROXY: &'static &'static str = &"ClientProxy { .. }";
|
||||
f.debug_struct("Client").field("proxy", PROXY).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Req, Resp, E> Client<Req, Resp, E>
|
||||
where Req: Serialize + Send + 'static,
|
||||
Resp: Deserialize + Send + 'static,
|
||||
E: Deserialize + Send + 'static
|
||||
{
|
||||
/// Drives an RPC call for the given request.
|
||||
pub fn call(&self, request: Req) -> Result<Resp, ::Error<E>> {
|
||||
// Must call wait here to block on the response.
|
||||
// The request handler relies on this fact to safely unwrap the
|
||||
// oneshot send.
|
||||
self.proxy.call(request).wait()
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional options to configure how the client connects and operates.
|
||||
pub struct Options {
|
||||
/// Max packet size in bytes.
|
||||
max_payload_size: u64,
|
||||
#[cfg(feature = "tls")]
|
||||
tls_ctx: Option<Context>,
|
||||
}
|
||||
|
||||
impl Default for Options {
|
||||
#[cfg(not(feature = "tls"))]
|
||||
fn default() -> Self {
|
||||
Options { max_payload_size: 2_000_000 }
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
fn default() -> Self {
|
||||
Options {
|
||||
max_payload_size: 2_000_000,
|
||||
tls_ctx: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Options {
|
||||
/// Set the max payload size in bytes. The default is 2,000,000 (2 MB).
|
||||
pub fn max_payload_size(mut self, bytes: u64) -> Self {
|
||||
self.max_payload_size = bytes;
|
||||
self
|
||||
}
|
||||
|
||||
/// Connect using the given `Context`
|
||||
#[cfg(feature = "tls")]
|
||||
pub fn tls(mut self, ctx: Context) -> Self {
|
||||
self.tls_ctx = Some(ctx);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Options {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
#[cfg(feature = "tls")]
|
||||
const SOME: &'static &'static str = &"Some(_)";
|
||||
#[cfg(feature = "tls")]
|
||||
const NONE: &'static &'static str = &"None";
|
||||
let mut f = f.debug_struct("Options");
|
||||
#[cfg(feature = "tls")]
|
||||
f.field("tls_ctx", if self.tls_ctx.is_some() { SOME } else { NONE });
|
||||
f.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<FutureOptions> for (reactor::Handle, Options) {
|
||||
#[cfg(feature = "tls")]
|
||||
fn into(self) -> FutureOptions {
|
||||
let (handle, options) = self;
|
||||
let mut opts = FutureOptions::default().handle(handle);
|
||||
if let Some(tls_ctx) = options.tls_ctx {
|
||||
opts = opts.tls(tls_ctx);
|
||||
}
|
||||
opts
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tls"))]
|
||||
fn into(self) -> FutureOptions {
|
||||
let (handle, _) = self;
|
||||
FutureOptions::default().handle(handle)
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension methods for Clients.
|
||||
pub trait ClientExt: Sized {
|
||||
/// Connects to a server located at the given address.
|
||||
fn connect<A>(addr: A, options: Options) -> io::Result<Self> where A: ToSocketAddrs;
|
||||
}
|
||||
|
||||
impl<Req, Resp, E> ClientExt for Client<Req, Resp, E>
|
||||
where Req: Serialize + Send + 'static,
|
||||
Resp: Deserialize + Send + 'static,
|
||||
E: Deserialize + Send + 'static
|
||||
{
|
||||
fn connect<A>(addr: A, options: Options) -> io::Result<Self>
|
||||
where A: ToSocketAddrs
|
||||
{
|
||||
let addr = addr.try_first_socket_addr()?;
|
||||
let (connect_tx, connect_rx) = mpsc::channel();
|
||||
thread::spawn(move || match RequestHandler::connect(addr, options) {
|
||||
Ok((proxy, mut handler)) => {
|
||||
connect_tx.send(Ok(proxy)).unwrap();
|
||||
handler.handle_requests();
|
||||
}
|
||||
Err(e) => connect_tx.send(Err(e)).unwrap(),
|
||||
});
|
||||
Ok(connect_rx.recv().unwrap()?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Forwards incoming requests of type `Req`
|
||||
/// with expected response `Result<Resp, ::Error<E>>`
|
||||
/// to service `S`.
|
||||
struct RequestHandler<Req, Resp, E, S> {
|
||||
reactor: reactor::Core,
|
||||
client: S,
|
||||
requests: Receiver<Req, Resp, ::Error<E>>,
|
||||
}
|
||||
|
||||
impl<Req, Resp, E> RequestHandler<Req, Resp, E, FutureClient<Req, Resp, E>>
|
||||
where Req: Serialize + Send + 'static,
|
||||
Resp: Deserialize + Send + 'static,
|
||||
E: Deserialize + Send + 'static
|
||||
{
|
||||
/// Creates a new `RequestHandler` by connecting a `FutureClient` to the given address
|
||||
/// using the given options.
|
||||
fn connect(addr: SocketAddr, options: Options) -> io::Result<(Client<Req, Resp, E>, Self)> {
|
||||
let mut reactor = reactor::Core::new()?;
|
||||
let options = (reactor.handle(), options).into();
|
||||
let client = reactor.run(FutureClient::connect(addr, options))?;
|
||||
let (proxy, requests) = pair();
|
||||
Ok((Client { proxy },
|
||||
RequestHandler {
|
||||
reactor,
|
||||
client,
|
||||
requests,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Req, Resp, E, S> RequestHandler<Req, Resp, E, S>
|
||||
where Req: Serialize + 'static,
|
||||
Resp: Deserialize + 'static,
|
||||
E: Deserialize + 'static,
|
||||
S: Service<Request = Req, Response = Resp, Error = ::Error<E>>,
|
||||
S::Future: 'static
|
||||
{
|
||||
fn handle_requests(&mut self) {
|
||||
let RequestHandler {
|
||||
ref mut reactor,
|
||||
ref mut requests,
|
||||
ref mut client,
|
||||
} = *self;
|
||||
let handle = reactor.handle();
|
||||
let requests =
|
||||
requests
|
||||
.map(|result| {
|
||||
match result {
|
||||
Ok(req) => req,
|
||||
// The ClientProxy never sends Err currently
|
||||
Err(e) => panic!("Unimplemented error handling in RequestHandler: {}", e),
|
||||
}
|
||||
})
|
||||
.for_each(|(request, response_tx)| {
|
||||
let request = client.call(request)
|
||||
.then(move |response| {
|
||||
// Safe to unwrap because clients always block on the response future.
|
||||
response_tx.send(response)
|
||||
.map_err(|_| ())
|
||||
.expect("Client should block on response");
|
||||
Ok(())
|
||||
});
|
||||
handle.spawn(request);
|
||||
Ok(())
|
||||
});
|
||||
reactor.run(requests).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handle_requests() {
|
||||
use futures::future;
|
||||
|
||||
struct Client;
|
||||
impl Service for Client {
|
||||
type Request = i32;
|
||||
type Response = i32;
|
||||
type Error = ::Error<()>;
|
||||
type Future = future::FutureResult<i32, ::Error<()>>;
|
||||
|
||||
fn call(&self, req: i32) -> Self::Future {
|
||||
future::ok(req)
|
||||
}
|
||||
}
|
||||
|
||||
let (request, requests) = ::futures::sync::mpsc::unbounded();
|
||||
let reactor = reactor::Core::new().unwrap();
|
||||
let client = Client;
|
||||
let mut request_handler = RequestHandler {
|
||||
reactor,
|
||||
client,
|
||||
requests,
|
||||
};
|
||||
// Test that `handle_requests` returns when all request senders are dropped.
|
||||
drop(request);
|
||||
request_handler.handle_requests();
|
||||
}
|
||||
4
src/sync/mod.rs
Normal file
4
src/sync/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
/// Provides the base client stubs used by the service macro.
|
||||
pub mod client;
|
||||
/// Provides the base server boilerplate used by service implementations.
|
||||
pub mod server;
|
||||
238
src/sync/server.rs
Normal file
238
src/sync/server.rs
Normal file
@@ -0,0 +1,238 @@
|
||||
use {bincode, future, num_cpus};
|
||||
use future::server::{Response, Shutdown};
|
||||
use futures::{Future, future as futures};
|
||||
use futures::sync::oneshot;
|
||||
#[cfg(feature = "tls")]
|
||||
use native_tls_inner::TlsAcceptor;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use std::time::Duration;
|
||||
use std::usize;
|
||||
use thread_pool::{self, Sender, Task, ThreadPool};
|
||||
use tokio_core::reactor;
|
||||
use tokio_service::{NewService, Service};
|
||||
|
||||
/// Additional options to configure how the server operates.
|
||||
#[derive(Debug)]
|
||||
pub struct Options {
|
||||
thread_pool: thread_pool::Builder,
|
||||
opts: future::server::Options,
|
||||
}
|
||||
|
||||
impl Default for Options {
|
||||
fn default() -> Self {
|
||||
let num_cpus = num_cpus::get();
|
||||
Options {
|
||||
thread_pool: thread_pool::Builder::new()
|
||||
.keep_alive(Duration::from_secs(60))
|
||||
.max_pool_size(num_cpus * 100)
|
||||
.core_pool_size(num_cpus)
|
||||
.work_queue_capacity(usize::MAX)
|
||||
.name_prefix("request-thread-"),
|
||||
opts: future::server::Options::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Options {
|
||||
/// Set the max payload size in bytes. The default is 2,000,000 (2 MB).
|
||||
pub fn max_payload_size(mut self, bytes: u64) -> Self {
|
||||
self.opts = self.opts.max_payload_size(bytes);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the thread pool builder to use when creating the server's thread pool.
|
||||
pub fn thread_pool(mut self, builder: thread_pool::Builder) -> Self {
|
||||
self.thread_pool = builder;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the `TlsAcceptor`
|
||||
#[cfg(feature = "tls")]
|
||||
pub fn tls(mut self, tls_acceptor: TlsAcceptor) -> Self {
|
||||
self.opts = self.opts.tls(tls_acceptor);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to a bound server. Must be run to start serving requests.
|
||||
#[must_use = "A server does nothing until `run` is called."]
|
||||
pub struct Handle {
|
||||
reactor: reactor::Core,
|
||||
handle: future::server::Handle,
|
||||
server: Box<Future<Item = (), Error = ()>>,
|
||||
}
|
||||
|
||||
impl Handle {
|
||||
/// Runs the server on the current thread, blocking indefinitely.
|
||||
pub fn run(mut self) {
|
||||
trace!("Running...");
|
||||
match self.reactor.run(self.server) {
|
||||
Ok(()) => debug!("Server successfully shutdown."),
|
||||
Err(()) => debug!("Server shutdown due to error."),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a hook for shutting down the server.
|
||||
pub fn shutdown(&self) -> Shutdown {
|
||||
self.handle.shutdown().clone()
|
||||
}
|
||||
|
||||
/// The socket address the server is bound to.
|
||||
pub fn addr(&self) -> SocketAddr {
|
||||
self.handle.addr()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Handle {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
const CORE: &'static &'static str = &"Core { .. }";
|
||||
const SERVER: &'static &'static str = &"Box<Future<Item = (), Error = ()>>";
|
||||
|
||||
f.debug_struct("Handle")
|
||||
.field("reactor", CORE)
|
||||
.field("handle", &self.handle)
|
||||
.field("server", SERVER)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn listen<S, Req, Resp, E>(new_service: S,
|
||||
addr: SocketAddr,
|
||||
options: Options)
|
||||
-> io::Result<Handle>
|
||||
where S: NewService<Request = Result<Req, bincode::Error>,
|
||||
Response = Response<Resp, E>,
|
||||
Error = io::Error> + 'static,
|
||||
<S::Instance as Service>::Future: Send + 'static,
|
||||
S::Response: Send,
|
||||
S::Error: Send,
|
||||
Req: Deserialize + 'static,
|
||||
Resp: Serialize + 'static,
|
||||
E: Serialize + 'static
|
||||
{
|
||||
let new_service = NewThreadService::new(new_service, options.thread_pool);
|
||||
let reactor = reactor::Core::new()?;
|
||||
let (handle, server) =
|
||||
future::server::listen(new_service, addr, &reactor.handle(), options.opts)?;
|
||||
let server = Box::new(server);
|
||||
Ok(Handle {
|
||||
reactor: reactor,
|
||||
handle: handle,
|
||||
server: server,
|
||||
})
|
||||
}
|
||||
|
||||
/// A service that uses a thread pool.
|
||||
struct NewThreadService<S>
|
||||
where S: NewService
|
||||
{
|
||||
new_service: S,
|
||||
sender: Sender<ServiceTask<<S::Instance as Service>::Future>>,
|
||||
_pool: ThreadPool<ServiceTask<<S::Instance as Service>::Future>>,
|
||||
}
|
||||
|
||||
/// A service that runs by executing request handlers in a thread pool.
|
||||
struct ThreadService<S>
|
||||
where S: Service
|
||||
{
|
||||
service: S,
|
||||
sender: Sender<ServiceTask<S::Future>>,
|
||||
}
|
||||
|
||||
/// A task that handles a single request.
|
||||
struct ServiceTask<F>
|
||||
where F: Future
|
||||
{
|
||||
future: F,
|
||||
tx: oneshot::Sender<Result<F::Item, F::Error>>,
|
||||
}
|
||||
|
||||
impl<S> NewThreadService<S>
|
||||
where S: NewService,
|
||||
<S::Instance as Service>::Future: Send + 'static,
|
||||
S::Response: Send,
|
||||
S::Error: Send
|
||||
{
|
||||
/// Create a NewThreadService by wrapping another service.
|
||||
fn new(new_service: S, pool: thread_pool::Builder) -> Self {
|
||||
let (sender, _pool) = pool.build();
|
||||
NewThreadService {
|
||||
new_service,
|
||||
sender,
|
||||
_pool,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> NewService for NewThreadService<S>
|
||||
where S: NewService,
|
||||
<S::Instance as Service>::Future: Send + 'static,
|
||||
S::Response: Send,
|
||||
S::Error: Send
|
||||
{
|
||||
type Request = S::Request;
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Instance = ThreadService<S::Instance>;
|
||||
|
||||
fn new_service(&self) -> io::Result<Self::Instance> {
|
||||
Ok(ThreadService {
|
||||
service: self.new_service.new_service()?,
|
||||
sender: self.sender.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> Task for ServiceTask<F>
|
||||
where F: Future + Send + 'static,
|
||||
F::Item: Send,
|
||||
F::Error: Send
|
||||
{
|
||||
fn run(self) {
|
||||
// Don't care if sending fails. It just means the request is no longer
|
||||
// being handled (I think).
|
||||
let _ = self.tx.send(self.future.wait());
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Service for ThreadService<S>
|
||||
where S: Service,
|
||||
S::Future: Send + 'static,
|
||||
S::Response: Send,
|
||||
S::Error: Send
|
||||
{
|
||||
type Request = S::Request;
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Future = futures::AndThen<futures::MapErr<oneshot::Receiver<Result<Self::Response,
|
||||
Self::Error>>,
|
||||
fn(oneshot::Canceled) -> Self::Error>,
|
||||
Result<Self::Response, Self::Error>,
|
||||
fn(Result<Self::Response, Self::Error>)
|
||||
-> Result<Self::Response, Self::Error>>;
|
||||
|
||||
fn call(&self, request: Self::Request) -> Self::Future {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.sender
|
||||
.send(ServiceTask {
|
||||
future: self.service.call(request),
|
||||
tx: tx,
|
||||
})
|
||||
.unwrap();
|
||||
rx.map_err(unreachable as _).and_then(ident)
|
||||
}
|
||||
}
|
||||
|
||||
fn unreachable<T, U>(t: T) -> U
|
||||
where T: fmt::Display
|
||||
{
|
||||
unreachable!(t)
|
||||
}
|
||||
|
||||
fn ident<T>(t: T) -> T {
|
||||
t
|
||||
}
|
||||
50
src/tls.rs
Normal file
50
src/tls.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
/// TLS-specific functionality for clients.
|
||||
pub mod client {
|
||||
use native_tls::{Error, TlsConnector};
|
||||
use std::fmt;
|
||||
|
||||
/// TLS context for client
|
||||
pub struct Context {
|
||||
/// Domain to connect to
|
||||
pub domain: String,
|
||||
/// TLS connector
|
||||
pub tls_connector: TlsConnector,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Try to construct a new `Context`.
|
||||
///
|
||||
/// The provided domain will be used for both
|
||||
/// [SNI](https://en.wikipedia.org/wiki/Server_Name_Indication) and certificate hostname
|
||||
/// validation.
|
||||
pub fn new<S: Into<String>>(domain: S) -> Result<Self, Error> {
|
||||
Ok(Context {
|
||||
domain: domain.into(),
|
||||
tls_connector: TlsConnector::builder()?.build()?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Construct a new `Context` using the provided domain and `TlsConnector`
|
||||
///
|
||||
/// The domain will be used for both
|
||||
/// [SNI](https://en.wikipedia.org/wiki/Server_Name_Indication) and certificate hostname
|
||||
/// validation.
|
||||
pub fn from_connector<S: Into<String>>(domain: S, tls_connector: TlsConnector) -> Self {
|
||||
Context {
|
||||
domain: domain.into(),
|
||||
tls_connector: tls_connector,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Context {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
const TLS_CONNECTOR: &'static &'static str = &"TlsConnector { .. }";
|
||||
f.debug_struct("Context")
|
||||
.field("domain", &self.domain)
|
||||
.field("tls_connector", TLS_CONNECTOR)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
176
src/util.rs
Normal file
176
src/util.rs
Normal file
@@ -0,0 +1,176 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
use futures::{Future, IntoFuture, Poll};
|
||||
use futures::stream::Stream;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::{fmt, io, mem};
|
||||
use std::error::Error;
|
||||
use std::net::{SocketAddr, ToSocketAddrs};
|
||||
|
||||
/// A bottom type that impls `Error`, `Serialize`, and `Deserialize`. It is impossible to
|
||||
/// instantiate this type.
|
||||
#[allow(unreachable_code)]
|
||||
pub struct Never(!);
|
||||
|
||||
impl fmt::Debug for Never {
|
||||
fn fmt(&self, _: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for Never {
|
||||
fn description(&self) -> &str {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Never {
|
||||
fn fmt(&self, _: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for Never {
|
||||
type Item = Never;
|
||||
type Error = Never;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for Never {
|
||||
type Item = Never;
|
||||
type Error = Never;
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Never {
|
||||
fn serialize<S>(&self, _: S) -> Result<S::Ok, S::Error>
|
||||
where S: Serializer
|
||||
{
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
// Please don't try to deserialize this. :(
|
||||
impl Deserialize for Never {
|
||||
fn deserialize<D>(_: D) -> Result<Self, D::Error>
|
||||
where D: Deserializer
|
||||
{
|
||||
panic!("Never cannot be instantiated!");
|
||||
}
|
||||
}
|
||||
|
||||
/// A `String` that impls `std::error::Error`. Useful for quick-and-dirty error propagation.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Message(pub String);
|
||||
|
||||
impl Error for Message {
|
||||
fn description(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Message {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt::Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Into<String>> From<S> for Message {
|
||||
fn from(s: S) -> Self {
|
||||
Message(s.into())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Provides a utility method for more ergonomically parsing a `SocketAddr` when only one is
|
||||
/// needed.
|
||||
pub trait FirstSocketAddr: ToSocketAddrs {
|
||||
/// Returns the first resolved `SocketAddr`, if one exists.
|
||||
fn try_first_socket_addr(&self) -> io::Result<SocketAddr> {
|
||||
if let Some(a) = self.to_socket_addrs()?.next() {
|
||||
Ok(a)
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::AddrNotAvailable,
|
||||
"`ToSocketAddrs::to_socket_addrs` returned an empty iterator."))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the first resolved `SocketAddr` or panics otherwise.
|
||||
fn first_socket_addr(&self) -> SocketAddr {
|
||||
self.try_first_socket_addr().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: ToSocketAddrs> FirstSocketAddr for A {}
|
||||
|
||||
/// Creates a new future which will eventually be the same as the one created
|
||||
/// by calling the closure provided with the arguments provided.
|
||||
///
|
||||
/// The provided closure is only run once the future has a callback scheduled
|
||||
/// on it, otherwise the callback never runs. Once run, however, this future is
|
||||
/// the same as the one the closure creates.
|
||||
pub fn lazy<F, A, R>(f: F, args: A) -> Lazy<F, A, R>
|
||||
where F: FnOnce(A) -> R,
|
||||
R: IntoFuture
|
||||
{
|
||||
Lazy { inner: _Lazy::First(f, args) }
|
||||
}
|
||||
|
||||
/// A future which defers creation of the actual future until a callback is
|
||||
/// scheduled.
|
||||
///
|
||||
/// This is created by the `lazy` function.
|
||||
#[derive(Debug)]
|
||||
#[must_use = "futures do nothing unless polled"]
|
||||
pub struct Lazy<F, A, R: IntoFuture> {
|
||||
inner: _Lazy<F, A, R::Future>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum _Lazy<F, A, R> {
|
||||
First(F, A),
|
||||
Second(R),
|
||||
Moved,
|
||||
}
|
||||
|
||||
impl<F, A, R> Lazy<F, A, R>
|
||||
where F: FnOnce(A) -> R,
|
||||
R: IntoFuture
|
||||
{
|
||||
fn get(&mut self) -> &mut R::Future {
|
||||
match self.inner {
|
||||
_Lazy::First(..) => {}
|
||||
_Lazy::Second(ref mut f) => return f,
|
||||
_Lazy::Moved => panic!(), // can only happen if `f()` panics
|
||||
}
|
||||
match mem::replace(&mut self.inner, _Lazy::Moved) {
|
||||
_Lazy::First(f, args) => self.inner = _Lazy::Second(f(args).into_future()),
|
||||
_ => panic!(), // we already found First
|
||||
}
|
||||
match self.inner {
|
||||
_Lazy::Second(ref mut f) => f,
|
||||
_ => panic!(), // we just stored Second
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, A, R> Future for Lazy<F, A, R>
|
||||
where F: FnOnce(A) -> R,
|
||||
R: IntoFuture
|
||||
{
|
||||
type Item = R::Item;
|
||||
type Error = R::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<R::Item, R::Error> {
|
||||
self.get().poll()
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
[package]
|
||||
name = "tarpc"
|
||||
version = "0.2.0"
|
||||
authors = ["Adam Wright <adam.austin.wright@gmail.com>", "Tim Kuehn <timothy.j.kuehn@gmail.com>"]
|
||||
license = "MIT"
|
||||
documentation = "https://google.github.io/tarpc"
|
||||
homepage = "https://github.com/google/tarpc"
|
||||
repository = "https://github.com/google/tarpc"
|
||||
keywords = ["rpc", "protocol", "remote", "procedure", "serialize"]
|
||||
readme = "../README.md"
|
||||
description = "An RPC framework for Rust with a focus on ease of use."
|
||||
|
||||
[dependencies]
|
||||
bincode = "^0.4.0"
|
||||
log = "^0.3.5"
|
||||
scoped-pool = "^0.1.4"
|
||||
serde = "^0.6.13"
|
||||
|
||||
[dev-dependencies]
|
||||
lazy_static = "^0.1.15"
|
||||
env_logger = "^0.3.2"
|
||||
@@ -1,8 +0,0 @@
|
||||
# Copyright 2016 Google Inc. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
# This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
#!/bin/sh
|
||||
ln -s ../../hooks/pre-commit .git/hooks/pre-commit
|
||||
ln -s ../../hooks/pre-push .git/hooks/pre-push
|
||||
@@ -1,66 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
//! An RPC library for Rust.
|
||||
//!
|
||||
//! Example usage:
|
||||
//!
|
||||
//! ```
|
||||
//! # #[macro_use] extern crate tarpc;
|
||||
//! mod my_server {
|
||||
//! service! {
|
||||
//! rpc hello(name: String) -> String;
|
||||
//! rpc add(x: i32, y: i32) -> i32;
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! use self::my_server::*;
|
||||
//! use std::time::Duration;
|
||||
//!
|
||||
//! struct Server;
|
||||
//! impl my_server::Service for Server {
|
||||
//! fn hello(&self, s: String) -> String {
|
||||
//! format!("Hello, {}!", s)
|
||||
//! }
|
||||
//! fn add(&self, x: i32, y: i32) -> i32 {
|
||||
//! x + y
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let addr = "127.0.0.1:9000";
|
||||
//! let shutdown = my_server::serve(addr,
|
||||
//! Server,
|
||||
//! Some(Duration::from_secs(30)))
|
||||
//! .unwrap();
|
||||
//! let client = Client::new(addr, None).unwrap();
|
||||
//! assert_eq!(3, client.add(1, 2).unwrap());
|
||||
//! assert_eq!("Hello, Mom!".to_string(),
|
||||
//! client.hello("Mom".to_string()).unwrap());
|
||||
//! drop(client);
|
||||
//! shutdown.shutdown();
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
extern crate serde;
|
||||
extern crate bincode;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate scoped_pool;
|
||||
|
||||
macro_rules! pos {
|
||||
() => (concat!(file!(), ":", line!()))
|
||||
}
|
||||
|
||||
/// Provides the tarpc client and server, which implements the tarpc protocol.
|
||||
/// The protocol is defined by the implementation.
|
||||
pub mod protocol;
|
||||
|
||||
/// Provides the macro used for constructing rpc services and client stubs.
|
||||
pub mod macros;
|
||||
|
||||
pub use protocol::{Error, Result, ServeHandle};
|
||||
@@ -1,540 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
/// Serde re-exports required by macros. Not for general use.
|
||||
pub mod serde {
|
||||
pub use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
/// Deserialization re-exports required by macros. Not for general use.
|
||||
pub mod de {
|
||||
pub use serde::de::{EnumVisitor, Error, Visitor, VariantVisitor};
|
||||
}
|
||||
}
|
||||
|
||||
// Required because if-let can't be used with irrefutable patterns, so it needs
|
||||
// to be special cased.
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! client_methods {
|
||||
(
|
||||
{ $(#[$attr:meta])* }
|
||||
$fn_name:ident( ($($arg:ident,)*) : ($($in_:ty,)*) ) -> $out:ty
|
||||
) => (
|
||||
$(#[$attr])*
|
||||
pub fn $fn_name(&self, $($arg: $in_),*) -> $crate::Result<$out> {
|
||||
let reply = try!((self.0).rpc(__Request::$fn_name(($($arg,)*))));
|
||||
let __Reply::$fn_name(reply) = reply;
|
||||
::std::result::Result::Ok(reply)
|
||||
}
|
||||
);
|
||||
($(
|
||||
{ $(#[$attr:meta])* }
|
||||
$fn_name:ident( ($( $arg:ident,)*) : ($($in_:ty, )*) ) -> $out:ty
|
||||
)*) => ( $(
|
||||
$(#[$attr])*
|
||||
pub fn $fn_name(&self, $($arg: $in_),*) -> $crate::Result<$out> {
|
||||
let reply = try!((self.0).rpc(__Request::$fn_name(($($arg,)*))));
|
||||
if let __Reply::$fn_name(reply) = reply {
|
||||
::std::result::Result::Ok(reply)
|
||||
} else {
|
||||
panic!("Incorrect reply variant returned from protocol::Clientrpc; expected `{}`, but got {:?}", stringify!($fn_name), reply);
|
||||
}
|
||||
}
|
||||
)*);
|
||||
}
|
||||
|
||||
// Required because if-let can't be used with irrefutable patterns, so it needs
|
||||
// to be special cased.
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! async_client_methods {
|
||||
(
|
||||
{ $(#[$attr:meta])* }
|
||||
$fn_name:ident( ($( $arg:ident, )*) : ($( $in_:ty, )*) ) -> $out:ty
|
||||
) => (
|
||||
$(#[$attr])*
|
||||
pub fn $fn_name(&self, $($arg: $in_),*) -> Future<$out> {
|
||||
fn mapper(reply: __Reply) -> $out {
|
||||
let __Reply::$fn_name(reply) = reply;
|
||||
reply
|
||||
}
|
||||
let reply = (self.0).rpc_async(__Request::$fn_name(($($arg,)*)));
|
||||
Future {
|
||||
future: reply,
|
||||
mapper: mapper,
|
||||
}
|
||||
}
|
||||
);
|
||||
($(
|
||||
{ $(#[$attr:meta])* }
|
||||
$fn_name:ident( ($( $arg:ident, )*) : ($( $in_:ty, )*) ) -> $out:ty
|
||||
)*) => ( $(
|
||||
$(#[$attr])*
|
||||
pub fn $fn_name(&self, $($arg: $in_),*) -> Future<$out> {
|
||||
fn mapper(reply: __Reply) -> $out {
|
||||
if let __Reply::$fn_name(reply) = reply {
|
||||
reply
|
||||
} else {
|
||||
panic!("Incorrect reply variant returned from protocol::Clientrpc; expected `{}`, but got {:?}", stringify!($fn_name), reply);
|
||||
}
|
||||
}
|
||||
let reply = (self.0).rpc_async(__Request::$fn_name(($($arg,)*)));
|
||||
Future {
|
||||
future: reply,
|
||||
mapper: mapper,
|
||||
}
|
||||
}
|
||||
)*);
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! impl_serialize {
|
||||
($impler:ident, $(@($name:ident $n:expr))* -- #($_n:expr) ) => (
|
||||
impl $crate::macros::serde::Serialize for $impler {
|
||||
#[inline]
|
||||
fn serialize<S>(&self, serializer: &mut S) -> ::std::result::Result<(), S::Error>
|
||||
where S: $crate::macros::serde::Serializer
|
||||
{
|
||||
match *self {
|
||||
$(
|
||||
$impler::$name(ref field) =>
|
||||
$crate::macros::serde::Serializer::visit_newtype_variant(
|
||||
serializer,
|
||||
stringify!($impler),
|
||||
$n,
|
||||
stringify!($name),
|
||||
field,
|
||||
)
|
||||
),*
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
// All args are wrapped in a tuple so we can use the newtype variant for each one.
|
||||
($impler:ident, $(@$finished:tt)* -- #($n:expr) $name:ident($field:ty) $($req:tt)*) => (
|
||||
impl_serialize!($impler, $(@$finished)* @($name $n) -- #($n + 1) $($req)*);
|
||||
);
|
||||
// Entry
|
||||
($impler:ident, $($started:tt)*) => (impl_serialize!($impler, -- #(0) $($started)*););
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! impl_deserialize {
|
||||
($impler:ident, $(@($name:ident $n:expr))* -- #($_n:expr) ) => (
|
||||
impl $crate::macros::serde::Deserialize for $impler {
|
||||
#[inline]
|
||||
fn deserialize<D>(deserializer: &mut D)
|
||||
-> ::std::result::Result<$impler, D::Error>
|
||||
where D: $crate::macros::serde::Deserializer
|
||||
{
|
||||
#[allow(non_camel_case_types)]
|
||||
enum __Field {
|
||||
$($name),*
|
||||
}
|
||||
impl $crate::macros::serde::Deserialize for __Field {
|
||||
#[inline]
|
||||
fn deserialize<D>(deserializer: &mut D)
|
||||
-> ::std::result::Result<__Field, D::Error>
|
||||
where D: $crate::macros::serde::Deserializer
|
||||
{
|
||||
struct __FieldVisitor;
|
||||
impl $crate::macros::serde::de::Visitor for __FieldVisitor {
|
||||
type Value = __Field;
|
||||
|
||||
fn visit_usize<E>(&mut self, value: usize)
|
||||
-> ::std::result::Result<__Field, E>
|
||||
where E: $crate::macros::serde::de::Error,
|
||||
{
|
||||
$(
|
||||
if value == $n {
|
||||
return ::std::result::Result::Ok(__Field::$name);
|
||||
}
|
||||
)*
|
||||
return ::std::result::Result::Err(
|
||||
$crate::macros::serde::de::Error::syntax("expected a field")
|
||||
);
|
||||
}
|
||||
}
|
||||
deserializer.visit_struct_field(__FieldVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct __Visitor;
|
||||
impl $crate::macros::serde::de::EnumVisitor for __Visitor {
|
||||
type Value = $impler;
|
||||
|
||||
fn visit<__V>(&mut self, mut visitor: __V)
|
||||
-> ::std::result::Result<$impler, __V::Error>
|
||||
where __V: $crate::macros::serde::de::VariantVisitor
|
||||
{
|
||||
match try!(visitor.visit_variant()) {
|
||||
$(
|
||||
__Field::$name => {
|
||||
let val = try!(visitor.visit_newtype());
|
||||
Ok($impler::$name(val))
|
||||
}
|
||||
),*
|
||||
}
|
||||
}
|
||||
}
|
||||
const VARIANTS: &'static [&'static str] = &[
|
||||
$(
|
||||
stringify!($name)
|
||||
),*
|
||||
];
|
||||
deserializer.visit_enum(stringify!($impler), VARIANTS, __Visitor)
|
||||
}
|
||||
}
|
||||
);
|
||||
// All args are wrapped in a tuple so we can use the newtype variant for each one.
|
||||
($impler:ident, $(@$finished:tt)* -- #($n:expr) $name:ident($field:ty) $($req:tt)*) => (
|
||||
impl_deserialize!($impler, $(@$finished)* @($name $n) -- #($n + 1) $($req)*);
|
||||
);
|
||||
// Entry
|
||||
($impler:ident, $($started:tt)*) => (impl_deserialize!($impler, -- #(0) $($started)*););
|
||||
}
|
||||
|
||||
/// The main macro that creates RPC services.
|
||||
///
|
||||
/// Rpc methods are specified, mirroring trait syntax:
|
||||
///
|
||||
/// ```
|
||||
/// # #[macro_use] extern crate tarpc;
|
||||
/// # fn main() {}
|
||||
/// # service! {
|
||||
/// #[doc="Say hello"]
|
||||
/// rpc hello(name: String) -> String;
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Attributes can be attached to each rpc. These attributes
|
||||
/// will then be attached to the generated `Service` trait's
|
||||
/// corresponding method, as well as to the `Client` stub's rpcs methods.
|
||||
///
|
||||
/// The following items are expanded in the enclosing module:
|
||||
///
|
||||
/// * `Service` -- the trait defining the RPC service
|
||||
/// * `Client` -- a client that makes synchronous requests to the RPC server
|
||||
/// * `AsyncClient` -- a client that makes asynchronous requests to the RPC server
|
||||
/// * `Future` -- a handle for asynchronously retrieving the result of an RPC
|
||||
/// * `serve` -- the function that starts the RPC server
|
||||
///
|
||||
/// **Warning**: In addition to the above items, there are a few expanded items that
|
||||
/// are considered implementation details. As with the above items, shadowing
|
||||
/// these item names in the enclosing module is likely to break things in confusing
|
||||
/// ways:
|
||||
///
|
||||
/// * `__Server` -- an implementation detail
|
||||
/// * `__Request` -- an implementation detail
|
||||
/// * `__Reply` -- an implementation detail
|
||||
#[macro_export]
|
||||
macro_rules! service {
|
||||
(
|
||||
$( $tokens:tt )*
|
||||
) => {
|
||||
service_inner! {{
|
||||
$( $tokens )*
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! service_inner {
|
||||
// Pattern for when the next rpc has an implicit unit return type
|
||||
(
|
||||
{
|
||||
$(#[$attr:meta])*
|
||||
rpc $fn_name:ident( $( $arg:ident : $in_:ty ),* );
|
||||
|
||||
$( $unexpanded:tt )*
|
||||
}
|
||||
$( $expanded:tt )*
|
||||
) => {
|
||||
service_inner! {
|
||||
{ $( $unexpanded )* }
|
||||
|
||||
$( $expanded )*
|
||||
|
||||
$(#[$attr])*
|
||||
rpc $fn_name( $( $arg : $in_ ),* ) -> ();
|
||||
}
|
||||
};
|
||||
// Pattern for when the next rpc has an explicit return type
|
||||
(
|
||||
{
|
||||
$(#[$attr:meta])*
|
||||
rpc $fn_name:ident( $( $arg:ident : $in_:ty ),* ) -> $out:ty;
|
||||
|
||||
$( $unexpanded:tt )*
|
||||
}
|
||||
$( $expanded:tt )*
|
||||
) => {
|
||||
service_inner! {
|
||||
{ $( $unexpanded )* }
|
||||
|
||||
$( $expanded )*
|
||||
|
||||
$(#[$attr])*
|
||||
rpc $fn_name( $( $arg : $in_ ),* ) -> $out;
|
||||
}
|
||||
};
|
||||
// Pattern when all return types have been expanded
|
||||
(
|
||||
{ } // none left to expand
|
||||
$(
|
||||
$(#[$attr:meta])*
|
||||
rpc $fn_name:ident ( $( $arg:ident : $in_:ty ),* ) -> $out:ty;
|
||||
)*
|
||||
) => {
|
||||
#[doc="Defines the RPC service"]
|
||||
pub trait Service: Send + Sync {
|
||||
$(
|
||||
$(#[$attr])*
|
||||
fn $fn_name(&self, $($arg:$in_),*) -> $out;
|
||||
)*
|
||||
}
|
||||
|
||||
impl<P, S> Service for P
|
||||
where P: Send + Sync + ::std::ops::Deref<Target=S>,
|
||||
S: Service
|
||||
{
|
||||
$(
|
||||
$(#[$attr])*
|
||||
fn $fn_name(&self, $($arg:$in_),*) -> $out {
|
||||
Service::$fn_name(&**self, $($arg),*)
|
||||
}
|
||||
)*
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Debug)]
|
||||
enum __Request {
|
||||
$(
|
||||
$fn_name(( $($in_,)* ))
|
||||
),*
|
||||
}
|
||||
|
||||
impl_serialize!(__Request, $($fn_name(($($in_),*)))*);
|
||||
impl_deserialize!(__Request, $($fn_name(($($in_),*)))*);
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Debug)]
|
||||
enum __Reply {
|
||||
$(
|
||||
$fn_name($out),
|
||||
)*
|
||||
}
|
||||
|
||||
impl_serialize!(__Reply, $($fn_name($out))*);
|
||||
impl_deserialize!(__Reply, $($fn_name($out))*);
|
||||
|
||||
/// An asynchronous RPC call
|
||||
pub struct Future<T> {
|
||||
future: $crate::protocol::Future<__Reply>,
|
||||
mapper: fn(__Reply) -> T,
|
||||
}
|
||||
|
||||
impl<T> Future<T> {
|
||||
/// Block until the result of the RPC call is available
|
||||
pub fn get(self) -> $crate::Result<T> {
|
||||
self.future.get().map(self.mapper)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc="The client stub that makes RPC calls to the server."]
|
||||
pub struct Client($crate::protocol::Client<__Request, __Reply>);
|
||||
|
||||
impl Client {
|
||||
#[doc="Create a new client that connects to the given address."]
|
||||
pub fn new<A>(addr: A, timeout: ::std::option::Option<::std::time::Duration>)
|
||||
-> $crate::Result<Self>
|
||||
where A: ::std::net::ToSocketAddrs,
|
||||
{
|
||||
let inner = try!($crate::protocol::Client::new(addr, timeout));
|
||||
::std::result::Result::Ok(Client(inner))
|
||||
}
|
||||
|
||||
client_methods!(
|
||||
$(
|
||||
{ $(#[$attr])* }
|
||||
$fn_name(($($arg,)*) : ($($in_,)*)) -> $out
|
||||
)*
|
||||
);
|
||||
|
||||
#[doc="Attempt to clone the client object. This might fail if the underlying TcpStream \
|
||||
clone fails."]
|
||||
pub fn try_clone(&self) -> ::std::io::Result<Self> {
|
||||
::std::result::Result::Ok(Client(try!(self.0.try_clone())))
|
||||
}
|
||||
}
|
||||
|
||||
#[doc="The client stub that makes asynchronous RPC calls to the server."]
|
||||
pub struct AsyncClient($crate::protocol::Client<__Request, __Reply>);
|
||||
|
||||
impl AsyncClient {
|
||||
#[doc="Create a new asynchronous client that connects to the given address."]
|
||||
pub fn new<A>(addr: A, timeout: ::std::option::Option<::std::time::Duration>)
|
||||
-> $crate::Result<Self>
|
||||
where A: ::std::net::ToSocketAddrs,
|
||||
{
|
||||
let inner = try!($crate::protocol::Client::new(addr, timeout));
|
||||
::std::result::Result::Ok(AsyncClient(inner))
|
||||
}
|
||||
|
||||
async_client_methods!(
|
||||
$(
|
||||
{ $(#[$attr])* }
|
||||
$fn_name(($($arg,)*): ($($in_,)*)) -> $out
|
||||
)*
|
||||
);
|
||||
|
||||
#[doc="Attempt to clone the client object. This might fail if the underlying TcpStream \
|
||||
clone fails."]
|
||||
pub fn try_clone(&self) -> ::std::io::Result<Self> {
|
||||
::std::result::Result::Ok(AsyncClient(try!(self.0.try_clone())))
|
||||
}
|
||||
}
|
||||
|
||||
struct __Server<S: 'static + Service>(S);
|
||||
|
||||
impl<S> $crate::protocol::Serve for __Server<S>
|
||||
where S: 'static + Service
|
||||
{
|
||||
type Request = __Request;
|
||||
type Reply = __Reply;
|
||||
fn serve(&self, request: __Request) -> __Reply {
|
||||
match request {
|
||||
$(
|
||||
__Request::$fn_name(( $($arg,)* )) =>
|
||||
__Reply::$fn_name((self.0).$fn_name($($arg),*)),
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc="Start a running service."]
|
||||
pub fn serve<A, S>(addr: A,
|
||||
service: S,
|
||||
read_timeout: ::std::option::Option<::std::time::Duration>)
|
||||
-> $crate::Result<$crate::protocol::ServeHandle>
|
||||
where A: ::std::net::ToSocketAddrs,
|
||||
S: 'static + Service
|
||||
{
|
||||
let server = ::std::sync::Arc::new(__Server(service));
|
||||
::std::result::Result::Ok(try!($crate::protocol::serve_async(addr, server, read_timeout)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // because we're just testing that the macro expansion compiles
|
||||
#[cfg(test)]
|
||||
mod syntax_test {
|
||||
// Tests a service definition with a fn that takes no args
|
||||
mod qux {
|
||||
service! {
|
||||
rpc hello() -> String;
|
||||
}
|
||||
}
|
||||
// Tests a service definition with an attribute.
|
||||
mod bar {
|
||||
service! {
|
||||
#[doc="Hello bob"]
|
||||
rpc baz(s: String) -> String;
|
||||
}
|
||||
}
|
||||
|
||||
// Tests a service with implicit return types.
|
||||
mod no_return {
|
||||
service! {
|
||||
rpc ack();
|
||||
rpc apply(foo: String) -> i32;
|
||||
rpc bi_consume(bar: String, baz: u64);
|
||||
rpc bi_fn(bar: String, baz: u64) -> String;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod functional_test {
|
||||
extern crate env_logger;
|
||||
use std::time::Duration;
|
||||
|
||||
fn test_timeout() -> Option<Duration> {
|
||||
Some(Duration::from_secs(5))
|
||||
}
|
||||
|
||||
service! {
|
||||
rpc add(x: i32, y: i32) -> i32;
|
||||
}
|
||||
|
||||
struct Server;
|
||||
|
||||
impl Service for Server {
|
||||
fn add(&self, x: i32, y: i32) -> i32 {
|
||||
x + y
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple() {
|
||||
let _ = env_logger::init();
|
||||
let handle = serve( "localhost:0", Server, test_timeout()).unwrap();
|
||||
let client = Client::new(handle.local_addr(), None).unwrap();
|
||||
assert_eq!(3, client.add(1, 2).unwrap());
|
||||
drop(client);
|
||||
handle.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_async() {
|
||||
let _ = env_logger::init();
|
||||
let handle = serve("localhost:0", Server, test_timeout()).unwrap();
|
||||
let client = AsyncClient::new(handle.local_addr(), None).unwrap();
|
||||
assert_eq!(3, client.add(1, 2).get().unwrap());
|
||||
drop(client);
|
||||
handle.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_clone() {
|
||||
let handle = serve( "localhost:0", Server, test_timeout()).unwrap();
|
||||
let client1 = Client::new(handle.local_addr(), None).unwrap();
|
||||
let client2 = client1.try_clone().unwrap();
|
||||
assert_eq!(3, client1.add(1, 2).unwrap());
|
||||
assert_eq!(3, client2.add(1, 2).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn async_try_clone() {
|
||||
let handle = serve("localhost:0", Server, test_timeout()).unwrap();
|
||||
let client1 = AsyncClient::new(handle.local_addr(), None).unwrap();
|
||||
let client2 = client1.try_clone().unwrap();
|
||||
assert_eq!(3, client1.add(1, 2).get().unwrap());
|
||||
assert_eq!(3, client2.add(1, 2).get().unwrap());
|
||||
}
|
||||
|
||||
// Tests that a server can be wrapped in an Arc; no need to run, just compile
|
||||
#[allow(dead_code)]
|
||||
fn serve_arc_server() {
|
||||
let _ = serve("localhost:0", ::std::sync::Arc::new(Server), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serde() {
|
||||
use bincode;
|
||||
let _ = env_logger::init();
|
||||
|
||||
let request = __Request::add((1, 2));
|
||||
let ser = bincode::serde::serialize(&request, bincode::SizeLimit::Infinite).unwrap();
|
||||
let de = bincode::serde::deserialize(&ser).unwrap();
|
||||
if let __Request::add((1, 2)) = de {
|
||||
// success
|
||||
} else {
|
||||
panic!("Expected __Request::add, got {:?}", de);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,252 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
use serde;
|
||||
use std::fmt;
|
||||
use std::io::{self, BufReader, BufWriter, Read};
|
||||
use std::collections::HashMap;
|
||||
use std::mem;
|
||||
use std::net::{TcpStream, ToSocketAddrs};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::mpsc::{Receiver, Sender, channel};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use super::{Serialize, Deserialize, Error, Packet, Result};
|
||||
|
||||
/// A client stub that connects to a server to run rpcs.
|
||||
pub struct Client<Request, Reply>
|
||||
where Request: serde::ser::Serialize
|
||||
{
|
||||
// The guard is in an option so it can be joined in the drop fn
|
||||
reader_guard: Arc<Option<thread::JoinHandle<()>>>,
|
||||
outbound: Sender<(Request, Sender<Result<Reply>>)>,
|
||||
requests: Arc<Mutex<RpcFutures<Reply>>>,
|
||||
shutdown: TcpStream,
|
||||
}
|
||||
|
||||
impl<Request, Reply> Client<Request, Reply>
|
||||
where Request: serde::ser::Serialize + Send + 'static,
|
||||
Reply: serde::de::Deserialize + Send + 'static
|
||||
{
|
||||
/// Create a new client that connects to `addr`. The client uses the given timeout
|
||||
/// for both reads and writes.
|
||||
pub fn new<A: ToSocketAddrs>(addr: A, timeout: Option<Duration>) -> io::Result<Self> {
|
||||
let stream = try!(TcpStream::connect(addr));
|
||||
try!(stream.set_read_timeout(timeout));
|
||||
try!(stream.set_write_timeout(timeout));
|
||||
let reader_stream = try!(stream.try_clone());
|
||||
let writer_stream = try!(stream.try_clone());
|
||||
let requests = Arc::new(Mutex::new(RpcFutures::new()));
|
||||
let reader_requests = requests.clone();
|
||||
let writer_requests = requests.clone();
|
||||
let (tx, rx) = channel();
|
||||
let reader_guard = thread::spawn(move || read(reader_requests, reader_stream));
|
||||
thread::spawn(move || write(rx, writer_requests, writer_stream));
|
||||
Ok(Client {
|
||||
reader_guard: Arc::new(Some(reader_guard)),
|
||||
outbound: tx,
|
||||
requests: requests,
|
||||
shutdown: stream,
|
||||
})
|
||||
}
|
||||
|
||||
/// Clones the Client so that it can be shared across threads.
|
||||
pub fn try_clone(&self) -> io::Result<Client<Request, Reply>> {
|
||||
Ok(Client {
|
||||
reader_guard: self.reader_guard.clone(),
|
||||
outbound: self.outbound.clone(),
|
||||
requests: self.requests.clone(),
|
||||
shutdown: try!(self.shutdown.try_clone()),
|
||||
})
|
||||
}
|
||||
|
||||
fn rpc_internal(&self, request: Request) -> Receiver<Result<Reply>>
|
||||
where Request: serde::ser::Serialize + fmt::Debug + Send + 'static
|
||||
{
|
||||
let (tx, rx) = channel();
|
||||
self.outbound.send((request, tx)).expect(pos!());
|
||||
rx
|
||||
}
|
||||
|
||||
/// Run the specified rpc method on the server this client is connected to
|
||||
pub fn rpc(&self, request: Request) -> Result<Reply>
|
||||
where Request: serde::ser::Serialize + fmt::Debug + Send + 'static
|
||||
{
|
||||
self.rpc_internal(request)
|
||||
.recv()
|
||||
.map_err(|_| self.requests.lock().expect(pos!()).get_error())
|
||||
.and_then(|reply| reply)
|
||||
}
|
||||
|
||||
/// Asynchronously run the specified rpc method on the server this client is connected to
|
||||
pub fn rpc_async(&self, request: Request) -> Future<Reply>
|
||||
where Request: serde::ser::Serialize + fmt::Debug + Send + 'static
|
||||
{
|
||||
Future {
|
||||
rx: self.rpc_internal(request),
|
||||
requests: self.requests.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Request, Reply> Drop for Client<Request, Reply>
|
||||
where Request: serde::ser::Serialize
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
debug!("Dropping Client.");
|
||||
if let Some(reader_guard) = Arc::get_mut(&mut self.reader_guard) {
|
||||
debug!("Attempting to shut down writer and reader threads.");
|
||||
if let Err(e) = self.shutdown.shutdown(::std::net::Shutdown::Both) {
|
||||
warn!("Client: couldn't shutdown writer and reader threads: {:?}", e);
|
||||
} else {
|
||||
// We only join if we know the TcpStream was shut down. Otherwise we might never
|
||||
// finish.
|
||||
debug!("Joining writer and reader.");
|
||||
reader_guard.take()
|
||||
.expect(pos!())
|
||||
.join()
|
||||
.expect(pos!());
|
||||
debug!("Successfully joined writer and reader.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An asynchronous RPC call
|
||||
pub struct Future<T> {
|
||||
rx: Receiver<Result<T>>,
|
||||
requests: Arc<Mutex<RpcFutures<T>>>
|
||||
}
|
||||
|
||||
impl<T> Future<T> {
|
||||
/// Block until the result of the RPC call is available
|
||||
pub fn get(self) -> Result<T> {
|
||||
let requests = self.requests;
|
||||
self.rx.recv()
|
||||
.map_err(|_| requests.lock().expect(pos!()).get_error())
|
||||
.and_then(|reply| reply)
|
||||
}
|
||||
}
|
||||
|
||||
struct RpcFutures<Reply>(Result<HashMap<u64, Sender<Result<Reply>>>>);
|
||||
|
||||
impl<Reply> RpcFutures<Reply> {
|
||||
fn new() -> RpcFutures<Reply> {
|
||||
RpcFutures(Ok(HashMap::new()))
|
||||
}
|
||||
|
||||
fn insert_tx(&mut self, id: u64, tx: Sender<Result<Reply>>) -> Result<()> {
|
||||
match self.0 {
|
||||
Ok(ref mut requests) => {
|
||||
requests.insert(id, tx);
|
||||
Ok(())
|
||||
}
|
||||
Err(ref e) => Err(e.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_tx(&mut self, id: u64) -> Result<()> {
|
||||
match self.0 {
|
||||
Ok(ref mut requests) => {
|
||||
requests.remove(&id);
|
||||
Ok(())
|
||||
}
|
||||
Err(ref e) => Err(e.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
fn complete_reply(&mut self, packet: Packet<Reply>) {
|
||||
if let Some(tx) = self.0.as_mut().expect(pos!()).remove(&packet.rpc_id) {
|
||||
if let Err(e) = tx.send(Ok(packet.message)) {
|
||||
info!("Reader: could not complete reply: {:?}", e);
|
||||
}
|
||||
} else {
|
||||
warn!("RpcFutures: expected sender for id {} but got None!", packet.rpc_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_error(&mut self, err: Error) {
|
||||
let _ = mem::replace(&mut self.0, Err(err));
|
||||
}
|
||||
|
||||
fn get_error(&self) -> Error {
|
||||
self.0.as_ref().err().expect(pos!()).clone()
|
||||
}
|
||||
}
|
||||
|
||||
fn write<Request, Reply>(outbound: Receiver<(Request, Sender<Result<Reply>>)>,
|
||||
requests: Arc<Mutex<RpcFutures<Reply>>>,
|
||||
stream: TcpStream)
|
||||
where Request: serde::Serialize,
|
||||
Reply: serde::Deserialize,
|
||||
{
|
||||
let mut next_id = 0;
|
||||
let mut stream = BufWriter::new(stream);
|
||||
loop {
|
||||
let (request, tx) = match outbound.recv() {
|
||||
Err(e) => {
|
||||
debug!("Writer: all senders have exited ({:?}). Returning.", e);
|
||||
return;
|
||||
}
|
||||
Ok(request) => request,
|
||||
};
|
||||
if let Err(e) = requests.lock().expect(pos!()).insert_tx(next_id, tx.clone()) {
|
||||
report_error(&tx, e);
|
||||
// Once insert_tx returns Err, it will continue to do so. However, continue here so
|
||||
// that any other clients who sent requests will also recv the Err.
|
||||
continue;
|
||||
}
|
||||
let id = next_id;
|
||||
next_id += 1;
|
||||
let packet = Packet {
|
||||
rpc_id: id,
|
||||
message: request,
|
||||
};
|
||||
debug!("Writer: writing rpc, id={:?}", id);
|
||||
if let Err(e) = stream.serialize(&packet) {
|
||||
report_error(&tx, e.into());
|
||||
// Typically we'd want to notify the client of any Err returned by remove_tx, but in
|
||||
// this case the client already hit an Err, and doesn't need to know about this one, as
|
||||
// well.
|
||||
let _ = requests.lock().expect(pos!()).remove_tx(id);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
fn report_error<Reply>(tx: &Sender<Result<Reply>>, e: Error)
|
||||
where Reply: serde::Deserialize
|
||||
{
|
||||
// Clone the err so we can log it if sending fails
|
||||
if let Err(e2) = tx.send(Err(e.clone())) {
|
||||
debug!("Error encountered while trying to send an error. \
|
||||
Initial error: {:?}; Send error: {:?}",
|
||||
e,
|
||||
e2);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn read<Reply>(requests: Arc<Mutex<RpcFutures<Reply>>>, stream: TcpStream)
|
||||
where Reply: serde::Deserialize
|
||||
{
|
||||
let mut stream = BufReader::new(stream);
|
||||
loop {
|
||||
match stream.deserialize::<Packet<Reply>>() {
|
||||
Ok(packet) => {
|
||||
debug!("Client: received message, id={}", packet.rpc_id);
|
||||
requests.lock().expect(pos!()).complete_reply(packet);
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("Client: reader thread encountered an unexpected error while parsing; \
|
||||
returning now. Error: {:?}",
|
||||
err);
|
||||
requests.lock().expect(pos!()).set_error(err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,235 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
use bincode::{self, SizeLimit};
|
||||
use bincode::serde::{deserialize_from, serialize_into};
|
||||
use serde;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::convert;
|
||||
use std::sync::Arc;
|
||||
|
||||
mod client;
|
||||
mod server;
|
||||
mod packet;
|
||||
|
||||
pub use self::packet::Packet;
|
||||
pub use self::client::{Client, Future};
|
||||
pub use self::server::{Serve, ServeHandle, serve_async};
|
||||
|
||||
/// Client errors that can occur during rpc calls
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Error {
|
||||
/// An IO-related error
|
||||
Io(Arc<io::Error>),
|
||||
/// The server hung up.
|
||||
ConnectionBroken,
|
||||
}
|
||||
|
||||
impl convert::From<bincode::serde::SerializeError> for Error {
|
||||
fn from(err: bincode::serde::SerializeError) -> Error {
|
||||
match err {
|
||||
bincode::serde::SerializeError::IoError(err) => Error::Io(Arc::new(err)),
|
||||
err => panic!("Unexpected error during serialization: {:?}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl convert::From<bincode::serde::DeserializeError> for Error {
|
||||
fn from(err: bincode::serde::DeserializeError) -> Error {
|
||||
match err {
|
||||
bincode::serde::DeserializeError::IoError(ref err)
|
||||
if err.kind() == io::ErrorKind::ConnectionReset => Error::ConnectionBroken,
|
||||
bincode::serde::DeserializeError::EndOfStreamError => Error::ConnectionBroken,
|
||||
bincode::serde::DeserializeError::IoError(err) => Error::Io(Arc::new(err)),
|
||||
err => panic!("Unexpected error during deserialization: {:?}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl convert::From<io::Error> for Error {
|
||||
fn from(err: io::Error) -> Error {
|
||||
Error::Io(Arc::new(err))
|
||||
}
|
||||
}
|
||||
|
||||
/// Return type of rpc calls: either the successful return value, or a client error.
|
||||
pub type Result<T> = ::std::result::Result<T, Error>;
|
||||
|
||||
trait Deserialize: Read + Sized {
|
||||
fn deserialize<T: serde::Deserialize>(&mut self) -> Result<T> {
|
||||
deserialize_from(self, SizeLimit::Infinite)
|
||||
.map_err(Error::from)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read> Deserialize for R {}
|
||||
|
||||
trait Serialize: Write + Sized {
|
||||
fn serialize<T: serde::Serialize>(&mut self, value: &T) -> Result<()> {
|
||||
try!(serialize_into(self, value, SizeLimit::Infinite));
|
||||
try!(self.flush());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> Serialize for W {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
extern crate env_logger;
|
||||
use super::{Client, Serve, serve_async};
|
||||
use scoped_pool::Pool;
|
||||
use std::sync::{Arc, Barrier, Mutex};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
fn test_timeout() -> Option<Duration> {
|
||||
Some(Duration::from_secs(1))
|
||||
}
|
||||
|
||||
struct Server {
|
||||
counter: Mutex<u64>,
|
||||
}
|
||||
|
||||
impl Serve for Server {
|
||||
type Request = ();
|
||||
type Reply = u64;
|
||||
|
||||
fn serve(&self, _: ()) -> u64 {
|
||||
let mut counter = self.counter.lock().unwrap();
|
||||
let reply = *counter;
|
||||
*counter += 1;
|
||||
reply
|
||||
}
|
||||
}
|
||||
|
||||
impl Server {
|
||||
fn new() -> Server {
|
||||
Server { counter: Mutex::new(0) }
|
||||
}
|
||||
|
||||
fn count(&self) -> u64 {
|
||||
*self.counter.lock().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handle() {
|
||||
let _ = env_logger::init();
|
||||
let server = Arc::new(Server::new());
|
||||
let serve_handle = serve_async("localhost:0", server.clone(), test_timeout()).unwrap();
|
||||
let client: Client<(), u64> = Client::new(serve_handle.local_addr(), None).unwrap();
|
||||
drop(client);
|
||||
serve_handle.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple() {
|
||||
let _ = env_logger::init();
|
||||
let server = Arc::new(Server::new());
|
||||
let serve_handle = serve_async("localhost:0", server.clone(), test_timeout()).unwrap();
|
||||
let addr = serve_handle.local_addr().clone();
|
||||
// The explicit type is required so that it doesn't deserialize a u32 instead of u64
|
||||
let client: Client<(), u64> = Client::new(addr, None).unwrap();
|
||||
assert_eq!(0, client.rpc(()).unwrap());
|
||||
assert_eq!(1, server.count());
|
||||
assert_eq!(1, client.rpc(()).unwrap());
|
||||
assert_eq!(2, server.count());
|
||||
drop(client);
|
||||
serve_handle.shutdown();
|
||||
}
|
||||
|
||||
struct BarrierServer {
|
||||
barrier: Barrier,
|
||||
inner: Server,
|
||||
}
|
||||
|
||||
impl Serve for BarrierServer {
|
||||
type Request = ();
|
||||
type Reply = u64;
|
||||
fn serve(&self, request: ()) -> u64 {
|
||||
self.barrier.wait();
|
||||
self.inner.serve(request)
|
||||
}
|
||||
}
|
||||
|
||||
impl BarrierServer {
|
||||
fn new(n: usize) -> BarrierServer {
|
||||
BarrierServer {
|
||||
barrier: Barrier::new(n),
|
||||
inner: Server::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn count(&self) -> u64 {
|
||||
self.inner.count()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn force_shutdown() {
|
||||
let _ = env_logger::init();
|
||||
let server = Arc::new(Server::new());
|
||||
let serve_handle = serve_async("localhost:0", server, Some(Duration::new(0, 10))).unwrap();
|
||||
let addr = serve_handle.local_addr().clone();
|
||||
let client: Client<(), u64> = Client::new(addr, None).unwrap();
|
||||
let thread = thread::spawn(move || serve_handle.shutdown());
|
||||
info!("force_shutdown:: rpc1: {:?}", client.rpc(()));
|
||||
thread.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_failed_rpc() {
|
||||
let _ = env_logger::init();
|
||||
let server = Arc::new(Server::new());
|
||||
let serve_handle = serve_async("localhost:0", server, test_timeout()).unwrap();
|
||||
let addr = serve_handle.local_addr().clone();
|
||||
let client: Arc<Client<(), u64>> = Arc::new(Client::new(addr, None).unwrap());
|
||||
client.rpc(()).unwrap();
|
||||
serve_handle.shutdown();
|
||||
match client.rpc(()) {
|
||||
Err(super::Error::ConnectionBroken) => {} // success
|
||||
otherwise => panic!("Expected Err(ConnectionBroken), got {:?}", otherwise),
|
||||
}
|
||||
let _ = client.rpc(()); // Test whether second failure hangs
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn concurrent() {
|
||||
let _ = env_logger::init();
|
||||
let concurrency = 10;
|
||||
let pool = Pool::new(concurrency);
|
||||
let server = Arc::new(BarrierServer::new(concurrency));
|
||||
let serve_handle = serve_async("localhost:0", server.clone(), test_timeout()).unwrap();
|
||||
let addr = serve_handle.local_addr().clone();
|
||||
let client: Client<(), u64> = Client::new(addr, None).unwrap();
|
||||
pool.scoped(|scope| {
|
||||
for _ in 0..concurrency {
|
||||
let client = client.try_clone().unwrap();
|
||||
scope.execute(move || { client.rpc(()).unwrap(); });
|
||||
}
|
||||
});
|
||||
assert_eq!(concurrency as u64, server.count());
|
||||
drop(client);
|
||||
serve_handle.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn async() {
|
||||
let _ = env_logger::init();
|
||||
let server = Arc::new(Server::new());
|
||||
let serve_handle = serve_async("localhost:0", server.clone(), None).unwrap();
|
||||
let addr = serve_handle.local_addr().clone();
|
||||
let client: Client<(), u64> = Client::new(addr, None).unwrap();
|
||||
|
||||
// Drop future immediately; does the reader channel panic when sending?
|
||||
client.rpc_async(());
|
||||
// If the reader panicked, this won't succeed
|
||||
client.rpc_async(());
|
||||
|
||||
drop(client);
|
||||
serve_handle.shutdown();
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer, de, ser};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// Packet shared between client and server.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Packet<T> {
|
||||
/// Packet id to map response to request.
|
||||
pub rpc_id: u64,
|
||||
/// Packet payload.
|
||||
pub message: T,
|
||||
}
|
||||
|
||||
const PACKET: &'static str = "Packet";
|
||||
const RPC_ID: &'static str = "rpc_id";
|
||||
const MESSAGE: &'static str = "message";
|
||||
|
||||
impl<T: Serialize> Serialize for Packet<T> {
|
||||
#[inline]
|
||||
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
|
||||
where S: Serializer
|
||||
{
|
||||
serializer.visit_struct(PACKET, MapVisitor {
|
||||
value: self,
|
||||
state: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct MapVisitor<'a, T: 'a> {
|
||||
value: &'a Packet<T>,
|
||||
state: u8,
|
||||
}
|
||||
|
||||
impl <'a, T: Serialize> ser::MapVisitor for MapVisitor<'a, T> {
|
||||
fn visit<S>(&mut self, serializer: &mut S) -> Result<Option<()>, S::Error>
|
||||
where S: Serializer
|
||||
{
|
||||
match self.state {
|
||||
0 => {
|
||||
self.state += 1;
|
||||
Ok(Some(try!(serializer.visit_struct_elt(RPC_ID, &self.value.rpc_id))))
|
||||
}
|
||||
1 => {
|
||||
self.state += 1;
|
||||
Ok(Some(try!(serializer.visit_struct_elt(MESSAGE, &self.value.message))))
|
||||
}
|
||||
_ => {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Deserialize> Deserialize for Packet<T> {
|
||||
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
|
||||
where D: Deserializer
|
||||
{
|
||||
const FIELDS: &'static [&'static str] = &[RPC_ID, MESSAGE];
|
||||
deserializer.visit_struct(PACKET, FIELDS, Visitor(PhantomData))
|
||||
}
|
||||
}
|
||||
|
||||
struct Visitor<T>(PhantomData<T>);
|
||||
|
||||
impl<T: Deserialize> de::Visitor for Visitor<T> {
|
||||
type Value = Packet<T>;
|
||||
|
||||
fn visit_seq<V>(&mut self, mut visitor: V) -> Result<Packet<T>, V::Error>
|
||||
where V: de::SeqVisitor
|
||||
{
|
||||
let packet = Packet {
|
||||
rpc_id: match try!(visitor.visit()) {
|
||||
Some(rpc_id) => rpc_id,
|
||||
None => return Err(de::Error::end_of_stream()),
|
||||
},
|
||||
message: match try!(visitor.visit()) {
|
||||
Some(message) => message,
|
||||
None => return Err(de::Error::end_of_stream()),
|
||||
},
|
||||
};
|
||||
try!(visitor.end());
|
||||
Ok(packet)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate env_logger;
|
||||
|
||||
#[test]
|
||||
fn serde() {
|
||||
use bincode;
|
||||
let _ = env_logger::init();
|
||||
|
||||
let packet = Packet { rpc_id: 1, message: () };
|
||||
let ser = bincode::serde::serialize(&packet, bincode::SizeLimit::Infinite).unwrap();
|
||||
let de = bincode::serde::deserialize(&ser);
|
||||
assert_eq!(packet, de.unwrap());
|
||||
}
|
||||
@@ -1,256 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
use serde;
|
||||
use scoped_pool::{Pool, Scope};
|
||||
use std::fmt;
|
||||
use std::io::{self, BufReader, BufWriter};
|
||||
use std::net::{SocketAddr, TcpListener, TcpStream, ToSocketAddrs};
|
||||
use std::sync::mpsc::{Receiver, Sender, TryRecvError, channel};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::Duration;
|
||||
use std::thread::{self, JoinHandle};
|
||||
use super::{Deserialize, Error, Packet, Result, Serialize};
|
||||
|
||||
struct ConnectionHandler<'a, S>
|
||||
where S: Serve
|
||||
{
|
||||
read_stream: BufReader<TcpStream>,
|
||||
write_stream: BufWriter<TcpStream>,
|
||||
server: S,
|
||||
shutdown: &'a AtomicBool,
|
||||
}
|
||||
|
||||
impl<'a, S> ConnectionHandler<'a, S>
|
||||
where S: Serve
|
||||
{
|
||||
fn handle_conn<'b>(&'b mut self, scope: &Scope<'b>) -> Result<()> {
|
||||
let ConnectionHandler {
|
||||
ref mut read_stream,
|
||||
ref mut write_stream,
|
||||
ref server,
|
||||
shutdown,
|
||||
} = *self;
|
||||
trace!("ConnectionHandler: serving client...");
|
||||
let (tx, rx) = channel();
|
||||
scope.execute(move || Self::write(rx, write_stream));
|
||||
loop {
|
||||
match read_stream.deserialize() {
|
||||
Ok(Packet { rpc_id, message, }) => {
|
||||
let tx = tx.clone();
|
||||
scope.execute(move || {
|
||||
let reply = server.serve(message);
|
||||
let reply_packet = Packet {
|
||||
rpc_id: rpc_id,
|
||||
message: reply
|
||||
};
|
||||
tx.send(reply_packet).expect(pos!());
|
||||
});
|
||||
if shutdown.load(Ordering::SeqCst) {
|
||||
info!("ConnectionHandler: server shutdown, so closing connection.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(Error::Io(ref err)) if Self::timed_out(err.kind()) => {
|
||||
if !shutdown.load(Ordering::SeqCst) {
|
||||
info!("ConnectionHandler: read timed out ({:?}). Server not \
|
||||
shutdown, so retrying read.",
|
||||
err);
|
||||
continue;
|
||||
} else {
|
||||
info!("ConnectionHandler: read timed out ({:?}). Server shutdown, so \
|
||||
closing connection.",
|
||||
err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("ConnectionHandler: closing client connection due to {:?}",
|
||||
e);
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn timed_out(error_kind: io::ErrorKind) -> bool {
|
||||
match error_kind {
|
||||
io::ErrorKind::TimedOut | io::ErrorKind::WouldBlock => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn write(rx: Receiver<Packet<<S as Serve>::Reply>>, stream: &mut BufWriter<TcpStream>) {
|
||||
loop {
|
||||
match rx.recv() {
|
||||
Err(e) => {
|
||||
debug!("Write thread: returning due to {:?}", e);
|
||||
return;
|
||||
}
|
||||
Ok(reply_packet) => {
|
||||
if let Err(e) = stream.serialize(&reply_packet) {
|
||||
warn!("Writer: failed to write reply to Client: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides methods for blocking until the server completes,
|
||||
pub struct ServeHandle {
|
||||
tx: Sender<()>,
|
||||
join_handle: JoinHandle<()>,
|
||||
addr: SocketAddr,
|
||||
}
|
||||
|
||||
impl ServeHandle {
|
||||
/// Block until the server completes
|
||||
pub fn wait(self) {
|
||||
self.join_handle.join().expect(pos!());
|
||||
}
|
||||
|
||||
/// Returns the address the server is bound to
|
||||
pub fn local_addr(&self) -> &SocketAddr {
|
||||
&self.addr
|
||||
}
|
||||
|
||||
/// Shutdown the server. Gracefully shuts down the serve thread but currently does not
|
||||
/// gracefully close open connections.
|
||||
pub fn shutdown(self) {
|
||||
info!("ServeHandle: attempting to shut down the server.");
|
||||
self.tx.send(()).expect(pos!());
|
||||
if let Ok(_) = TcpStream::connect(self.addr) {
|
||||
self.join_handle.join().expect(pos!());
|
||||
} else {
|
||||
warn!("ServeHandle: best effort shutdown of serve thread failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Server<'a, S: 'a> {
|
||||
server: &'a S,
|
||||
listener: TcpListener,
|
||||
read_timeout: Option<Duration>,
|
||||
die_rx: Receiver<()>,
|
||||
shutdown: &'a AtomicBool,
|
||||
}
|
||||
|
||||
impl<'a, S: 'a> Server<'a, S>
|
||||
where S: Serve + 'static
|
||||
{
|
||||
fn serve<'b>(self, scope: &Scope<'b>) where 'a: 'b {
|
||||
for conn in self.listener.incoming() {
|
||||
match self.die_rx.try_recv() {
|
||||
Ok(_) => {
|
||||
info!("serve: shutdown received.");
|
||||
return;
|
||||
}
|
||||
Err(TryRecvError::Disconnected) => {
|
||||
info!("serve: shutdown sender disconnected.");
|
||||
return;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
let conn = match conn {
|
||||
Err(err) => {
|
||||
error!("serve: failed to accept connection: {:?}", err);
|
||||
return;
|
||||
}
|
||||
Ok(c) => c,
|
||||
};
|
||||
if let Err(err) = conn.set_read_timeout(self.read_timeout) {
|
||||
info!("serve: could not set read timeout: {:?}", err);
|
||||
continue;
|
||||
}
|
||||
let read_conn = match conn.try_clone() {
|
||||
Err(err) => {
|
||||
error!("serve: could not clone tcp stream; possibly out of file descriptors? \
|
||||
Err: {:?}",
|
||||
err);
|
||||
continue;
|
||||
}
|
||||
Ok(conn) => conn,
|
||||
};
|
||||
let mut handler = ConnectionHandler {
|
||||
read_stream: BufReader::new(read_conn),
|
||||
write_stream: BufWriter::new(conn),
|
||||
server: self.server,
|
||||
shutdown: self.shutdown,
|
||||
};
|
||||
scope.recurse(move |scope| {
|
||||
scope.zoom(|scope| {
|
||||
if let Err(err) = handler.handle_conn(scope) {
|
||||
info!("ConnectionHandler: err in connection handling: {:?}", err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S> Drop for Server<'a, S> {
|
||||
fn drop(&mut self) {
|
||||
debug!("Shutting down connection handlers.");
|
||||
self.shutdown.store(true, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
/// Start
|
||||
pub fn serve_async<A, S>(addr: A,
|
||||
server: S,
|
||||
read_timeout: Option<Duration>)
|
||||
-> io::Result<ServeHandle>
|
||||
where A: ToSocketAddrs,
|
||||
S: 'static + Serve
|
||||
{
|
||||
let listener = try!(TcpListener::bind(&addr));
|
||||
let addr = try!(listener.local_addr());
|
||||
info!("serve_async: spinning up server on {:?}", addr);
|
||||
let (die_tx, die_rx) = channel();
|
||||
let join_handle = thread::spawn(move || {
|
||||
let pool = Pool::new(100); // TODO(tjk): make this configurable, and expire idle threads
|
||||
let shutdown = AtomicBool::new(false);
|
||||
let server = Server {
|
||||
server: &server,
|
||||
listener: listener,
|
||||
read_timeout: read_timeout,
|
||||
die_rx: die_rx,
|
||||
shutdown: &shutdown,
|
||||
};
|
||||
pool.scoped(|scope| {
|
||||
server.serve(scope);
|
||||
});
|
||||
});
|
||||
Ok(ServeHandle {
|
||||
tx: die_tx,
|
||||
join_handle: join_handle,
|
||||
addr: addr.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// A service provided by a server
|
||||
pub trait Serve: Send + Sync {
|
||||
/// The type of request received by the server
|
||||
type Request: 'static + fmt::Debug + serde::ser::Serialize + serde::de::Deserialize + Send;
|
||||
/// The type of reply sent by the server
|
||||
type Reply: 'static + fmt::Debug + serde::ser::Serialize + serde::de::Deserialize + Send;
|
||||
|
||||
/// Return a reply for a given request
|
||||
fn serve(&self, request: Self::Request) -> Self::Reply;
|
||||
}
|
||||
|
||||
impl<P, S> Serve for P
|
||||
where P: Send + Sync + ::std::ops::Deref<Target=S>,
|
||||
S: Serve
|
||||
{
|
||||
type Request = S::Request;
|
||||
type Reply = S::Reply;
|
||||
|
||||
fn serve(&self, request: S::Request) -> S::Reply {
|
||||
S::serve(self, request)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
[package]
|
||||
name = "tarpc_examples"
|
||||
version = "0.1.0"
|
||||
authors = ["Adam Wright <adam.austin.wright@gmail.com>", "Tim Kuehn <timothy.j.kuehn@gmail.com>"]
|
||||
|
||||
[dev-dependencies]
|
||||
tarpc = { path = "../tarpc" }
|
||||
lazy_static = "^0.1.15"
|
||||
env_logger = "^0.3.2"
|
||||
@@ -1,73 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
#![cfg_attr(test, feature(test))]
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate tarpc;
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(dead_code)] // generated Client isn't used in this benchmark
|
||||
mod benchmark {
|
||||
extern crate env_logger;
|
||||
extern crate test;
|
||||
|
||||
use tarpc::ServeHandle;
|
||||
use self::test::Bencher;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
service! {
|
||||
rpc hello(s: String) -> String;
|
||||
}
|
||||
|
||||
struct HelloServer;
|
||||
impl Service for HelloServer {
|
||||
fn hello(&self, s: String) -> String {
|
||||
format!("Hello, {}!", s)
|
||||
}
|
||||
}
|
||||
|
||||
// Prevents resource exhaustion when benching
|
||||
lazy_static! {
|
||||
static ref HANDLE: Arc<Mutex<ServeHandle>> = {
|
||||
let handle = serve("localhost:0", HelloServer, None).unwrap();
|
||||
Arc::new(Mutex::new(handle))
|
||||
};
|
||||
static ref CLIENT: Arc<Mutex<AsyncClient>> = {
|
||||
let addr = HANDLE.lock().unwrap().local_addr().clone();
|
||||
let client = AsyncClient::new(addr, None).unwrap();
|
||||
Arc::new(Mutex::new(client))
|
||||
};
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn hello(bencher: &mut Bencher) {
|
||||
let _ = env_logger::init();
|
||||
let client = CLIENT.lock().unwrap();
|
||||
let concurrency = 100;
|
||||
let mut futures = Vec::with_capacity(concurrency);
|
||||
let mut count = 0;
|
||||
bencher.iter(|| {
|
||||
futures.push(client.hello("Bob".into()));
|
||||
count += 1;
|
||||
if count % concurrency == 0 {
|
||||
// We can't block on each rpc call, otherwise we'd be
|
||||
// benchmarking latency instead of throughput. It's also
|
||||
// not ideal to call more than one rpc per iteration, because
|
||||
// it makes the output of the bencher harder to parse (you have
|
||||
// to mentally divide the number by `concurrency` to get
|
||||
// the ns / iter for one rpc
|
||||
for f in futures.drain(..) {
|
||||
f.get().unwrap();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
BIN
test/identity.p12
Normal file
BIN
test/identity.p12
Normal file
Binary file not shown.
BIN
test/root-ca.der
Normal file
BIN
test/root-ca.der
Normal file
Binary file not shown.
21
test/root-ca.pem
Normal file
21
test/root-ca.pem
Normal file
@@ -0,0 +1,21 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDXTCCAkWgAwIBAgIJAOIvDiVb18eVMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
|
||||
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
|
||||
aWRnaXRzIFB0eSBMdGQwHhcNMTYwODE0MTY1NjExWhcNMjYwODEyMTY1NjExWjBF
|
||||
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
|
||||
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
|
||||
CgKCAQEArVHWFn52Lbl1l59exduZntVSZyDYpzDND+S2LUcO6fRBWhV/1Kzox+2G
|
||||
ZptbuMGmfI3iAnb0CFT4uC3kBkQQlXonGATSVyaFTFR+jq/lc0SP+9Bd7SBXieIV
|
||||
eIXlY1TvlwIvj3Ntw9zX+scTA4SXxH6M0rKv9gTOub2vCMSHeF16X8DQr4XsZuQr
|
||||
7Cp7j1I4aqOJyap5JTl5ijmG8cnu0n+8UcRlBzy99dLWJG0AfI3VRJdWpGTNVZ92
|
||||
aFff3RpK3F/WI2gp3qV1ynRAKuvmncGC3LDvYfcc2dgsc1N6Ffq8GIrkgRob6eBc
|
||||
klDHp1d023Lwre+VaVDSo1//Y72UFwIDAQABo1AwTjAdBgNVHQ4EFgQUbNOlA6sN
|
||||
XyzJjYqciKeId7g3/ZowHwYDVR0jBBgwFoAUbNOlA6sNXyzJjYqciKeId7g3/Zow
|
||||
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAVVaR5QWLZIRR4Dw6TSBn
|
||||
BQiLpBSXN6oAxdDw6n4PtwW6CzydaA+creiK6LfwEsiifUfQe9f+T+TBSpdIYtMv
|
||||
Z2H2tjlFX8VrjUFvPrvn5c28CuLI0foBgY8XGSkR2YMYzWw2jPEq3Th/KM5Catn3
|
||||
AFm3bGKWMtGPR4v+90chEN0jzaAmJYRrVUh9vea27bOCn31Nse6XXQPmSI6Gyncy
|
||||
OAPUsvPClF3IjeL1tmBotWqSGn1cYxLo+Lwjk22A9h6vjcNQRyZF2VLVvtwYrNU3
|
||||
mwJ6GCLsLHpwW/yjyvn8iEltnJvByM/eeRnfXV6WDObyiZsE/n6DxIRJodQzFqy9
|
||||
GA==
|
||||
-----END CERTIFICATE-----
|
||||
Reference in New Issue
Block a user