How can I make protected routes in actix-web - authentication

I need to verify if the user has permission for some routes.
I have made 3 "scopes" (guest, auth-user, admin) and now I don't know how to check if the user has access to these routes.
I'm trying to implement auth-middleware and this middleware should check if the user has the correct cookie or token. (I'm able to print out a cookie from request header), but I have no idea how to import, use actix_identity, and have access to id parameter inside this middleware.
I believe that my problem isn't only regarding Actix-identity, but I'm not able to pass parameters inside middleware.
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
let cookie_key = conf.server.key;
// Register http routes
let mut server = HttpServer::new(move || {
App::new()
// Enable logger
.wrap(Logger::default())
.wrap(IdentityService::new(
CookieIdentityPolicy::new(cookie_key.as_bytes())
.name("auth-cookie")
.path("/")
.secure(false),
))
//limit the maximum amount of data that server will accept
.data(web::JsonConfig::default().limit(4096))
//normal routes
.service(web::resource("/").route(web::get().to(status)))
// .configure(routes)
.service(
web::scope("/api")
// guest endpoints
.service(web::resource("/user_login").route(web::post().to(login)))
.service(web::resource("/user_logout").route(web::post().to(logout)))
// admin endpoints
.service(
web::scope("/admin")
// .wrap(AdminAuthMiddleware)
.service(
web::resource("/create_admin").route(web::post().to(create_admin)),
)
.service(
web::resource("/delete_admin/{username}/{_:/?}")
.route(web::delete().to(delete_admin)),
),
)
//user auth routes
.service(
web::scope("/auth")
// .wrap(UserAuthMiddleware)
.service(web::resource("/get_user").route(web::get().to(get_user))),
),
)
});
// Enables us to hot reload the server
let mut listenfd = ListenFd::from_env();
server = if let Some(l) = listenfd.take_tcp_listener(0).unwrap() {
server.listen(l)?
} else {
server.bind(ip)?
};
server.run().await
resources that I have tried:
Creating authentication middleware for Actix API
https://www.jamesbaum.co.uk/blether/creating-authentication-middleware-actix-rust-react/
Actix-web token validation in middleware https://users.rust-lang.org/t/actix-web-token-validation-in-middleware/38205
Actix middleware examples https://github.com/actix/examples/tree/master/middleware
Maybe I think completely wrong and auth-middleware isn't the best solution for my problem.
I hope that you can help me create "protected routes"

Try extractors instead
Trying to implement this pattern in Actix 3 I banged my head for awhile trying to use middleware, basically making a guard and then figuring out how to pass data from the middleware into the handler. It was painful and eventually I realized that I was working against Actix rather than with it.
Finally I learned out that the way to get information to a handler is to create a struct (AuthedUser, perhaps?) and implement the FromRequest trait on that struct.
Then every handler that asks for an AuthedUser in the function signature will be auth gated and if the user is logged in will have any user information you attach to AuthedUser in the FromRequest::from_request method.
Actix refers to these structs that implement FromRequest as extractors. It's a bit of magic that could use more attention in the guide.

The following does not use middleware(a little bit more work is needed) but it solves the problem with the bear minimum and seems to be the approach suggested in documentation:
#[macro_use]
extern crate actix_web;
use actix::prelude::*;
use actix_identity::{CookieIdentityPolicy, Identity, IdentityService};
use actix_web::{
dev::Payload, error::ErrorUnauthorized, web, App, Error, FromRequest, HttpRequest,
HttpResponse, HttpServer, Responder,
};
use log::{info, warn};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, pin::Pin, sync::RwLock};
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
struct Sessions {
map: HashMap<String, User>,
}
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
#[serde(rename_all = "camelCase")]
struct Login {
id: String,
username: String,
scope: Scope,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
enum Scope {
Guest,
User,
Admin,
}
impl Default for Scope {
fn default() -> Self {
Scope::Guest
}
}
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
#[serde(rename_all = "camelCase")]
struct User {
id: String,
first_name: Option<String>,
last_name: Option<String>,
authorities: Scope,
}
impl FromRequest for User {
type Config = ();
type Error = Error;
type Future = Pin<Box<dyn Future<Output = Result<User, Error>>>>;
fn from_request(req: &HttpRequest, pl: &mut Payload) -> Self::Future {
let fut = Identity::from_request(req, pl);
let sessions: Option<&web::Data<RwLock<Sessions>>> = req.app_data();
if sessions.is_none() {
warn!("sessions is empty(none)!");
return Box::pin(async { Err(ErrorUnauthorized("unauthorized")) });
}
let sessions = sessions.unwrap().clone();
Box::pin(async move {
if let Some(identity) = fut.await?.identity() {
if let Some(user) = sessions
.read()
.unwrap()
.map
.get(&identity)
.map(|x| x.clone())
{
return Ok(user);
}
};
Err(ErrorUnauthorized("unauthorized"))
})
}
}
#[get("/admin")]
async fn admin(user: User) -> impl Responder {
if user.authorities != Scope::Admin {
return HttpResponse::Unauthorized().finish();
}
HttpResponse::Ok().body("You are an admin")
}
#[get("/account")]
async fn account(user: User) -> impl Responder {
web::Json(user)
}
#[post("/login")]
async fn login(
login: web::Json<Login>,
sessions: web::Data<RwLock<Sessions>>,
identity: Identity,
) -> impl Responder {
let id = login.id.to_string();
let scope = &login.scope;
//let user = fetch_user(login).await // from db?
identity.remember(id.clone());
let user = User {
id: id.clone(),
last_name: Some(String::from("Doe")),
first_name: Some(String::from("John")),
authorities: scope.clone(),
};
sessions.write().unwrap().map.insert(id, user.clone());
info!("login user: {:?}", user);
HttpResponse::Ok().json(user)
}
#[post("/logout")]
async fn logout(sessions: web::Data<RwLock<Sessions>>, identity: Identity) -> impl Responder {
if let Some(id) = identity.identity() {
identity.forget();
if let Some(user) = sessions.write().unwrap().map.remove(&id) {
warn!("logout user: {:?}", user);
}
}
HttpResponse::Unauthorized().finish()
}
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
env_logger::init();
let sessions = web::Data::new(RwLock::new(Sessions {
map: HashMap::new(),
}));
HttpServer::new(move || {
App::new()
.app_data(sessions.clone())
.wrap(IdentityService::new(
CookieIdentityPolicy::new(&[0; 32])
.name("test")
.secure(false),
))
.service(account)
.service(login)
.service(logout)
.service(admin)
})
.bind("127.0.0.1:8088")?
.run()
.await
}
You can clone and run it here: https://github.com/geofmureithi/actix-acl-example

I think actix-web grants crate is perfect for you.
It allows you to check authorization using Guard, or a procedural macro (see examples on github).
It also integrates nicely with existing authorization middleware (like actix-web-httpauth).
A couple of examples for clarity:
proc-macro way
#[get("/secure")]
#[has_permissions("ROLE_ADMIN")]
async fn macro_secured() -> HttpResponse {
HttpResponse::Ok().body("ADMIN_RESPONSE")
}
Guard way
App::new()
.wrap(GrantsMiddleware::with_extractor(extract))
.service(web::resource("/admin")
.to(|| async { HttpResponse::Ok().finish() })
.guard(PermissionGuard::new("ROLE_ADMIN".to_string())))
And you can also take a look towards actix-casbin-auth (implementation of casbin integrated into actix)

Well this is in fact quite difficult to achieve in the newest actix-web version 3.0. What I did was copy the CookieIdentityPolicy middleware from the actix-web 1.0 version and modified it to my liking. However this is not plug & play code. Here and here is my version of it. Generally I would avoid actix-web, getting a thread / actor to spawn in the background and having it perform HTTP Requests are a nightmare. Then trying to share the results with handlers even more so.

middleware doesn't look very friendly with all the generics and internal types it defines, but it is a simple struct that wrap the next service to
be called. What is the next service is determined by the chain call when you create your App or define your routes. You use a generic S in your middleware which will be monomorphized at compile time so you don't have to care about which concrete type the middleware will protect.
The following middleware use a simple config passed to your App with .data() to check if the 'token' header contains the same magic value. It either go through the next service or return a not authorized error (futures).
use crate::config::Config;
use actix_service::{Service, Transform};
use actix_web::{
dev::{ServiceRequest, ServiceResponse},
error::ErrorUnauthorized,
web::Data,
Error,
};
use futures::future::{err, ok, Either, Ready};
use std::task::{Context, Poll};
pub struct TokenAuth;
impl<S, B> Transform<S> for TokenAuth
where
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{ type Request = ServiceRequest;
type Response = ServiceResponse<B>;
type Error = Error;
type InitError = ();
type Transform = TokenAuthMiddleware<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ok(TokenAuthMiddleware { service })
}
}
pub struct TokenAuthMiddleware<S> {
service: S,
}
impl<S, B> Service for TokenAuthMiddleware<S>
where
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
{ type Request = ServiceRequest;
type Response = ServiceResponse<B>;
type Error = Error;
type Future = Either<S::Future, Ready<Result<Self::Response, Self::Error>>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}
fn call(&mut self, req: ServiceRequest) -> Self::Future {
if let Some(token) = req
.headers()
.get("token")
.and_then(|token| token.to_str().ok())
{
if let Some(config) = req.app_data::<Data<Config>>() {
if token == config.token {
return Either::Left(self.service.call(req));
}
}
}
Either::Right(err(ErrorUnauthorized("not authorized")))
}
}
to protect your functions is then as simple as
#[post("/upload", wrap="TokenAuth")]
async fn upload(mut payload: Multipart) -> Result<HttpResponse, Error> {
}
Note that you need actix_service 1.x for this to compile. actix_service 2 remove the request internal type to make it generic and I couldn't make it work with the wrap="" syntax

Related

Idiomatic way to collect all errors from an iterator

Let's say I have a attrs: Vec<Attribute> of some function attributes and a function fn map_attribute(attr: &Attribute) -> Result<TokenStream, Error> that maps the attributes to some code.
I know that I could write something like this:
attrs.into_iter()
.map(map_attribute)
.collect::<Result<Vec<_>, _>()?
However, this is not what I want. What I want is spit out all errors at once, not stop with the first Error. Currently I do something like this:
let mut codes : Vec<TokenStream> = Vec::new();
let mut errors: Vec<Error> = Vec::new();
for attr in attrs {
match map_attribute(attr) {
Ok(code) => codes.push(code),
Err(err) => errors.push(err)
}
}
let mut error_iter = errors.into_iter();
if let Some(first) = error_iter.nth(0) {
return Err(iter.fold(first, |mut e0, e1| { e0.combine(e1); e0 }));
}
This second version does what I want, but is considerably more verbose than the first version. Is there a better / more idiomatic way to acchieve this, if possible without creating my own iterator?
The standard library does not have a convenient one-liner for this as far as I know, however the excellent itertools library does:
use itertools::Itertools; // 0.9.0
fn main() {
let foo = vec![Ok(42), Err(":("), Ok(321), Err("oh noes")];
let (codes, errors): (Vec<_>, Vec<_>)
= foo.into_iter().partition_map(From::from);
println!("codes={:?}", codes);
println!("errors={:?}", errors);
}
(Permalink to the playground)
I ended up writing my own extension for Iterator, which allows me to stop collecting codes when I encounter my first error. This is in my use case probably a bit more efficient than the answer by mcarton, since I only need the first partition bucket if the second one is empty. Also, I need to fold the errors anyways if I want to combine them into a single error.
pub trait CollectToResult
{
type Item;
fn collect_to_result(self) -> Result<Vec<Self::Item>, Error>;
}
impl<Item, I> CollectToResult for I
where
I : Iterator<Item = Result<Item, Error>>
{
type Item = Item;
fn collect_to_result(self) -> Result<Vec<Item>, Error>
{
self.fold(<Result<Vec<Item>, Error>>::Ok(Vec::new()), |res, code| {
match (code, res) {
(Ok(code), Ok(mut codes)) => { codes.push(code); Ok(codes) },
(Ok(_), Err(errors)) => Err(errors),
(Err(err), Ok(_)) => Err(err),
(Err(err), Err(mut errors)) => { errors.combine(err); Err(errors) }
}
})
}
}

How can I use rust Try trait with Option NoneError?

I've written a custom protocol where I've defined my own struct for a frame and it parses from bytes. My function accepts a Vec and parses the elements accordingly. To account for invalid frames, I am returning a Result<Frame> and calling .get() on the byte array. Here's my code:
fn main(){
let emptyvec = Vec::new();
match Frame::from_bytes(emptyvec) {
Err(e) => {
println!("Received invalid frame");
},
Ok(frame) => {
println!("Received valid frame");
}
}
}
struct Frame {
txflag: u8, // indicates if chunked
msgtype: u8, // a flag for message type
sender: u8, // which node ID sent this frame?
routeoffset: u8, // size of array of route for frame
route: Vec<u8>, // a list of node IDs that frame should pass
payload: Vec<u8>, // payload data
}
impl Frame {
/// parse from raw bytes
pub fn from_bytes(bytes: &Vec<u8>) -> std::io::Result<Self> {
let txflag = bytes.get(0)?.clone();
let msgtype = bytes.get(1)?.clone();
let sender = bytes.get(2)?.clone();
let routesoffset = bytes.get(3)?.clone();
let routes = &bytes.get(4..(4+routesoffset as usize))?;
let (left, right) = bytes.split_at(2);
let data = Vec::from(right);
Ok(Frame {
txflag,
msgtype,
sender,
routeoffset: routesoffset,
route: Vec::from(routes),
payload: data
})
}
}
However when I try to use this pattern I get the following compilation error, and when attempting to implement the trait I get an error that the Try trait is unstable.
error[E0277]: `?` couldn't convert the error to `std::io::Error`
--> src/stack/frame.rs:121:34
|
121 | let txflag = bytes.get(0)?.clone();
| ^ the trait `std::convert::From<std::option::NoneError>` is not implemented for `std::io::Error`
Not quite sure how to proceed but I'd like to use stable features to solve this. The goal here is to be able to parse bytes and handle an invalid frame as necessary.
This is probably what you want
use std::io::{Error, ErrorKind};
fn main() {
let emptyvec = Vec::new();
match Frame::from_bytes(&emptyvec) {
Err(e) => {
println!("Received invalid frame");
}
Ok(frame) => {
println!("Received valid frame");
}
}
}
struct Frame {
txflag: u8,
// indicates if chunked
msgtype: u8,
// a flag for message type
sender: u8,
// which node ID sent this frame?
routeoffset: u8,
// size of array of route for frame
route: Vec<u8>,
// a list of node IDs that frame should pass
payload: Vec<u8>, // payload data
}
impl Frame {
/// parse from raw bytes
pub fn from_bytes(bytes: &Vec<u8>) -> std::io::Result<Self> {
let txflag = bytes.get(0).ok_or(Error::from(ErrorKind::InvalidData))?.clone();
let msgtype = bytes.get(1).ok_or(Error::from(ErrorKind::InvalidData))?.clone();
let sender = bytes.get(2).ok_or(Error::from(ErrorKind::InvalidData))?.clone();
let routesoffset = bytes.get(3).ok_or(Error::from(ErrorKind::InvalidData))?.clone();
let routes = bytes
.get(4..(4 + routesoffset as usize))
.ok_or(Error::from(ErrorKind::InvalidData))?
.clone();
let (_, right) = bytes.split_at(2);
let data = Vec::from(right);
Ok(Frame {
txflag,
msgtype,
sender,
routeoffset: routesoffset,
route: Vec::from(routes),
payload: data,
})
}
}
Here is Rust Playground
You are trying to call ? on Option. You have to convert Option to Result (If you still want to use ?).
I want to add to what Đorðe Zeljić said:
As he already pointed out the result of bytes.get(0) is a std::option::Option. When you use the ? operator on that you already left the grounds of stable Rust. This application is only supported in unstable Rust at the moment.
If you want to stay in stable Rust, it's probably best to do what Đorðe wrote. If you want to keep using the ? operator because it produces nicer looking code, here is what's going on:
Rust has a lot of error types, each being only able to represent what they are made for. If you are using a std::io::Result this implicitly uses the error type std::io::Error which is only able to represent typical I/O errors. This type is not able to represent “there was no value when I expected one”. That's why from applying ? to a Option with the None value, you don't get a std::io::Error but a different kind of error: std::option::NoneError.
When your Rust application grows it will happen often, that you have to return a Result that can contain different types of errors. In that case you normally define your own error type (enum), that can represent different kinds of errors. Then for each error, that can be contained, you have to define the From trait on your own enum. This can be a lot of repeated work, so there is a macro in the quick-error crate, that helps with that and implements the From trait automatically for each error that can be contained.
To get your code compiling, you could define the following error enum, that can represent std::io::Error as well as std::option::NoneError:
quick_error! {
#[derive(Debug)]
pub enum FrameError {
IoError(err: std::io::Error) {from() cause(err)}
MissingValue(err: std::option::NoneError) {from()}
}
}
Instead of std::io::Result<Self> your from_bytes function then has to return a std::result::Result that uses your new error type: Result<Self, FrameError>.
Completely assembled that looks like this:
#![feature(try_trait)]
use quick_error::*;
quick_error! {
#[derive(Debug)]
pub enum FrameError {
IoError(err: std::io::Error) {from() cause(err)}
MissingValue(err: std::option::NoneError) {from()}
}
}
fn main() {
let emptyvec = Vec::new();
match Frame::from_bytes(&emptyvec) {
Err(_e) => {
println!("Received invalid frame");
}
Ok(_frame) => {
println!("Received valid frame");
}
}
}
struct Frame {
txflag: u8, // indicates if chunked
msgtype: u8, // a flag for message type
sender: u8, // which node ID sent this frame?
routeoffset: u8, // size of array of route for frame
route: Vec<u8>, // a list of node IDs that frame should pass
payload: Vec<u8>, // payload data
}
impl Frame {
/// parse from raw bytes
pub fn from_bytes(bytes: &Vec<u8>) -> Result<Self, FrameError> {
let txflag = bytes.get(0)?.clone();
let msgtype = bytes.get(1)?.clone();
let sender = bytes.get(2)?.clone();
let routesoffset = bytes.get(3)?.clone();
let routes = bytes.get(4..(4 + routesoffset as usize))?;
let (left, right) = bytes.split_at(2);
let data = Vec::from(right);
Ok(Frame {
txflag,
msgtype,
sender,
routeoffset: routesoffset,
route: Vec::from(routes),
payload: data,
})
}
}
To use the quick-error crate, you have to add the following to your Cargo.toml:
[dependencies]
quick-error = "1.2.3"

Is there a more ergonomic syntax for Either when using futures?

Here's an example of using Tokio to run a function that returns a future:
use futures::sync::oneshot;
use futures::Future;
use std::thread;
use std::time::Duration;
use tokio;
#[derive(Debug)]
struct MyError {
error_code: i32,
}
impl From<oneshot::Canceled> for MyError {
fn from(_: oneshot::Canceled) -> MyError {
MyError { error_code: 1 }
}
}
fn deferred_task() -> impl Future<Item = i32, Error = MyError> {
let (sx, rx) = oneshot::channel();
thread::spawn(move || {
thread::sleep(Duration::from_millis(100));
sx.send(100).unwrap();
});
return rx.map_err(|e| MyError::from(e));
}
fn main() {
tokio::run(deferred_task().then(|r| {
println!("{:?}", r);
Ok(())
}));
}
However, when the function in question (i.e. deferred_task) is non-trivial, the code becomes much more complex when I write it, because the ? operation doesn't seem to easily mix with returning a future:
fn send_promise_to_worker(sx: oneshot::Sender<i32>) -> Result<(), ()> {
// Send the oneshot somewhere in a way that might fail, eg. over a channel
thread::spawn(move || {
thread::sleep(Duration::from_millis(100));
sx.send(100).unwrap();
});
Ok(())
}
fn deferred_task() -> impl Future<Item = i32, Error = MyError> {
let (sx, rx) = oneshot::channel();
send_promise_to_worker(sx)?; // <-------- Can't do this, because the return is not a result
return rx.map_err(|e| MyError::from(e));
}
A Future is a Result, it's meaningless to wrap it in result, and it breaks the impl Future return type.
Instead you get a deeply nested chain of:
fn deferred_task() -> impl Future<Item = i32, Error = MyError> {
let (sx, rx) = oneshot::channel();
match query_data() {
Ok(_i) => match send_promise_to_worker(sx) {
Ok(_) => Either::A(rx.map_err(|e| MyError::from(e))),
Err(_e) => Either::B(futures::failed(MyError { error_code: 2 })),
},
Err(_) => Either::B(futures::failed(MyError { error_code: 2 })),
}
}
full code
The more results you have, the deeper the nesting; exactly what the ? operator solves normally.
Am I missing something? Is there some syntax sugar to make this easier?
I do not see how async / await syntax will categorically help you with Either. Ultimately, you still need to return a single concrete type, and that's what Either provides. async / await will reduce the need for combinators like Future::map or Future::and_then however.
See also:
Why can impl trait not be used to return multiple / conditional types?
That being said, you don't need to use Either here.
You have consecutive Result-returning functions, so you can borrow a trick from JavaScript and use an IIFE to use use the ? operator. Then, we can "lift up" the combined Result into a future and chain it with the future from the receiver:
fn deferred_task() -> impl Future<Item = i32, Error = MyError> {
let (tx, rx) = oneshot::channel();
let x = (|| {
let _i = query_data().map_err(|_| MyError { error_code: 1 })?;
send_promise_to_worker(tx).map_err(|_| MyError { error_code: 2 })?;
Ok(())
})();
future::result(x).and_then(|()| rx.map_err(MyError::from))
}
In the future, that IIFE could be replaced with a try block, as I understand it.
You could also go the other way and convert everything to a future:
fn deferred_task() -> impl Future<Item = i32, Error = MyError> {
let (tx, rx) = oneshot::channel();
query_data()
.map_err(|_| MyError { error_code: 1 })
.into_future()
.and_then(|_i| {
send_promise_to_worker(tx)
.map_err(|_| MyError { error_code: 2 })
.into_future()
})
.and_then(|_| rx.map_err(MyError::from))
}
This would be helped with async / await syntax:
async fn deferred_task() -> Result<i32, MyError> {
let (tx, rx) = oneshot::channel();
query_data().map_err(|_| MyError { error_code: 1 })?;
send_promise_to_worker(tx).map_err(|_| MyError { error_code: 2 })?;
let v = await! { rx }?;
Ok(v)
}
I have also seen improved syntax for constructing the Either by adding left and right methods to the Future trait:
foo.left();
// vs
Either::left(foo);
However, this doesn't appear in any of the current implementations.
A Future is a Result
No, it is not.
There are two relevant Futures to talk about:
From the futures 0.1 crate
From the (nightly) standard library
Notably, Future::poll returns a type that can be in two states:
Complete
Not complete
In the futures crate, "success" and "failure" are tied to "complete", whereas in the standard library they are not. In the crate, Result implements IntoFuture, and in the standard library you can use future::ready. Both of these allow converting a Result into a future, but that doesn't mean that Result is a future, no more than saying that a Vec<u8> is an iterator, even though it can be converted into one.
It's possible that the ? operator (powered by the Try trait), will be enhanced to automatically convert from a Result to a specific type of Future, or that Result will even implement Future directly, but I have not heard of any such plans.
Is there some syntax sugar to make this easier?
Yes, it's called async/await, but it's not quite ready for wide consumption. It is only supported on nightly, it uses a slightly different version of futures that Tokio only supports via an interop library that causes additional syntactic overhead, and documentation for the whole thing is still spotty.
Here are some relevant links:
What is the purpose of async/await in Rust?
https://jsdw.me/posts/rust-asyncawait-preview/
https://areweasyncyet.rs/

error handling in angular 5, catch errors from backend api in frontend

I need advise for handling errors in front-end of web application.
When I call a service to get the community according to community in web app, I want it to catch an error. For example for catching errors like 404.
There is a service for getting community according to id provided.
getCommunity(id: number) {
return this.http.get(`${this.api}/communities/` + id + ``);
}
that is called in events.ts file
setCommunityBaseUrl() {
this.listingService.getCommunity(environment.communityId).subscribe((data: any) => {
this.communityUrl = data.url + `/` + data.domain;
});
}
The id is provided in environment. Let's say there are 20 communities in total. When I provide id = 1 the events according to community = 1 appears.
export const environment = {
production: ..,
version: 'v2',
apiUrl: '...',
organization: '...',
websiteTitle: '...',
communityId: 1,
googleMapsApiKey: '...'
};
The problem is that when I provide id = null all community events are occurring | all events list in the backend is occurring.
Please, help ^^
When you subscribe you subscribe with an Observer pattern. So the first function you pass in
.subscribe(() => {} );
fires when the Observable calls .next(...)
and after that you can provide another function which will fire whenever the Observable calls .error(...)
so
.subscribe(() => {}, (error) => { handleTheError(error); } );
The this.http.get(...); returns an Observable which will fire the .error(...) on http error
We also know that this.http.get(...) completes or "errors" and it's not an endless one (a one that never completes). So you can make it a promise and manipulate on it promise like.
async getMeSomething(...) {
try {
this.mydata = await this.http.get(...).toPromise();
}
catch(error) {
handleTheError(error)
}
}
But what I really recommend is to use Swagger for your backend and then generate the API Client class with NSwagStudio so you don't have to write the client manually or adjust it or deal with error catching. I use it all the time and it saves us an enormous amount of time
Because you are using ".subscribe" you can create your own error handler and catch the errors like this, directly on the method.
This is an example on how you can use this:
constructor(
private _suiteAPIService: SuitesAPIService,
private _testcaseService: TestcaseService,
public _tfsApiService: TfsApiService,
private _notificationService: NotificationService) { }
errorHandler(error: HttpErrorResponse) {
return observableThrowError(error.message || "Server Error")
}
public something = "something";
GetTestcasesFromSuiteSubscriber(Project, BuildNumber, SuiteId) {
this._suiteAPIService.GetTestResults(Project, BuildNumber, SuiteId).subscribe(
data => {
console.log(data);
this._testcaseService.ListOfTestcases = data;
//Notofication service to get data.
this._notificationService.TestcasesLoaded();
},
error => {
//Here we write som error
return this.something;
}
);
}

Angular2 - Multiple dependent sequential http api calls

I am building an Angular2 app and one of the components needs to make multiple API calls which are dependent on the previous ones.
I currently have a service which makes an API call to get a list of TV shows. For each show, I then need to call a different API multiple times to step through the structure to determine if the show exists on a Plex server.
The API documentation is here
For each show, I need to make the following calls and get the correct data to determine if it exists: (Assume we have variables <TVShow>, <Season>, <Episode>)
http://baseURL/library/sections/?X-Plex-Token=xyz will tell me:
title="TV Shows" key="2"
http://baseURL/library/sections/2/all?X-Plex-Token=xyz&title=<TVShow> will tell me: key="/library/metadata/2622/children"
http://baseURL/library/metadata/2622/children?X-Plex-Token=xyz will tell me: index="<Season>" key="/library/metadata/14365/children"
http://baseURL/library/metadata/14365/children?X-Plex-Token=xyz will tell me: index="<Episode>" which implies that the episode I have exists.
The responses are in json, I have removed a lot of the excess text. At each stage I need to check that the right fields exist (<TVShow>, <Season>, <Episode>) so that they can be used for the next call. If not, I need to return that the show does not exist. If it does, I will probably want to return an id for the show.
I have looked at lots of examples including promise, async & flatmap, but am not sure how to solve this based on the other examples I have seen.
How to chain Http calls in Angular2
Angular 2.0 And Http
Angular 2 - What to do when an Http request depends on result of another Http request
Angular 2 chained Http Get Requests with Iterable Array
nodejs async: multiple dependant HTTP API calls
How to gather the result of Web APIs on nodeJS with 'request' and 'async'
Here is what I have for getting the list of shows. (shows.service.ts)
export class ShowsHttpService {
getShows(): Observable<Show[]> {
let shows$ = this._http
.get(this._showHistoryUrl)
.map(mapShows)
.catch(this.handleError);
return shows$;
}
}
function mapShows(response:Response): Show[] {
return response.json().data.map(toShow);
}
function toShow(r:any): Show {
let show = <Show>({
episode: r.episode,
show_name: r.show_name,
season: r.season,
available : false, // I need to fill in this variable if the show is available when querying the Plex API mentioned above.
});
// My best guess is here would be the right spot to call the Plex API as we are dealing with a single show at a time at this point, but I cannot see how.
return show;
}
Here is the relevant code from the component (shows.component.ts)
public getShows():any {
this._ShowsHttpService
.getShows()
.subscribe(w => this.shows = w);
console.log(this.shows);
}
Bonus points
Here are the obvious next questions that are interesting, but not necessary:
The first API query will be much faster than waiting for all of the other queries to take place (4 queries * ~10 shows). Can the initial list be returned and then updated with the available status when it is ready.
The first Plex call to get the key="2" only needs to be performed once. It could be hard coded, but instead, can it be performmed once and remembered?
Is there a way to reduce the number of API calls? I can see that I could remove the show filter, and search through the results on the client, but this doesn't seam ideal either.
The 4 calls for each show must be done sequentially, but each show can be queried in parallel for speed. Is this achievable?
Any thoughts would be much appreciated!
Not sure if I totally understand your question, but here is what I do:
I make the first http call, then when the subscribe fires, it calls completeLogin. I could then fire another http call with its own complete function and repeat the chain.
Here is the component code. The user has filled in the login information and pressed login:
onSubmit() {
console.log(' in on submit');
this.localUser.email = this.loginForm.controls["email"].value;
this.localUser.password = this.loginForm.controls["password"].value;
this.loginMessage = "";
this.checkUserValidation();
}
checkUserValidation() {
this.loginService.getLoggedIn()
.subscribe(loggedIn => {
console.log("in logged in user validation")
if(loggedIn.error != null || loggedIn.error != undefined || loggedIn.error != "") {
this.loginMessage = loggedIn.error;
}
});
this.loginService.validateUser(this.localUser);
}
This calls the loginservice ValidateUser method
validateUser(localUser: LocalUser) {
this.errorMessage = "";
this.email.email = localUser.email;
var parm = "validate~~~" + localUser.email + "/"
var creds = JSON.stringify(this.email);
var headers = new Headers();
headers.append("content-type", this.constants.jsonContentType);
console.log("making call to validate");
this.http.post(this.constants.taskLocalUrl + parm, { headers: headers })
.map((response: Response) => {
console.log("json = " + response.json());
var res = response.json();
var result = <AdminResponseObject>response.json();
console.log(" result: " + result);
return result;
})
.subscribe(
aro => {
this.aro = aro
},
error => {
console.log("in error");
var errorObject = JSON.parse(error._body);
this.errorMessage = errorObject.error_description;
console.log(this.errorMessage);
},
() => this.completeValidateUser(localUser));
console.log("done with post");
}
completeValidateUser(localUser: LocalUser) {
if (this.aro != undefined) {
if (this.aro.errorMessage != null && this.aro.errorMessage != "") {
console.log("aro err " + this.aro.errorMessage);
this.setLoggedIn({ email: localUser.email, password: localUser.password, error: this.aro.errorMessage });
} else {
console.log("log in user");
this.loginUser(localUser);
}
} else {
this.router.navigate(['/verify']);
}
}
In my login service I make a call to the authorization service which returns an observable of token.
loginUser(localUser: LocalUser) {
this.auth.loginUser(localUser)
.subscribe(
token => {
console.log('token = ' + token)
this.token = token
},
error => {
var errorObject = JSON.parse(error._body);
this.errorMessage = errorObject.error_description;
console.log(this.errorMessage);
this.setLoggedIn({ email: "", password: "", error: this.errorMessage });
},
() => this.completeLogin(localUser));
}
In the authorization service:
loginUser(localUser: LocalUser): Observable<Token> {
var email = localUser.email;
var password = localUser.password;
var headers = new Headers();
headers.append("content-type", this.constants.formEncodedContentType);
var creds:string = this.constants.grantString + email + this.constants.passwordString + password;
return this.http.post(this.constants.tokenLocalUrl, creds, { headers: headers })
.map(res => res.json())
}
The point here in this code, is to first call the validateUser method of the login service, upon response, based on the return information, if its valid, I call the loginUser method on the login service. This chain could continue as long as you need it to. You can set class level variables to hold the information that you need in each method of the chain to make decisions on what to do next.
Notice also that you can subscribe to the return in the service and process it there, it doesn't have to return to the component.
Okay, Here goes:
public getShows():any {
this._ShowsHttpService
.getShows()
.subscribe(
w => this.shows = w,
error => this.errorMessage = error,
() => this.completeGetShows());
}
completeGetShow() {
//any logic here to deal with previous get;
this.http.get#2()
.subscribe(
w => this.??? = w),
error => this.error = error,
() => this.completeGet#2);
}
completeGet#2() {
//any logic here to deal with previous get;
this.http.get#3()
.subscribe(
w => this.??? = w),
error => this.error = error,
() => this.completeGet#3);
}
completeGet#3() {
//any logic here to deal with previous get;
//another http: call like above to infinity....
}