mirror of
https://github.com/storytold/voder-proxy.git
synced 2026-10-19 07:43:12 +00:00
cleanup
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
use anyhow::Result as AnyhowResult;
|
use anyhow::Result as AnyhowResult;
|
||||||
use futures::TryStreamExt;
|
use futures::TryStreamExt;
|
||||||
use hyper::http::HeaderValue;
|
use hyper::http::HeaderValue;
|
||||||
use hyper::{Body, Request, HeaderMap};
|
use hyper::{Body, Request, HeaderMap, Response, StatusCode};
|
||||||
|
use hyper::body::Bytes;
|
||||||
|
|
||||||
/// NB: This much match the shape of SpeakRequest in the 'voder/tts_service' code.
|
/// NB: This much match the shape of SpeakRequest in the 'voder/tts_service' code.
|
||||||
/// This is used for both /speak and /speak_spectrogram requests.
|
/// This is used for both /speak and /speak_spectrogram requests.
|
||||||
@@ -37,6 +38,21 @@ pub struct InternalSpeakRequest {
|
|||||||
pub skip_rate_limiter: bool,
|
pub skip_rate_limiter: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum ErrorType {
|
||||||
|
/// The proxy couldn't find a healthy host (or it died mid-connection)
|
||||||
|
ProxyError,
|
||||||
|
// The backend server reported a 500
|
||||||
|
ServerError,
|
||||||
|
/// Client is rate limited
|
||||||
|
RateLimitError,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ErrorResponse {
|
||||||
|
/// The type of error
|
||||||
|
pub error_type: ErrorType,
|
||||||
|
/// Description of the error
|
||||||
|
pub error_description: String,
|
||||||
|
}
|
||||||
|
|
||||||
impl ExternalHttpRequest {
|
impl ExternalHttpRequest {
|
||||||
|
|
||||||
@@ -54,6 +70,27 @@ impl ExternalHttpRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ErrorType {
|
||||||
|
pub fn status_code(&self) -> StatusCode {
|
||||||
|
match *self {
|
||||||
|
ErrorType::ProxyError =>StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
ErrorType::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
ErrorType::RateLimitError => StatusCode::TOO_MANY_REQUESTS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ErrorResponse {
|
||||||
|
pub fn to_response(&self) -> Response<Body> {
|
||||||
|
let status_code = self.error_type.status_code();
|
||||||
|
let body = Body::from(self.error_description.clone());
|
||||||
|
Response::builder()
|
||||||
|
.status(status_code)
|
||||||
|
.body(body)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn body_to_bytes(body: Body) -> AnyhowResult<Vec<u8>> {
|
async fn body_to_bytes(body: Body) -> AnyhowResult<Vec<u8>> {
|
||||||
// Also: let body_bytes = hyper::body::to_bytes(body)
|
// Also: let body_bytes = hyper::body::to_bytes(body)
|
||||||
let bytes = body.try_fold(Vec::new(), |mut data, chunk| async move {
|
let bytes = body.try_fold(Vec::new(), |mut data, chunk| async move {
|
||||||
+14
-73
@@ -4,20 +4,20 @@
|
|||||||
#[macro_use] extern crate log;
|
#[macro_use] extern crate log;
|
||||||
#[macro_use] extern crate serde_derive;
|
#[macro_use] extern crate serde_derive;
|
||||||
|
|
||||||
|
pub mod api;
|
||||||
pub mod newrelic_logger;
|
pub mod newrelic_logger;
|
||||||
pub mod request;
|
|
||||||
pub mod speak_proxy;
|
pub mod speak_proxy;
|
||||||
|
|
||||||
use anyhow::Result as AnyhowResult;
|
use anyhow::Result as AnyhowResult;
|
||||||
use crate::newrelic_logger::{NewRelicLogger, MaybeNewRelicTransaction};
|
use crate::newrelic_logger::{NewRelicLogger, MaybeNewRelicTransaction};
|
||||||
use futures::future::{self, Future};
|
|
||||||
use crate::speak_proxy::speak_proxy_with_retry;
|
use crate::speak_proxy::speak_proxy_with_retry;
|
||||||
use hyper::{Method, StatusCode};
|
use futures::future::{self, Future};
|
||||||
use hyper::header::HeaderValue;
|
use hyper::header::HeaderValue;
|
||||||
use hyper::http::header::HeaderName;
|
use hyper::http::header::HeaderName;
|
||||||
use hyper::server::conn::AddrStream;
|
use hyper::server::conn::AddrStream;
|
||||||
use hyper::service::{service_fn, make_service_fn};
|
use hyper::service::{service_fn, make_service_fn};
|
||||||
use hyper::{Body, Request, Response, Server};
|
use hyper::{Body, Request, Response, Server};
|
||||||
|
use hyper::{Method, StatusCode};
|
||||||
use rand::seq::IteratorRandom;
|
use rand::seq::IteratorRandom;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
@@ -28,9 +28,6 @@ use std::net::{SocketAddr, IpAddr};
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
//type BoxFut = Box<dyn Future + Send>;
|
|
||||||
type BoxFut = Box<dyn Future<Output=Response<Body>> + Send>;
|
|
||||||
|
|
||||||
const ENV_DEFAULT_ROUTE : &'static str = "DEFAULT_ROUTE";
|
const ENV_DEFAULT_ROUTE : &'static str = "DEFAULT_ROUTE";
|
||||||
const ENV_NEWRELIC_API_KEY : &'static str = "NEWRELIC_API_KEY";
|
const ENV_NEWRELIC_API_KEY : &'static str = "NEWRELIC_API_KEY";
|
||||||
const ENV_PROXY_CONFIG_FILE : &'static str = "PROXY_CONFIG_FILE";
|
const ENV_PROXY_CONFIG_FILE : &'static str = "PROXY_CONFIG_FILE";
|
||||||
@@ -92,17 +89,6 @@ fn get_env_num<T>(env_name: &str, default: T) -> AnyhowResult<T>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*fn _debug_request(req: Request<Body>) -> BoxFut {
|
|
||||||
let body_str = format!("{:?}", req);
|
|
||||||
let response = Response::new(Body::from(body_str));
|
|
||||||
Box::new(future::ok(response))
|
|
||||||
}*/
|
|
||||||
|
|
||||||
/*fn health_check_response(_req: Request<Body>) -> BoxFut {
|
|
||||||
let response = Response::new(Body::from("healthy"));
|
|
||||||
Box::new(future::ok(response))
|
|
||||||
}*/
|
|
||||||
|
|
||||||
pub struct Router {
|
pub struct Router {
|
||||||
pub routes: HashMap<String, String>,
|
pub routes: HashMap<String, String>,
|
||||||
pub default_route: String,
|
pub default_route: String,
|
||||||
@@ -129,6 +115,10 @@ fn debug_request(req: Request<Body>) -> Result<Response<Body>, Infallible> {
|
|||||||
Ok(Response::new(Body::from(body_str)))
|
Ok(Response::new(Body::from(body_str)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn health_check_response(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
|
||||||
|
Ok(Response::new(Body::from("healthy")))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn handle(
|
pub async fn handle(
|
||||||
client_ip: IpAddr,
|
client_ip: IpAddr,
|
||||||
req: Request<Body>,
|
req: Request<Body>,
|
||||||
@@ -140,36 +130,21 @@ pub async fn handle(
|
|||||||
let nr_transaction = newrelic_logger.web_transaction("/speak");
|
let nr_transaction = newrelic_logger.web_transaction("/speak");
|
||||||
let result = speak_proxy_with_retry(client_ip, req, router, nr_transaction, "/speak").await;
|
let result = speak_proxy_with_retry(client_ip, req, router, nr_transaction, "/speak").await;
|
||||||
match result {
|
match result {
|
||||||
Ok(response) => {
|
Ok(response) => Ok(response),
|
||||||
Ok(response)
|
Err(error) => Ok(error.to_response()),
|
||||||
},
|
|
||||||
Err(_error) => {
|
|
||||||
Ok(Response::builder()
|
|
||||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
|
||||||
.body(Body::empty())
|
|
||||||
.unwrap())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/speak_spectrogram" => {
|
"/speak_spectrogram" => {
|
||||||
let nr_transaction = newrelic_logger.web_transaction("/speak_spectrogram");
|
let nr_transaction = newrelic_logger.web_transaction("/speak_spectrogram");
|
||||||
let result = speak_proxy_with_retry(client_ip, req, router, nr_transaction, "/speak_spectrogram").await;
|
let result = speak_proxy_with_retry(client_ip, req, router, nr_transaction, "/speak_spectrogram").await;
|
||||||
match result {
|
match result {
|
||||||
Ok(response) => {
|
Ok(response) => Ok(response),
|
||||||
Ok(response)
|
Err(error) => Ok(error.to_response()),
|
||||||
},
|
|
||||||
Err(_error) => {
|
|
||||||
Ok(Response::builder()
|
|
||||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
|
||||||
.body(Body::empty())
|
|
||||||
.unwrap())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/proxy_health" => {
|
"/proxy_health" => {
|
||||||
let _droppable_transaction = newrelic_logger.web_transaction("/proxy_health");
|
let _droppable_transaction = newrelic_logger.web_transaction("/proxy_health");
|
||||||
//health_check_response(req)
|
health_check_response(req)
|
||||||
debug_request(req)
|
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
let _droppable_transaction = newrelic_logger
|
let _droppable_transaction = newrelic_logger
|
||||||
@@ -182,10 +157,10 @@ pub async fn handle(
|
|||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
Ok(response)
|
Ok(response)
|
||||||
},
|
},
|
||||||
Err(error) => {
|
Err(_) => {
|
||||||
Ok(Response::builder()
|
Ok(Response::builder()
|
||||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||||
.body(Body::empty())
|
.body(Body::from("unknown proxy error"))
|
||||||
.unwrap())
|
.unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,40 +216,6 @@ async fn main() {
|
|||||||
|
|
||||||
let newrelic_logger = Arc::new(newrelic_logger);
|
let newrelic_logger = Arc::new(newrelic_logger);
|
||||||
|
|
||||||
/*let make_service = make_service_fn(move |socket: &AddrStream| {
|
|
||||||
let remote_addr = socket.remote_addr();
|
|
||||||
info!("Got a request from: {:?}", remote_addr);
|
|
||||||
|
|
||||||
let router2 = router.clone();
|
|
||||||
let newrelic_logger2 = newrelic_logger.clone();
|
|
||||||
|
|
||||||
service_fn(move |req: Request<Body>| {
|
|
||||||
let router3 = router2.clone();
|
|
||||||
let newrelic_logger3 = newrelic_logger2.clone();
|
|
||||||
|
|
||||||
match (req.method(), req.uri().path()) {
|
|
||||||
/*(&Method::POST, "/speak") => {
|
|
||||||
let nr_transaction = newrelic_logger3.web_transaction("/speak");
|
|
||||||
speak_proxy(req, remote_addr.clone(), router3, nr_transaction, "/speak")
|
|
||||||
},
|
|
||||||
(&Method::POST, "/speak_spectrogram") => {
|
|
||||||
let nr_transaction = newrelic_logger3.web_transaction("/speak_spectrogram");
|
|
||||||
speak_proxy(req, remote_addr.clone(), router3, nr_transaction, "/speak_spectrogram")
|
|
||||||
},*/
|
|
||||||
/*(&Method::GET, "/proxy_health") => {
|
|
||||||
let _droppable_transaction = newrelic_logger3.web_transaction("/proxy_health");
|
|
||||||
health_check_response(req)
|
|
||||||
},*/
|
|
||||||
_ => {
|
|
||||||
let _droppable_transaction = newrelic_logger3.web_transaction("unmatched endpoint");
|
|
||||||
let forward = router3.get_random_host();
|
|
||||||
info!("Forwarding to `{}` random host: {}", &forward, req.uri());
|
|
||||||
hyper_reverse_proxy::call(remote_addr.ip(), &forward, req)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});*/
|
|
||||||
|
|
||||||
let make_service = make_service_fn(move |conn: &AddrStream| {
|
let make_service = make_service_fn(move |conn: &AddrStream| {
|
||||||
let remote_addr = conn.remote_addr().ip();
|
let remote_addr = conn.remote_addr().ip();
|
||||||
let router2 = router.clone();
|
let router2 = router.clone();
|
||||||
|
|||||||
+19
-16
@@ -1,6 +1,7 @@
|
|||||||
use anyhow::Result as AnyhowResult;
|
use anyhow::Result as AnyhowResult;
|
||||||
|
use crate::Router;
|
||||||
|
use crate::api::{ExternalSpeakRequest, ExternalHttpRequest, InternalSpeakRequest, ErrorResponse, ErrorType};
|
||||||
use crate::newrelic_logger::{NewRelicLogger, MaybeNewRelicTransaction};
|
use crate::newrelic_logger::{NewRelicLogger, MaybeNewRelicTransaction};
|
||||||
use crate::{BoxFut, Router};
|
|
||||||
use futures::TryStreamExt;
|
use futures::TryStreamExt;
|
||||||
use futures::future::{self, Future};
|
use futures::future::{self, Future};
|
||||||
use hyper::header::HeaderValue;
|
use hyper::header::HeaderValue;
|
||||||
@@ -13,20 +14,20 @@ use hyper_reverse_proxy::ProxyError;
|
|||||||
use rand::seq::IteratorRandom;
|
use rand::seq::IteratorRandom;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
use std::env;
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::net::{SocketAddr, IpAddr};
|
use std::net::{SocketAddr, IpAddr};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use crate::request::{ExternalSpeakRequest, ExternalHttpRequest, InternalSpeakRequest};
|
use std::time::Duration;
|
||||||
|
use std::{env, thread};
|
||||||
|
|
||||||
pub async fn speak_proxy_with_retry(
|
pub async fn speak_proxy_with_retry(
|
||||||
ip_addr: IpAddr,
|
ip_addr: IpAddr,
|
||||||
request: Request<Body>,
|
request: Request<Body>,
|
||||||
router: Arc<Router>,
|
router: Arc<Router>,
|
||||||
_transaction: MaybeNewRelicTransaction,
|
_transaction: MaybeNewRelicTransaction,
|
||||||
endpoint: &'static str) -> Result<Response<Body>, ResponseError>
|
endpoint: &'static str) -> Result<Response<Body>, ErrorResponse>
|
||||||
{
|
{
|
||||||
let mut attempt : u8 = 0;
|
let mut attempt : u8 = 0;
|
||||||
|
|
||||||
@@ -79,14 +80,20 @@ pub async fn speak_proxy_with_retry(
|
|||||||
&load_balancer_hostname,
|
&load_balancer_hostname,
|
||||||
endpoint).await;
|
endpoint).await;
|
||||||
|
|
||||||
if result.is_ok() {
|
match result {
|
||||||
return result;
|
Ok(good_response) => return Ok(good_response),
|
||||||
|
Err(_) => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
attempt += 1;
|
attempt += 1;
|
||||||
|
|
||||||
if attempt > 3 {
|
thread::sleep(Duration::from_millis(400)); // TODO: Needs backoff + jitter
|
||||||
return Err(ResponseError { reason: "max attempts reached".to_string() });
|
|
||||||
|
if attempt > 2 {
|
||||||
|
return Err(ErrorResponse {
|
||||||
|
error_type: ErrorType::ProxyError,
|
||||||
|
error_description: "max internal retry attempts reached".to_string(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,9 +108,10 @@ async fn speak_proxy(
|
|||||||
internal_speak_request: InternalSpeakRequest,
|
internal_speak_request: InternalSpeakRequest,
|
||||||
headers: HeaderMap<HeaderValue>,
|
headers: HeaderMap<HeaderValue>,
|
||||||
load_balancer_hostname: &str,
|
load_balancer_hostname: &str,
|
||||||
endpoint: &'static str) -> Result<Response<Body>, ResponseError>
|
endpoint: &'static str) -> Result<Response<Body>, ()>
|
||||||
{
|
{
|
||||||
info!("Routing {} for {} to {}", endpoint, &internal_speak_request.speaker, &load_balancer_hostname);
|
info!("Routing {} for {} to {} (attempt # {})", endpoint, &internal_speak_request.speaker,
|
||||||
|
&load_balancer_hostname, internal_speak_request.retry_attempt_number);
|
||||||
|
|
||||||
let mut request_builder = Request::builder()
|
let mut request_builder = Request::builder()
|
||||||
.method(Method::POST)
|
.method(Method::POST)
|
||||||
@@ -116,17 +124,12 @@ async fn speak_proxy(
|
|||||||
|
|
||||||
let json_speak_request = serde_json::to_string(&internal_speak_request).unwrap();
|
let json_speak_request = serde_json::to_string(&internal_speak_request).unwrap();
|
||||||
|
|
||||||
println!("Request: {:?}", internal_speak_request);
|
|
||||||
|
|
||||||
let request = request_builder
|
let request = request_builder
|
||||||
.body(Body::from(json_speak_request))
|
.body(Body::from(json_speak_request))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
//Box::new(future::ok(Vec::new()))
|
|
||||||
match hyper_reverse_proxy::call(ip_addr, &load_balancer_hostname, request).await {
|
match hyper_reverse_proxy::call(ip_addr, &load_balancer_hostname, request).await {
|
||||||
Ok(result) => Ok(result),
|
Ok(result) => Ok(result),
|
||||||
Err(_result) => Err(ResponseError {
|
Err(_result) => Err(()), // TODO: Surface this?
|
||||||
reason: "Uknown failure".to_string()
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user