202 Commits

Author SHA1 Message Date
Tim
f0ad99b900 Prepare for Crates.io release (#140)
* Prepare tarpc-plugins for Crates.io release.

* Add a 0.7 bullet to RELEASES.md.

* Fix some outdated doc comments.

* Reexport ShutdownFuture.
2017-03-31 14:15:20 -07:00
Tim
5add81b5f3 Feature rollup (#129)
* Create a directory for the `future::server` module, which has become quite large. server.rs => server/mod.rs. Server submodules for shutdown and connection logic are added.

* Add fn thread_pool(...) to sync::server::Options

* Configure idle threads to expire after one minute

* Add tarpc::util::lazy for lazily executing functions. Similar to `futures::lazy` but useful in different circumstances. Specifically, `futures::lazy` typically requires a closure, whereas `util::lazy` kind of deconstructs a closure into its function and args.

* Remove some unstable features, and `cfg(plugin)` only in tests. Features `unboxed_closures` and `fn_traits` are removed by replacing manual Fn impls with Stream impls. This actually leads to slightly more performant code, as well, because some `Rc`s could be removed.

* Fix tokio deprecation warnings. Update to use tokio-io in lieu of deprecated tokio-core items. impl AsyncRead's optional `unsafe fn prepare_uninitialized_buffer` for huge perf wins

* Add debug impls to all public items and add `deny(missing_debug_implementations)` to the crate.

* Bump tokio core version.
2017-03-31 12:16:40 -07:00
Tim
15080b2889 Set a default max packet size. (#128)
The default max packet size of 2 << 20
2017-03-23 00:08:08 -07:00
Jon Gjengset
8598d4beaf Fix following removal of parse_ty_path (#138)
The merge of rust-lang/rust#40043 removes parse_ty_path in the latest
nightly, which we depended on. This patch rewrites that code path using
parse_path, and in the process eliminates an unreachable!() if let arm.
2017-03-22 21:20:03 -07:00
Tim
40faf25d99 Fix vulnerability. (#126)
0 is a sentinel value used to make all enums refutable. This is a hack around issues in maros
where you're unknowingly treating irrefutable patterns as refutable, which is unfortunately
a hard error.

The server panics if it ever encountered the 0-variant, which before this patch was possible. Now,
it's not possible, because 0-variants are now not able to be deserialized.
2017-03-22 18:17:37 -07:00
Jon Gjengset
c8be9b690b Fix following removal of Attribute.value (#136)
Since rust-lang/rust#40346 has now been merged, Attribute no longer has
a .value field. Instead, we must follow the token stream and modify the
tokens directly. For Docstring attributes, there should only be one
token, the docstring value.
2017-03-21 11:00:52 -07:00
Malte Schwarzkopf
f4018a431e bincode 1.0.0-alpha6 changed SizeLimit to a trait (#134)
Unfortunately, cargo's semantic versioning gets confused by the
"-alpha" suffix in current bincode versions (I think): even though
tarpc's Cargo.toml specified version "1.0.0-alpha4", cargo will
download the more recent "1.0.0-alpha6", which has breaking changes
to the `SizeLimit` enum.

This change makes tarpc work with bincode 1.0.0-alpha6 and updates the
dependency.
2017-03-19 11:12:17 -07:00
Tim
79aee18d17 Change module structure. (#122)
* Change `client::{future, sync}, server::{future, sync}` to `future::{client, server}, sync::{client, server}`

This reflects the most common usage pattern and allows for some types to not need to be fully qualified when used together (e.g. the previously-named `client::future::Options` and `server::future::Options` can now be `client::Options` and `server::Options`).

* sync::client: create a RequestHandler struct to encapsulate logic of processing client requests.

The largest benefit is that unit testing becomes easier, e.g. testing that the request processing stops when all request senders are dropped.

* Rename Serialize error variants to make sense.

* Rename tarpc_service_ConnectFuture__ => Connect (because it's public)

* Use tokio proto's ClientProxy.

Rather than reimplement the same logic. Curiously, this commit
isn't a net loss in LOC. Oh well.

* Factor out os-specific functionality in listener() into their own fns

* Remove service-fn dep
2017-03-07 18:20:46 -08:00
Tim
f9ff2c4e50 Merge pull request #121 from tikue/send-client
Make SyncClient Send again (and Clone too!).
2017-03-06 22:12:33 -08:00
Tim Kuehn
073bc25e18 Derive debug rather than manually impl 2017-03-06 21:12:44 -08:00
Tim Kuehn
d0d65c413a Make Client Send again (and Clone too!).
The basic strategy is to start a reactor on a dedicated thread running a request stream.
Requests are spawned onto the reactor, allowing multiple requests to be
processed concurrently. For example, if you clone the client to make requests
from multiple threads, they won't have to wait for each others'
requests to complete before theirs start being sent out.

Also, client rpcs only take &self now, which was also required for
clients to be usable in a service.

Also added a test to prevent regressions.
2017-03-06 21:12:44 -08:00
Tim
e59116fb48 Add server::Handle::shutdown (#117)
* Add server::Handle::shutdown
* Hybrid approach: lameduck + total shutdown when all clients disconnect.
* The future handle has addr() and shutdown(), but not run().
2017-03-06 20:57:12 -08:00
Tim
daa96a69a2 Latency benchmark: drive the client with the same reactor as server (so it's entirely single-threaded) (#116) 2017-02-22 21:46:28 -08:00
Tim
85ae614983 Add a test that dropping a client doesn't break the server (#115) 2017-02-22 21:12:52 -08:00
Adam Wright
e17549a1f7 Merge pull request #114 from shaladdle/fix-throughput
Fix throughput example
2017-02-21 22:39:51 -08:00
Adam Wright
0b8a845ec1 Actually run the server future 2017-02-21 22:24:05 -08:00
Tim
2b8f3db1fd Return a concrete type from server::listen (#113)
* Return a concrete type from `server::listen`.
* Change `FutureServiceExt::listen` to return `(SocketAddr, Listen)`, where `Listen` is a struct created by the `service!` macro that `impls Future<Item=(),  Error=()>` and represents server execution.
* Disable `conservative_impl_trait` as it's no longer used.
* Update `FutureServiceExt` doc comment.
* Update `SyncServiceExt` doc comment. Also annotate `server::Handle` with `#[must_use]`.
* `cargo fmt`
2017-02-21 22:01:59 -08:00
Adam Wright
44792347b1 Change sync listen to return a handle which runs the server (#112) 2017-02-21 09:50:01 -08:00
Adam Wright
f7c371930f Merge pull request #110 from shaladdle/prepush
Prepush script improvement/simplification
2017-02-20 18:43:51 -08:00
Adam Wright
3eec8fe1dd Actually fail the prepush in try_run 2017-02-20 18:10:23 -08:00
Adam Wright
9c973eb80b Switch back to verb_ing_ 2017-02-20 18:07:36 -08:00
Adam Wright
37763f25e4 Make whitespace consistent 2017-02-20 17:46:13 -08:00
Adam Wright
515ab90299 Various simplifications/improvements to pre-push 2017-02-20 17:44:30 -08:00
Adam Wright
9987eae290 --color=always has to be after the command 2017-02-20 17:11:05 -08:00
Adam Wright
22545c653c Fix prepush script? 2017-02-20 17:11:05 -08:00
Cyril Plisko
8b9847e347 Update chrono (#111) 2017-02-20 13:05:47 -08:00
Adam Wright
49bc4d0bce Merge pull request #109 from shaladdle/formatting
Run cargo fmt
2017-02-18 22:37:27 -08:00
Adam Wright
0780af9e05 Run cargo fmt 2017-02-18 15:37:31 -08:00
Adam Wright
bc16ffd2d0 Merge pull request #108 from tikue/doc-fix
Fix readme, but for real this time.
2017-02-16 21:08:01 -08:00
Tim Kuehn
c7831e8aa6 More spacing stuff 2017-02-16 20:56:04 -08:00
Tim Kuehn
8faba59d66 Fix tabs in pre-push, and remove dead code in readme 2017-02-16 20:51:57 -08:00
Tim Kuehn
bceaea1206 .travis.yml: Do tls tests, then rustdoc tests, then regular tests 2017-02-16 20:18:46 -08:00
Tim Kuehn
fea8d5eb1d I hate READMEs. 2017-02-16 17:57:17 -08:00
Tim Kuehn
db9c23058d Dumb thing 2017-02-16 17:53:59 -08:00
Tim Kuehn
77638b388d Have travis test README with rustdoc. Yay! 2017-02-16 17:53:06 -08:00
Tim Kuehn
eac6b64aeb Merge branch 'master' of github.com:google/tarpc into doc-fix 2017-02-16 17:52:21 -08:00
Tim Kuehn
7ae107cf2b Fix readme for real this time. 2017-02-16 17:46:55 -08:00
Adam Wright
12efcae80c Merge pull request #107 from tikue/master
Fix README future service examples
2017-02-16 16:22:48 -08:00
Adam Wright
5fbd45ea49 Merge branch 'master' into master 2017-02-16 14:43:20 -08:00
Tim Kuehn
e651838f19 remove unused thing 2017-02-16 13:41:13 -08:00
Adam Wright
cdb44090bd Merge pull request #106 from tikue/pre-push-bench
Add bench to pre-push and .travis.yml
2017-02-16 12:43:19 -08:00
Tim Kuehn
fc2edc89af Fix README future service examples 2017-02-16 12:07:49 -08:00
Tim Kuehn
30eed41c40 Unused imports 2017-02-16 01:04:40 -08:00
Tim Kuehn
517477129c Remove dead code 2017-02-16 01:00:40 -08:00
Tim Kuehn
1325d1a2d7 Merge branch 'master' of github.com:google/tarpc into pre-push-bench 2017-02-16 00:59:34 -08:00
Adam Wright
15f83961e9 Merge pull request #98 from tikue/into-future
Change bound on FutureService's associated types
2017-02-16 00:58:06 -08:00
Tim Kuehn
4fc37fe707 Add bench to pre-push and .travis.yml 2017-02-16 00:56:38 -08:00
Tim Kuehn
8ee6ce0307 Update README 2017-02-16 00:51:55 -08:00
Tim Kuehn
07f9d5a34d Merge branch 'master' of github.com:google/tarpc into into-future 2017-02-16 00:50:08 -08:00
Adam Wright
a0a11f8704 Merge pull request #97 from tikue/crates-io-categories
Add categories to Cargo.toml.
2017-02-16 00:46:55 -08:00
Tim Kuehn
6673869721 Merge branch 'master' of github.com:google/tarpc into tikue/crates-io-categories 2017-02-16 00:40:11 -08:00
Tim Kuehn
63caacf0c1 Merge branch master into tikue/into-future. 2017-02-16 00:37:19 -08:00
Tim
2c09a35705 Remove the Send bound from FutureService (#96)
* Make a reactor handle mandatory for server.

This removes the Send bound from FutureService. The Send bound
is still required for SyncService, since clones are sent to
new threads for each request. (This is more fodder for the argument
that there should be a distinct Options struct for each combination of
async/sync and client/server.)

This commit also makes FutureService::listen return an io::Result
rather than a Future; the future was never really necessary and
had the unintended consequence of making SyncService::listen
deadlock when the options specified a handle (because that means
the reactor driving the service lives on the same thread that
SyncService is waiting on).

`SyncClient` is no longer `Clone` because it needs to create
a new `reactor::Core` when cloning. Tokio Clients are `Clone` but
they don't allow moving the cloned client onto a new reactor.

* Change pubsub to use Rc<Refcell<>> instead of Arc<Mutex<>>.

This is possible since services no longer need to be Send.

* Remove some unnecessary unstable features.

There 3 remaining unstable features. The hardest to remove is plugin, because
we rely on compiler plugins to rewrite types from snake case to camel. It's
possible this can be removed before the proc macros rewrite lands if
impl Trait is extended to work with traits.

* Clean up example

* Sync servers now spawn a reactor on a thread. It's decided that
   sync users should not have to know about tokio at all.

* Don't allow specifying a reactor::Core on client options.

* Fail fast in server::listen if local_addr() returns Err.
2017-02-15 23:47:35 -08:00
Adam Wright
6bf4d171c1 Merge pull request #99 from tikue/deps-cleanup
Remove unused deps: bytes, take, and scoped-pool
2017-02-15 20:57:08 -08:00
Adam Wright
0f52b08426 Merge branch 'master' into deps-cleanup 2017-02-15 20:49:53 -08:00
Cyril Plisko
acdf03c8ca Fix compilation error for benches (#101) 2017-02-15 09:26:57 -08:00
Adam Wright
b2f69faa13 Merge pull request #92 from tikue/sync-reactor
Change SyncClient to drive its own execution with an internal reactor::Core.
2017-02-13 21:37:49 -08:00
Tim Kuehn
2749d33f88 Merge master into sync-reactor. 2017-02-13 21:29:39 -08:00
compressed
338c91d393 fix(bincode): updates to support bincode 1.0.0-alpha2 (#100)
Removed:
- `Error::ClientDeserialize` variant
- `Error::ServerDeserialize` variant
- `WireError::ServerDeserialize` variant
2017-02-12 14:52:38 -08:00
Tim Kuehn
fa2df184e9 Remove unused deps: bytes, take, and scoped-pool 2017-02-07 20:34:52 -08:00
Tim Kuehn
fe4eab38f1 Change FutureService's associated types to be bounded by IntoFuture rather than Future.
It's strictly more flexible, because everything that impls Future impls IntoFuture, and it
additionally allows returning types like Result. Which is nice.
2017-02-07 19:58:29 -08:00
Tim Kuehn
06beec3e5a Add keywords that aren't terrible 2017-02-06 19:24:35 -08:00
Tim Kuehn
d39a382012 Add travis-ci repository to Cargo.toml 2017-02-06 19:24:35 -08:00
Tim Kuehn
efcc914e65 Add categories to Cargo.toml.
Also remove a keyword because 5's the limit.
2017-02-06 19:24:34 -08:00
compressed
a1072c8c06 Upgrade serde to 0.9 (#94)
* feat(serde): upgrade serde to 0.9

If you are using `#[derive(Serialize, Deserialize)]` or implementing
your own (de)serialization behavior for your RPC types you will need to
ensure you are using serde 0.9.x.

[breaking-change]

* chore(byteorder): upgrade byteorder to 1.0
2017-02-02 13:36:19 -08:00
Tim Kuehn
ed90f4ecea Merge master into sync-reactor 2017-02-01 22:48:49 -08:00
Tim Kuehn
b5bf696017 Merge master into sync-reactor 2017-02-01 22:39:19 -08:00
Tim Kuehn
fe20c8af14 Add a reactor::Core field to SyncClient.
This allows the client to drive its own execution, as one would expect.
Previously, the reactor had to be driven on a separate thread, which was confusing.
This has a couple notable side effects:
  1. SyncClient is no longer `Clone`.  This is because `reactor::Core`
     is not `Clone`, and creating one is not infallible
     (`Core::new` returns a `Result`).
  2. SyncClient does not use the user-specified `client::Options::handle` or
     `client::Options::remote`, because it constructs its own reactor.
2017-02-01 22:32:25 -08:00
Adam Wright
2ffa1138dd Merge pull request #91 from tikue/master
Temporary workaround for compiler bugs in impl Trait feature.
2017-02-01 21:05:33 -08:00
Tim Kuehn
9d552e48a4 Temporary workaround for compiler bugs in impl Trait feature. 2017-02-01 15:52:30 -08:00
compressed
fafe569ebc Add TLS support (#81)
When `-- features tls` is specified for tarpc, RPC communication can
also occur over a `TlsStream<TcpStream>` instead of a `TcpStream`.

* The functional tests have been refactored to use a common set of
functions for constructing the client and server structs so that all
the tests are shared across non-tls and tls test runs.

* Update pre-push to test TLS

* The `cfg_attr` logic caused many false warnings from clippy, so for now the crate docs for TLS are not tested.
2017-01-31 10:21:13 -08:00
Adam Wright
c286c596bd Merge pull request #85 from tikue/master
Make port reusable
2017-01-30 22:46:29 -08:00
Tim Kuehn
348111a423 Add test for reusing addr:port 2017-01-30 17:11:31 -08:00
Tim Kuehn
984c1c29c5 Make port reusable 2017-01-30 15:51:22 -08:00
Adam Wright
cc1290636d Merge pull request #82 from tikue/master
Make sure synchronous RPCs are wrapped in a lazy future.
2017-01-23 00:48:10 -08:00
Tim Kuehn
41683eee1d Change variable obfuscation style from prefix underscores to suffix underscores. 2017-01-22 22:41:06 -08:00
Tim Kuehn
6c2239d6f2 Cargo clippy 2017-01-22 21:16:11 -08:00
Tim Kuehn
3196fd91ff Cargo fmt 2017-01-22 20:08:38 -08:00
Tim Kuehn
45fa4c7bf1 Make sure synchronous RPCs are wrapped in a lazy future.
Some future-returning fns implicitly require the presence of an execution task. Wrapping in a lazy future ensures that by the time the future is polled, there is a task present.
2017-01-22 19:39:19 -08:00
Adam Wright
c7c18cbaaa Merge pull request #78 from tikue/master
Simplify Never impls: return the inner uninhabited type for fns taking &self.
2017-01-22 15:31:43 -08:00
Adam Wright
1c0cf2a67f Merge branch 'master' into master 2017-01-22 15:27:45 -08:00
Adam Wright
15a3900f3d Merge pull request #79 from tikue/private-items
Make private a couple items that no longer need to be public.
2017-01-22 15:23:59 -08:00
Adam Wright
f0ecd7008d Merge branch 'master' into private-items 2017-01-22 15:13:32 -08:00
Adam Wright
3567202aa3 Merge pull request #80 from tikue/client-reexports
Don't reexport client implementation details from crate root.
2017-01-22 15:13:22 -08:00
Tim Kuehn
802ee838ca Don't reexport client implementation details from crate root. 2017-01-17 16:38:30 -08:00
Tim Kuehn
c976ca710a Make private a couple items that no longer need to be public. 2017-01-17 15:36:08 -08:00
Tim Kuehn
9d2d69b4f4 Simplify Never impls: return the inner uninhabited type for fns taking &self. 2017-01-17 15:25:48 -08:00
Adam Wright
558dda28ad Merge pull request #75 from tikue/master
Add Options to all connect and listen fns
2017-01-15 17:21:30 -08:00
Tim
4a4ffab611 Merge branch 'master' into master 2017-01-15 17:07:18 -08:00
Tim Kuehn
cabbbb2a0b Fix some doc comments and remove unused impl. 2017-01-15 17:00:04 -08:00
compressed
3dc1b6381d fix(futures): Either was only added in 0.1.7 (#76) 2017-01-13 15:21:07 -08:00
Adam Wright
c96ab77dcf Merge pull request #74 from shaladdle/docs
First pass at some more detailed documentation
2017-01-13 15:15:09 -08:00
Adam Wright
865712f36e First pass at some more detailed documentation 2017-01-12 20:11:03 -08:00
Tim Kuehn
a8e5bc45a1 Minor refactor 2017-01-12 00:31:46 -08:00
Tim Kuehn
95c57a4b2d Add Options to all connect and listen fns 2017-01-12 00:06:17 -08:00
Adam Wright
ab3e73812c Merge pull request #73 from tikue/master
Remove readme_expanded
2017-01-11 23:32:51 -08:00
Tim Kuehn
d34ca2acda Remove readme_expanded 2017-01-11 23:29:19 -08:00
Adam Wright
9e92666932 Merge pull request #72 from tikue/master
Fix readme examples
2017-01-11 23:29:06 -08:00
Tim Kuehn
a6b25dc268 Fix readme examples 2017-01-11 23:14:52 -08:00
Adam Wright
77e12f56cc Merge pull request #71 from tikue/master
Rework the future Connect trait to only have one method, which takes …
2017-01-11 23:03:58 -08:00
Tim Kuehn
05c6be192d Rework the future Connect trait to only have one method, which takes an Options arg. 2017-01-11 22:55:04 -08:00
Adam Wright
568484f14f Merge pull request #70 from tikue/master
Make spawn_core private
2017-01-11 21:00:15 -08:00
Tim Kuehn
918b6b3b75 Make spawn_core private 2017-01-11 20:44:59 -08:00
Adam Wright
d5854fd049 Merge pull request #69 from tikue/remove-error-trait
Remove unnecessary trait
2017-01-11 20:41:48 -08:00
Adam Wright
9646d92cae Merge pull request #68 from tikue/master
Remove unused macro code
2017-01-11 20:27:46 -08:00
Tim Kuehn
a660ed7f1a Remove unnecessary trait 2017-01-11 20:27:17 -08:00
Tim Kuehn
3eb2292841 Remove unused code 2017-01-11 20:22:52 -08:00
Adam Wright
5a525d9fb7 Merge pull request #67 from tikue/real-crates
Update Cargo.toml to use crates.io releases of tokio deps.
2017-01-11 18:01:39 -08:00
Tim Kuehn
e4ef0881e6 Update Cargo.toml to use crates.io releases of tokio deps. 2017-01-11 14:23:52 -08:00
Adam Wright
91e9ad3001 Merge pull request #63 from tikue/tokio-tracking
Track more changes to tokio
2017-01-09 13:05:15 -08:00
Tim Kuehn
b3c187cdac Remove commented out code 2017-01-08 22:14:26 -08:00
Tim Kuehn
ffea090726 Make SyncClient only require &self for RPCs. 2017-01-08 22:13:49 -08:00
Tim Kuehn
e8f942f463 Merge master into tokio-tracking. 2017-01-08 22:05:10 -08:00
Adam Wright
5e9527e583 Merge pull request #61 from tikue/proto-changes
Track the changes to tokio-proto/master
2017-01-08 21:45:20 -07:00
Adam Wright
48452c04a6 Remove unecessary unreachable! calls (#8) 2017-01-08 20:36:28 -08:00
Tim Kuehn
626254e836 Update tokio-core replacement to 0.1.3 2017-01-08 17:49:41 -08:00
Tim Kuehn
3719564efc Fix stale comment 2017-01-08 17:38:05 -08:00
Tim Kuehn
ef41d4349c Make connection backlog arg to listen a const 2017-01-08 17:34:44 -08:00
Tim Kuehn
200407a4c9 Make connection backlog arg to listen a const 2017-01-08 17:30:31 -08:00
Tim Kuehn
388c4be69a Fully commit to futures in hello world. 2016-12-29 12:08:31 +01:00
Tim Kuehn
77653282b6 Fix latency bench 2016-12-28 18:29:56 +01:00
Tim Kuehn
5dbfa99d0b Shuffle around the sync/future modules 2016-12-26 16:02:44 -05:00
Tim Kuehn
18cbbe5b15 impl Clone (again) for the clients 2016-12-26 15:30:36 -05:00
Tim Kuehn
e22210bfd8 Rename module framed -> protocol, and clarify some type parameters. 2016-12-26 14:54:31 -05:00
Tim Kuehn
d242bdbb82 Track latest tokio changes 2016-12-26 00:27:42 -05:00
Tim Kuehn
bdd6737914 Add a test for concurrent requests 2016-12-16 14:22:25 -08:00
Tim Kuehn
f2bf1adf8b Fix bug wherein the Codec was clearing the buf after decoding a message. Don't do thatgit stash pop! 2016-12-16 14:15:31 -08:00
Tim Kuehn
be156f4d6b Cut down size of readme_expanded. 2016-12-11 23:32:44 -08:00
Tim Kuehn
35f8aefb30 Small refactor 2016-12-11 16:43:41 -08:00
Tim Kuehn
8a29aa29b2 Add an version of the readme where all macros are expanded (with slight modifications to make it prettier). 2016-12-09 22:26:04 -08:00
Tim Kuehn
b5750365ca Change readme example to exhibit deadlock... 2016-12-06 15:15:42 -08:00
Tim Kuehn
f6b1660092 Count requests in concurrency example 2016-12-05 16:06:58 -08:00
Tim Kuehn
5c17ffacae Add listen_with fns. 2016-12-05 15:23:43 -08:00
Tim Kuehn
13e56481bb Track latest changes to tokio-proto. 2016-12-04 20:26:32 -08:00
Tim Kuehn
608be5372b Try to fix concurrency example 2016-11-05 19:31:45 -07:00
Adam Wright
583f2cd92f Merge pull request #58 from tikue/framed
Fixes to readme
2016-11-05 17:14:06 -07:00
Tim Kuehn
ef8deb2059 Merge branch 'master' of github.com:google/tarpc into framed 2016-11-05 16:51:24 -07:00
Tim Kuehn
c437d66603 Fixes to readme 2016-11-05 16:47:46 -07:00
Adam Wright
662a627e4e Merge pull request #55 from tikue/framed
Replace handrolled transport with higher-level `Framed` construct
2016-11-05 16:42:55 -07:00
Tim Kuehn
d47a931f9f Elaborate in the doc comment for Framed 2016-11-05 16:33:41 -07:00
Tim Kuehn
a30e929b63 Clarify some docs and fix some dumb code. 2016-11-05 15:43:31 -07:00
Tim Kuehn
b638f45d27 Add a method to util::FirstSocketAddr that returns a Result rather than panicking 2016-11-05 15:43:09 -07:00
Tim Kuehn
68ba7505ac Update naming of publisher client and server in pubsub example 2016-11-05 15:22:17 -07:00
Tim Kuehn
3afcfe6274 Track latest git deps and rust compiler 2016-11-05 15:06:39 -07:00
Tim Kuehn
85fbe411e6 Run clients in parallel (that's the whole point!). 2016-10-29 10:16:21 -07:00
Tim Kuehn
aaaaf942d6 Add future::Connect::connect_remotely. 2016-10-29 10:15:39 -07:00
Tim Kuehn
29b6425fb5 Add a util fn for running a reactor forever on a new thread. 2016-10-29 10:15:15 -07:00
Tim Kuehn
539776eb27 Make future::Connect take a lifetime param so that connect_with doesn't have to clone Handle. 2016-10-29 09:38:43 -07:00
Tim Kuehn
61e7d9aab8 Remove some logging and thread yielding. 2016-10-29 02:03:35 -07:00
Tim Kuehn
59988c6ee1 Initialize env_logger in example server_calling_server. 2016-10-29 02:02:33 -07:00
Tim Kuehn
a1de4c1b05 Re-work concurrency example to not use wait() everywhere. 2016-10-29 02:02:11 -07:00
Tim Kuehn
b6e9d61286 Add futures::Connect::connect_with, allowing the user to specify the reactor core to use. 2016-10-29 02:01:40 -07:00
Tim Kuehn
67ad2b90be Track nightly compiler 2016-10-28 16:13:59 -07:00
Tim Kuehn
3506397150 Track latest tokio changes. 2016-10-28 11:33:20 -07:00
Tim Kuehn
db665ebb60 Bump itertools from 0.4 => 0.5 2016-10-27 19:07:54 -07:00
Tim Kuehn
cff8782e18 Replace try! with ? 2016-10-16 13:49:53 -07:00
Tim Kuehn
531dc20d66 Track nightly 2016-10-16 13:19:35 -07:00
Adam Wright
d8d240ec12 Add docs to play nicer with deny(misisng_docs) (#6) 2016-10-10 12:25:28 -04:00
Adam Wright
eb49c30fdb Add an example to the readme for futures (#4)
And add the example under examples/ so we know when it breaks.
2016-10-04 16:25:57 -04:00
Tim Kuehn
b661ff0175 Complete ClientFuture on error. 2016-09-30 15:36:55 -04:00
Tim Kuehn
b880d65f44 Merge branch 'framed' of github.com:tikue/tarpc into framed 2016-09-30 15:19:16 -04:00
Tim Kuehn
451b99b92a Remove all remaining #[inline]s. 2016-09-30 15:16:29 -04:00
Tim Kuehn
99b13ae6fc Remove an unnecessary Box 2016-09-28 00:04:58 -07:00
Tim Kuehn
5bace01f2b Finish the multiplex implementation 2016-09-26 23:44:22 -07:00
Tim Kuehn
4a63064cbd Remove some panics, and don't use ToSocketAddrs in async methods. 2016-09-19 00:16:47 -07:00
Tim Kuehn
3eb57d4009 Move the static remote to the default reactor core to the crate root.
It didn't really make sense in the framed module, which doesn't care about such things.
2016-09-17 18:31:08 -07:00
Tim Kuehn
14c97b61f9 Rename things to align with tokio changes. 2016-09-17 18:23:35 -07:00
Tim Kuehn
20d1a019ae WIP multiplex Parse/Serialize/FramedIo impls 2016-09-17 12:50:48 -07:00
Tim Kuehn
8c0181633d Track crates.io deps 2016-09-14 10:11:28 -07:00
Tim Kuehn
987e445935 Update benches 2016-09-14 01:57:03 -07:00
Tim Kuehn
1c318182c4 Don't serialize on a thread pool 2016-09-14 01:54:03 -07:00
Tim Kuehn
ac7e2eedb2 Update readme 2016-09-14 01:47:21 -07:00
Tim Kuehn
6d1fbab73c Fix readme 2016-09-14 01:36:57 -07:00
Tim
e8902c21a2 Mangle a lot of names in macro expansion. (#53)
* Mangle a lot of names in macro expansion.

To lower the chance of any issues, prefix idents in service expansion with __tarpc_service.
In future_enum, prefix with __future_enum. The pattern is basically __macro_name_ident.

Any imported enum variant will conflict with a let binding or a function arg, so we basically
can't use any generic idents at all. Example:

    enum Req { request(..) }
    use self::Req::request;

    fn make_request(request: Request) { ... }

                    ^^^^^^^ conflict here

Additionally, suffix generated associated types with Fut to avoid conflicts with camelcased rpcs.
Why someone would do that, I don't know, but we shouldn't allow that wart.

* Trim long macro lines
2016-09-14 01:34:35 -07:00
Tim
be5f55c5f6 Extend snake_to_camel plugin to replace {} in the doc string with the original snake-cased ident. (#50)
* Extend snake_to_camel plugin to replace {} in the doc string with the origin snake-cased ident.

Also, track tokio-rs master.

This is really ad-hoc, undiscoverable, and unintuitive, but there's no way to programmatically create doc strings
in regular code, and I want to produce better doc strings for the associated types.

Given `fn foo_bar`:

Before: `/// The type of future returned by the function of the same name.`
After: ``/// The type of future returned by `{}`.``
    => `/// The type of future returned by foo_bar.`

* Fix some docs

* Use a helper fn on pipeline::Frame instead of handrolled match.

* Don't hide docs for ClientFuture.

It's exposed in the Connect impl of FutureService -- the tradeoff for not generating *another* item -- and hiding it breaks doc links.

* Formatting

* Rename snake_to_camel plugin => tarpc-plugins

* Update README

* Mangle a lot of names in macro expansion.

To lower the chance of any issues, prefix idents in service expansion with __tarpc_service.
In future_enum, prefix with __future_enum. The pattern is basically __macro_name_ident.

Any imported enum variant will conflict with a let binding or a function arg, so we basically
can't use any generic idents at all. Example:

    enum Req { request(..) }
    use self::Req::request;

    fn make_request(request: Request) { ... }

                    ^^^^^^^ conflict here

Additionally, suffix generated associated types with Fut to avoid conflicts with camelcased rpcs.
Why someone would do that, I don't know, but we shouldn't allow that wart.
2016-09-14 01:19:24 -07:00
Tim
54017839d1 Remove an unused trait and a few unnecessary Send bounds (#54) 2016-09-13 21:35:05 -07:00
Adam Wright
b5f2fe5f4f Merge pull request #51 from tikue/client-side-request
Use cleverness to declare the client-side request enum inside a fn.
2016-09-13 18:02:04 -07:00
Adam Wright
437997b2d1 Merge pull request #52 from tikue/serde_derive
Use the new serde_derive crate.
2016-09-13 18:00:45 -07:00
Tim Kuehn
b5e472b374 Use the new serde_derive crate. 2016-09-06 16:11:32 -07:00
Tim Kuehn
f76030ecd7 Correct typos 2016-09-06 14:05:32 -07:00
Tim Kuehn
b7952d3f47 Fix README Cargo.toml 2016-09-06 14:00:57 -07:00
Tim Kuehn
8d561d21b2 Use cleverness to declare the client-side request enum inside a fn.
Previously, the enum couldn't be declared in the fn it was used in, because
they were part of the same repeating pattern in the macro syntax, and you can't
nest the same repeating pattern. I fixed this by expanding it early,
as a macro argument separate from the repeating fn pattern.
2016-09-06 03:54:02 -07:00
Adam Wright
8968b1fbd2 Merge pull request #49 from tikue/master
Remove two unused error variants of tarpc::Error.
2016-09-04 19:15:06 -07:00
Tim
ab6e829ddd Remove two unused errors. 2016-09-04 19:03:52 -07:00
Tim
246c09c876 Remove outdated potential improvement 2016-09-04 16:34:01 -07:00
Tim
e7cfbc1085 Update readme 2016-09-04 16:14:59 -07:00
Tim
7aabfb3c14 Rewrite using tokio (#44)
* Rewrite tarpc on top of tokio.

* Add examples

* Move error types to their own module.

Also, cull unused error variants.

* Remove unused fn

* Remove CanonicalRpcError* types. They're 100% useless.

* Track tokio master (WIP)

* The great error revamp.

Removed the canonical rpc error type. Instead, the user declares
the error type for each rpc:

In the above example, the error type is Baz. Declaring an error is
optional; if none is specified, it defaults to Never, a convenience
struct that wraps the never type (exclamation mark) to impl Serialize, Deserialize,
Error, etc. Also adds the convenience type StringError for easily
using a String as an error type.

* Add missing license header

* Minor cleanup

* Rename StringError => Message

* Create a sync::Connect trait.

Along with this, the existing Connect trait moves to future::Connect. The future
and sync modules are reexported from the crate root.

Additionally, the utility errors Never and Message are no longer reexported from
the crate root.

* Update readme

* Track tokio/futures master. Add a Spawn utility trait to replace the removed forget.

* Fix pre-push hook

* Add doc comment to SyncServiceExt.

* Fix up some documentation

* Track tokio-proto master

* Don't set tcp nodelay

* Make future::Connect take an associated type for the future.

* Unbox FutureClient::connect return type

* Use type alias instead of newtype struct for ClientFuture

* Fix benches/latency.rs

* Write a plugin to convert lower_snake_case idents/types to UpperCamelCase.

Use it to add associated types to FutureService instead of boxing the return futures.

* Specify plugin = true in snake_to_camel/Cargo.toml. Weird things happen otherwise.

* Add clippy.toml
2016-09-04 16:09:50 -07:00
Morton Fox
64ca851209 Fix the license link in the top strip (#48) 2016-08-26 21:42:40 -07:00
Dustin H
1738864d32 make documentation link go to latest version (#47) 2016-08-26 16:48:42 -07:00
Tim
683617674c Point documentation to docs.rs 2016-08-26 16:43:51 -07:00
Adam Wright
3c4932f55a Add gitter badge 2016-08-24 12:37:32 -07:00
Tim Kuehn
3cf8e440f7 Bump minor version. 2016-08-07 13:02:04 -07:00
Tim Kuehn
2e02f33fc4 Merge branch 'master' of github.com:google/tarpc 2016-07-28 20:49:12 -07:00
Tim Kuehn
d8472dcd1c Update README to reflect latest version. 2016-07-28 20:48:07 -07:00
Tim
2c5846621f Update dependency versions (#43)
* Update dependency versions

* Bump minor version.
2016-07-28 20:46:30 -07:00
Tim Kuehn
6a6157948a Bump minor version. 2016-07-28 20:37:51 -07:00
Tim Kuehn
1c18a3c4fe Update dependency versions 2016-07-28 20:25:46 -07:00
shaladdle
e8ec295e85 Merge pull request #35 from tikue/missing-debug
Add missing Debug impls.
2016-04-24 21:19:42 -07:00
Tim Kuehn
44eec09418 Add missing Debug impls. 2016-04-24 21:06:42 -07:00
shaladdle
fe116a1b6b Merge pull request #34 from tikue/macro-cleanup
Minor macro implementation cleanup.
2016-04-24 20:36:08 -07:00
Tim Kuehn
ec4fa8636b Minor macro implementation cleanup.
* Fold service into service_inner.
* Rename service_inner => service.
2016-04-24 20:02:46 -07:00
shaladdle
2d58340d16 Merge pull request #33 from tikue/bump-version
Bump version to v0.5.0
2016-04-24 19:37:04 -07:00
51 changed files with 5213 additions and 2064 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
target
Cargo.lock
.cargo
*.swp
*.bk

View File

@@ -2,10 +2,11 @@ language: rust
sudo: false
rust:
- stable
- beta
- nightly
os:
- linux
addons:
apt:
packages:
@@ -20,11 +21,12 @@ before_script:
script:
- |
(cd tarpc && travis-cargo build) &&
(cd tarpc && travis-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:

54
Cargo.toml Normal file
View File

@@ -0,0 +1,54 @@
[package]
name = "tarpc"
version = "0.7.0"
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" }
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]

303
README.md
View File

@@ -1,84 +1,303 @@
## tarpc: Tim & Adam's RPC lib
[![Travis-CI Status](https://travis-ci.org/google/tarpc.png?branch=master)](https://travis-ci.org/google/tarpc)
[![Coverage Status](https://coveralls.io/repos/github/google/tarpc/badge.svg?branch=master)](https://coveralls.io/github/google/tarpc?branch=master)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.txt)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE)
[![Latest Version](https://img.shields.io/crates/v/tarpc.svg)](https://crates.io/crates/tarpc)
[![Join the chat at https://gitter.im/tarpc/Lobby](https://badges.gitter.im/tarpc/Lobby.svg)](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 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.
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://google.github.io/tarpc)
[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 compute the function instead. The original
function then returns the value produced by that other server.
"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.
[More information](https://www.cs.cf.ac.uk/Dave/C/node33.html)
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.5.0"
tarpc = { git = "https://github.com/google/tarpc" }
tarpc-plugins = { git = "https://github.com/google/tarpc" }
```
## Example
## 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 hello_service::Service as HelloService;
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;
}
#[derive(Clone)]
struct HelloServer;
impl HelloService for HelloServer {
fn hello(&self, name: String) -> String {
format!("Hello, {}!", name)
impl SyncService for HelloServer {
fn hello(&self, name: String) -> Result<String, Never> {
Ok(format!("Hello, {}!", name))
}
}
fn main() {
let addr = "localhost:10000";
let server_handle = HelloServer.spawn(addr).unwrap();
let client = hello_service::Client::new(addr).unwrap();
assert_eq!("Hello, Mom!", 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` (and `AsyncClient`) type, and a `Service` trait. The trait provides default `fn`s for
starting the service: `spawn` and `spawn_with_config`, which start the service listening over an
arbitrary transport. A `Client` (or `AsyncClient`) can connect to such a service. These generated
types 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
- Connect over any transport that `impl`s the `Transport` trait.
- Concurrent requests from a single client.
- Any type that `impl`s `serde`'s `Serialize` and `Deserialize` can be used in the rpc signatures.
- 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

View File

@@ -1,4 +1,25 @@
## 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:
@@ -6,6 +27,9 @@ 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

53
benches/latency.rs Normal file
View File

@@ -0,0 +1,53 @@
// 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
View File

@@ -0,0 +1 @@
doc-valid-idents = ["gRPC"]

192
examples/concurrency.rs Normal file
View File

@@ -0,0 +1,192 @@
// 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();
}

151
examples/pubsub.rs Normal file
View File

@@ -0,0 +1,151 @@
// 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));
}

65
examples/readme_errors.rs Normal file
View File

@@ -0,0 +1,65 @@
// 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());
}

View File

@@ -0,0 +1,49 @@
// 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();
}

43
examples/readme_sync.rs Normal file
View File

@@ -0,0 +1,43 @@
// 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());
}

View File

@@ -0,0 +1,101 @@
// 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();
}

View File

@@ -0,0 +1,97 @@
// 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);
}
}

114
examples/throughput.rs Normal file
View File

@@ -0,0 +1,114 @@
// 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
View 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.");
}

View File

@@ -89,22 +89,23 @@ fi
# 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=$(rustfmt --skip-children --write-mode=diff $file)
if grep --quiet "^Diff at line" <<< "$diff"; then
FMTRESULT=1
fi
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 '/Using rustfmt.*$/d'
echo "$diff" | sed 's/Using rustfmt config file.*$/d/'
else
printf "${SUCCESS}\n"
fi

View File

@@ -20,11 +20,6 @@
# 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.
#
# - TARPC_USE_CURRENT_TOOLCHAIN, default = 0
#
# Setting this variable to 1 will just run cargo build and cargo test, rather than running
# stable/beta/nightly.
#
# Note that these options are most useful for testing the hooks themselves. Use git push --no-verify
# to skip the pre-push hook altogether.
@@ -42,86 +37,65 @@ SUCCESS="${GREEN}ok${NC}"
printf "${PREFIX} Clean working copy ... "
git diff --exit-code &>/dev/null
if [ "$?" == 0 ]; then
printf "${SUCCESS}\n"
printf "${SUCCESS}\n"
else
if [ "${TARPC_ALLOW_DIRTY}" == "1" ]
then
printf "${SKIPPED}\n"
else
printf "${FAILURE}\n"
exit 1
fi
if [ "${TARPC_ALLOW_DIRTY}" == "1" ]
then
printf "${SKIPPED}\n"
else
printf "${FAILURE}\n"
exit 1
fi
fi
PREPUSH_RESULT=0
# args:
# 1 - cargo command to run (build/test)
# 2 - directory name of crate to build
# 3 - rust toolchain (nightly/stable/beta)
run_cargo() {
if [ "$1" == "build" ]; then
VERB=Building
else
VERB=Testing
fi
if [ "$3" != "" ]; then
printf "${PREFIX} $VERB $2 on $3 ... "
rustup run $3 cargo $1 --manifest-path $2/Cargo.toml &>/dev/null
else
printf "${PREFIX} $VERB $2 ... "
cargo $1 --manifest-path $2/Cargo.toml &>/dev/null
fi
if [ "$?" != "0" ]; then
printf "${FAILURE}\n"
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
else
printf "${SUCCESS}\n"
fi
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 $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 ... "
printf "${PREFIX} Checking for rustup or current toolchain directive... "
command -v rustup &>/dev/null
if [ "$?" == 0 ] && [ "${TARPC_USE_CURRENT_TOOLCHAIN}" == "" ]; then
printf "${SUCCESS}\n"
if [ "$?" == 0 ]; then
printf "${SUCCESS}\n"
check_toolchain stable
check_toolchain beta
check_toolchain nightly
if [ ${TOOLCHAIN_RESULT} == 1 ]; then
exit 1
fi
check_toolchain nightly
if [ ${TOOLCHAIN_RESULT} == 1 ]; then
exit 1
fi
run_cargo build tarpc stable
run_cargo build tarpc_examples stable
try_run "Building ... " cargo build --color=always
try_run "Testing ... " cargo test --color=always
try_run "Benching ... " cargo bench --color=always
run_cargo build tarpc beta
run_cargo build tarpc_examples beta
run_cargo build tarpc nightly
run_cargo build tarpc_examples nightly
# We still rely on some nightly stuff for tests
run_cargo test tarpc nightly
run_cargo test tarpc_examples nightly
else
printf "${YELLOW}NOT FOUND${NC}\n"
printf "${WARNING} Falling back to current toolchain: $(rustc -V)\n"
run_cargo test tarpc
run_cargo test tarpc_examples
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

92
src/errors.rs Normal file
View 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)
}

262
src/future/client.rs Normal file
View File

@@ -0,0 +1,262 @@
// 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 + Sync + Send + 'static,
Resp: Deserialize + Sync + Send + 'static,
E: Deserialize + Sync + 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 + Sync + Send + 'static,
Resp: Deserialize + Sync + Send + 'static,
E: Deserialize + Sync + 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 + Sync + Send + 'static,
Resp: Deserialize + Sync + Send + 'static,
E: Deserialize + Sync + 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
View 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;

View 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(),
})
}
}

448
src/future/server/mod.rs Normal file
View File

@@ -0,0 +1,448 @@
// 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_io::{AsyncRead, AsyncWrite};
use tokio_core::net::{Incoming, TcpListener, TcpStream};
use tokio_core::reactor;
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),
}
}
}

View File

@@ -0,0 +1,181 @@
use futures::{Async, Future, Poll, Stream, future as futures, stream};
use futures::sync::{mpsc, oneshot};
use futures::unsync;
use super::{AlwaysOkUnit, connection};
/// 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
View 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 {}
}

1319
src/macros.rs Normal file

File diff suppressed because it is too large Load Diff

21
src/plugins/Cargo.toml Normal file
View File

@@ -0,0 +1,21 @@
[package]
name = "tarpc-plugins"
version = "0.1.0"
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
View 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() {
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() {
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);
}

219
src/protocol.rs Normal file
View File

@@ -0,0 +1,219 @@
// 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 bincode::{self, Infinite};
use byteorder::{BigEndian, ReadBytesExt};
use bytes::BytesMut;
use bytes::buf::BufMut;
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
View 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),
}
}
}

237
src/sync/client.rs Normal file
View File

@@ -0,0 +1,237 @@
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;
use tokio_core::reactor;
use tokio_proto::util::client_proxy::{ClientProxy, Receiver, pair};
use tokio_service::Service;
use util::FirstSocketAddr;
#[cfg(feature = "tls")]
use tls::client::Context;
#[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 + Sync + Send + 'static,
Resp: Deserialize + Sync + Send + 'static,
E: Deserialize + Sync + 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 + Sync + Send + 'static,
Resp: Deserialize + Sync + Send + 'static,
E: Deserialize + Sync + 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 + Sync + Send + 'static,
Resp: Deserialize + Sync + Send + 'static,
E: Deserialize + Sync + 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
View 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;

225
src/sync/server.rs Normal file
View File

@@ -0,0 +1,225 @@
use {bincode, future, num_cpus};
use future::server::{Response, Shutdown};
use futures::{Future, future as futures};
use futures::sync::oneshot;
use serde::{Deserialize, Serialize};
use std::io;
use std::fmt;
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};
#[cfg(feature = "tls")]
use native_tls_inner::TlsAcceptor;
/// 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
}

51
src/tls.rs Normal file
View File

@@ -0,0 +1,51 @@
/// 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()
}
}
}

178
src/util.rs Normal file
View File

@@ -0,0 +1,178 @@
// 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()
}
}

View File

@@ -1,23 +0,0 @@
[package]
name = "tarpc"
version = "0.5.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.5"
log = "0.3"
scoped-pool = "0.1"
serde = "0.7"
unix_socket = "0.5"
[dev-dependencies]
lazy_static = "0.1"
env_logger = "0.3"
tempdir = "0.3"

View File

@@ -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 serve_handle = Server.spawn("localhost:0").unwrap();
//! let client = Client::new(serve_handle.dialer()).unwrap();
//! assert_eq!(3, client.add(1, 2).unwrap());
//! assert_eq!("Hello, Mom!".to_string(),
//! client.hello("Mom".to_string()).unwrap());
//! drop(client);
//! serve_handle.shutdown();
//! }
//! ```
#![deny(missing_docs)]
extern crate serde;
extern crate bincode;
#[macro_use]
extern crate log;
extern crate scoped_pool;
extern crate unix_socket;
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;
/// Provides transport traits and implementations.
pub mod transport;
pub use protocol::{Config, Error, Result, ServeHandle};

View File

@@ -1,629 +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, VariantVisitor, Visitor};
}
}
// 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
) => (
#[allow(unused)]
$(#[$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
)*) => ( $(
#[allow(unused)]
$(#[$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 rpc; 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
) => (
#[allow(unused)]
$(#[$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
)*) => ( $(
#[allow(unused)]
$(#[$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 rpc; 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::serialize_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, unused)]
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;
#[inline]
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::custom(
format!("No variants have a value of {}!", value))
);
}
}
deserializer.deserialize_struct_field(__FieldVisitor)
}
}
struct __Visitor;
impl $crate::macros::serde::de::EnumVisitor for __Visitor {
type Value = $impler;
#[inline]
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.deserialize_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;
/// # }
/// ```
///
/// There are two rpc names reserved for the default fns `spawn` and `spawn_with_config`.
///
/// 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. It comes with two default methods for
/// starting the server:
/// 1. `spawn` starts the service in another thread using default configuration.
/// 2. `spawn_with_config` starts the service in another thread using the specified
/// `Config`.
/// * `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
///
/// **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 + Sized {
$(
$(#[$attr])*
fn $fn_name(&self, $($arg:$in_),*) -> $out;
)*
#[doc="Spawn a running service."]
fn spawn<T>(self,
transport: T)
-> $crate::Result<
$crate::protocol::ServeHandle<
<T::Listener as $crate::transport::Listener>::Dialer>>
where T: $crate::transport::Transport,
Self: 'static,
{
self.spawn_with_config(transport, $crate::Config::default())
}
#[doc="Spawn a running service."]
fn spawn_with_config<T>(self,
transport: T,
config: $crate::Config)
-> $crate::Result<
$crate::protocol::ServeHandle<
<T::Listener as $crate::transport::Listener>::Dialer>>
where T: $crate::transport::Transport,
Self: 'static,
{
let server = __Server(self);
let result = $crate::protocol::Serve::spawn_with_config(server, transport, config);
let handle = try!(result);
::std::result::Result::Ok(handle)
}
}
impl<P, S> Service for P
where P: Send + Sync + Sized + 'static + ::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, unused)]
#[derive(Debug)]
enum __Request {
$(
$fn_name(( $($in_,)* ))
),*
}
impl_serialize!(__Request, $($fn_name(($($in_),*)))*);
impl_deserialize!(__Request, $($fn_name(($($in_),*)))*);
#[allow(non_camel_case_types, unused)]
#[derive(Debug)]
enum __Reply {
$(
$fn_name($out),
)*
}
impl_serialize!(__Reply, $($fn_name($out))*);
impl_deserialize!(__Reply, $($fn_name($out))*);
#[allow(unused)]
#[doc="An asynchronous RPC call"]
pub struct Future<T> {
future: $crate::protocol::Future<__Reply>,
mapper: fn(__Reply) -> T,
}
impl<T> Future<T> {
#[allow(unused)]
#[doc="Block until the result of the RPC call is available"]
pub fn get(self) -> $crate::Result<T> {
self.future.get().map(self.mapper)
}
}
#[allow(unused)]
#[doc="The client stub that makes RPC calls to the server."]
pub struct Client<S = ::std::net::TcpStream>(
$crate::protocol::Client<__Request, __Reply, S>
) where S: $crate::transport::Stream;
impl<S> Client<S>
where S: $crate::transport::Stream
{
#[allow(unused)]
#[doc="Create a new client with default configuration that connects to the given \
address."]
pub fn new<D>(dialer: D) -> $crate::Result<Self>
where D: $crate::transport::Dialer<Stream=S>,
{
Self::with_config(dialer, $crate::Config::default())
}
#[allow(unused)]
#[doc="Create a new client with the specified configuration that connects to the \
given address."]
pub fn with_config<D>(dialer: D, config: $crate::Config) -> $crate::Result<Self>
where D: $crate::transport::Dialer<Stream=S>,
{
let inner = try!($crate::protocol::Client::with_config(dialer, config));
::std::result::Result::Ok(Client(inner))
}
client_methods!(
$(
{ $(#[$attr])* }
$fn_name(($($arg,)*) : ($($in_,)*)) -> $out
)*
);
#[allow(unused)]
#[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())))
}
}
#[allow(unused)]
#[doc="The client stub that makes asynchronous RPC calls to the server."]
pub struct AsyncClient<S = ::std::net::TcpStream>(
$crate::protocol::Client<__Request, __Reply, S>
) where S: $crate::transport::Stream;
impl<S> AsyncClient<S>
where S: $crate::transport::Stream {
#[allow(unused)]
#[doc="Create a new asynchronous client with default configuration that connects to \
the given address."]
pub fn new<D>(dialer: D) -> $crate::Result<Self>
where D: $crate::transport::Dialer<Stream=S>,
{
Self::with_config(dialer, $crate::Config::default())
}
#[allow(unused)]
#[doc="Create a new asynchronous client that connects to the given address."]
pub fn with_config<D>(dialer: D, config: $crate::Config) -> $crate::Result<Self>
where D: $crate::transport::Dialer<Stream=S>,
{
let inner = try!($crate::protocol::Client::with_config(dialer, config));
::std::result::Result::Ok(AsyncClient(inner))
}
async_client_methods!(
$(
{ $(#[$attr])* }
$fn_name(($($arg,)*): ($($in_,)*)) -> $out
)*
);
#[allow(unused)]
#[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())))
}
}
#[allow(unused)]
struct __Server<S>(S)
where S: 'static + Service;
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),*)),
)*
}
}
}
}
}
#[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;
extern crate tempdir;
use transport::unix::UnixTransport;
service! {
rpc add(x: i32, y: i32) -> i32;
rpc hey(name: String) -> String;
}
struct Server;
impl Service for Server {
fn add(&self, x: i32, y: i32) -> i32 {
x + y
}
fn hey(&self, name: String) -> String {
format!("Hey, {}.", name)
}
}
#[test]
fn simple() {
let _ = env_logger::init();
let handle = Server.spawn("localhost:0").unwrap();
let client = Client::new(handle.dialer()).unwrap();
assert_eq!(3, client.add(1, 2).unwrap());
assert_eq!("Hey, Tim.", client.hey("Tim".into()).unwrap());
drop(client);
handle.shutdown();
}
#[test]
fn simple_async() {
let _ = env_logger::init();
let handle = Server.spawn("localhost:0").unwrap();
let client = AsyncClient::new(handle.dialer()).unwrap();
assert_eq!(3, client.add(1, 2).get().unwrap());
assert_eq!("Hey, Adam.", client.hey("Adam".into()).get().unwrap());
drop(client);
handle.shutdown();
}
#[test]
fn try_clone() {
let handle = Server.spawn("localhost:0").unwrap();
let client1 = Client::new(handle.dialer()).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 = Server.spawn("localhost:0").unwrap();
let client1 = AsyncClient::new(handle.dialer()).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());
}
#[test]
fn async_try_clone_unix() {
let temp_dir = tempdir::TempDir::new("tarpc").unwrap();
let temp_file = temp_dir.path()
.join("async_try_clone_unix.tmp");
let handle = Server.spawn(UnixTransport(temp_file)).unwrap();
let client1 = AsyncClient::new(handle.dialer()).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 _ = ::std::sync::Arc::new(Server).spawn("localhost:0");
}
// Tests that a tcp client can be created from &str
#[allow(dead_code)]
fn test_client_str() {
let _ = Client::new("localhost:0");
}
#[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);
}
}
}

View File

@@ -1,269 +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::sync::{Arc, Mutex};
use std::sync::mpsc::{Receiver, Sender, channel};
use std::thread;
use super::{Config, Deserialize, Error, Packet, Result, Serialize};
use transport::{Dialer, Stream};
/// A client stub that connects to a server to run rpcs.
pub struct Client<Request, Reply, S>
where Request: serde::ser::Serialize,
S: Stream
{
// 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: S,
}
impl<Request, Reply, S> Client<Request, Reply, S>
where Request: serde::ser::Serialize + Send + 'static,
Reply: serde::de::Deserialize + Send + 'static,
S: Stream
{
/// Create a new client that connects to `addr`. The client uses the given timeout
/// for both reads and writes.
pub fn new<D>(dialer: D) -> io::Result<Self>
where D: Dialer<Stream = S>
{
Self::with_config(dialer, Config::default())
}
/// Create a new client that connects to `addr`. The client uses the given timeout
/// for both reads and writes.
pub fn with_config<D>(dialer: D, config: Config) -> io::Result<Self>
where D: Dialer<Stream = S>
{
let stream = try!(dialer.dial());
try!(stream.set_read_timeout(config.timeout));
try!(stream.set_write_timeout(config.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<Self> {
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, S> Drop for Client<Request, Reply, S>
where Request: serde::ser::Serialize,
S: Stream
{
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() {
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, S>(outbound: Receiver<(Request, Sender<Result<Reply>>)>,
requests: Arc<Mutex<RpcFutures<Reply>>>,
stream: S)
where Request: serde::Serialize,
Reply: serde::Deserialize,
S: Stream
{
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, S>(requests: Arc<Mutex<RpcFutures<Reply>>>, stream: S)
where Reply: serde::Deserialize,
S: Stream
{
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;
}
}
}
}

View File

@@ -1,249 +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 serde::de::value::Error::EndOfStream;
use std::io::{self, Read, Write};
use std::convert;
use std::sync::Arc;
use std::time::Duration;
mod client;
mod server;
mod packet;
pub use self::packet::Packet;
pub use self::client::{Client, Future};
pub use self::server::{Serve, ServeHandle};
/// 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::Serde(EndOfStream) => Error::ConnectionBroken,
bincode::serde::DeserializeError::IoError(err) => {
match err.kind() {
io::ErrorKind::ConnectionReset |
io::ErrorKind::UnexpectedEof => Error::ConnectionBroken,
_ => 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))
}
}
/// Configuration for client and server.
#[derive(Debug, Default)]
pub struct Config {
/// Request/Response timeout between packet delivery.
pub timeout: Option<Duration>,
}
/// 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, Config, Serve};
use scoped_pool::Pool;
use std::net::TcpStream;
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 = server.spawn("localhost:0").unwrap();
let client: Client<(), u64, TcpStream> = Client::new(serve_handle.dialer()).unwrap();
drop(client);
serve_handle.shutdown();
}
#[test]
fn simple() {
let _ = env_logger::init();
let server = Arc::new(Server::new());
let serve_handle = server.clone().spawn("localhost:0").unwrap();
// The explicit type is required so that it doesn't deserialize a u32 instead of u64
let client: Client<(), u64, _> = Client::new(serve_handle.dialer()).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 = server.spawn_with_config("localhost:0",
Config { timeout: Some(Duration::new(0, 10)) })
.unwrap();
let client: Client<(), u64, _> = Client::new(serve_handle.dialer()).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 = server.spawn_with_config("localhost:0",
Config { timeout: test_timeout() })
.unwrap();
let client: Arc<Client<(), u64, _>> = Arc::new(Client::new(serve_handle.dialer()).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 = server.clone().spawn("localhost:0").unwrap();
let client: Client<(), u64, _> = Client::new(serve_handle.dialer()).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 = server.spawn("localhost:0").unwrap();
let client: Client<(), u64, _> = Client::new(serve_handle.dialer()).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();
}
}

View File

@@ -1,108 +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.serialize_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> {
#[inline]
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.serialize_struct_elt(RPC_ID, &self.value.rpc_id))))
}
1 => {
self.state += 1;
Ok(Some(try!(serializer.serialize_struct_elt(MESSAGE, &self.value.message))))
}
_ => Ok(None),
}
}
#[inline]
fn len(&self) -> Option<usize> {
Some(2)
}
}
impl<T: Deserialize> Deserialize for Packet<T> {
#[inline]
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
where D: Deserializer
{
const FIELDS: &'static [&'static str] = &[RPC_ID, MESSAGE];
deserializer.deserialize_struct(PACKET, FIELDS, Visitor(PhantomData))
}
}
struct Visitor<T>(PhantomData<T>);
impl<T: Deserialize> de::Visitor for Visitor<T> {
type Value = Packet<T>;
#[inline]
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());
}

View File

@@ -1,280 +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::sync::mpsc::{Receiver, Sender, TryRecvError, channel};
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
use std::thread::{self, JoinHandle};
use super::{Config, Deserialize, Error, Packet, Result, Serialize};
use transport::{Dialer, Listener, Stream, Transport};
use transport::tcp::TcpDialer;
struct ConnectionHandler<'a, S, St>
where S: Serve,
St: Stream
{
read_stream: BufReader<St>,
write_stream: BufWriter<St>,
server: S,
shutdown: &'a AtomicBool,
}
impl<'a, S, St> ConnectionHandler<'a, S, St>
where S: Serve,
St: Stream
{
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<St>) {
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<D = TcpDialer>
where D: Dialer
{
tx: Sender<()>,
join_handle: JoinHandle<()>,
dialer: D,
}
impl<D> ServeHandle<D>
where D: Dialer
{
/// Block until the server completes
pub fn wait(self) {
self.join_handle.join().expect(pos!());
}
/// Returns the dialer to the server.
pub fn dialer(&self) -> &D {
&self.dialer
}
/// 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(_) = self.dialer.dial() {
self.join_handle.join().expect(pos!());
} else {
warn!("ServeHandle: best effort shutdown of serve thread failed");
}
}
}
struct Server<'a, S: 'a, L>
where L: Listener
{
server: &'a S,
listener: L,
read_timeout: Option<Duration>,
die_rx: Receiver<()>,
shutdown: &'a AtomicBool,
}
impl<'a, S, L> Server<'a, S, L>
where S: Serve + 'static,
L: Listener
{
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, L> Drop for Server<'a, S, L>
where L: Listener
{
fn drop(&mut self) {
debug!("Shutting down connection handlers.");
self.shutdown.store(true, Ordering::SeqCst);
}
}
/// A service provided by a server
pub trait Serve: Send + Sync + Sized {
/// 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;
/// spawn
fn spawn<T>(self, transport: T) -> io::Result<ServeHandle<<T::Listener as Listener>::Dialer>>
where T: Transport,
Self: 'static
{
self.spawn_with_config(transport, Config::default())
}
/// spawn
fn spawn_with_config<T>(self,
transport: T,
config: Config)
-> io::Result<ServeHandle<<T::Listener as Listener>::Dialer>>
where T: Transport,
Self: 'static
{
let listener = try!(transport.bind());
let dialer = try!(listener.dialer());
info!("spawn_with_config: spinning up server.");
let (die_tx, die_rx) = channel();
let timeout = config.timeout;
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: &self,
listener: listener,
read_timeout: timeout,
die_rx: die_rx,
shutdown: &shutdown,
};
pool.scoped(|scope| {
server.serve(scope);
});
});
Ok(ServeHandle {
tx: die_tx,
join_handle: join_handle,
dialer: dialer,
})
}
}
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)
}
}

View File

@@ -1,91 +0,0 @@
use std::io::{self, Read, Write};
use std::time::Duration;
/// A factory for creating a listener on a given address.
/// For TCP, an address might be an IPv4 address; for Unix sockets, it
/// is just a file name.
pub trait Transport {
/// The type of listener that binds to the given address.
type Listener: Listener;
/// Return a listener on the given address, and a dialer to that address.
fn bind(&self) -> io::Result<Self::Listener>;
}
/// Accepts incoming connections from dialers.
pub trait Listener: Send + 'static {
/// The type of address being listened on.
type Dialer: Dialer;
/// The type of stream this listener accepts.
type Stream: Stream;
/// Accept an incoming stream.
fn accept(&self) -> io::Result<Self::Stream>;
/// Returns the local address being listened on.
fn dialer(&self) -> io::Result<Self::Dialer>;
/// Iterate over incoming connections.
fn incoming(&self) -> Incoming<Self> {
Incoming { listener: self }
}
}
/// A cloneable Reader/Writer.
pub trait Stream: Read + Write + Send + Sized + 'static {
/// Creates a new independently owned handle to the Stream.
///
/// The returned Stream should reference the same stream that this
/// object references. Both handles should read and write the same
/// stream of data, and options set on one stream should be propagated
/// to the other stream.
fn try_clone(&self) -> io::Result<Self>;
/// Sets a read timeout.
///
/// If the value specified is `None`, then read calls will block indefinitely.
/// It is an error to pass the zero `Duration` to this method.
fn set_read_timeout(&self, dur: Option<Duration>) -> io::Result<()>;
/// Sets a write timeout.
///
/// If the value specified is `None`, then write calls will block indefinitely.
/// It is an error to pass the zero `Duration` to this method.
fn set_write_timeout(&self, dur: Option<Duration>) -> io::Result<()>;
/// Shuts down both ends of the stream.
///
/// Implementations should cause all pending and future I/O on the specified
/// portions to return immediately with an appropriate value.
fn shutdown(&self) -> io::Result<()>;
}
/// A `Stream` factory.
pub trait Dialer {
/// The type of `Stream` this can create.
type Stream: Stream;
/// Open a stream.
fn dial(&self) -> io::Result<Self::Stream>;
}
impl<P, D: ?Sized> Dialer for P
where P: ::std::ops::Deref<Target = D>,
D: Dialer + 'static
{
type Stream = D::Stream;
fn dial(&self) -> io::Result<Self::Stream> {
(**self).dial()
}
}
/// Iterates over incoming connections.
pub struct Incoming<'a, L: Listener + ?Sized + 'a> {
listener: &'a L,
}
impl<'a, L: Listener> Iterator for Incoming<'a, L> {
type Item = io::Result<L::Stream>;
fn next(&mut self) -> Option<Self::Item> {
Some(self.listener.accept())
}
}
/// Provides a TCP transport.
pub mod tcp;
/// Provides a unix socket transport.
pub mod unix;

View File

@@ -1,75 +0,0 @@
use std::io;
use std::net::{SocketAddr, TcpListener, TcpStream, ToSocketAddrs};
use std::time::Duration;
/// A transport for TCP.
pub struct TcpTransport<A: ToSocketAddrs>(pub A);
impl<A: ToSocketAddrs> super::Transport for TcpTransport<A> {
type Listener = TcpListener;
fn bind(&self) -> io::Result<TcpListener> {
TcpListener::bind(&self.0)
}
}
impl<A: ToSocketAddrs> super::Transport for A {
type Listener = TcpListener;
fn bind(&self) -> io::Result<TcpListener> {
TcpListener::bind(self)
}
}
impl super::Listener for TcpListener {
type Dialer = TcpDialer<SocketAddr>;
type Stream = TcpStream;
fn accept(&self) -> io::Result<TcpStream> {
self.accept().map(|(stream, _)| stream)
}
fn dialer(&self) -> io::Result<TcpDialer<SocketAddr>> {
self.local_addr().map(|addr| TcpDialer(addr))
}
}
impl super::Stream for TcpStream {
fn try_clone(&self) -> io::Result<Self> {
self.try_clone()
}
fn set_read_timeout(&self, dur: Option<Duration>) -> io::Result<()> {
self.set_read_timeout(dur)
}
fn set_write_timeout(&self, dur: Option<Duration>) -> io::Result<()> {
self.set_write_timeout(dur)
}
fn shutdown(&self) -> io::Result<()> {
self.shutdown(::std::net::Shutdown::Both)
}
}
/// Connects to a socket address.
pub struct TcpDialer<A = SocketAddr>(pub A) where A: ToSocketAddrs;
impl<A> super::Dialer for TcpDialer<A>
where A: ToSocketAddrs
{
type Stream = TcpStream;
fn dial(&self) -> io::Result<TcpStream> {
TcpStream::connect(&self.0)
}
}
impl super::Dialer for str {
type Stream = TcpStream;
fn dial(&self) -> io::Result<TcpStream> {
TcpStream::connect(self)
}
}

View File

@@ -1,70 +0,0 @@
use std::io;
use std::path::{Path, PathBuf};
use std::time::Duration;
use unix_socket::{UnixListener, UnixStream};
/// A transport for unix sockets.
pub struct UnixTransport<P>(pub P) where P: AsRef<Path>;
impl<P> super::Transport for UnixTransport<P>
where P: AsRef<Path>
{
type Listener = UnixListener;
fn bind(&self) -> io::Result<UnixListener> {
UnixListener::bind(&self.0)
}
}
/// Connects to a unix socket address.
pub struct UnixDialer<P>(pub P) where P: AsRef<Path>;
impl<P> super::Dialer for UnixDialer<P>
where P: AsRef<Path>
{
type Stream = UnixStream;
fn dial(&self) -> io::Result<UnixStream> {
UnixStream::connect(&self.0)
}
}
impl super::Listener for UnixListener {
type Stream = UnixStream;
type Dialer = UnixDialer<PathBuf>;
fn accept(&self) -> io::Result<UnixStream> {
self.accept().map(|(stream, _)| stream)
}
fn dialer(&self) -> io::Result<UnixDialer<PathBuf>> {
self.local_addr().and_then(|addr| {
match addr.as_pathname() {
Some(path) => Ok(UnixDialer(path.to_owned())),
None => {
Err(io::Error::new(io::ErrorKind::AddrNotAvailable,
"Couldn't get a path to bound unix socket"))
}
}
})
}
}
impl super::Stream for UnixStream {
fn try_clone(&self) -> io::Result<Self> {
self.try_clone()
}
fn set_read_timeout(&self, timeout: Option<Duration>) -> io::Result<()> {
self.set_read_timeout(timeout)
}
fn set_write_timeout(&self, timeout: Option<Duration>) -> io::Result<()> {
self.set_write_timeout(timeout)
}
fn shutdown(&self) -> io::Result<()> {
self.shutdown(::std::net::Shutdown::Both)
}
}

View File

@@ -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"

View File

@@ -1,74 +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 = HelloServer.spawn("localhost:0").unwrap();
Arc::new(Mutex::new(handle))
};
static ref CLIENT: Arc<Mutex<AsyncClient>> = {
let lock = HANDLE.lock().unwrap();
let dialer = lock.dialer();
let client = AsyncClient::new(dialer).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

Binary file not shown.

BIN
test/root-ca.der Normal file

Binary file not shown.

21
test/root-ca.pem Normal file
View 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-----