Problem running actix-web as Windows service - rust

I'm trying to use windows-service to run an actix web app. It provides a nice API and mostly works. I can start my service just fine. However, when I try to stop my service, I get the following error: Error 109: The pipe has been ended (it does stop the service however).
I'm mostly just using the example provided for windows-service, but here is the relevant code (for context and all the wrapper functions, check out https://github.com/mullvad/windows-service-rs/blob/master/examples/ping_service.rs):
pub fn run_service() -> Result<()> {
fn hi() -> impl actix_web::Responder {
"Hello!\r\n"
}
let sys = actix_rt::System::new("test");
actix_web::HttpServer::new(move || {
actix_web::App::new()
.route("/", actix_web::web::get().to(hi))
})
.bind("0.0.0.0:3000").unwrap()
.start();
let event_handler = move |control_event| -> ServiceControlHandlerResult {
match control_event {
ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
ServiceControl::Stop => {
actix_rt::System::with_current(|s| s.stop());
ServiceControlHandlerResult::NoError
}
_ => ServiceControlHandlerResult::NotImplemented,
}
};
let status_handle = service_control_handler::register(SERVICE_NAME, event_handler)?;
status_handle.set_service_status(ServiceStatus {
service_type: SERVICE_TYPE,
current_state: ServiceState::Running,
controls_accepted: ServiceControlAccept::STOP,
exit_code: ServiceExitCode::Win32(0),
checkpoint: 0,
wait_hint: Duration::default(),
})?;
sys.run().unwrap();
status_handle.set_service_status(ServiceStatus {
service_type: SERVICE_TYPE,
current_state: ServiceState::Stopped,
controls_accepted: ServiceControlAccept::empty(),
exit_code: ServiceExitCode::Win32(0),
checkpoint: 0,
wait_hint: Duration::default(),
})?;
Ok(())
}
If I put the System::stop in a thread::spawn, I get a different error: The service did not return an error. This could be an internal Windows error or an internal service error. In this case it does not stop the service.
I've put in some logging, and it doesn't look like the code ever gets past the sys.run().unwrap(), which is strange.
Any thoughts? I've never used the Windows Service API before so I don't really know what I'm doing.
EDIT
I figured out what the main issue is: I have to notify Windows the service has stopped before stopping the service. I put together a clunky way to make it work:
std::thread::spawn(move || {
loop {
if shutdown_signal.load(Ordering::Relaxed) {
status_handle.set_service_status(ServiceStatus {
service_type: SERVICE_TYPE,
current_state: ServiceState::Stopped,
controls_accepted: ServiceControlAccept::empty(),
exit_code: ServiceExitCode::Win32(0),
checkpoint: 0,
wait_hint: Duration::default(),
}).unwrap();
actix_rt::System::current().stop();
break;
}
}
});
sys.run().unwrap();
// ...
where shutdown_signal is an AtomicBool I set to true in the event handler. I'm going to see if I can do this instead somehow through actix_rt.

Answering my own question. I think this is the best way to handle it, though I would be happy to see other solutions!
pub fn run_service() -> Result<()> {
use futures::Future;
fn hi() -> impl actix_web::Responder {
"Hello!\r\n"
}
let sys = actix_rt::System::new("test");
actix_web::HttpServer::new(move || {
actix_web::App::new()
.route("/", actix_web::web::get().to(hi))
})
.bind("0.0.0.0:3000").unwrap()
.start();
let (mut send_stop, recv_stop) = {
let (p, c) = futures::sync::oneshot::channel::<()>();
(Some(p), c)
};
let event_handler = move |control_event| -> ServiceControlHandlerResult {
match control_event {
ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
ServiceControl::Stop => {
send_stop.take().unwrap().send(()).unwrap();
ServiceControlHandlerResult::NoError
}
_ => ServiceControlHandlerResult::NotImplemented,
}
};
let status_handle = service_control_handler::register(SERVICE_NAME, event_handler)?;
status_handle.set_service_status(ServiceStatus {
service_type: SERVICE_TYPE,
current_state: ServiceState::Running,
controls_accepted: ServiceControlAccept::STOP,
exit_code: ServiceExitCode::Win32(0),
checkpoint: 0,
wait_hint: Duration::default(),
})?;
actix_rt::spawn(recv_stop.map(move |_| {
status_handle.set_service_status(ServiceStatus {
service_type: SERVICE_TYPE,
current_state: ServiceState::Stopped,
controls_accepted: ServiceControlAccept::empty(),
exit_code: ServiceExitCode::Win32(0),
checkpoint: 0,
wait_hint: Duration::default(),
}).unwrap();
actix_rt::System::current().stop()
}).map_err(|_| ()));
sys.run().unwrap();
Ok(())
}

Related

How to select a file as bytes or text in Rust WASM?

I am trying to get the Vec<u8> or String (or more ideally a Blob ObjectURL) of a file uploaded as triggered by a button click.
I am guessing this will require an invisible <input> somewhere in the DOM but I can't figure out how to leverage web_sys and/or gloo to either get the contents nor a Blob ObjectURL.
A js-triggered input probably won't do the trick, as many browsers won't let you trigger a file input from JS, for good reasons. You can use labels to hid the input if you think it is ugly. Other than that, you need to wiggle yourself through the files api of HtmlInputElement. Pretty painful, that:
use js_sys::{Object, Reflect, Uint8Array};
use wasm_bindgen::{prelude::*, JsCast};
use wasm_bindgen_futures::JsFuture;
use web_sys::*;
#[wasm_bindgen(start)]
pub fn init() {
// Just some setup for the example
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
let window = window().unwrap();
let document = window.document().unwrap();
let body = document.body().unwrap();
while let Some(child) = body.first_child() {
body.remove_child(&child).unwrap();
}
// Create the actual input element
let input = document
.create_element("input")
.expect_throw("Create input")
.dyn_into::<HtmlInputElement>()
.unwrap();
input
.set_attribute("type", "file")
.expect_throw("Set input type file");
let recv_file = {
let input = input.clone();
Closure::<dyn FnMut()>::wrap(Box::new(move || {
let input = input.clone();
wasm_bindgen_futures::spawn_local(async move {
file_callback(input.files()).await;
})
}))
};
input
.add_event_listener_with_callback("change", recv_file.as_ref().dyn_ref().unwrap())
.expect_throw("Listen for file upload");
recv_file.forget(); // TODO: this leaks. I forgot how to get around that.
body.append_child(&input).unwrap();
}
async fn file_callback(files: Option<FileList>) {
let files = match files {
Some(files) => files,
None => return,
};
for i in 0..files.length() {
let file = match files.item(i) {
Some(file) => file,
None => continue,
};
console::log_2(&"File:".into(), &file.name().into());
let reader = file
.stream()
.get_reader()
.dyn_into::<ReadableStreamDefaultReader>()
.expect_throw("Reader is reader");
let mut data = Vec::new();
loop {
let chunk = JsFuture::from(reader.read())
.await
.expect_throw("Read")
.dyn_into::<Object>()
.unwrap();
// ReadableStreamReadResult is somehow wrong. So go by hand. Might be a web-sys bug.
let done = Reflect::get(&chunk, &"done".into()).expect_throw("Get done");
if done.is_truthy() {
break;
}
let chunk = Reflect::get(&chunk, &"value".into())
.expect_throw("Get chunk")
.dyn_into::<Uint8Array>()
.expect_throw("bytes are bytes");
let data_len = data.len();
data.resize(data_len + chunk.length() as usize, 255);
chunk.copy_to(&mut data[data_len..]);
}
console::log_2(
&"Got data".into(),
&String::from_utf8_lossy(&data).into_owned().into(),
);
}
}
(If you've got questions about the code, ask. But it's too much to explain it in detail.)
And extra, the features you need on web-sys for this to work:
[dependencies.web-sys]
version = "0.3.60"
features = ["Window", "Navigator", "console", "Document", "HtmlInputElement", "Event", "EventTarget", "FileList", "File", "Blob", "ReadableStream", "ReadableStreamDefaultReader", "ReadableStreamReadResult"]
Thanks to Caesar I ended up with this code for use with dominator as the Dom crate.
pub fn upload_file_input(mimes: &str, mutable: Mutable<Vec<u8>>) -> Dom {
input(|i| {
i.class("file-input")
.prop("type", "file")
.prop("accept", mimes)
.apply(|el| {
let element: HtmlInputElement = el.__internal_element();
let recv_file = {
let input = element.clone();
Closure::<dyn FnMut()>::wrap(Box::new(move || {
let input = input.clone();
let mutable = mutable.clone();
wasm_bindgen_futures::spawn_local(async move {
file_callback(input.files(), mutable.clone()).await;
})
}))
};
element
.add_event_listener_with_callback(
"change",
recv_file.as_ref().dyn_ref().unwrap(),
)
.expect("Listen for file upload");
recv_file.forget();
el
})
})
}
async fn file_callback(files: Option<FileList>, mutable: Mutable<Vec<u8>>) {
let files = match files {
Some(files) => files,
None => return,
};
for i in 0..files.length() {
let file = match files.item(i) {
Some(file) => file,
None => continue,
};
// gloo::console::console_dbg!("File:", &file.name());
let reader = file
.stream()
.get_reader()
.dyn_into::<ReadableStreamDefaultReader>()
.expect("Reader is reader");
let mut data = Vec::new();
loop {
let chunk = JsFuture::from(reader.read())
.await
.expect("Read")
.dyn_into::<Object>()
.unwrap();
// ReadableStreamReadResult is somehow wrong. So go by hand. Might be a web-sys bug.
let done = Reflect::get(&chunk, &"done".into()).expect("Get done");
if done.is_truthy() {
break;
}
let chunk = Reflect::get(&chunk, &"value".into())
.expect("Get chunk")
.dyn_into::<Uint8Array>()
.expect("bytes are bytes");
let data_len = data.len();
data.resize(data_len + chunk.length() as usize, 255);
chunk.copy_to(&mut data[data_len..]);
}
mutable.set(data);
// gloo::console::console_dbg!(
// "Got data",
// &String::from_utf8_lossy(&data).into_owned(),
// );
}
}

When I use warp,it just work on localhost, but not public network

I wrote a Web application based on rust-warp,it works well.But it can just access by localhost:3030(127.0.0.1:3030).Now I want to access it by public network(101.35.56.79)/Local Area Network(192.168.1.18),but it didn't work.I can't find the relevant information to this problem.Here's my code:
//#![deny(warnings)]
#![allow(non_snake_case)]
#![allow(unused)]
use warp::Filter;
use warp::body::json;
use warp::reply;
use serde_json::json;
// struct Article{
// articleTitle: String,
// articleText: String,
// }
// struct ArticleList {
// articles: Vec<Article>,
// }
#[tokio::main]
async fn main() {
println!("Welcome to little guy's sever console!");
//展示主页
//let show_HomePage = warp::fs::dir("../");
//成功连接后发送一条欢迎信息
let say_hello =
warp::path::end().map(|| {
println!("Someone connected!");
"Welcome to Little Guy's HomePage~
try to visit ./HomePage.html".replace(" ", "")
});
//获取文章
let get_article =
warp::path("getArticles")
.and(warp::path::param())//参数是文章分区
.map(|article_partition: String| {
format!("The article_partition you request is: {}", article_partition);
//let article_list = ArticleList{articles: vec![Article{articleTitle:String::from("title1"), articleText: String::from("text1"})]};
//let article_list = articles{vec![{articleTitle: "tt1"},]};
let article_list = json!({
"articles":[
{
"articleTitle": "tt1",
"articleText": "text1",
},
{
"articleTitle": "tt2",
"articleText": "text2",
}
]
});
warp::reply::json(&article_list)
});
let get_introduction =
warp::path("getIntroduction")
.map(||{
let introduction = json!({
"introduction": {
"introduction": "This is Little Guy's introduction",
}
});
warp::reply::json(&introduction)
});
let routes =
//show_HomePage
say_hello
.or(get_article)
.or(get_introduction);
warp::serve(routes)
.run(([127, 0, 0, 1], 80))
.await;
}
I thought the problem is on
.run(([127, 0, 0, 1], 80))
.await;
but I can't find more details about it in warp's document(https://docs.rs/warp/latest/warp/).I need your help,Thanks.
你好。You are correct about where the error lies. In the provided code, the server is binding to 127.0.0.1 on port 80. To make the server accessible from all interfaces, change the binding address to 0.0.0.0 for the same port. The new code should read.
warp::serve(routes)
.run(([0, 0, 0, 0], 80))
.await;

Actix-Web runtime error: thread 'actix-rt:worker:2' panicked at 'AuthenticationMiddleware was called already'

I have an actix-web server using HttpAuthentication middleware to authenticate all requests. The server runs fine and responds correctly to most requests, but occasionally certain requests trigger the error:
thread 'actix-rt:worker:2' panicked at 'AuthenticationMiddleware was called already
A request to the same endpoint will only trigger the error some of the time, so I am not sure what the root cause is.
My main() function (with only the relevant code included is:
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
// Some configuration code here
HttpServer::new(move || {
App::new()
.wrap(
HttpAuthentication::basic(validator)
)
// other code here
})
.bind(ip)?
.run()
.await
}
The validator function passed as the process_fn argument to HttpAuthentication::basic is:
async fn validator(
req: ServiceRequest,
credentials: BasicAuth,
) -> Result<ServiceRequest, Error> {
let config = req.app_data::<Config>()
.map(|data| data.get_ref().clone())
.unwrap_or_else(Default::default)
.scope("urn:example:channel=HBO&urn:example:rating=G,PG-13");
let username = env::var("USERNAME")
.expect("USERNAME must be set");
let password = env::var("PASSWORD")
.expect("USERNAME must be set");
if credentials.user_id().deref() == username {
match credentials.password() {
Some(pass) => {
if pass.deref() == password {
Ok(req)
} else {
Err(AuthenticationError::from(config).into())
}
}
None => Err(AuthenticationError::from(config).into())
}
} else {
Err(AuthenticationError::from(config).into())
}
}
This function essentially is just checking for the validity of the basic authentication username and password sent in the request. As I understand it, this should be wrapping every endpoint on the server and only allowing authenticated requests through.
What I do not understand is why I am getting this runtime error. Does anyone have any ideas as to why this is happening?

Error handling and conditional chaining of Actix actors

This is my first attempt at writing a small webservice with rust, using actix-web.
The code below is a request handler that is intended to do three things, insert an entry in the database, send an email if that db call was successful, and then return a json payload as the response.
data.dal (database call) and data.email_service are references to Actors.
The issue: is I am unable to capture the error returned by data.dal. Any attempt to reconfigure the below code seems to give me an error stating the compiler wasn't able to find a conversion from Actix Mailbox to [Type].
Is there an alternate/better way to rewrite this? Basically when the request is issued, I'd like to be able to call Actor A. And if the result from A is Ok then call Actor B. If the results from both are okay return a JSON payload. If either A or B return an error (can have different error types), return an custom error message.
pub fn register_email(
invitation: Json<EmailInvitationInput>,
data: web::Data<AppState>,
) -> impl Future<Item=HttpResponse, Error=Error> {
let m = dal::queries::CreateEmailInvitation { email: invitation.email.clone() };
data.dal.send(m)
.from_err()
.and_then(move |res| {
let invite = res.unwrap();
let email_input = email::SendLoginLink {
from: "from_email".to_string(),
to: "to_email".to_string(),
};
data.email_service.send(email_input)
.from_err()
.and_then(move |res| match res {
Ok(_) => {
Ok(HttpResponse::Ok().json(EmailInvitationOutput { expires_at: invite.expires_at }))
}
Err(err) => {
debug!("{:#?}", err);
Ok(ServiceError::InternalServerError.error_response())
}
})
})
}
What I usually do is to have an Error type that agglomerates all different errors, the coercion to this type can be achieved implicitly by declaring the appropriate From implementations and what you are doing from_err() but here I am being explicit:
I haven't tested this code snippet but this is how I have done it in projects I'm working on that use Actix:
data.dal.send(m)
.map_err(Error::Mailbox)
.and_then(|res| res.map_err(Error::Service))
.and_then(move |invite| {
let email_input = email::SendLoginLink {
from: "from_email".to_string(),
to: "to_email".to_string(),
};
data.email_service.send(email_input)
.map_err(Error::Mailbox)
.and_then(|res| res.map_err(Error::Service))
.and_then(move |res| HttpResponse::Ok().json(EmailInvitationOutput { expires_at: invite.expires_at }))
})
.or_else(|err| {
debug!("{:#?}", err);
ServiceError::InternalServerError.error_response()
})
(I'm assuming ServiceError implements IntoFuture just like HttpResponse does)

How to check the authorization header using Warp?

I'm building a graphql api with Rust and Warp. I've looked through the docs, but I have still not figured out how to chain the filters, especially for checking the authorization in request header.
let context_extractor = warp::any()
// this code rejects all request which doesn't contain the authorization in header
// I'd like to make to check if authorization in header
.and(warp::header::<String>("authorization"))
.map(|token: String| -> Context {
let token_data = match verify_jwt(token) {
Ok(t) => t,
Err(_) => return Context { user_id: 0 },
};
Context {
user_id: token_data.claims.user_id,
}
});
let handle_request = move |context: Context,
request: juniper::http::GraphQLRequest|
-> Result<Vec<u8>, serde_json::Error> {
serde_json::to_vec(&request.execute(&schema, &context))
};
warp::post2()
.and(warp::path(path.into()))
.and(context_extractor)
.and(warp::body::json())
.map(handle_request)
.map(build_response)
.boxed()
This is my part of code. It works fine, but there is one problem. I've set up one route context_extractor with .and(warp::header::<String>("authorization"), then it rejects all requests which doesn't contain authorization in header.
How can I make
if request header has a authorization in header, then return Context with the proper user_id
if not, return Context with user_id: 0?
I've found the solution in github issues of Warp.
here is a small snippet.
let context_extractor = warp::any().and(
warp::header::<String>("authorization")
.map(|token: String| -> Context {
let token_data = match verify_jwt(token) {
Ok(t) => t,
Err(_) => return Context { user_id: 0 },
};
Context {
user_id: token_data.claims.user_id,
}
})
.or(warp::any().map(|| Context { user_id: 0 }))
.unify(),
);

Resources