< 返回版块

eweca-d 发表于 2021-05-14 20:27

更新一下:FormData确实很好用,不用刷新网页的情况下,可以直接完成我要的功能!

就是和warp搭配起来,有点麻烦。

            loadDom.onclick = function () {
                if (filesDom.value != "") {
                    var url = 'http://127.0.0.1:3030/';

                    var formData = new FormData();
                    loadedFile = filesDom.value;
                    formData.append("load", loadedFile);

                    var request = new XMLHttpRequest();
                    request.open("POST", url);

                    request.onload = function() {
                        if (request.readyState === request.DONE) {
                            if (request.status == 200) {
                                let response = request.responseText.split("\r\n");

                                let ew = JSON.parse( response[0] );
                                let yy = JSON.parse( response[1] );
                                for (var i = 0; i < ew.length; i++) {
                                    ewValue[i].value = ew[i];
                                    yyValue[i].value = yy[i];
                                }
                            }
                        }
                    };

                    request.send(formData);
                }
            }

rust做如下处理

#[tokio::main]
async fn main() {
    let default_reply2 = warp::path::end().and(warp::get()).map(|| Response::builder().body(default_reply()));

    let post_reply = warp::post()
        .and(warp::multipart::form())
        .and_then(post_reply);

    let routers = warp::get().and(default_reply2).or(post_reply);

    warp::serve(routers).run(([127, 0, 0, 1], 3030)).await;
}

async fn post_reply(form: warp::multipart::FormData) -> Result<impl warp::Reply, warp::Rejection> {
    let mut parts: Vec<warp::multipart::Part> = form
        .try_collect()
        .await
        .map_err(|_e| warp::reject::reject())?;
    
     if parts[0].name() == "save" {
        //....
     } else {
        //....
        let response_string = read_setting();
        Ok(Response::builder().body(response_string))  
     }
}

评论区

写评论
作者 eweca-d 2021-05-23 17:04

我直接在svelte里加<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.2/css/bulma.min.css">,bulma挺好用的。颜值又上升了不少。wasm我之前有过一遍官方的书。但是应该不会考虑用它,因为我想做的是WEB界面,RUST计算的本地个人用server,计算密集型的部分全部由服务器承压,不太需要wasm的提速。

感谢大佬的建议!

--
👇
c5soft: 前端Svelte搭配Typescript,无惧大型应用。前端引入css框架,比如bulma,ui搞定,想要扫二维码吗? 上wasm!

c5soft 2021-05-20 14:27

前端Svelte搭配Typescript,无惧大型应用。前端引入css框架,比如bulma,ui搞定,想要扫二维码吗? 上wasm!

作者 eweca-d 2021-05-19 12:54

感谢大佬的回答,我一步步地在实践这些代码。这两天有空的时候在学svelte,确实好用。目前尝试着做了一个登录界面,尝试着用进去binddom event,实用!生成的代码量也少。

但svelete的build貌似有点问题?WINDOWS系统下,我直接npm run dev可以,但build之后index.html一直打开是空白,后来把<script defer src='/build/bundle.js'></script>改成<script defer src='./build/bundle.js'></script>就成功了。

svelte的问题解决了,静态文件服务就也解决了,warp调用静态文件本来我之间就是实践过的。之后几天看看大佬给的multipart的例子,感谢感谢!

我之前一直都是写无UI程序,rust也没有合适的gui库可以用,准备放弃gui转而用web写一点自己用得到的或者有意思的程序了。

--
👇
c5soft: 这段代码启用 Warp的静态文件服务:

   let api = api.or(warp::fs::dir(
            server_config["static_path"].string("wwwroot"),
        )

静态文件目录Svelte输出目录

tokyohuang123 2021-05-19 09:32

好吧 还是谢谢

--
👇
c5soft: 抱歉,tide没用过

--
👇
tokyohuang123: tide的 multipart怎么写 大佬

--
👇
c5soft: 如果上传文件,比如写FilePond的服务端,需要用到multipart/form-data,Warp核心代码这样写:

use bytes::BufMut;
use futures::{TryFutureExt, TryStreamExt};
...
pub(crate) fn api(
    context: Arc<AppContext>,
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
    let ctx = context.clone();
    let get_ask = warp::get()
        .and(warp::path!("ask"))
        .map(move || ctx.clone())
        .and(warp::query::<HashMap<String, String>>())
        .and_then(ask);
    let ctx = context.clone();
    let post_upload = warp::post()
        .and(warp::path!("upload"))
        .and(warp::body::content_length_limit(1024 * 1024 * 1024))
        .map(move || ctx.clone())
        .and(warp::multipart::form().max_length(1024 * 1024 * 1024))
        .and_then(upload);
    get_ask.or(post_upload)
}

async fn ask(ctx: Arc<AppContext>, q: HashMap<String, String>) -> Result<impl Reply, Infallible> {
    Ok(format!("{:?}", q))
}

async fn upload(ctx: Arc<AppContext>, form: multipart::FormData) -> Result<impl Reply, Infallible> {
    let uploaded: Result<Vec<(String, Vec<u8>)>, warp::Rejection> = form
        .and_then(|part| {
            let name = part.name().to_string();
            let file_name = part.filename().unwrap_or_default();
            let name = if file_name.is_empty() {
                name
            } else {
                name + "-" + file_name
            };
            let value = part.stream().try_fold(Vec::new(), |mut vec, data| {
                vec.put(data);
                async move { Ok(vec) }
            });
            value.map_ok(move |vec| (name, vec))
        })
        .try_collect()
        .await
        .map_err(|e| {
            panic!("multipart error: {:?}", e);
        });
    if let Ok(parts) = uploaded {
        parts.into_iter().for_each(|(name, buffer)| {
            println!("{} size is {}", name, buffer.len());
            //在这里,,每个buffer是客户端上传的文件,可以保存到服务器上
        });
    };
    Ok("done")
}

客户端通过 POST /api/upload调用,实例代码的上传文件大小限制为1GB

c5soft 2021-05-18 14:06

抱歉,tide没用过

--
👇
tokyohuang123: tide的 multipart怎么写 大佬

--
👇
c5soft: 如果上传文件,比如写FilePond的服务端,需要用到multipart/form-data,Warp核心代码这样写:

use bytes::BufMut;
use futures::{TryFutureExt, TryStreamExt};
...
pub(crate) fn api(
    context: Arc<AppContext>,
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
    let ctx = context.clone();
    let get_ask = warp::get()
        .and(warp::path!("ask"))
        .map(move || ctx.clone())
        .and(warp::query::<HashMap<String, String>>())
        .and_then(ask);
    let ctx = context.clone();
    let post_upload = warp::post()
        .and(warp::path!("upload"))
        .and(warp::body::content_length_limit(1024 * 1024 * 1024))
        .map(move || ctx.clone())
        .and(warp::multipart::form().max_length(1024 * 1024 * 1024))
        .and_then(upload);
    get_ask.or(post_upload)
}

async fn ask(ctx: Arc<AppContext>, q: HashMap<String, String>) -> Result<impl Reply, Infallible> {
    Ok(format!("{:?}", q))
}

async fn upload(ctx: Arc<AppContext>, form: multipart::FormData) -> Result<impl Reply, Infallible> {
    let uploaded: Result<Vec<(String, Vec<u8>)>, warp::Rejection> = form
        .and_then(|part| {
            let name = part.name().to_string();
            let file_name = part.filename().unwrap_or_default();
            let name = if file_name.is_empty() {
                name
            } else {
                name + "-" + file_name
            };
            let value = part.stream().try_fold(Vec::new(), |mut vec, data| {
                vec.put(data);
                async move { Ok(vec) }
            });
            value.map_ok(move |vec| (name, vec))
        })
        .try_collect()
        .await
        .map_err(|e| {
            panic!("multipart error: {:?}", e);
        });
    if let Ok(parts) = uploaded {
        parts.into_iter().for_each(|(name, buffer)| {
            println!("{} size is {}", name, buffer.len());
            //在这里,,每个buffer是客户端上传的文件,可以保存到服务器上
        });
    };
    Ok("done")
}

客户端通过 POST /api/upload调用,实例代码的上传文件大小限制为1GB

tokyohuang123 2021-05-18 13:10

tide的 multipart怎么写 大佬

--
👇
c5soft: 如果上传文件,比如写FilePond的服务端,需要用到multipart/form-data,Warp核心代码这样写:

use bytes::BufMut;
use futures::{TryFutureExt, TryStreamExt};
...
pub(crate) fn api(
    context: Arc<AppContext>,
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
    let ctx = context.clone();
    let get_ask = warp::get()
        .and(warp::path!("ask"))
        .map(move || ctx.clone())
        .and(warp::query::<HashMap<String, String>>())
        .and_then(ask);
    let ctx = context.clone();
    let post_upload = warp::post()
        .and(warp::path!("upload"))
        .and(warp::body::content_length_limit(1024 * 1024 * 1024))
        .map(move || ctx.clone())
        .and(warp::multipart::form().max_length(1024 * 1024 * 1024))
        .and_then(upload);
    get_ask.or(post_upload)
}

async fn ask(ctx: Arc<AppContext>, q: HashMap<String, String>) -> Result<impl Reply, Infallible> {
    Ok(format!("{:?}", q))
}

async fn upload(ctx: Arc<AppContext>, form: multipart::FormData) -> Result<impl Reply, Infallible> {
    let uploaded: Result<Vec<(String, Vec<u8>)>, warp::Rejection> = form
        .and_then(|part| {
            let name = part.name().to_string();
            let file_name = part.filename().unwrap_or_default();
            let name = if file_name.is_empty() {
                name
            } else {
                name + "-" + file_name
            };
            let value = part.stream().try_fold(Vec::new(), |mut vec, data| {
                vec.put(data);
                async move { Ok(vec) }
            });
            value.map_ok(move |vec| (name, vec))
        })
        .try_collect()
        .await
        .map_err(|e| {
            panic!("multipart error: {:?}", e);
        });
    if let Ok(parts) = uploaded {
        parts.into_iter().for_each(|(name, buffer)| {
            println!("{} size is {}", name, buffer.len());
            //在这里,,每个buffer是客户端上传的文件,可以保存到服务器上
        });
    };
    Ok("done")
}

客户端通过 POST /api/upload调用,实例代码的上传文件大小限制为1GB

tokyohuang123 2021-05-18 13:07

哈哈

--
👇
c5soft: 这段代码启用 Warp的静态文件服务:

   let api = api.or(warp::fs::dir(
            server_config["static_path"].string("wwwroot"),
        )

静态文件目录Svelte输出目录

c5soft 2021-05-17 16:39

这段代码启用 Warp的静态文件服务:

   let api = api.or(warp::fs::dir(
            server_config["static_path"].string("wwwroot"),
        )

静态文件目录Svelte输出目录

c5soft 2021-05-17 16:35

核心代码放在service.rs文件中,主程序main.rs这样写:

mod config;
mod json_helper;
mod service;
mod addr;
mod context;

use json_helper::JsonHelper;
use chrono::prelude::*;
use warp::Filter;
use context::AppContext;
use std::sync::Arc;

const VERSION: &str = "0.0.1";

#[tokio::main]
async fn main() {
    pretty_env_logger::init_timed();
    let context=AppContext::new().await;
    let ctx=context.clone();
    let http = tokio::spawn(async { server(ctx,false).await });
    let ctx=context.clone();
    let https = tokio::spawn(async { server(ctx,true).await });
    https.await.unwrap();
    http.await.unwrap();
}
async fn server(context:Arc<AppContext>,is_https: bool) {
    let config = &context.config;

    let server_config = &config["config"];
    let server_active = server_config[if is_https {
        "https_active"
    } else {
        "http_active"
    }]
    .bool(false);
    let now = Local::now().to_string();
    let now = &now[0..19];
    if server_active {
        let ctx=context.clone();
        let api = warp::path("api").and(service::api(ctx));
        let api = api.or(warp::fs::dir(
            server_config["static_path"].string("wwwroot"),
        )/*.with(warp::compression::brotli()) */);

        let addr = addr::Addr::new(server_config, is_https);
        println!(
            "{} HTTP{} Server V{} is starting at {:19}, {}",
            server_config["server_name"].string("W3"),
            if is_https { "S" } else { "" },
            VERSION,
            now,
            addr
        );
        let (https_active, addr) = addr.parse();
        let server = warp::serve(api);
        if https_active {
            let server = server
                .tls()
                .cert_path(server_config["https_cert"].str("cert.pem"))
                .key_path(server_config["https_key"].str("key.pem"));
            server.run(addr).await;
        } else {
            server.run(addr).await;
        };
    }
    println!(
        "{} HTTP{} Server is closed at {}",
        server_config["server_name"].string("W3"),
        if is_https { "S" } else { "" },
        now
    )
}

系统参数设置AppContext放在配置文件比如config.json中,服务启动时一次读取,用JSON格式传递到下级函数中去。AppContext里可以放置任何东西,比如数据库连接缓存池Pool等等。

c5soft 2021-05-17 16:27

如果上传文件,比如写FilePond的服务端,需要用到multipart/form-data,Warp核心代码这样写:

use bytes::BufMut;
use futures::{TryFutureExt, TryStreamExt};
...
pub(crate) fn api(
    context: Arc<AppContext>,
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
    let ctx = context.clone();
    let get_ask = warp::get()
        .and(warp::path!("ask"))
        .map(move || ctx.clone())
        .and(warp::query::<HashMap<String, String>>())
        .and_then(ask);
    let ctx = context.clone();
    let post_upload = warp::post()
        .and(warp::path!("upload"))
        .and(warp::body::content_length_limit(1024 * 1024 * 1024))
        .map(move || ctx.clone())
        .and(warp::multipart::form().max_length(1024 * 1024 * 1024))
        .and_then(upload);
    get_ask.or(post_upload)
}

async fn ask(ctx: Arc<AppContext>, q: HashMap<String, String>) -> Result<impl Reply, Infallible> {
    Ok(format!("{:?}", q))
}

async fn upload(ctx: Arc<AppContext>, form: multipart::FormData) -> Result<impl Reply, Infallible> {
    let uploaded: Result<Vec<(String, Vec<u8>)>, warp::Rejection> = form
        .and_then(|part| {
            let name = part.name().to_string();
            let file_name = part.filename().unwrap_or_default();
            let name = if file_name.is_empty() {
                name
            } else {
                name + "-" + file_name
            };
            let value = part.stream().try_fold(Vec::new(), |mut vec, data| {
                vec.put(data);
                async move { Ok(vec) }
            });
            value.map_ok(move |vec| (name, vec))
        })
        .try_collect()
        .await
        .map_err(|e| {
            panic!("multipart error: {:?}", e);
        });
    if let Ok(parts) = uploaded {
        parts.into_iter().for_each(|(name, buffer)| {
            println!("{} size is {}", name, buffer.len());
            //在这里,,每个buffer是客户端上传的文件,可以保存到服务器上
        });
    };
    Ok("done")
}

客户端通过 POST /api/upload调用,实例代码的上传文件大小限制为1GB

作者 eweca-d 2021-05-16 16:39

感谢你的建议!我尝试着用FormData,前端部分确实很容易。

但是,有个问题就是warp处理起来非常麻烦。我使用multipart::FormData处理,尝试了半个小时,没有成功,而且相关资料太少了。尝试body系列的,只有bytes能成功,但是处理起来一样很麻烦,解码后是一大串字符串,包含一串数字,然后是content-disposition,然后是name=...\r\n\r\n...(key)...\r\n--------------。

远不如之前解码后"save=...&&ew=....."这种来得方便啊。用warp的multipart貌似要处理Futures::Stream,挺麻烦的样子。

之前那个代码是抄的,FormData相比那个有什么特殊优势吗?

--
👇
viruscamp: 1. 你前端那块代码最好用 FormData 对象的使用 很容易用的

  1. read 的问题跟http和tcp关系都不大,要学会 stream(输入流) 的用法,而这种用法,c c++ java node 都差不多。 一次的 read 是不保证返回你要的所有数据的,给再大的 buffer 都不保证,所以它的返回值一定要用。
viruscamp 2021-05-15 17:56
  1. 你前端那块代码最好用 FormData 对象的使用 很容易用的

  2. read 的问题跟http和tcp关系都不大,要学会 stream(输入流) 的用法,而这种用法,c c++ java node 都差不多。 一次的 read 是不保证返回你要的所有数据的,给再大的 buffer 都不保证,所以它的返回值一定要用。

--
👇
eweca-d: http协议还真是复杂呢。感谢让我又知道了一些web相关的知识。

现在看来,确实用一些现成的框架可以避免很多麻烦。虽然缺点就是没系统学习过web编程的,用完框架之后经常是知其然而不知其所以然就是了。

--
👇
Aya0wind: 不是有长度限制,而是因为tcp的特性,一端一次send的内容,在另一端只用一次read可能读不完,需要多次read。能读多少是一个未知数,所以你要通过读到的http协议规定的终止符才能判断读完了一个完整的http请求,而不是仅仅read一次就以为自己已经把对方send的东西全部读完了。

作者 eweca-d 2021-05-15 17:51

谢谢你的耐心回复!哈哈,之后我会好好看看这个。然后看看是不是整个重构下这个小工具来练练手。

--
👇
c5soft: 不知道如何用warp可以看看这个帖子:https://rustcc.cn/article?id=0e8e1b38-5180-4021-b6fe-e017eb8ff315 好多资料到收集在这里: How to use Rust Warp

Creating a REST API in Rust with warp

Create an async CRUD web service in Rust with warp

How do I inject dependencies into my route handlers in Warp?

如何使用Warp来处理通过http头传送认证信息

c5soft 2021-05-15 14:33

不知道如何用warp可以看看这个帖子:https://rustcc.cn/article?id=0e8e1b38-5180-4021-b6fe-e017eb8ff315 好多资料到收集在这里: How to use Rust Warp

Creating a REST API in Rust with warp

Create an async CRUD web service in Rust with warp

How do I inject dependencies into my route handlers in Warp?

如何使用Warp来处理通过http头传送认证信息

作者 eweca-d 2021-05-15 13:17

http协议还真是复杂呢。感谢让我又知道了一些web相关的知识。

现在看来,确实用一些现成的框架可以避免很多麻烦。虽然缺点就是没系统学习过web编程的,用完框架之后经常是知其然而不知其所以然就是了。

--
👇
Aya0wind: 不是有长度限制,而是因为tcp的特性,一端一次send的内容,在另一端只用一次read可能读不完,需要多次read。能读多少是一个未知数,所以你要通过读到的http协议规定的终止符才能判断读完了一个完整的http请求,而不是仅仅read一次就以为自己已经把对方send的东西全部读完了。

Aya0wind 2021-05-15 12:56

不是有长度限制,而是因为tcp的特性,一端一次send的内容,在另一端只用一次read可能读不完,需要多次read。能读多少是一个未知数,所以你要通过读到的http协议规定的终止符才能判断读完了一个完整的http请求,而不是仅仅read一次就以为自己已经把对方send的东西全部读完了。

👇
eweca-d: 你的意思是,tecpstream的read是有长度限制的,就算是我的buffer再大,也会因此而被截断。所以需要再次read或者read_to_end是吗?

有道理,我尝试了下二次读取到buffer2里,成功在buffer2得到了内容。

但是又遇到了问题,假如我在第一次读取stream完毕后,第二次读取stream内没有内容,那么我的stream.write和stream.flush就会失效。怀疑是服务器会第二次尝试读取时遇不到EOF,然后无限等待。所以每次read都要判断是否读到末尾,我不知道rust或者http协议里是怎么判断的,但是我看到的buffer里,在我的所有内容后直接就是\u{0}了。

由于buffer尺寸和tcpstream的read最大尺寸不同,不管读没读完后面都是一大串\u{0}。那么我能想到的办法就是在post最后加一个end=true,每次read之后解码看是否包含end=true,不包含则read到包含为止。

最后成功解决了问题!web编程真是复杂啊。

--
👇
Aya0wind: 抛开rust,我猜是因为tcp提供的最基本的read函数,是无法指定一次读的长度,只能指定最大长度的(rust的tcpstream也是如此)。也就是说一次read可能并不会把对方本次发送的所有内容读完,也有人把这个现象称作tcp粘包。本质上是因为tcp并不是包式协议,而是流式协议,如果需要分包,需要上层规定另外的分包协议。 而你这里使用read,如果一次发送的长度比较长,那么一次read读不完,所以你就会发现header是断的。 解决方法就是严格按照http协议来,首先去了解http协议是怎么分隔各个部位的,一次read后判断没有读到末尾,就需要继续调用read。

作者 eweca-d 2021-05-15 11:26

你的意思是,tecpstream的read是有长度限制的,就算是我的buffer再大,也会因此而被截断。所以需要再次read或者read_to_end是吗?

有道理,我尝试了下二次读取到buffer2里,成功在buffer2得到了内容。

但是又遇到了问题,假如我在第一次读取stream完毕后,第二次读取stream内没有内容,那么我的stream.write和stream.flush就会失效。怀疑是服务器会第二次尝试读取时遇不到EOF,然后无限等待。所以每次read都要判断是否读到末尾,我不知道rust或者http协议里是怎么判断的,但是我看到的buffer里,在我的所有内容后直接就是\u{0}了。

由于buffer尺寸和tcpstream的read最大尺寸不同,不管读没读完后面都是一大串\u{0}。那么我能想到的办法就是在post最后加一个end=true,每次read之后解码看是否包含end=true,不包含则read到包含为止。

最后成功解决了问题!web编程真是复杂啊。

--
👇
Aya0wind: 抛开rust,我猜是因为tcp提供的最基本的read函数,是无法指定一次读的长度,只能指定最大长度的(rust的tcpstream也是如此)。也就是说一次read可能并不会把对方本次发送的所有内容读完,也有人把这个现象称作tcp粘包。本质上是因为tcp并不是包式协议,而是流式协议,如果需要分包,需要上层规定另外的分包协议。 而你这里使用read,如果一次发送的长度比较长,那么一次read读不完,所以你就会发现header是断的。 解决方法就是严格按照http协议来,首先去了解http协议是怎么分隔各个部位的,一次read后判断没有读到末尾,就需要继续调用read。

Aya0wind 2021-05-15 10:36

抛开rust,我猜是因为tcp提供的最基本的read函数,是无法指定一次读的长度,只能指定最大长度的(rust的tcpstream也是如此)。也就是说一次read可能并不会把对方本次发送的所有内容读完,也有人把这个现象称作tcp粘包。本质上是因为tcp并不是包式协议,而是流式协议,如果需要分包,需要上层规定另外的分包协议。 而你这里使用read,如果一次发送的长度比较长,那么一次read读不完,所以你就会发现content是断的。 解决方法就是严格按照http协议来,首先去了解http协议是怎么分隔各个部位的,一次read后判断没有读到末尾,就需要继续调用read。

作者 eweca-d 2021-05-15 09:46

我现在服务器端口是这样的:

#[tokio::main]
async fn main() {
    let default_reply = warp::path::end().map(|| Response::builder().body(default_reply()));

    let post_reply = warp::post()
        .and(warp::body::bytes())
        .map(|content: Bytes| Response::builder().body(parse_post(content)));

    let routers = warp::get().and(default_reply).or(post_reply);

    warp::serve(routers).run(([127, 0, 0, 1], 3030)).await;
}

default_reply()会读取html模板并进行一定的修改作为html格式的String来返回从而构建出网页,而post返回的主体里根据使用者传到服务器参数,也是读取html模板并进行一定的修改作为html格式的String来返回从而构建出网页。之前我是用std的stream.write()stream.flush()来完成这个功能。

我的理解是,这个就是静态文件服务吧?

用svelte重写页面这个建议,我会认真考虑下的!这两天可以先了解下svelte,技多不压身。感谢!

👇
c5soft: 再把页面用svelte重写一下,warp里启用静态文件服务,立马高大上。exe文件大小根本不是问题,我生产环境,warp+reqwest+tiberius, msvc 64比特, 9MB。

c5soft 2021-05-15 08:21

再把页面用svelte重写一下,warp里启用静态文件服务,立马高大上。exe文件大小根本不是问题,我生产环境,warp+reqwest+tiberius, msvc 64比特, 9MB。

1 2 共 26 条评论, 2 页