Since the task is to distribute pictures - I follow the next guide:
https://actix.rs/docs/static-files/
For example, I created a directory (static) in the project and uploaded 1 image to it:
Then I write the following code:
(pay attention only to the lines (there are only two) with comments: // Taken from the guide)
use actix_cors::Cors;
use actix_web::{http, web, get, post, App, HttpResponse, HttpServer, Result};
use serde::{Deserialize, Serialize};
use actix_files::Files; // Taken from the guide
#[derive(Serialize, Deserialize)]
struct MyObj {
name: String,
}
#[derive(Serialize, Deserialize, Clone)]
struct MyParams {
foo: Option<String>,
}
#[derive(Serialize, Deserialize)]
struct MyResponseObj {
name: String,
params: MyParams,
}
#[get("/{name}")]
async fn index_get(path: web::Path<MyObj>, params: web::Query<MyParams>) -> Result<HttpResponse> {
Ok(HttpResponse::Ok().json(MyResponseObj {
name: path.name.to_string(),
params: params.clone(),
}))
}
#[post("/{name}")]
async fn index_post(path: web::Path<MyObj>, params: web::Json<MyParams>) -> Result<HttpResponse> {
hello().await;
println!("{:?}", params.foo);
println!("{:?}", path.name);
Ok(HttpResponse::Ok().json(MyResponseObj {
name: path.name.to_string(),
params: params.clone(),
}))
}
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new()
.wrap(
Cors::new() // <- Construct CORS middleware builder
.allowed_origin("http://localhost:3000")
.allowed_methods(vec!["GET", "POST"])
.allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT])
.allowed_header(http::header::CONTENT_TYPE)
.max_age(3600)
.finish())
.service(Files::new("/static", ".")) // Taken from the guide
.service(index_get)
.service(index_post)
)
.bind("127.0.0.1:8088")?
.run()
.await
}
Cargo.toml
[package]
name = "hello_world"
version = "0.1.0"
authors = ["Mike_Kharkov <yamaradg#gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
actix-web = "3.0.1"
actix-rt = "1.1.1"
actix-cors = "0.3.0"
actix-files = "0.3.0"
postgres = "0.17.5"
serde = { version = "1.0.116", features = ["derive"] }
serde_json = "1.0"
json = "0.12"
tokio-postgres = "0.5.5"
tokio = "0.2.22"
env_logger = "0.7.1"
Question:
What else needs to be written (and where exactly) so that it becomes possible to refer to the above picture (for example, from localhost) and not get this kind of error?
The problem is solved:
.service(Files::new("/static", std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("static")))
Related
new to rust here. I am trying to build a simple server that provides and decodes JWT tokens and I am missing a big part. Here is the code:
pub struct Server {
pub host: String,
pub port: String,
pub public_key: String,
pub private_key: String
}
impl Server {
pub async fn start(&self) {
let routes = Router::new()
.route("/", get(check))
.route("/auth", post(auth));
let mut hostport = String::from(&self.host);
hostport.push_str(":");
hostport.push_str(&self.port);
println!("{}", hostport);
let addr : SocketAddr = hostport.parse().expect("invalid host:port pair");
axum::Server::bind(
&addr
).serve(routes.into_make_service()).await.unwrap();
}
}
async fn auth(Json(payload): Json<LoginInput>) -> impl IntoResponse {
let claims = Claims::create(Duration::from_hours(1));
RS384PublicKey::from_pem("id_like_to_put_Server::public_key_here").sign(claims)?;
let lo = LoginOutput{
token: payload.username
};
(StatusCode::OK, Json(lo))
}
As you can see Server holds routing logic and applies configuration. Among configuration there is a public key I'd like to use in order to sign the JWT token (I am using jwt_simple to achieve that). Since public key is a Server's attribute, I want to pass that value to the auth handler but I can't figure out how to do that. How can I pass a parameter to an Axum handler and sign the token is generated inside?
Although here are the examples using both Extension and State, State is preferred as from the docs of Axum:
You should prefer using State if possible since it’s more type safe. The downside is that its less dynamic than request extensions.
In Cargo.toml:
[dependencies]
axum = "0.6.0-rc.2"
serde = { version = "1.0.147", features = ["derive"] }
tokio = { version = "1.21.2", features = ["macros", "rt-multi-thread"] }
Using State:
use std::net::SocketAddr;
use axum::{
extract::State,
response::IntoResponse,
routing::{get, post},
Json, Router,
};
use serde::Deserialize;
#[derive(Clone)]
pub struct ServerConfig {
pub host: String,
pub port: String,
pub public_key: String,
pub private_key: String,
}
#[derive(Deserialize)]
pub struct LoginInput {
username: String,
password: String,
}
#[tokio::main]
async fn main() {
let server_config = ServerConfig {
host: "0.0.0.0".into(),
port: "8080".into(),
public_key: "public_key".into(),
private_key: "private_key".into(),
};
let addr: SocketAddr = format!("{}:{}", server_config.host, server_config.port)
.parse()
.unwrap();
let routes = Router::with_state(server_config) // state will be available to all the routes
.route("/", get(check))
.route("/auth", post(auth));
axum::Server::bind(&addr)
.serve(routes.into_make_service())
.await
.unwrap();
}
async fn check() -> &'static str {
"check"
}
async fn auth(
State(server_config): State<ServerConfig>, // extract state in this handler
// `Json` supports any type that implements `serde::Deserialize`
Json(payload): Json<LoginInput>,
) -> impl IntoResponse {
// use server_config and payload to run the `auth` logic
println!("host: {}", server_config.host);
"jwt"
}
Using Extension
use std::net::SocketAddr;
use axum::{
response::IntoResponse,
routing::{get, post},
Extension, Json, Router,
};
use serde::Deserialize;
#[derive(Clone)]
pub struct ServerConfig {
pub host: String,
pub port: String,
pub public_key: String,
pub private_key: String,
}
#[derive(Deserialize)]
pub struct LoginInput {
username: String,
password: String,
}
#[tokio::main]
async fn main() {
let server_config = ServerConfig {
host: "0.0.0.0".into(),
port: "8080".into(),
public_key: "public_key".into(),
private_key: "private_key".into(),
};
let addr: SocketAddr = format!("{}:{}", server_config.host, server_config.port)
.parse()
.unwrap();
let routes = Router::new()
.route("/", get(check))
.route("/auth", post(auth))
.layer(Extension(server_config));
axum::Server::bind(&addr)
.serve(routes.into_make_service())
.await
.unwrap();
}
async fn check() -> &'static str {
"check"
}
async fn auth(
Extension(server_config): Extension<ServerConfig>,
// `Json` supports any type that implements `serde::Deserialize`
Json(payload): Json<LoginInput>,
) -> impl IntoResponse {
// use server_config and payload to run the `auth` logic
println!("host: {}", server_config.host);
"jwt"
}
I'm want to extract my AppState struct from HttpRequest in my handler function.
It works when I'm calling that handler through my main app instance, but doesn't work inside integration tests.
Handler function:
pub async fn handle(req: HttpRequest, user: Json<NewUser>) -> Result<HttpResponse, ShopError> {
let state = req.app_data::<Data<AppState>>().expect("app_data is empty!");
Ok(HttpResponse::Ok().finish())
}
main.rs:
#[actix_web::main]
pub async fn main() -> std::io::Result<()> {
dotenv().ok();
let chat_server = Lobby::default().start();
let state = AppState {
static_data: String::from("my_data")
};
HttpServer::new(move || {
App::new()
.app_data(Data::new(state.clone()))
.service(web::scope("/").configure(routes::router))
.app_data(Data::new(chat_server.clone()))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
Test:
#[actix_web::test]
async fn sanity_test() {
let app = test::init_service(App::new().route("/", web::post().to(handle))).await;
let user = NewUser {
username: String::from("legit_user"),
password: String::from("123"),
};
let state = AppState {
static_data: String::from("my_data")
};
let req = test::TestRequest::post()
.app_data(Data::new(state))
.set_json(&user)
.uri("/")
.to_request();
let resp = test::call_service(&app, req).await;
assert!(resp.status().is_success());
}
Test output:
running 1 test
thread 'routes::login::tests::sanity_test' panicked at 'app_data is empty!', src/routes/login.rs:35:50
For some reason it is always None. I've tried using Data<AppState> extractor but then whole handler is not even called, again, only when testing, otherwise everything works.
Cargo.toml:
[dependencies]
diesel = { version = "1.4.4", features = ["postgres", "r2d2", "chrono"] }
diesel_migrations = "1.4.0"
chrono = { version = "0.4.19", features = ["serde"] }
dotenv = "0.15.0"
actix = "0.13.0"
actix-web = "4.1.0"
actix-web-actors = "4.1.0"
bcrypt = "0.13.0"
uuid = { version = "1.1.2", features = ["serde", "v4"] }
serde_json = "1.0.82"
serde = { version = "1.0.139", features = ["derive"] }
validator = { version = "0.16.0", features = ["derive"] }
derive_more = "0.99.17"
r2d2 = "0.8.10"
lazy_static = "1.4.0"
jsonwebtoken = "8.1.1"
I'm aware of app_data is always None in request handler thread, and it does not solve my problem since for me everything works except when testing.
From what I see in your integration test, app_data is not configured for the app instance passed to test::init_service, which is why the handler panics. When the app is configured with app_data as you have done it in main, app_data becomes available for the handler.
With the code below, the handler can access AppData in the integration test. The main difference from the original post is that the app in the integration test is configured with app_data, not the request.
use actix_web::{
HttpServer,
HttpRequest,
HttpResponse,
App,
web::{Data, Json},
post,
};
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize)]
pub struct NewUser{
username: String,
password: String,
}
#[derive(Clone)]
struct AppState{
static_data: String,
}
#[post("/")]
pub async fn handle(
req: HttpRequest,
_user: Json<NewUser>
) -> HttpResponse {
let state = req
.app_data::<Data<AppState>>()
.expect("app_data is empty!");
println!("Static data: {}", state.static_data);
HttpResponse::Ok().finish()
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let state = AppState{
static_data: "Sparta".to_string(),
};
HttpServer::new(move || {
App::new()
.app_data(Data::new(state.clone()))
.service(handle)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
#[cfg(test)]
mod tests {
use super::*;
use actix_web::{
test,
};
#[actix_web::test]
async fn itest() {
// Set up app with test::init_service
let state = AppState {
static_data: "Sparta".to_string(),
};
let app = test::init_service(
App::new()
.app_data(Data::new(state.clone()))
.service(handle)
).await;
// Prepare request
let sample_user = NewUser{
username: "legit_user".to_string(),
password: "nosecret123".to_string(),
};
let req = test::TestRequest::post()
.set_json(&sample_user)
.uri("/")
.to_request();
// Send request and await response
let resp = test::call_service(&app, req).await;
assert!(resp.status().is_success())
}
}
I'm trying to use Actix-SQLx-Juniper in my Rust project. I've followed and combined any tutorial that I've found, it successfully compiled and run. But when I try to post a query, I got this error in terminal:
thread 'actix-web' panicked at 'Tried to resolve async field users on type Some("Query") with a sync resolver', src/graphql.rs:15:1
and my GraphiQL shows "Thread pool is gone"
this is the src/graphql.rs:
#[derive(Clone, Debug)]
pub struct Context {
pub pool: PgPool,
}
impl juniper::Context for Context {}
pub struct Query;
#[graphql_object(Context = Context)] // this is line 15
impl Query {
fn apiVersion() -> &str {
"1.0"
}
#[graphql(description = "Hello")]
pub async fn users(ctx: &Context) -> FieldResult<Vec<User>> {
println!("{:#?}", ctx);
sqlx::query_as::<_, User>("SELECT * FROM users")
.fetch_all(&ctx.pool)
.await
.map_err(|e| e.into())
}
}
pub type Schema = RootNode<'static, Query, EmptyMutation<Context>, EmptySubscription<Context>>;
pub fn create_schema() -> Schema {
Schema::new(Query {}, EmptyMutation::new(), EmptySubscription::new())
}
But after I trace the error, it panicked when I tried to use execute_sync in my src/handler.rs:
pub fn graphql_handlers(config: &mut ServiceConfig) {
config
.data(create_schema())
.route("/graphql", web::get().to(graphql_playground))
.route("/graphql", web::post().to(graphql));
}
...
...
async fn graphql(
pool: web::Data<PgPool>,
schema: web::Data<Schema>,
data: web::Json<GraphQLRequest>,
) -> Result<HttpResponse, Error> {
let ctx = Context {
pool: pool.get_ref().to_owned(),
};
let res = block(move || {
let res = data.execute_sync(&schema, &ctx);
Ok::<_, serde_json::error::Error>(serde_json::to_string(&res)?)
})
.await
.map_err(Error::from)?;
Ok(HttpResponse::Ok()
.content_type("application/json")
.body(res))
}
I've tried to find the solution or boilerplate code but still couldn't find it.
Here's my main.rs:
#[actix_web::main]
async fn main() -> Result<()> {
let pool = create_pool().await.expect("Failed connecting to postgres");
HttpServer::new(move || {
App::new()
.data(pool.clone())
.wrap(Logger::default())
.configure(graphql_handlers)
})
.bind("127.0.0.1:8000")?
.run()
.await
}
Here's my dependencies:
actix-web = "3"
juniper = "0.15"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.64"
uuid = { version = "0.8", features = [ "serde", "v4"] }
sqlx = { version = "0.4", features = [ "runtime-actix-rustls", "postgres", "uuid" ] }
I am attempting to use Tokio-Diesel to use async/await with my ORM. I am not able to use Diesel's belonging_to associations, as it complains that I need a static lifetime. I've tried async blocks but nothing changes the lifetime error.
Cargo.toml
[package]
name = "tokio_diesel"
version = "0.1.0"
authors = ["servonlewis"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
diesel = { version = "1.4.3", features = ["postgres", "r2d2", "serde_json", "chrono", "uuid"] }
r2d2 = "0.8.8"
tokio = { version = "0.2", features = ["full"] }
tokio-diesel = "0.3.0"
uuid = { version = "0.6.5", features = ["serde", "v4"] }
main.rs
#[macro_use]
extern crate diesel;
use diesel::prelude::*;
use tokio_diesel::*;
table! {
articles (id) {
id -> Integer,
title -> Text,
}
}
table! {
articles_visible_to_groups (id) {
id -> Integer,
article_id -> Integer,
groups_id -> Integer,
}
}
table! {
groups (id) {
id -> Integer,
name -> Varchar,
}
}
joinable!(articles_visible_to_groups -> articles (article_id));
joinable!(articles_visible_to_groups -> groups (groups_id));
allow_tables_to_appear_in_same_query!(articles, articles_visible_to_groups, groups,);
use crate::articles::dsl::*;
use crate::groups::dsl::*;
use diesel::pg::PgConnection;
use diesel::r2d2::{ConnectionManager, Pool};
pub type PostgresPool = Pool<ConnectionManager<PgConnection>>;
pub fn get_pool() -> PostgresPool {
let url = "postgres://actix:actix#localhost:5432/actix".to_string();
let mgr = ConnectionManager::<PgConnection>::new(url);
r2d2::Pool::builder()
.build(mgr)
.expect("could not build connection pool")
}
#[derive(Queryable, Identifiable, Associations, Debug)]
#[table_name = "articles"]
pub struct Article {
pub id: i32,
pub title: String,
}
#[derive(Identifiable, Queryable, Associations, Debug, PartialEq)]
#[belongs_to(Group, foreign_key = "groups_id")]
#[belongs_to(Article)]
#[table_name = "articles_visible_to_groups"]
pub struct ArticleVisibleToGroup {
pub id: i32,
pub article_id: i32,
pub groups_id: i32,
}
#[derive(Queryable, Identifiable, Associations, Debug, PartialEq)]
pub struct Group {
pub id: i32,
pub name: String,
}
pub async fn groups_who_can_see(conn: &PostgresPool, an_id: i32) -> Vec<Group> {
use diesel::pg::expression::dsl::any;
let record = articles
.find(an_id)
.load_async::<Article>(conn)
.await
.unwrap();
let list = ArticleVisibleToGroup::belonging_to(&record)
.select(articles_visible_to_groups::groups_id)
.load_async::<i32>(conn)
.await
.unwrap();
groups
.filter(groups::id.eq_all(any(list)))
.load_async::<Group>(conn)
.await
.unwrap()
}
#[tokio::main]
async fn main() {
println!("Hello, world!");
// pool.get_ref().to_owned(),
let pool = get_pool();
let res = groups_who_can_see(&pool, 1).await;
println!("{:#?}", res);
}
error[E0597]: `record` does not live long enough
--> src/main.rs:80:52
|
80 | let list = ArticleVisibleToGroup::belonging_to(&record)
| ------------------------------------^^^^^^^-
| | |
| | borrowed value does not live long enough
| argument requires that `record` is borrowed for `'static`
...
91 | }
| - `record` dropped here while still borrowed
Looks like i was able to get around this issue by just not using belonging_to and instead using filter.
Instead of the example above, this one works asyncronously.
pub async fn groups_who_can_see(
conn: &PostgresPool,
an_id: String,
) -> FieldResult<Vec<Group>> {
use diesel::pg::expression::dsl::any;
let my_id = uuid::Uuid::parse_str(&an_id)?;
let list = articles_visible_to_groups
.filter(article_id.eq_all(my_id))
.select(articles_visible_to_groups::groups_id)
.load_async::<uuid::Uuid>(&conn)
.await?;
let data = groups
.filter(groups::id.eq_all(any(list)))
.load_async::<Group>(&conn)
.await;
graphql_translate(data)
}
It's possible to pass an array of integers like this:
const js = import("./webassembly_rust");
let array_nums = [1,2,3,4,5,6,7,8,9];
js.then(js => {
js.test( array_nums );
});
to WebAssembly and save it in a vector like this:
extern crate serde_json;
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[macro_use]
extern crate serde_derive;
#[wasm_bindgen]
pub fn test(array: JsValue) {
let elements: Vec<u32> = array.into_serde().unwrap();
}
It's also possible to pass a single object like this:
const js = import("./webassembly_rust");
let jsObject = {name: "hello world", id: "99", parent_id: "11"};
js.then(js => {
js.test( jsObject );
});
to WebAssembly and save it as a Element struct like this:
extern crate serde_json;
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[macro_use]
extern crate serde_derive;
#[derive(Serialize, Deserialize)]
pub struct Element {
name: String,
id: String,
parent_id: String,
}
#[wasm_bindgen]
pub fn test(js_object: &JsValue) {
let element: Element = js_object.into_serde().unwrap();
}
The next thing I tried is to pass an array of objects like this:
const js = import("./webassembly_rust");
let arrayOfObjects = [
{name: "hello world", id: "99", parent_id: "88"},
{name: "hello world2", id: "88", parent_id: "12"},
{name: "hello world3", id: "77", parent_id: "88"}
]
js.then(js => {
js.test( arrayOfObjects );
});
to WebAssembly and save it as a vector of Element structs like this:
extern crate serde_json;
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[macro_use]
extern crate serde_derive;
#[derive(Serialize, Deserialize)]
pub struct Element {
name: String,
id: String,
parent_id: String,
}
#[wasm_bindgen]
pub fn test(js_objects: &JsValue) {
let elements: Vec<Element> = js_objects.into_serde().unwrap();
}
This compiles, but when I run this code I get the error:
func $__rust_start_panic (param i32) (result i32)
unreachable
unreachable
end
Passing an array of objects filled with numbers like this:
const js = import("./webassembly_rust");
let arrayOfNumObjects = [
{name: 1, id: 2, parent_id: 3 },
{name: 1, id: 2, parent_id: 3 },
{name: 1, id: 2, parent_id: 3 }
]
js.then(js => {
js.test( arrayOfNumObjects );
});
to WebAssembly is possible when the Element struct contains only u32 values.
extern crate serde_json;
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[macro_use]
extern crate serde_derive;
#[derive(Serialize, Deserialize)]
pub struct Element {
name: u32,
id: u32,
parent_id: u32,
}
#[wasm_bindgen]
pub fn test(js_objects: &JsValue) {
let elements: Vec<Element> = js_objects.into_serde().unwrap();
}
It seems like the problem is caused by the String type in the Element struct.
What did I do wrong?
I found the following articles, but I can't find a solution for my problem in them:
Serializing and Deserializing Arbitrary Data Into and From JsValue with Serde
This explains how to convert a JavaScript object to a struct, but not how to convert an array of objects to a vector of structs.
js_sys crate
This crate allows to use JavaScript types like arrays or objects in Rust, but this is not what I want. I want to convert JavaScript values into their Rust counterparts. This crate only allows to use JavaScript inline in Rust, as far as I understand... and this isn't as fast as using just Rust.
github issue
Follow the instructions to get a basic Rust / WASM setup, then add support for arbitrary data via Serde.
I've changed your code to return a number and print out that number, just to see that it's working.
Cargo.toml
[package]
name = "ww"
version = "0.1.0"
authors = ["An Devloper <an.devloper#example.com>"]
edition = "2018"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = { version = "0.2", features = ["serde-serialize"] }
serde_json = "1.0.32"
serde_derive = "1.0.80"
serde = "1.0.80"
src/lib.rs
extern crate serde_json;
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[macro_use]
extern crate serde_derive;
#[derive(Serialize, Deserialize)]
pub struct Element {
name: String,
id: String,
parent_id: String,
}
#[wasm_bindgen]
pub fn test(js_objects: &JsValue) -> i32 {
let elements: Vec<Element> = js_objects.into_serde().unwrap();
elements
.iter()
.map(|e| {
let id = e.id.parse::<i32>().unwrap_or(0);
let parent_id = e.parent_id.parse::<i32>().unwrap_or(0);
id + parent_id
})
.sum()
}
index.js
const js = import("./ww");
let arrayOfObjects = [
{ name: "hello world", id: "99", parent_id: "88" },
{ name: "hello world2", id: "88", parent_id: "12" },
{ name: "hello world3", id: "77", parent_id: "88" },
]
js.then(js => {
const sum = js.test(arrayOfObjects);
console.log(sum);
});
package.json
{
"scripts": {
"serve": "webpack-dev-server"
},
"devDependencies": {
"html-webpack-plugin": "^3.2.0",
"webpack": "^4.0.1",
"webpack-cli": "^3.1.1",
"webpack-dev-server": "^3.1.0"
}
}
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: "./index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "index.js",
},
plugins: [
new HtmlWebpackPlugin({
title: "Getting started with WASM"
})
],
mode: "development"
};
Then run:
# once
npm install
# every time the code changes
cargo +nightly build --target wasm32-unknown-unknown
wasm-bindgen target/wasm32-unknown-unknown/debug/*.wasm --out-dir .
npm run serve
Visit the page in your WASM-enabled browser.
Observant readers will note that I didn't do anything different from the OP. That's because this code already worked as-is. Every time you change your Rust code, make sure that you:
Build your Rust code
Re-run wasm-bindgen