I'm using a guard to authenticated a user.
How can i easily redirect the user to the login page if a guard fail (redirect to /login in my example) ?
#[rocket::async_trait]
impl<'r> FromRequest<'r> for User {
type Error = ();
async fn from_request(request: &'r Request<'_>) -> Outcome<User, ()> {
let db = request.guard::<&State<Db>>().await.succeeded().unwrap();
/* Get user_id cookie */
let c = request.cookies().get_private("user_id");
match c {
Some(c) => {
...
Outcome::Success(user)
},
None => {
Outcome::Failure((Status::BadRequest, ()))
}
}
}
}
#[get("/")]
async fn home(user: User) -> Template {
...
}
#[get("/login")]
async fn login() -> Template {
...
}
As doc illustrate:
use rocket::response::Redirect;
#[get("/", rank = 2)]
async fn not_user() -> Redirect {
Redirect::to(uri!(login))
}
Register a 401 catcher:
https://api.rocket.rs/v0.5-rc/rocket/struct.Rocket.html#method.register
use rocket::Request;
#[catch(401)]
fn not_authorized(req: &Request) -> Redirect {
// redirect here
}
#[launch]
fn rocket() -> _ {
rocket::build().register("/", catchers![not_authorized])
}
Related
I'm quite new to rust and actix, but I tried to build a technology prototype where a server sends protobuf messages to clients via websockets. The protobuf part works and is no problem, but I struggle with the websocket part.
I've tried to modify the official example of Actix-Websockets with the Actix-Broker (Chat-Broker-Example), but I'm having a hard time debugging it (beeing not familiar with VSCode, but thats another story).
The effect is, that the broker instance is not started and does not receive any messages. If I start the server manually via a supervisor (the example does not need to do that) than it still won't receive any messages.
Question:
Has anybody any idea why the broker wont start atomaticly or doesnt receive the messages?
Do I have any fundamential missunderstandings?
Has anybody any idea how to make the programm work?
The code is uploaded publicly at github (GitHub Repository).
For your convinience I'll add the ws_client, ws_server and main.rs files below.
The changes I've done compared to the example are:
Removed #[derive(Default)] from WsChatServer and implemented it myself
Wrapped WsChatServer rooms in Arc and RwLock to ensure memory safty. (Needs to be overhauled)
Removed Message ListRooms and corresponding functions
I'd highly appreciate any help, tips or suggestions!
ws_server.rs:
use std::{collections::HashMap, sync::{Arc, RwLock}};
use actix::prelude::*;
use actix_broker::BrokerSubscribe;
use crate::{messages::{ChatMessage, JoinRoom, LeaveRoom, SendMessage}};
type Client = Recipient<ChatMessage>;
type Room = HashMap<usize, Client>;
#[derive(Clone)]
pub struct WsChatServer {
rooms: Arc<RwLock<HashMap<String, Room>>>,
}
lazy_static! {
static ref ROOMS: Arc<RwLock<HashMap<String, Room>>> = Arc::new(RwLock::new(Default::default()));
}
impl Default for WsChatServer {
fn default() -> Self {
let ws = WsChatServer { rooms: ROOMS.clone() };
return ws;
}
}
impl SystemService for WsChatServer {}
impl Supervised for WsChatServer {}
impl WsChatServer {
pub fn create_room(room_name: &str) {
let mut rooms = match ROOMS.write() {
Ok(rooms) => rooms,
Err(err) => {
log::debug!("Error while requesting write lock. Error was: {}", err);
return;
},
};
if !rooms.contains_key(room_name) {
let room: HashMap<usize, Client> = HashMap::new();
rooms.insert(room_name.to_string(), room);
}
}
fn take_room(&mut self, room_name: &str) -> Option<Room> {
let mut guard = match self.rooms.write() {
Ok(guard) => guard,
Err(err) => {
log::debug!("Error waiting for write lock. Error was: {}", err);
return None;
},
};
let room = match guard.get_mut(room_name){
Some(room) => room,
None => {
log::debug!("Failed to get mutable reference of RW Guard");
return None;
},
};
let room = std::mem::take(room);
Some(room)
}
fn add_client_to_room(&mut self, room_name: &str, id: Option<usize>, client: Client) -> usize {
log::info!("In add_client_to_room Handler. Adding Client to room: {}", room_name);
let mut id = id.unwrap_or_else(rand::random::<usize>);
if let Some(room) = self.rooms.write().unwrap().get_mut(room_name) {
loop {
if room.contains_key(&id) {
id = rand::random::<usize>();
} else {
break;
}
}
room.insert(id, client);
return id;
}
// Create a new room for the first client
let mut room: Room = HashMap::new();
room.insert(id, client);
self.rooms.write().unwrap().insert(room_name.to_owned(), room);
id
}
pub fn send_chat_message(&mut self, room_name: &str, msg: &str, _src: usize) -> Option<()> {
let mut room = match self.take_room(room_name) {
Some(room) => room,
None => {
log::debug!("Error, could not take room.");
return None;
},
};
for (id, client) in room.drain() {
if client.try_send(ChatMessage(msg.to_owned())).is_ok() {
self.add_client_to_room(room_name, Some(id), client);
}
}
Some(())
}
}
impl Actor for WsChatServer {
type Context = Context<Self>;
fn started(&mut self, ctx: &mut Self::Context) {
log::info!("WsChatServer has started.");
self.subscribe_system_async::<LeaveRoom>(ctx);
self.subscribe_system_async::<SendMessage>(ctx);
}
}
impl Handler<JoinRoom> for WsChatServer {
type Result = MessageResult<JoinRoom>;
fn handle(&mut self, msg: JoinRoom, _ctx: &mut Self::Context) -> Self::Result {
log::info!("In Join Room Handler.");
let JoinRoom(room_name, client) = msg;
let id = self.add_client_to_room(&room_name, None, client);
MessageResult(id)
}
}
impl Handler<LeaveRoom> for WsChatServer {
type Result = ();
fn handle(&mut self, msg: LeaveRoom, _ctx: &mut Self::Context) {
log::info!("Removing ws client from room.");
if let Some(room) = self.rooms.write().unwrap().get_mut(&msg.0) {
room.remove(&msg.1);
}
}
}
impl Handler<SendMessage> for WsChatServer {
type Result = ();
fn handle(&mut self, msg: SendMessage, _ctx: &mut Self::Context) {
let SendMessage(room_name, id, msg) = msg;
self.send_chat_message(&room_name, &msg, id);
}
}
ws_client.rs:
use actix::{Actor, ActorContext, StreamHandler, Handler, SystemService, AsyncContext, WrapFuture, ActorFutureExt, fut, ContextFutureSpawner};
use actix_web_actors::ws;
use actix_broker::BrokerIssue;
use crate::messages::{ChatMessage, LeaveRoom, JoinRoom};
use crate::ws_server::WsChatServer;
pub struct WsConn {
room: String,
id: usize,
}
impl WsConn {
pub fn new(room: &str) -> WsConn {
WsConn {
room: room.to_string(),
id: rand::random::<usize>(),
}
}
pub fn join_room(&mut self, room_name: &str, ctx: &mut ws::WebsocketContext<Self>) {
let room_name = room_name.to_owned();
// First send a leave message for the current room
let leave_msg = LeaveRoom(self.room.clone(), self.id);
// issue_sync comes from having the `BrokerIssue` trait in scope.
self.issue_system_sync(leave_msg, ctx);
log::info!("Ws client sent leave msg.");
// Then send a join message for the new room
let join_msg = JoinRoom(
room_name.to_owned(),
ctx.address().recipient(),
);
WsChatServer::from_registry()
.send(join_msg)
.into_actor(self)
.then(move |id, act, _ctx| {
if let Ok(id) = id {
act.id = id;
act.room = room_name.clone().to_string();
}
fut::ready(())
})
.wait(ctx);
log::info!("Ws client sent join msg.");
}
}
impl Actor for WsConn {
type Context = ws::WebsocketContext<Self>;
fn started(&mut self, ctx: &mut Self::Context) {
log::info!("ws client started.");
self.join_room(self.room.to_owned().as_str(), ctx);
}
fn stopped(&mut self, _ctx: &mut Self::Context) {
log::info!(
"WsConn closed for {} in room {}",
self.id,
self.room
);
}
}
impl Handler<ChatMessage> for WsConn {
type Result = ();
fn handle(&mut self, msg: ChatMessage, ctx: &mut Self::Context) {
ctx.text(msg.0);
}
}
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for WsConn {
fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
let msg = match msg {
Err(_) => {
ctx.stop();
return;
}
Ok(msg) => msg,
};
log::debug!("WEBSOCKET MESSAGE: {:?}", msg);
match msg {
ws::Message::Text(_) => (),
ws::Message::Close(reason) => {
ctx.close(reason);
ctx.stop();
}
_ => {}
}
}
}
main.rs:
use std::time::Duration;
use std::{env, thread::sleep};
use std::fs::File;
use std::io::Write;
use actix_web::{App, HttpServer, middleware::Logger};
use actix_cors::Cors;
use tokio::task;
#[macro_use]
extern crate lazy_static;
mod protobuf_messages;
mod actions;
mod data;
mod ws_clients;
mod messages;
mod ws_server;
pub async fn write_to_file(buf: &[u8], file_name: &str) -> Result<(), std::io::Error> {
let dir = env::current_dir().unwrap();
let file_handler = dir.join(file_name);
let mut file = File::create(file_handler).unwrap();
file.write_all(buf)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "debug");
env_logger::init();
data::MAIN_CONFIG.version = "1.0".to_string();
data::MAIN_CONFIG.mqtt_broker_address = "test".to_string();
data::MAIN_CONFIG.wildcard = "#".to_string();
data::MAIN_CONFIG.splitting_character = ".".to_string();
data::MAIN_CONFIG.active_configs = [].to_vec();
let ws_chat_server = ws_server::WsChatServer::default();
let mut ws_chat_server_2 = ws_chat_server.clone();
actix::Supervisor::start(|_| ws_chat_server);
ws_server::WsChatServer::create_room("Main");
let msg = serde_json::to_string_pretty(&data::MAIN_CONFIG).expect("Expected parsable string");
task::spawn(async move {
loop {
match ws_chat_server_2.send_chat_message("Main", &msg.clone(), 0) {
Some(()) => (),//log::debug!("Got a result from sending chat message"),
None => (),//log::debug!("Got no result from sending chat message"),
}
sleep(Duration::from_secs(1));
}
});
HttpServer::new(move|| App::new()
.wrap(Logger::default())
.wrap(Cors::default().allow_any_origin().allow_any_header().allow_any_method())
.service(actions::get_json)
.service(actions::get_protobuf)
.service(actions::start_ws_connection)
)
.bind(("127.0.0.1", 3000))?
.workers(2)
.run().await
}
I finally figured a way out to solve the problem.
First of all I could not get the broker example to work.
My issue was, that I tried to externally send a message to all ws clients via the ws server broker. To do that you need to get the Addr of the server. If that is done the same way the Actor clients receive the lobby address with from_registry the result seems to be a different server instance and therfore the messages were not sent to the clients.
I could not find a way to get the same broker addr returned so I switched to the actix websocket example called "chat-tcp". In this example is a WS Server Actor created in the Main function and then added as variable to all requests. In the request handler were the clients set up with the server addr.
To make sure I can send to the same broker I encapsulated the Addr with an Arc and RwLock. Now I needed to dismantel it in the request handler. When I started a tokio green thread that sends messages to the ws server (and moved an Arc copy in the scope) it worked.
Does anyone know why the registered broker addresses where different?
can anyone help get my head around the FromRequest trait.
As I understand, we can use it to share data with the resolvers.
I'm trying to inject the http headers so I can authenticate users before some actions in the resolvers.
See my code below.
Looks like the from_request isn't even called.
I'm using rocket 0.5.rc1
Thank you for your help
use juniper::{graphql_object, EmptySubscription, RootNode};
use mongodb::Client;
use mongodb::Database;
use rocket::{catch, catchers};
use rocket::{response::content, Rocket, State};
type Schema = RootNode<'static, Query, Mutations, EmptySubscription<AppContext>>;
use rocket::{
http::Status,
request::{FromRequest, Outcome},
Request,
};
struct Mutations;
struct Query;
#[derive(Debug)]
pub struct AppContext {
pub mongodb_pool: Database,
pub user: String,
}
impl juniper::Context for AppContext {}
#[rocket::async_trait]
impl<'r> FromRequest<'r> for AppContext {
type Error = ();
async fn from_request(request: &'r Request<'_>) -> Outcome<AppContext, Self::Error> {
let client = Client::with_uri_str("mongodb://admin:admin#localhost:28017").await;
let db = client.unwrap().database("override");
Outcome::Success(AppContext { mongodb_pool: db, user: "from request".to_string() }, )
}
}
#[graphql_object(context = AppContext)]
impl Query {
fn api_version(db: &AppContext) -> &str {
println!("{:?}", db.user);
"1.0"
}
}
#[graphql_object(context = AppContext)]
impl Mutations {
fn api_version(db: &AppContext) -> String {
"1.0".to_string()
}
}
#[rocket::get("/")]
fn graphiql() -> content::Html<String> {
juniper_rocket::graphiql_source("/graphql", None)
}
#[rocket::get("/graphql?<request>")]
async fn get_graphql_handler(
context: &State<AppContext>,
request: juniper_rocket::GraphQLRequest,
schema: &State<Schema>,
) -> juniper_rocket::GraphQLResponse {
request.execute(&*schema, &*context).await
}
#[rocket::post("/graphql", data = "<request>")]
async fn post_graphql_handler(
context: &State<AppContext>,
request: juniper_rocket::GraphQLRequest,
schema: &State<Schema>,
) -> juniper_rocket::GraphQLResponse {
request.execute(&*schema, &*context).await
}
#[tokio::main]
async fn main() {
let client = Client::with_uri_str("mongodb://admin:admin#localhost:28017").await;
let db = client.unwrap().database("app_db");
Rocket::build()
.manage(AppContext{ mongodb_pool: db, user: "from build".to_string() })
.manage(Schema::new(
Query,
Mutations,
EmptySubscription::<AppContext>::new(),
))
.mount(
"/",
rocket::routes![graphiql, get_graphql_handler, post_graphql_handler],
)
.launch()
.await
.expect("server failed to launch");
}
Is there a way to shorten the if/else portion of the from_request?
use uuid::Uuid;
#[derive(Debug)]
struct User {
token: Option<Uuid>,
}
impl FromRequest for User {
type Error = Error;
type Future = Ready<Result<Self>>;
type Config = ();
fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
if let Some(t) = actix_web::HttpMessage::cookie(req,"token") {
if let Ok(u) = Uuid::parse_str(&t.value().to_owned()) {
futures::future::ok(User{token: Some(u)})
} else {
futures::future::ok(User{token: None})
}
} else {
futures::future::ok(User{token: None})
}
}
}
Any way to remove the futures::future::ok(User{token: None}) twice?
I tried to use map as shown in this:
Rust: Is there a way to shorten this if/else code using map?
but couldn't get it to work for the nested if/else.
Something like this?
fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
let token = actix_web::HttpMessage::cookie(req, "token")
.and_then(|t| Uuid::parse_str(&t.value().to_owned()).ok());
futures::future::ok(User { token })
}
Since you don't care about the error from parsing the Uuid, it calls .ok() which converts the Result into an Option. This lets you chain the optional values together with and_then.
I'm using syn to parse Rust code. When I read a named field's type using field.ty, I get a syn::Type. When I print it using quote!{#ty}.to_string() I get "Option<String>".
How can I get just "String"? I want to use #ty in quote! to print "String" instead of "Option<String>".
I want to generate code like:
impl Foo {
pub set_bar(&mut self, v: String) {
self.bar = Some(v);
}
}
starting from
struct Foo {
bar: Option<String>
}
My attempt:
let ast: DeriveInput = parse_macro_input!(input as DeriveInput);
let data: Data = ast.data;
match data {
Data::Struct(ref data) => match data.fields {
Fields::Named(ref fields) => {
fields.named.iter().for_each(|field| {
let name = &field.ident.clone().unwrap();
let ty = &field.ty;
quote!{
impl Foo {
pub set_bar(&mut self, v: #ty) {
self.bar = Some(v);
}
}
};
});
}
_ => {}
},
_ => panic!("You can derive it only from struct"),
}
My updated version of the response from #Boiethios, tested and used in a public crate, with support of several syntaxes for Option:
Option
std::option::Option
::std::option::Option
core::option::Option
::core::option::Option
fn extract_type_from_option(ty: &syn::Type) -> Option<&syn::Type> {
use syn::{GenericArgument, Path, PathArguments, PathSegment};
fn extract_type_path(ty: &syn::Type) -> Option<&Path> {
match *ty {
syn::Type::Path(ref typepath) if typepath.qself.is_none() => Some(&typepath.path),
_ => None,
}
}
// TODO store (with lazy static) the vec of string
// TODO maybe optimization, reverse the order of segments
fn extract_option_segment(path: &Path) -> Option<&PathSegment> {
let idents_of_path = path
.segments
.iter()
.into_iter()
.fold(String::new(), |mut acc, v| {
acc.push_str(&v.ident.to_string());
acc.push('|');
acc
});
vec!["Option|", "std|option|Option|", "core|option|Option|"]
.into_iter()
.find(|s| &idents_of_path == *s)
.and_then(|_| path.segments.last())
}
extract_type_path(ty)
.and_then(|path| extract_option_segment(path))
.and_then(|path_seg| {
let type_params = &path_seg.arguments;
// It should have only on angle-bracketed param ("<String>"):
match *type_params {
PathArguments::AngleBracketed(ref params) => params.args.first(),
_ => None,
}
})
.and_then(|generic_arg| match *generic_arg {
GenericArgument::Type(ref ty) => Some(ty),
_ => None,
})
}
You should do something like this untested example:
use syn::{GenericArgument, PathArguments, Type};
fn extract_type_from_option(ty: &Type) -> Type {
fn path_is_option(path: &Path) -> bool {
leading_colon.is_none()
&& path.segments.len() == 1
&& path.segments.iter().next().unwrap().ident == "Option"
}
match ty {
Type::Path(typepath) if typepath.qself.is_none() && path_is_option(typepath.path) => {
// Get the first segment of the path (there is only one, in fact: "Option"):
let type_params = typepath.path.segments.iter().first().unwrap().arguments;
// It should have only on angle-bracketed param ("<String>"):
let generic_arg = match type_params {
PathArguments::AngleBracketed(params) => params.args.iter().first().unwrap(),
_ => panic!("TODO: error handling"),
};
// This argument must be a type:
match generic_arg {
GenericArgument::Type(ty) => ty,
_ => panic!("TODO: error handling"),
}
}
_ => panic!("TODO: error handling"),
}
}
There's not many things to explain, it just "unrolls" the diverse components of a type:
Type -> TypePath -> Path -> PathSegment -> PathArguments -> AngleBracketedGenericArguments -> GenericArgument -> Type.
If there is an easier way to do that, I would be happy to know it.
Note that since syn is a parser, it works with tokens. You cannot know for sure that this is an Option. The user could, for example, type std::option::Option, or write type MaybeString = std::option::Option<String>;. You cannot handle those arbitrary names.
I try to find differences from two streams (represented by iterators) for later analysis, the code below works just fine, but looks a little bit ugly and error prone (copy-paste!) in updating values in update_v? functions. Is there any ways to generalise it assuming that source is matter?
struct Data {};
struct S {
v1: Option<Data>,
v2: Option<Data>
}
...
fn update_v1(diffs: &mut HashMap<u64, Data>, key: u64, data: Data) {
match diffs.entry(key) {
Entry::Vacant(v) => {
let variant = S {
v1: Some(data),
v2: None
};
v.insert(variant);
},
Entry::Occupied(e) => {
let new_variant = Some(data);
if e.get().v2 == new_variant {
e.remove();
} else {
let existing = e.into_mut();
existing.v1 = new_variant;
}
}
}
}
fn update_v2(diffs: &mut HashMap<u64, Data>, key: u64, data: Data) {
match diffs.entry(key) {
Entry::Vacant(v) => {
let variant = S {
v2: Some(data),
v1: None
};
v.insert(variant);
},
Entry::Occupied(e) => {
let new_variant = Some(data);
if e.get().v1 == new_variant {
e.remove();
} else {
let existing = e.into_mut();
existing.v2 = new_variant;
}
}
}
}
Instead of writing one function for each field, receive a pair of Fns as arguments:
fn(&S) -> Option<Data>, which can be used to replace this condition
if e.get().v1 == new_variant { /* ... */ }
with this
if getter(e.get()) == new_variant { /* ... */ }
fn(&mut S, Option<Data>) -> (), which replaces
existing.v2 = new_variant;
with
setter(&mut existing, new_variant);
Then on the call site you pass a couple lambdas like this
Getter: |d| d.v1
Setter: |s, d| s.v2 = d
Or vice-versa for the other function.
And if you want to keep the update_v1 and update_v2 function names, just write those as wrappers to this new generalized function that automatically pass the proper lambdas.
You can create a trait to facilitate different ways of accessing the structure.
trait SAccessor {
type RV;
fn new(Data) -> S;
fn v2(&S) -> &Self::RV;
fn v1_mut(&mut S) -> &mut Self::RV;
}
struct DirectSAccessor;
impl SAccessor for DirectSAccessor {
type RV = Option<Data>;
fn new(data: Data) -> S {
S {
v1: Some(data),
v2: None
}
}
fn v2(s: &S) -> &Self::RV {
&s.v2
}
fn v1_mut(s: &mut S) -> &mut Self::RV {
&mut s.v1
}
}
fn update<A>(diffs: &mut HashMap<u64, S>, key: u64, data: Data)
where A: SAccessor<RV=Option<Data>>
{
match diffs.entry(key) {
Entry::Vacant(v) => {
let variant = A::new(data);
v.insert(variant);
},
Entry::Occupied(e) => {
let new_variant = Some(data);
if A::v2(e.get()) == &new_variant {
e.remove();
} else {
let existing = e.into_mut();
*A::v1_mut(existing) = new_variant;
}
}
}
}
// ...
// update::<DirectSAccessor>( ... );
Full code