How to send HTML, CSS and JavaScript together - rust

I try to give the Client the HTML, CSS and JavaScript file but it doesn't work.
async fn get_homepage() -> impl Responder {
let html = include_str!("../../../../Frontend/Code/homepage.html");
HttpResponse::Ok().content_type("text/html").body(html)
}
This can only send the HTML and it works fine, but when I try:
async fn get_homepage() -> impl Responder {
let html = include_str!("Path of HTML-File");
HttpResponse::Ok().content_type("text/html").body(html);
HttpResponse::Ok()
.content_type("text/css")
.body("Path of CSS-File")
}
I only see the CSS.
How can I display the complete website?

In Rust, the last statement without a semicolon will be treated as the return value so you'll see the first function to return the html file and the second function to return the css file because it is the last statement.
fn return_two() -> i32 {
1;
2
}
You need to serve the css and html file on 2 different urls.
#[get("/")]
async fn ...
#[get("/style.css")]
async fn ...
Alternatively, you can inline the css in the html file like this
<head>
<style> /* your style goes here */ </style>
</head>

Related

Render Template Rust Rocket Handlebars

I am trying to render a template with rust rocket and handlebars, this works fine but I can't get any arguments to the html file to work, the page loads, but the argument is not there. I am familiar with flask and jinja for python but not as much with rocket and handlebars. I have tried countless things but I can't find any documentation that works.
main.rs
#![feature(decl_macro)]
extern crate rocket;
extern crate rocket_contrib;
use rocket::{get, routes};
use rocket_contrib::templates::Template;
#[get("/one")]
fn one() -> String {
format!("One")
}
#[get("/page")]
fn page() -> Template {
let context = "string";
Template::render("page", &context)
}
fn main() {
rocket::ignite()
.mount("/", routes![one])
.attach(Template::fairing())
.launch();
}
page.html.hbs
<!DOCTYPE html>
<head></head>
<html>
<body>
<h1>Argument is: {{context}}</h1>
</body>
</html>
The template system doesn't know that the local variable is called context. It just knows that your context is a string, which doesn't have an attribute called context. You need to provide a type implementing Serialize, with a member called context for the template expression {{context}} to expand to anything. You could do this with a HashMap or a custom type.
With a HashMap:
#[get("/page")]
fn page() -> Template {
let mut context = HashMap::new();
context.insert("context", "string");
Template::render("page", &context)
}
With a custom type:
#[get("/page")]
fn page() -> Template {
#[derive(serde_derive::Serialize)]
struct PageContext {
pub context: String,
}
let context = PageContext { context: "string" };
Template::render("page", &context)
}
Custom types will generally perform better, and are self-documenting.

How to show a multiple line Display impl via the format! macro?

I have the following Display impl for a Vec struct:
impl fmt::Display for Response {
// This trait requires `fmt` with this exact signature.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Write strictly the first element into the supplied output
// stream: `f`. Returns `fmt::Result` which indicates whether the
// operation succeeded or failed. Note that `write!` uses syntax which
// is very similar to `println!`.
if &self.data.transactions.len() <= &0 {
write!(f, "No transactions today")?;
}
for t in &self.data.transactions {
writeln!(f, "{:.2} USD {}. Taken from {}. Account: {}", t.amount, t.memo, t.category_name, t.account_name)?;
}
Ok(())
}
I want to pass this like the following to a format! where I generate a HTML E-Mail body:
format!("<html><body><h2>Transactions</h2>{}</body></html>", response)
However, the writeln! macro doesn't produce multiple lines in the generated String. When I print it out to stdout, it works fine.
Is there an option to do this somehow with format! as well?
Since at a specific location in your code you decide to explicitly generate HTML tags (<html><body>...</html>), maybe a reasonable solution would be to keep the text formatting (for the console) as you already did, but in this specific case (HTML) replace all the \n by <br>.
let txt = format!("{}", response);
println!("~~~~ console ~~~~");
println!("{}", txt);
println!("~~~~ html ~~~~");
let html = format!(
"<html><body><h2>Transactions</h2>{}</body></html>",
txt.replace("\n", "<br>")
);
println!("{}", html);

How do I ensure the right HTML element is passed from JavaScript to my Rust function?

If I create a Rust function with wasm_bindgen that accepts an HtmlCanvasElement, how do I make sure it fails when it gets to the Rust side?
JavaScript:
(async () => {
const demo = await import('./pkg/demo').catch(console.error);
demo.setCanvas('Hello Element!');
})();
Rust:
use wasm_bindgen::prelude::*;
use web_sys::{console, HtmlCanvasElement};
#[wasm_bindgen]
pub fn setCanvas(canvas: &HtmlCanvasElement) {
// Why does this even get here? I didn't pass in an HtmlCanvasElement
console::log_1(&canvas);
}
It looks like I'm getting the type HtmlCanvasElement but if I try to use it as an HtmlCanvasElement, it doesn't have the functions because I'm passing in a string instead of the actual canvas element. I want it to fail when I set it, not at some later time when I try to use it.
There's not much type control at the boundary between the DOM and Rust in wasm_bindgen. The JavaScript part handles what is seen from Rust as JsValue.
It means you have to do the checked conversions yourself (or let a future lib or framework do it for you).
The JsCast trait helps for this. It lets you write this for example:
#[wasm_bindgen]
pub fn setElement(canvas: JsValue) -> Result<(), JsValue> {
match canvas.dyn_into::<HtmlCanvasElement>() {
Ok(canvas) => {
// ...
Ok(())
}
Err(_) => Err(JsValue::from_str("argument not a HtmlCanvas")),
}
}

What's the easiest way to get the HTML output of an actix-web endpoint handler to be rendered properly?

I've defined an endpoint with actix-web like so:
#[derive(Deserialize)]
struct RenderInfo {
filename: String,
}
fn render(info: actix_web::Path<RenderInfo>) -> Result<String> {
// ...
}
App::new()
.middleware(middleware::Logger::Default())
.resource("/{filename}", |r| r.get().with(render))
The problem I've run into is that the raw HTML gets displayed in the browser rather than being rendered. I assume the content-type is not being set properly.
Most of the actix-web examples I saw used impl Responder for the return type, but I wasn't able to figure out how to fix the type inference issues that created. The reason seems to have something to do with file operations returning a standard failure::Error-based type. It looks like actix_web requires implementation of a special WebError to block unintended propagation of errors. For this particular instance, I don't really care, because it's more of an internal tool.
From the actix-web examples, use HttpResponse:
fn welcome(req: &HttpRequest) -> Result<HttpResponse> {
println!("{:?}", req);
// session
let mut counter = 1;
if let Some(count) = req.session().get::<i32>("counter")? {
println!("SESSION value: {}", count);
counter = count + 1;
}
// set counter to session
req.session().set("counter", counter)?;
// response
Ok(HttpResponse::build(StatusCode::OK)
.content_type("text/html; charset=utf-8")
.body(include_str!("../static/welcome.html")))
}

How to insert data submitted from an HTML form into a Redis database?

I am trying to save the user-submitted HTTP data into Redis. This is my code using Nickel:
#[macro_use]
extern crate nickel;
extern crate redis;
extern crate hyper;
use redis::{Client,Parser, Commands, Connection, RedisResult};
use nickel::{Nickel, Request, Response, HttpRouter, MiddlewareResult,Middleware, MediaType,JsonBody};
use std::collections::HashMap;
use nickel::status::StatusCode;
use nickel_redis::{RedisMiddleware, RedisRequestExtensions};
use hyper::Url;
use url::form_urlencoded;
use std::sync::{Arc, Mutex};
use std::io;
use std::io::prelude::*;
use std::fs::File;
fn save_db<'a>(req: &mut Request, res: Response<'a>) -> MiddlewareResult<'a> {
let mut server = Nickel::new();
/* FIXME: connect redis*/
let client = Client::open("redis://127.0.0.1/").unwrap();
let conn = client.get_connection().unwrap();
server.post("/confirmation", middleware!{|req, res|
let mut form_data = String::new();
req.origin.read_to_string(&mut form_data).unwrap();
let _: () = conn.set("contents", form_data).unwrap();
println!("{}", form_data);
let mut data = HashMap::<&str, String>::new();
data.insert("content", form_data);
data.insert("page_title", "Save blog data".to_string());
return res.render("app/views/blog/save.tpl", &data);
});
}
fn main() {
let mut server = Nickel::new();
// start using router
let mut router = Nickel::router();
let client = Client::open("redis://127.0.0.1/").unwrap();
let conn = client.get_connection().unwrap();
router.get("/test-save-db", save_db);
server.utilize(router);
server.listen("127.0.0.12:8080");
}
Save.tpl
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>{{ page_title }}</title>
</head>
<body>
<h1>Saved new blog.</h1>
<h2>Author {{form_data}}</h2>
<h2>Content {{content}}</h2>
<form>
First name:<br>
<input type="text" name="firstname" >
<br>
<button type="submit" formenctype="application/x-www-form-urlencoded" formaction="/test-save-db" formmethod="post">SEND</button>
</form>
</body>
</html>
But I get this error
error: cannot infer an appropriate lifetime for lifetime parameter 'b due to conflicting requirements [E0495](req.origin.read_to_string(&mut form_data).unwrap();)
The problem I see in your code is that you're trying to register a handler for a route (POST /confirmation) inside the handler that gets called when the /test-save-db route is matched. That doesn't seem correct.
To make your code and intent clearer, I suggest you use the middleware! macro, which I find to be the simplest way to describe routes in Nickel. At least this has been my experience since starting to use the framework yesterday :-)
So your example might be rewritten as follows:
fn main() {
let client = Client::open("redis://127.0.0.1/").unwrap();
let mut server = Nickel::new();
server.get("/test-save-db", middleware! { |req, res|
// do something with the request and send a response
});
server.listen("127.0.0.12:8080");
}
If you need multiple handlers sharing the same client, you will need to use std::sync::Arc to have thread safe shared state, and clone it for each handler (it will only clone the Arc, not the client).

Resources