Unable to call paginated API actix - rust

I am writing an service that calls a paginated API with actix, I have searched docs and cannot figure out how to do this.
Can anyone please provide pointers?
Here is what I currently have:
#[derive(Deserialize, Debug)]
pub struct Balances {
balances: Vec<Balance>,
#[serde(rename = "current-round")]
current_round: i64,
#[serde(rename = "next-token")]
next_token: Option<String>,
}
async fn get_data() -> i64 {
let client = reqwest::Client::new();
let response = client
.get(&env::var("ALGOD_URL_1").unwrap())
.header("x-api-key", &env::var("ALGOD_TOKEN").unwrap())
.header(CONTENT_TYPE, "application/json")
.header(ACCEPT, "application/json")
.header("pragma", "public")
.send()
.await
.unwrap()
.json::<Balances>()
.await
.unwrap();
response.balances.len() as i64
}
async fn main() -> std::io::Result<()> {
dotenv().ok();
get_data();
}
Unfortunately, this only returns the first page how the results.

Related

Rust Actix-Web and Dependency Injection [duplicate]

I want to create a actix-web server where I can provide my Search trait as application data in order to easily swap between multiple implementations or use mock implementation for testing. Whatever I try I can't get it to compile or when I get it to compile I get the following error when visiting the route in the web browser:
App data is not configured, to configure use App::data()
Here is what I have so far
# Cargo.toml
[dependencies]
actix-rt = "1.1.1"
actix-web = "3.3.2"
[dev-dependencies]
tokio = "0.2.22"
//! main.rs
use actix_web::dev::Server;
use actix_web::{get, web, App, HttpServer, Responder};
pub trait Search {
fn search(&self, query: &str) -> String;
}
#[derive(Clone)]
pub struct SearchClient {
base_url: String,
}
impl SearchClient {
pub fn new() -> Self {
Self {
base_url: String::from("/search"),
}
}
}
impl Search for SearchClient {
fn search(&self, query: &str) -> String {
format!("Searching in SearchClient: {}", query)
}
}
#[get("/{query}")]
async fn index(
web::Path(query): web::Path<String>,
search: web::Data<dyn Search>,
) -> impl Responder {
search.into_inner().search(&query)
}
pub fn create_server(
search: impl Search + Send + Sync + 'static + Clone,
) -> Result<Server, std::io::Error> {
let server = HttpServer::new(move || App::new().data(search.clone()).service(index))
.bind("127.0.0.1:8080")?
.run();
Ok(server)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let search_client = SearchClient::new();
create_server(search_client).unwrap().await
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone)]
pub struct TestClient;
impl Search for TestClient {
fn search(&self, query: &str) -> String {
format!("Searching in TestClient: {}", query)
}
}
#[actix_rt::test]
async fn test_search() {
let search_client = TestClient {};
let server = create_server(search_client).unwrap();
tokio::spawn(server);
}
}
When adding the data to your App, you have to specify that you want it to be downcasted as a trait object. Data does not accept unsized types directly, so you have to first create an Arc (which does accept unsized types) and then convert it to a Data. We will use the app_data method to avoid wrapping the searcher in a double arc.
pub fn create_server(
search: impl Search + Send + Sync + 'static,
) -> Result<Server, std::io::Error> {
let search: Data<dyn Search> = Data::from(Arc::new(search));
HttpServer::new(move || {
App::new()
.app_data(search.clone())
})
}
async fn index(
query: Path<String>,
search: Data<dyn Search>,
) -> impl Responder {
search.into_inner().search(&*query)
}
An alternative approach is using generics. Your handler and create_server functions would be generic over a Search implementation:
async fn index<T: Search>(
web::Path(query): web::Path<String>,
search: web::Data<T>,
-> impl Responder {
search.into_inner().search(&query)
}
pub fn create_server<T: Search + Send + Sync + 'static + Clone>(
search: T,
) -> Result<Server, std::io::Error> {
let server = HttpServer::new(move || {
App::new()
.data(search.clone())
.route("/{query}", web::get().to(index::<T>))
})
.bind("127.0.0.1:8080")?
.run();
Ok(server)
}
Now, when you create the server in main, you can use SearchClient:
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let search_client = SearchClient::new();
create_server(search_client).unwrap().await
}
And when you create the server for testing purposes, you could use a TestClient:
#[actix_rt::test]
async fn test_search() {
let search_client = TestClient {};
let server = create_server(search_client).unwrap();
}
The downside to the generics based approach is that you cannot use the #[get("")] macros for routing because you have to specify the handler's generic parameters:
App::new()
.route("/{query}", web::get().to(index::<T>))

How to retrieve the IP address of the client from HttpRequest in actix-web?

Is it possible to obtain the IP address from a HttpRequest argument?
This is my code:
#[get("/collect")]
pub async fn collect(req: HttpRequest) -> impl Responder {
println!("collect {:?}", req);
HttpResponse::Ok()
}
[dependencies]
actix-web = "3"
If you do not have a proxy in front of the service, it should be retrievable from the request peer_addr().
Otherwise, you can retrieve the request connection_info(), and from there, the realip_remote_addr().
Example:
#[get("/collect")]
pub async fn collect(req: HttpRequest) -> impl Responder {
if let Some(val) = req.peer_addr() {
println!("Address {:?}", val.ip());
};
HttpResponse::Ok()
}

Errors when moving to actix-web 3.0

Better late than never and so I started re-learning Rust and decided to focus on actix and actix-web.
I have these codes running in actix-web 1.0 and it seems not to run in actix-web 3.0:
main.rs
use messages_actix::MessageApp;
fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init();
let app = MessageApp::new(8081);
app.run() // error here
}
error: "no method named run found for opaque type impl std::future::Future in the current scope method not found in impl std::future::Future
lib.rs
#[macro_use]
extern crate actix_web;
use actix_web::{middleware, web, App, HttpRequest, HttpServer, Result};
use serde::Serialize;
pub struct MessageApp {
pub port: u16,
}
#[derive(Serialize)]
pub struct IndexResponse{
pub message: String,
}
#[get("/")]
pub fn index(req: HttpRequest) -> Result<web::Json<IndexResponse>> { // error here
let hello = req
.headers()
.get("hello")
.and_then(|v| v.to_str().ok())
.unwrap_or_else(|| "world");
Ok(web::Json(IndexResponse {
message: hello.to_owned(),
}))
}
error for index: the trait Factory<_, _, _> is not implemented for fn(HttpRequest) -> std::result::Result<Json<IndexResponse>, actix_web::Error> {<index as HttpServiceFactory>::register::index}
impl MessageApp {
pub fn new(port: u16) -> Self {
MessageApp{ port }
}
pub fn run(&self) -> std::io::Result<()> {
println!("Starting HTTP server at 127.0.0.1:{}", self.port);
HttpServer::new(move || {
App::new()
.wrap(middleware::Logger::default())
.service(index)
})
.bind(("127.0.0.1", self.port))?
.workers(8)
.run() //error here
}
}
error: expected enum std::result::Result, found struct Server
checked the migration doc yet can't find what I'm looking for in relation to the errors listed.
Any help greatly appreciated...Thanks...
Newer versions of actix-web now use the async-await syntax, which was made stable as of Rust 1.39. You have to make your handlers async:
#[get("/")]
pub async fn index(req: HttpRequest) -> Result<web::Json<IndexResponse>> {
// ...
}
Creating an HttpServer is now an async operation:
impl MessageApp {
pub fn run(&self) -> std::io::Result<()>
HttpServer::new(...)
.run()
.await
}
}
And you can use the main macro to use async/await in your main function:
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let app = MessageApp::new(8081);
app.run().await
}

How do I turn invalid enum variants into None when used as query parameters in actix-web

With the example provided in the documentation for actix_web::web::Query, how can I make the response_type resort to None when providing an unknown variant?
If I have the following:
use actix_web::{web, App, HttpServer};
use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub enum ResponseType {
Token,
Code,
}
#[derive(Deserialize)]
pub struct AuthRequest {
id: u64,
response_type: Option<ResponseType>,
}
async fn index(web::Query(info): web::Query<AuthRequest>) -> String {
format!(
"Authorization request for client with id={} and type={:?}!",
info.id, info.response_type
)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().route("/", web::get().to(index)))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
And I visit http://localhost:8080/?id=1&response_type=foo, I get this 400 response:
Query deserialize error: unknown variant foo, expected Token or Code
When I instead would like it to only accept the values of the Enum as valid values, and if no value or an invalid value is provided I want it to be set to None.
This can be dealt with deserialize_with.
use actix_web::{web, App, HttpServer};
use serde::Deserialize;
use serde::de::{Deserializer};
#[derive(Debug, Deserialize)]
pub enum ResponseType {
Token,
Code,
}
fn from_response_type<'de, D>(deserializer: D) -> Result<Option<ResponseType>, D::Error>
where
D: Deserializer<'de>,
{
let res: Option<ResponseType> = Deserialize::deserialize(deserializer).unwrap_or(None);
Ok(res)
}
#[derive(Debug, Deserialize)]
pub struct AuthRequest {
id: u64,
#[serde(deserialize_with = "from_response_type")]
response_type: Option<ResponseType>,
}
async fn index(web::Query(info): web::Query<AuthRequest>) -> String {
format!(
"Authorization request for client with id={} and type={:?}!",
info.id, info.response_type
)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().route("/", web::get().to(index)))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
Any invalid value is considered a None. The key line being
let res: Option<ResponseType> = Deserialize::deserialize(deserializer).unwrap_or(None);

Implementing hyper::client::Connect for testing

I'm trying to test some code that uses a hyper::Client by implementing my own hyper::client::Connect using a static response. I've got the types figured out, but can't figure out a runtime issue where tokio-proto complains saying request / response mismatch. Here's a simplified version of my code that demonstrates the failure:
extern crate futures;
extern crate hyper;
extern crate tokio_core;
extern crate tokio_io;
use futures::{future, Future, Stream};
use std::str::from_utf8;
use std::io::Cursor;
struct Client<'a, C: 'a> {
client: &'a hyper::Client<C>,
url: &'a str,
}
impl<'a, C: hyper::client::Connect> Client<'a, C> {
fn get(&self) -> Box<Future<Item = String, Error = hyper::Error>> {
Box::new(self.client.get(self.url.parse().unwrap()).and_then(|res| {
let body = Vec::new();
res.body()
.fold(body, |mut acc, chunk| {
acc.extend_from_slice(chunk.as_ref());
Ok::<_, hyper::Error>(acc)
})
.and_then(move |value| Ok(String::from(from_utf8(&value).unwrap())))
}))
}
}
struct StaticConnector<'a> {
body: &'a [u8],
}
impl<'a> StaticConnector<'a> {
fn new(body: &'a [u8]) -> StaticConnector {
StaticConnector { body: body }
}
}
impl<'a> hyper::server::Service for StaticConnector<'a> {
type Request = hyper::Uri;
type Response = Cursor<Vec<u8>>;
type Error = std::io::Error;
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
fn call(&self, _: Self::Request) -> Self::Future {
Box::new(future::ok(Cursor::new(self.body.to_vec())))
}
}
fn main() {
let mut core = tokio_core::reactor::Core::new().unwrap();
let handle = core.handle();
// My StaticConnector for testing
let hyper_client = hyper::Client::configure()
.connector(StaticConnector::new(
b"\
HTTP/1.1 200 OK\r\n\
Content-Length: 8\r\n\
\r\n\
Maldives\
",
))
.build(&handle);
// Real Connector
/*
let hyper_client = hyper::Client::configure().build(&handle);
*/
let client = Client {
client: &hyper_client,
url: "http://ifconfig.co/country",
};
let result = core.run(client.get()).unwrap();
println!("{}", result);
}
Playground
I'm guessing it's my use of the Cursor for Io that is incomplete in some way, but I'm failing to debug and make progress. One thought is that the writes to this Cursor the hyper::Client presumably makes are not working as expected. Maybe I need a combination of a sink for the writes and the static content for the reads? All ideas I've failed to make progress using!
The reason the original code didn't work was because the reader side provided the response before the client sent the request, so tokio-proto errored out with request / response mismatch. The fix turns out to be non trivial in that first we need to arrange for the reader to block, or more specifically error out with std::io::ErrorKind::WouldBlock to indicate to the event loop that there isn't anything yet, but don't consider it an EOF. Additionally once we get the write which indicates the request has been sent and the tokio-proto machinery is waiting for a response, we use futures::task::current.notify to unblock the read. Here's an updated implementation that works as expected:
extern crate futures;
extern crate hyper;
extern crate tokio_core;
extern crate tokio_io;
use futures::{future, Future, Stream, task, Poll};
use std::str::from_utf8;
use std::io::{self, Cursor, Read, Write};
use tokio_io::{AsyncRead, AsyncWrite};
struct Client<'a, C: 'a> {
client: &'a hyper::Client<C>,
url: &'a str,
}
impl<'a, C: hyper::client::Connect> Client<'a, C> {
fn get(&self) -> Box<Future<Item = String, Error = hyper::Error>> {
Box::new(self.client.get(self.url.parse().unwrap()).and_then(|res| {
let body = Vec::new();
res.body()
.fold(body, |mut acc, chunk| {
acc.extend_from_slice(chunk.as_ref());
Ok::<_, hyper::Error>(acc)
})
.and_then(move |value| Ok(String::from(from_utf8(&value).unwrap())))
}))
}
}
struct StaticStream {
wrote: bool,
body: Cursor<Vec<u8>>,
}
impl Read for StaticStream {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.wrote {
self.body.read(buf)
} else {
Err(io::ErrorKind::WouldBlock.into())
}
}
}
impl Write for StaticStream {
fn write<'a>(&mut self, buf: &'a [u8]) -> io::Result<usize> {
self.wrote = true;
task::current().notify();
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl AsyncRead for StaticStream {}
impl AsyncWrite for StaticStream {
fn shutdown(&mut self) -> Poll<(), io::Error> {
Ok(().into())
}
}
struct StaticConnector<'a> {
body: &'a [u8],
}
impl<'a> StaticConnector<'a> {
fn new(body: &'a [u8]) -> StaticConnector {
StaticConnector { body: body }
}
}
impl<'a> hyper::server::Service for StaticConnector<'a> {
type Request = hyper::Uri;
type Response = StaticStream;
type Error = std::io::Error;
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
fn call(&self, _: Self::Request) -> Self::Future {
Box::new(future::ok(StaticStream {
wrote: false,
body: Cursor::new(self.body.to_vec()),
}))
}
}
fn main() {
let mut core = tokio_core::reactor::Core::new().unwrap();
let handle = core.handle();
// My StaticConnector for testing
let hyper_client = hyper::Client::configure()
.connector(StaticConnector::new(
b"\
HTTP/1.1 200 OK\r\n\
Content-Length: 8\r\n\
\r\n\
Maldives\
",
))
.build(&handle);
// Real Connector
/*
let hyper_client = hyper::Client::configure().build(&handle);
*/
let client = Client {
client: &hyper_client,
url: "http://ifconfig.co/country",
};
let result = core.run(client.get()).unwrap();
println!("{}", result);
}
Playground
Note: This implementation works for simple cases, but I haven't tested more complex scenarios. For example one thing I'm unsure of is how large request/responses behave as they may involve more than 1 read/write call.

Resources