Skip to content

Commit

Permalink
[rust-server] Enhance middleware support (+ perf fix) (#8114)
Browse files Browse the repository at this point in the history
* Revert change to use a new hyper client on every request

* Fix some formatting

* Update sample after fixing formatting

* Add constant with API version

* Use semver::Version for ApiVersion

* Go back to API version as a string

* Rust composite services

* added context type parameter to API trait

* use Has<XSpanId> trait

* added context type parameter to Service and Client structs

* made AuthData in Context an option in client

* updated client example using generic contexts

* added generic context parameters in server/auth

* use ExtendsWith with associated types

* added (fake) X-Span-ID in auth::Service

* updated server example with generic contexts

* use real X-Span-ID in auth wrapper service and remove from main server code

* only require Has<Option<Authorization>> if API has auth methods

* tidy up swagger imports

* Actually use the version from the swagger file

* remove old comments

* add AuthData/Authorization requirements only when AuthMethods are present

* updated auth to use new version of the Has trait

* update example code to use new Has trait

* updated examples to use version of AllowAllAuthenticator that consumes AuthData

* update examples to use macros for constructing contexts

* use new versions of context traits

* autogen sample

* rename EmpContext to EmptyContext

* fix indentation

* remove unecessary uses of Context, and rename existing ones to ContextBuilder

* replace Has::<T>::get(&context) with (&context as &Has<T>).get()

* remove github dependency for swagger-rs

* tidy up swagger entry in Cargo.toml

* Update to swagger-rs 0.12.0, and remove warning-inducing extra parentheses

* Update petstore examples

* Bump to swagger-rs 0.12.1
  • Loading branch information
bjgill authored and wing328 committed May 3, 2018
1 parent 8ab1cbc commit 415edb8
Show file tree
Hide file tree
Showing 22 changed files with 772 additions and 744 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ chrono = { version = "0.4", features = ["serde"] }
futures = "0.1"
hyper = {version = "0.11", optional = true}
hyper-tls = {version = "0.1.2", optional = true}
swagger = "0.10.0"
swagger = "0.12.1"

# Not required by example server.
#
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use std::collections::{HashMap, BTreeMap};
#[allow(unused_imports)]
use swagger;

use swagger::{Context, ApiError, XSpanId};
use swagger::{ApiError, XSpanId, XSpanIdString, Has, AuthData};

use {Api{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}},
{{operationId}}Response{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
Expand Down Expand Up @@ -67,8 +67,7 @@ fn into_base_path(input: &str, correct_scheme: Option<&'static str>) -> Result<S
/// A client that implements the API by making HTTP calls out to a server.
#[derive(Clone)]
pub struct Client {
hyper_client: Arc<Fn(&Handle) -> Box<hyper::client::Service<Request=hyper::Request<hyper::Body>, Response=hyper::Response, Error=hyper::Error, Future=hyper::client::FutureResponse>> + Sync + Send>,
handle: Arc<Handle>,
hyper_client: Arc<Box<hyper::client::Service<Request=hyper::Request<hyper::Body>, Response=hyper::Response, Error=hyper::Error, Future=hyper::client::FutureResponse>>>,
base_path: String,
}

Expand Down Expand Up @@ -173,25 +172,13 @@ impl Client {
where
C: hyper::client::Connect + hyper::client::Service,
{
let hyper_client = {
move |handle: &Handle| -> Box<
hyper::client::Service<
Request = hyper::Request<hyper::Body>,
Response = hyper::Response,
Error = hyper::Error,
Future = hyper::client::FutureResponse,
>,
> {
let connector = connector_fn(handle);
Box::new(hyper::Client::configure().connector(connector).build(
handle,
))
}
};
let connector = connector_fn(&handle);
let hyper_client = Box::new(hyper::Client::configure().connector(connector).build(
&handle,
));
Ok(Client {
hyper_client: Arc::new(hyper_client),
handle: Arc::new(handle),
base_path: into_base_path(base_path, protocol)?,
})
}
Expand All @@ -205,22 +192,21 @@ impl Client {
/// The reason for this function's existence is to support legacy test code, which did mocking at the hyper layer.
/// This is not a recommended way to write new tests. If other reasons are found for using this function, they
/// should be mentioned here.
pub fn try_new_with_hyper_client(hyper_client: Arc<Fn(&Handle) -> Box<hyper::client::Service<Request=hyper::Request<hyper::Body>, Response=hyper::Response, Error=hyper::Error, Future=hyper::client::FutureResponse>> + Sync + Send>,
pub fn try_new_with_hyper_client(hyper_client: Arc<Box<hyper::client::Service<Request=hyper::Request<hyper::Body>, Response=hyper::Response, Error=hyper::Error, Future=hyper::client::FutureResponse>>>,
handle: Handle,
base_path: &str)
-> Result<Client, ClientInitError>
{
Ok(Client {
hyper_client: hyper_client,
handle: Arc::new(handle),
base_path: into_base_path(base_path, None)?,
})
}
}

impl Api for Client {
impl<C> Api<C> for Client where C: Has<XSpanIdString> {{#hasAuthMethods}}+ Has<Option<AuthData>>{{/hasAuthMethods}}{
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
fn {{#vendorExtensions}}{{operation_id}}{{/vendorExtensions}}(&self{{#allParams}}, param_{{paramName}}: {{^required}}{{#isFile}}Box<Future<Item={{/isFile}}Option<{{/required}}{{#isListContainer}}&{{/isListContainer}}{{{dataType}}}{{^required}}>{{#isFile}}, Error=Error> + Send>{{/isFile}}{{/required}}{{/allParams}}, context: &Context) -> Box<Future<Item={{operationId}}Response, Error=ApiError>> {
fn {{#vendorExtensions}}{{operation_id}}{{/vendorExtensions}}(&self{{#allParams}}, param_{{paramName}}: {{^required}}{{#isFile}}Box<Future<Item={{/isFile}}Option<{{/required}}{{#isListContainer}}&{{/isListContainer}}{{{dataType}}}{{^required}}>{{#isFile}}, Error=Error> + Send>{{/isFile}}{{/required}}{{/allParams}}, context: &C) -> Box<Future<Item={{operationId}}Response, Error=ApiError>> {
{{#queryParams}}{{#-first}}
// Query parameters
{{/-first}}{{#required}} let query_{{paramName}} = format!("{{baseName}}={{=<% %>=}}{<% paramName %>}<%={{ }}=%>&", {{paramName}}=param_{{paramName}}{{#isListContainer}}.join(","){{/isListContainer}}{{^isListContainer}}.to_string(){{/isListContainer}});
Expand Down Expand Up @@ -306,9 +292,9 @@ impl Api for Client {

request.headers_mut().set(ContentType(mimetypes::requests::{{#vendorExtensions}}{{uppercase_operation_id}}{{/vendorExtensions}}.clone()));
{{/bodyParam}}
context.x_span_id.as_ref().map(|header| request.headers_mut().set(XSpanId(header.clone())));
{{#authMethods}}{{#isBasic}} context.auth_data.as_ref().map(|auth_data| {
if let &swagger::AuthData::Basic(ref basic_header) = auth_data {
request.headers_mut().set(XSpanId((context as &Has<XSpanIdString>).get().0.clone()));
{{#authMethods}}{{#isBasic}} (context as &Has<Option<AuthData>>).get().as_ref().map(|auth_data| {
if let &AuthData::Basic(ref basic_header) = auth_data {
request.headers_mut().set(hyper::header::Authorization(
basic_header.clone(),
))
Expand All @@ -326,8 +312,7 @@ impl Api for Client {
request.set_body(body_string.into_bytes());
{{/hasFile}}{{/vendorExtensions}}

let hyper_client = (self.hyper_client)(&*self.handle);
Box::new(hyper_client.call(request)
Box::new(self.hyper_client.call(request)
.map_err(|e| ApiError(format!("No response received: {}", e)))
.and_then(|mut response| {
match response.status().as_u16() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ extern crate {{externCrateName}};
#[allow(unused_extern_crates)]
extern crate futures;
#[allow(unused_extern_crates)]
#[macro_use]
extern crate swagger;
#[allow(unused_extern_crates)]
extern crate uuid;
extern crate clap;
extern crate tokio_core;

use swagger::{ContextBuilder, EmptyContext, XSpanIdString, Has, Push, AuthData};

#[allow(unused_imports)]
use futures::{Future, future, Stream, stream};
use tokio_core::reactor;
Expand Down Expand Up @@ -60,15 +63,16 @@ fn main() {
.expect("Failed to create HTTP client")
};

// Using a non-default `Context` is not required; this is just an example!
let client = client.with_context({{externCrateName}}::Context::new_with_span_id(self::uuid::Uuid::new_v4().to_string()));
let context: make_context_ty!(ContextBuilder, EmptyContext, Option<AuthData>, XSpanIdString) =
make_context!(ContextBuilder, EmptyContext, None, XSpanIdString(self::uuid::Uuid::new_v4().to_string()));
let client = client.with_context(context);

match matches.value_of("operation") {
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
{{#vendorExtensions}}{{#noClientExample}}// Disabled because there's no example.
// {{/noClientExample}}Some("{{operationId}}") => {
{{#noClientExample}}// {{/noClientExample}} let result = core.run(client.{{operation_id}}{{/vendorExtensions}}({{#allParams}}{{^-first}}, {{/-first}}{{#vendorExtensions}}{{{example}}}{{/vendorExtensions}}{{/allParams}}));
{{#vendorExtensions}}{{#noClientExample}}// {{/noClientExample}}{{/vendorExtensions}} println!("{:?} (X-Span-ID: {:?})", result, client.context().x_span_id.clone().unwrap_or(String::from("<none>")));
{{#vendorExtensions}}{{#noClientExample}}// {{/noClientExample}}{{/vendorExtensions}} println!("{:?} (X-Span-ID: {:?})", result, (client.context() as &Has<XSpanIdString>).get().clone());
{{#vendorExtensions}}{{#noClientExample}}// {{/noClientExample}}{{/vendorExtensions}} },
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
_ => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use hyper::server::Http;
use tokio_proto::TcpServer;
use clap::{App, Arg};
use swagger::auth::AllowAllAuthenticator;
use swagger::EmptyContext;

mod server_lib;

Expand All @@ -54,9 +55,9 @@ fn main() {
.get_matches();
let service_fn =
{{externCrateName}}::server::auth::NewService::new(
{{externCrateName}}::server::auth::NewService::<_, EmptyContext>::new(
AllowAllAuthenticator::new(
server_lib::NewService,
server_lib::NewService::new(),
"cosmo"
)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,31 @@ mod errors {

pub use self::errors::*;
use std::io;
use std::clone::Clone;
use std::marker::PhantomData;
use hyper;
use {{externCrateName}};
use swagger::{Has, XSpanIdString};
use swagger::auth::Authorization;

pub struct NewService;
pub struct NewService<C>{
marker: PhantomData<C>
}

impl<C> NewService<C>{
pub fn new() -> Self {
NewService{marker:PhantomData}
}
}

impl hyper::server::NewService for NewService {
type Request = (hyper::Request, {{externCrateName}}::Context);
impl<C> hyper::server::NewService for NewService<C> where C: Has<XSpanIdString> {{#hasAuthMethods}}+ Has<Option<Authorization>>{{/hasAuthMethods}} + Clone + 'static {
type Request = (hyper::Request, C);
type Response = hyper::Response;
type Error = hyper::Error;
type Instance = {{externCrateName}}::server::Service<server::Server>;
type Instance = {{externCrateName}}::server::Service<server::Server<C>, C>;

/// Instantiate a new server.
fn new_service(&self) -> io::Result<Self::Instance> {
Ok({{externCrateName}}::server::Service::new(server::Server))
Ok({{externCrateName}}::server::Service::new(server::Server::new()))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,33 @@ use chrono;
{{#apiHasFile}}use futures::Stream;{{/apiHasFile}}
use std::collections::HashMap;
{{#apiHasFile}}use std::io::Error;{{/apiHasFile}}
use std::marker::PhantomData;
{{#apiUsesUuid}}use uuid;{{/apiUsesUuid}}
use swagger;
use swagger::{Has, XSpanIdString};

use {{externCrateName}}::{Api, ApiError, Context{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}},
use {{externCrateName}}::{Api, ApiError{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}},
{{operationId}}Response{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
};
use {{externCrateName}}::models;

#[derive(Copy, Clone)]
pub struct Server;
pub struct Server<C> {
marker: PhantomData<C>,
}

impl<C> Server<C> {
pub fn new() -> Self {
Server{marker: PhantomData}
}
}

impl Api for Server {
impl<C> Api<C> for Server<C> where C: Has<XSpanIdString>{
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
{{#summary}} /// {{{summary}}}{{/summary}}
fn {{#vendorExtensions}}{{operation_id}}{{/vendorExtensions}}(&self{{#allParams}}, {{paramName}}: {{^required}}{{#isFile}}Box<Future<Item={{/isFile}}Option<{{/required}}{{#isListContainer}}&{{/isListContainer}}{{{dataType}}}{{^required}}>{{#isFile}}, Error=Error> + Send>{{/isFile}}{{/required}}{{/allParams}}, context: &Context) -> Box<Future<Item={{operationId}}Response, Error=ApiError>> {
fn {{#vendorExtensions}}{{operation_id}}{{/vendorExtensions}}(&self{{#allParams}}, {{paramName}}: {{^required}}{{#isFile}}Box<Future<Item={{/isFile}}Option<{{/required}}{{#isListContainer}}&{{/isListContainer}}{{{dataType}}}{{^required}}>{{#isFile}}, Error=Error> + Send>{{/isFile}}{{/required}}{{/allParams}}, context: &C) -> Box<Future<Item={{operationId}}Response, Error=ApiError>> {
let context = context.clone();
println!("{{#vendorExtensions}}{{operation_id}}{{/vendorExtensions}}({{#allParams}}{{^isFile}}{{#vendorExtensions}}{{{formatString}}}{{/vendorExtensions}}{{#hasMore}}, {{/hasMore}}{{/isFile}}{{/allParams}}) - X-Span-ID: {:?}"{{#allParams}}{{^isFile}}, {{paramName}}{{/isFile}}{{/allParams}}, context.x_span_id.unwrap_or(String::from("<none>")).clone());{{#allParams}}{{#isFile}}
println!("{{#vendorExtensions}}{{operation_id}}{{/vendorExtensions}}({{#allParams}}{{^isFile}}{{#vendorExtensions}}{{{formatString}}}{{/vendorExtensions}}{{#hasMore}}, {{/hasMore}}{{/isFile}}{{/allParams}}) - X-Span-ID: {:?}"{{#allParams}}{{^isFile}}, {{paramName}}{{/isFile}}{{/allParams}}, context.get().0.clone());{{#allParams}}{{#isFile}}
let _ = {{paramName}}; //Suppresses unused param warning{{/isFile}}{{/allParams}}
Box::new(futures::failed("Generic failure".into()))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub use futures::Future;
#[cfg(any(feature = "client", feature = "server"))]
mod mimetypes;

pub use swagger::{ApiError, Context, ContextWrapper};
pub use swagger::{ApiError, ContextWrapper};

pub const BASE_PATH: &'static str = "{{basePathWithoutHost}}";
pub const API_VERSION: &'static str = "{{appVersion}}";
Expand All @@ -48,10 +48,10 @@ pub enum {{operationId}}Response {
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}

/// API
pub trait Api {
pub trait Api<C> {
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
{{#summary}} /// {{{summary}}}{{/summary}}
fn {{#vendorExtensions}}{{operation_id}}{{/vendorExtensions}}(&self{{#allParams}}, {{paramName}}: {{^required}}{{#isFile}}Box<Future<Item={{/isFile}}Option<{{/required}}{{#isListContainer}}&{{/isListContainer}}{{{dataType}}}{{^required}}>{{#isFile}}, Error=Error> + Send>{{/isFile}}{{/required}}{{/allParams}}, context: &Context) -> Box<Future<Item={{operationId}}Response, Error=ApiError>>;
fn {{#vendorExtensions}}{{operation_id}}{{/vendorExtensions}}(&self{{#allParams}}, {{paramName}}: {{^required}}{{#isFile}}Box<Future<Item={{/isFile}}Option<{{/required}}{{#isListContainer}}&{{/isListContainer}}{{{dataType}}}{{^required}}>{{#isFile}}, Error=Error> + Send>{{/isFile}}{{/required}}{{/allParams}}, context: &C) -> Box<Future<Item={{operationId}}Response, Error=ApiError>>;
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
}

Expand All @@ -64,18 +64,18 @@ pub trait ApiNoContext {
}

/// Trait to extend an API to make it easy to bind it to a context.
pub trait ContextWrapperExt<'a> where Self: Sized {
pub trait ContextWrapperExt<'a, C> where Self: Sized {
/// Binds this API to a context.
fn with_context(self: &'a Self, context: Context) -> ContextWrapper<'a, Self>;
fn with_context(self: &'a Self, context: C) -> ContextWrapper<'a, Self, C>;
}

impl<'a, T: Api + Sized> ContextWrapperExt<'a> for T {
fn with_context(self: &'a T, context: Context) -> ContextWrapper<'a, T> {
ContextWrapper::<T>::new(self, context)
impl<'a, T: Api<C> + Sized, C> ContextWrapperExt<'a, C> for T {
fn with_context(self: &'a T, context: C) -> ContextWrapper<'a, T, C> {
ContextWrapper::<T, C>::new(self, context)
}
}

impl<'a, T: Api> ApiNoContext for ContextWrapper<'a, T> {
impl<'a, T: Api<C>, C> ApiNoContext for ContextWrapper<'a, T, C> {
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
{{#summary}} /// {{{summary}}}{{/summary}}
fn {{#vendorExtensions}}{{operation_id}}{{/vendorExtensions}}(&self{{#allParams}}, {{paramName}}: {{^required}}{{#isFile}}Box<Future<Item={{/isFile}}Option<{{/required}}{{#isListContainer}}&{{/isListContainer}}{{{dataType}}}{{^required}}>{{#isFile}}, Error=Error> + Send>{{/isFile}}{{/required}}{{/allParams}}) -> Box<Future<Item={{operationId}}Response, Error=ApiError>> {
Expand Down
Loading

0 comments on commit 415edb8

Please sign in to comment.