牛客Rust社区(web server Chapter 1)
找了很久牛客上面也有很多rust爱好者,但是Rust就没有类似Java求职、C++项目等圈子广场,希望官方可以提供一个Rust爱好交流圈子啥的。
C++项目者应该做过最多的就是web server服务器了,用Rust简单复现一个.
前置依赖:
toml
文件
tracing = "0.1.37" tracing-subscriber = "0.3.17" // 日志文件
第一步先注册一下全局日志,我不太喜欢println的方式打印,感觉那样太简陋且也没有可读性。
// 注册日志 tracing_subscriber::registry() .with(fmt::layer()) .init();
然后是监听端口:127.0.0.1:8888
let listener = TcpListener::bind("127.0.0.1:8888").unwrap(); listener.incoming().map(Result::unwrap).for_each(handle_connection); // handle_connection 是后面具体请求处理的方法,这儿先过滤,后面会讲
这样子就可以监听一个请求了,可以直接通过浏览器进行访问,但是我们怎么判断一个请求是否正确呢?这儿就得考虑http请求格式了。
可以通过这种方式在handle_connection
方法中打印http_request看看格式,上面我写的方式是函数式编程,可能没怎么了解过rust的同学看起来估计可读性不高,我简单解释下:
listener.incoming()
返回一个流(stream)的迭代器,该迭代器代表监听器接收到的传入连接。map(Result::unwrap)
对流中的每个元素执行Result::unwrap
操作,将Ok(T)
转换为对应的T
值,忽略Err
的情况。这是因为listener.incoming()
返回的是一个Result<TcpStream, Error>
类型,使用Result::unwrap
可以提取出成功的TcpStream
值。for_each(handle_connection)
对经过映射处理后的每个流应用handle_connection
函数。for_each
方法接受一个闭包作为参数,并对流进行迭代并执行闭包中定义的操作。在这里,handle_connection
函数被应用于每个流上,实现对每个连接的处理。
故而上面其实最终传入handle_connection
方法的都是一个TcpStream值。所以我们可以这么打印。
let buf_reader = BufReader::new(&mut stream); let http_request: Vec<_> = buf_reader .lines() .map(|result| result.unwrap()) .take_while(|line| !line.is_empty()) .collect(); tracing::info!("Request: {:#?}", http_request);
可以看到格式是这样的:
这儿我们判断比较简单,通过request的第一个值判断是否走http请求
let buf_reader = BufReader::new(&mut stream); let request_line = buf_reader.lines().next().unwrap().unwrap(); let (status_line, filename) = match request_line.as_str() { "GET / HTTP/1.1" => ("HTTP/1.1 200 OK", "hello.html"), _ => ("HTTP/1.1 404 NOT FOUND", "404.html"), }; let contents = fs::read_to_string(filename).unwrap(); let length = contents.len(); let response = format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}"); stream.write_all(response.as_bytes()).unwrap();
这儿的hello.html
和 `404.html` 是我个人基于根目录创建的文件,这样子就可以给用户基本的展示了,这儿其实非常简单,就和用springboot写一个hello world的controller一样,当前是单线程的,肯定会出现阻塞的情况,下个文章准备写一个线程池来实现多线程不阻塞工作。