国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Rust Web 全棧開發(fā)之自建TCP、HTTP Server

這篇具有很好參考價(jià)值的文章主要介紹了Rust Web 全棧開發(fā)之自建TCP、HTTP Server。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

Rust Web 全棧開發(fā)之自建TCP、HTTP Server

課程簡(jiǎn)介

預(yù)備知識(shí)

  • Rust 編程語(yǔ)言入門

  • https://www.bilibili.com/video/BV1hp4y1k7SV

課程主要內(nèi)容

  • WebService
  • 服務(wù)器端Web App
  • 客戶端Web App(WebAssembly)
  • Web框架:Actix
  • 數(shù)據(jù)庫(kù):PostgreSQL
  • 數(shù)據(jù)庫(kù)連接:SQLx

全部使用純Rust編寫!

一、構(gòu)建TCP Server

本節(jié)內(nèi)容

  • 編寫TCP Server和Client

std::net模塊

  • 標(biāo)準(zhǔn)庫(kù)的std::net模塊,提供網(wǎng)絡(luò)基本功能
  • 支持TCP和UDP通信
  • TcpListener和TcpStream

創(chuàng)建項(xiàng)目

~/rust via ?? base
? cargo new s1 && cd s1
     Created binary (application) `s1` package

s1 on  master [?] via ?? 1.67.1 via ?? base
? cargo new tcpserver
     Created binary (application) `tcpserver` package

s1 on  master [?] via ?? 1.67.1 via ?? base
? cargo new tcpclient
     Created binary (application) `tcpclient` package

s1 on  master [?] via ?? 1.67.1 via ?? base
? c

s1 on  master [?] via ?? 1.67.1 via ?? base
?

目錄

s1 on  master [?] via ?? 1.67.1 via ?? base 
? tree
.
├── Cargo.lock
├── Cargo.toml
├── src
│   └── main.rs
├── target
│   ├── CACHEDIR.TAG
│   └── debug
│       ├── build
│       ├── deps
│       ├── examples
│       └── incremental
├── tcpclient
│   ├── Cargo.toml
│   └── src
│       └── main.rs
└── tcpserver
    ├── Cargo.toml
    └── src
        └── main.rs

24 directories, 44 files

s1 on  master [?] via ?? 1.67.1 via ?? base 

s1/Cargo.toml

[workspace]

members = ["tcpserver", "tcpclient"]

tcpserver/src/main.rs

use std::net::TcpListener;

fn main() {
    let listener = TcpListener::bind("127.0.0.1:3000").unwrap();
    println!("Running on port 3000...");

    // let result = listener.accept().unwrap(); // 只接收一次請(qǐng)求

    for stream in listener.incoming() {
        let _stream = stream.unwrap();
        println!("Connection established!")
    }
}

運(yùn)行

s1 on  master [?] via ?? 1.67.1 via ?? base 
? cargo run -p tcpserver            
   Compiling tcpserver v0.1.0 (/Users/qiaopengjun/rust/s1/tcpserver)
    Finished dev [unoptimized + debuginfo] target(s) in 0.52s
     Running `target/debug/tcpserver`
Running on port 3000...

tcpclient/src/main.rs

use std::net::TcpStream;

fn main() {
    let _stream = TcpStream::connect("localhost:3000").unwrap();
}

運(yùn)行

s1 on  master [?] via ?? 1.67.1 via ?? base 
? cargo run -p tcpclient
   Compiling tcpclient v0.1.0 (/Users/qiaopengjun/rust/s1/tcpclient)
    Finished dev [unoptimized + debuginfo] target(s) in 0.32s
     Running `target/debug/tcpclient`

s1 on  master [?] via ?? 1.67.1 via ?? base 
? 

s1 on  master [?] via ?? 1.67.1 via ?? base 
? cargo run -p tcpserver            
   Compiling tcpserver v0.1.0 (/Users/qiaopengjun/rust/s1/tcpserver)
    Finished dev [unoptimized + debuginfo] target(s) in 0.52s
     Running `target/debug/tcpserver`
Running on port 3000...
Connection established!

tcpserver/src/main.rs

use std::io::{Read, Write};
use std::net::TcpListener;

fn main() {
    let listener = TcpListener::bind("127.0.0.1:3000").unwrap();
    println!("Running on port 3000...");

    // let result = listener.accept().unwrap(); // 只接收一次請(qǐng)求

    for stream in listener.incoming() {
        let mut stream = stream.unwrap();
        println!("Connection established!");
        let mut buffer = [0; 1024];

        stream.read(&mut buffer).unwrap();
        stream.write(&mut buffer).unwrap();
    }
}

tcpclient/src/main.rs

use std::io::{Read, Write};
use std::net::TcpStream;
use std::str;

fn main() {
    let mut stream = TcpStream::connect("localhost:3000").unwrap();
    stream.write("Hello".as_bytes()).unwrap();

    let mut buffer = [0; 5];
    stream.read(&mut buffer).unwrap();

    println!(
        "Response from server: {:?}",
        str::from_utf8(&buffer).unwrap()
    );
}

運(yùn)行

s1 on  master [?] via ?? 1.67.1 via ?? base 
? cargo run -p tcpclient
   Compiling tcpclient v0.1.0 (/Users/qiaopengjun/rust/s1/tcpclient)
    Finished dev [unoptimized + debuginfo] target(s) in 0.15s
     Running `target/debug/tcpclient`
Response from server: "Hello"

s1 on  master [?] via ?? 1.67.1 via ?? base 
? 

二、構(gòu)建HTTP Server - 解析 HTTP 請(qǐng)求

本節(jié)內(nèi)容

  • 編寫HTTP Server
  • 測(cè)試HTTP Server

Web Server的消息流動(dòng)圖

客戶端 Internet 請(qǐng)求(1) -> Server (HTTP Library)->(2) Router ->(3) -> Handlers ->處理請(qǐng)求并返回響應(yīng)(4) 客戶端

注意

  • Rust沒有內(nèi)置的HTTP支持

Web Server

  • Server
    • 監(jiān)聽進(jìn)來的TCP字節(jié)流
  • Router
    • 接受HTTP請(qǐng)求,并決定調(diào)用哪個(gè)Handler
  • Handler
    • 處理HTTP請(qǐng)求,構(gòu)建HTTP響應(yīng)
  • HTTP Library
    • 解釋字節(jié)流,把它轉(zhuǎn)化為HTTP請(qǐng)求
    • 把HTTP響應(yīng)轉(zhuǎn)化回字節(jié)流

構(gòu)建步驟

  • 解析HTTP請(qǐng)求消息
  • 構(gòu)建HTTP響應(yīng)消息
  • 路由與Handler
  • 測(cè)試Web Server

解析HTTP請(qǐng)求(消息)

三個(gè)數(shù)據(jù)結(jié)構(gòu)

數(shù)據(jù)結(jié)構(gòu)名稱 數(shù)據(jù)類型 描述
HttpRequest struct 表示HTTP請(qǐng)求
Method enum 指定所允許的HTTP方法
Version enum 指定所允許的HTTP版本

HTTP請(qǐng)求

Structure of an HTTP Request

Request Line Method Path Version

Header Line 1

Header Line 2

Header Line 3

Empty line

Message body (optional)

s1 on  master [?] via ?? 1.67.1 via ?? base
? cargo new httpserver
warning: compiling this new package may not work due to invalid workspace configuration

current package believes it's in a workspace when it's not:
current:   /Users/qiaopengjun/rust/s1/httpserver/Cargo.toml
workspace: /Users/qiaopengjun/rust/s1/Cargo.toml

this may be fixable by adding `httpserver` to the `workspace.members` array of the manifest located at: /Users/qiaopengjun/rust/s1/Cargo.toml
Alternatively, to keep it out of the workspace, add the package to the `workspace.exclude` array, or add an empty `[workspace]` table to the package's manifest.
     Created binary (application) `httpserver` package

s1 on  master [?] via ?? 1.67.1 via ?? base
? cargo new --lib http
warning: compiling this new package may not work due to invalid workspace configuration

current package believes it's in a workspace when it's not:
current:   /Users/qiaopengjun/rust/s1/http/Cargo.toml
workspace: /Users/qiaopengjun/rust/s1/Cargo.toml

this may be fixable by adding `http` to the `workspace.members` array of the manifest located at: /Users/qiaopengjun/rust/s1/Cargo.toml
Alternatively, to keep it out of the workspace, add the package to the `workspace.exclude` array, or add an empty `[workspace]` table to the package's manifest.
     Created library `http` package

s1 on  master [?] via ?? 1.67.1 via ?? base
?

需要實(shí)現(xiàn)的Trait

Trait 描述
From<&str> 用于把傳進(jìn)來的字符串切片轉(zhuǎn)化為HttpRequest
Debug 打印調(diào)試信息
PartialEq 用于解析和自動(dòng)化測(cè)試腳本里做比較

目錄

s1 on  master [?] via ?? 1.67.1 via ?? base 
? tree
.
├── Cargo.lock
├── Cargo.toml
├── http
│   ├── Cargo.toml
│   └── src
│       ├── httprequest.rs
│       ├── httpresponse.rs
│       └── lib.rs
├── httpserver
│   ├── Cargo.toml
│   └── src
│       └── main.rs
├── src
│   └── main.rs
├── target
│   ├── CACHEDIR.TAG
│   └── debug
│       ├── build
│       ├── deps
│       ├── examples
│       ├── httpserver
│       ├── httpserver.d
│       ├── incremental
│       ├── libhttp.d
│       ├── libhttp.rlib
│       ├── tcpclient
│       ├── tcpclient.d
│       ├── tcpserver
│       └── tcpserver.d
├── tcpclient
│   ├── Cargo.toml
│   └── src
│       └── main.rs
└── tcpserver
    ├── Cargo.toml
    └── src
        └── main.rs

48 directories, 302 files

s1 on  master [?] via ?? 1.67.1 via ?? base 
? 

http/lib.rs

pub mod httprequest;

Cargo.toml

[workspace]

members = ["tcpserver", "tcpclient", "http", "httpserver"]

http/src/httprequest.rs

#[derive(Debug, PartialEq)]
pub enum Method {
    Get,
    Post,
    Uninitialized,
}

impl From<&str> for Method {
    fn from(s: &str) -> Method {
        match s {
            "GET" => Method::Get,
            "POST" => Method::Post,
            _ => Method::Uninitialized,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_method_into() {
        let m: Method = "GET".into();
        assert_eq!(m, Method::Get);
    }
}

運(yùn)行

s1 on  master [?] via ?? 1.67.1 via ?? base 
? cargo test -p http   
   Compiling http v0.1.0 (/Users/qiaopengjun/rust/s1/http)
    Finished test [unoptimized + debuginfo] target(s) in 0.19s
     Running unittests src/lib.rs (target/debug/deps/http-ca93876a5b13f05e)

running 1 test
test httprequest::tests::test_method_into ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests http

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s


s1 on  master [?] via ?? 1.67.1 via ?? base 
? 

http/src/httprequest.rs

#[derive(Debug, PartialEq)]
pub enum Method {
    Get,
    Post,
    Uninitialized,
}

impl From<&str> for Method {
    fn from(s: &str) -> Method {
        match s {
            "GET" => Method::Get,
            "POST" => Method::Post,
            _ => Method::Uninitialized,
        }
    }
}

#[derive(Debug, PartialEq)]
pub enum Version {
    V1_1,
    V2_0,
    Uninitialized,
}

impl From<&str> for Version {
    fn from(s: &str) -> Version {
        match s {
            "HTTP/1.1" => Version::V1_1,
            _ => Version::Uninitialized,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_method_into() {
        let m: Method = "GET".into();
        assert_eq!(m, Method::Get);
    }

    #[test]
    fn test_version_into() {
        let v: Version = "HTTP/1.1".into();
        assert_eq!(v, Version::V1_1);
    }
}

運(yùn)行

s1 on  master [?] via ?? 1.67.1 via ?? base 
? cargo test -p http
   Compiling http v0.1.0 (/Users/qiaopengjun/rust/s1/http)
    Finished test [unoptimized + debuginfo] target(s) in 0.58s
     Running unittests src/lib.rs (target/debug/deps/http-ca93876a5b13f05e)

running 2 tests
test httprequest::tests::test_version_into ... ok
test httprequest::tests::test_method_into ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests http

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s


s1 on  master [?] via ?? 1.67.1 via ?? base 
? 

http/src/httprequest.rs

use std::collections::HashMap;

#[derive(Debug, PartialEq)]
pub enum Method {
    Get,
    Post,
    Uninitialized,
}

impl From<&str> for Method {
    fn from(s: &str) -> Method {
        match s {
            "GET" => Method::Get,
            "POST" => Method::Post,
            _ => Method::Uninitialized,
        }
    }
}

#[derive(Debug, PartialEq)]
pub enum Version {
    V1_1,
    V2_0,
    Uninitialized,
}

impl From<&str> for Version {
    fn from(s: &str) -> Version {
        match s {
            "HTTP/1.1" => Version::V1_1,
            _ => Version::Uninitialized,
        }
    }
}

#[derive(Debug, PartialEq)]
pub enum Resource {
    Path(String),
}

#[derive(Debug)]
pub struct HttpRequest {
    pub method: Method,
    pub version: Version,
    pub resource: Resource,
    pub headers: HashMap<String, String>,
    pub msg_body: String,
}

impl From<String> for HttpRequest {
    fn from(req: String) -> Self {
        let mut parsed_method = Method::Uninitialized;
        let mut parsed_version = Version::V1_1;
        let mut parsed_resource = Resource::Path("".to_string());
        let mut parsed_headers = HashMap::new();
        let mut parsed_msg_body = "";

        for line in req.lines() {
            if line.contains("HTTP") {
                let (method, resource, version) = process_req_line(line);
                parsed_method = method;
                parsed_resource = resource;
                parsed_version = version;
            } else if line.contains(":") {
                let (key, value) = process_header_line(line);
                parsed_headers.insert(key, value);
            } else if line.len() == 0 {
            } else {
                parsed_msg_body = line;
            }
        }

        HttpRequest {
            method: parsed_method,
            version: parsed_version,
            resource: parsed_resource,
            headers: parsed_headers,
            msg_body: parsed_msg_body.to_string(),
        }
    }
}

fn process_req_line(s: &str) -> (Method, Resource, Version) {
    let mut words = s.split_whitespace();
    let method = words.next().unwrap();
    let resource = words.next().unwrap();
    let version = words.next().unwrap();

    (
        method.into(),
        Resource::Path(resource.to_string()),
        version.into(),
    )
}

fn process_header_line(s: &str) -> (String, String) {
    let mut header_items = s.split(":");
    let mut key = String::from("");
    let mut value = String::from("");
    if let Some(k) = header_items.next() {
        key = k.to_string();
    }
    if let Some(v) = header_items.next() {
        value = v.to_string();
    }

    (key, value)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_method_into() {
        let m: Method = "GET".into();
        assert_eq!(m, Method::Get);
    }

    #[test]
    fn test_version_into() {
        let v: Version = "HTTP/1.1".into();
        assert_eq!(v, Version::V1_1);
    }

    #[test]
    fn test_read_http() {
        let s: String = String::from("GET /greeting HTTP/1.1\r\nHost: localhost:3000\r\nUser-Agent: curl/7.71.1\r\nAccept: */*\r\n\r\n");
        let mut headers_expected = HashMap::new();
        headers_expected.insert("Host".into(), " localhost".into());
        headers_expected.insert("Accept".into(), " */*".into());
        headers_expected.insert("User-Agent".into(), " curl/7.71.1".into());
        let req: HttpRequest = s.into();

        assert_eq!(Method::Get, req.method);
        assert_eq!(Version::V1_1, req.version);
        assert_eq!(Resource::Path("/greeting".to_string()), req.resource);
        assert_eq!(headers_expected, req.headers);
    }
}

測(cè)試

s1 on  master [?] via ?? 1.67.1 via ?? base 
? cargo test -p http
   Compiling http v0.1.0 (/Users/qiaopengjun/rust/s1/http)
    Finished test [unoptimized + debuginfo] target(s) in 0.37s
     Running unittests src/lib.rs (target/debug/deps/http-ca93876a5b13f05e)

running 3 tests
test httprequest::tests::test_method_into ... ok
test httprequest::tests::test_version_into ... ok
test httprequest::tests::test_read_http ... ok

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests http

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s


s1 on  master [?] via ?? 1.67.1 via ?? base 
? 

三、構(gòu)建HTTP響應(yīng)

HTTP響應(yīng)

Structure of an HTTP Response

Status Line Version Status code Status text

Header Line 1

Header Line 2

Empty line

Message body(optional)

HttpResponse 需要實(shí)現(xiàn)的方法

需要實(shí)現(xiàn)的方法或trait 用途
Default trait 指定成員的默認(rèn)值
new() 使用默認(rèn)值創(chuàng)建一個(gè)新的結(jié)構(gòu)體
send_response() 構(gòu)建響應(yīng),將原始字節(jié)通過TCP傳送
getter 方法 獲得成員的值
From trait 能夠?qū)ttpResponse轉(zhuǎn)化為String

http/lib.rs

pub mod httprequest;
pub mod httpresponse;

http/src/httpresponse.rs

use std::collections::HashMap;
use std::io::{Result, Write};

#[derive(Debug, PartialEq, Clone)]
pub struct HttpResponse<'a> {
    version: &'a str,
    status_code: &'a str,
    status_text: &'a str,
    headers: Option<HashMap<&'a str, &'a str>>,
    body: Option<String>,
}

impl<'a> Default for HttpResponse<'a> {
    fn default() -> Self {
        Self {
            version: "HTTP/1.1".into(),
            status_code: "200".into(),
            status_text: "OK".into(),
            headers: None,
            body: None,
        }
    }
}

impl<'a> From<HttpResponse<'a>> for String {
    fn from(res: HttpResponse<'a>) -> String {
        let res1 = res.clone();
        format!(
            "{} {} {}\r\n{}Content-Length: {}\r\n\r\n{}",
            &res1.version(),
            &res1.status_code(),
            &res1.status_text(),
            &res1.headers(),
            &res.body.unwrap().len(),
            &res1.body()
        )
    }
}

impl<'a> HttpResponse<'a> {
    pub fn new(
        status_code: &'a str,
        headers: Option<HashMap<&'a str, &'a str>>,
        body: Option<String>,
    ) -> HttpResponse<'a> {
        let mut response: HttpResponse<'a> = HttpResponse::default();
        if status_code != "200" {
            response.status_code = status_code.into();
        };
        response.headers = match &headers {
            Some(_h) => headers,
            None => {
                let mut h = HashMap::new();
                h.insert("Content-Type", "text/html");
                Some(h)
            }
        };
        response.status_text = match response.status_code {
            "200" => "OK".into(),
            "400" => "Bad Request".into(),
            "404" => "Not Found".into(),
            "500" => "Internal Server Error".into(),
            _ => "Not Found".into(),
        };

        response.body = body;

        response
    }

    pub fn send_response(&self, write_stream: &mut impl Write) -> Result<()> {
        let res = self.clone();
        let response_string: String = String::from(res);
        let _ = write!(write_stream, "{}", response_string);

        Ok(())
    }

    fn version(&self) -> &str {
        self.version
    }

    fn status_code(&self) -> &str {
        self.status_code
    }

    fn status_text(&self) -> &str {
        self.status_text
    }

    fn headers(&self) -> String {
        let map: HashMap<&str, &str> = self.headers.clone().unwrap();
        let mut header_string: String = "".into();
        for (k, v) in map.iter() {
            header_string = format!("{}{}:{}\r\n", header_string, k, v);
        }
        header_string
    }

    pub fn body(&self) -> &str {
        match &self.body {
            Some(b) => b.as_str(),
            None => "",
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_response_struct_creation_200() {
        let response_actual = HttpResponse::new("200", None, Some("xxxx".into()));
        let response_expected = HttpResponse {
            version: "HTTP/1.1",
            status_code: "200",
            status_text: "OK",
            headers: {
                let mut h = HashMap::new();
                h.insert("Content-Type", "text/html");
                Some(h)
            },
            body: Some("xxxx".into()),
        };
        assert_eq!(response_actual, response_expected);
    }

    #[test]
    fn test_response_struct_creation_404() {
        let response_actual = HttpResponse::new("404", None, Some("xxxx".into()));
        let response_expected = HttpResponse {
            version: "HTTP/1.1",
            status_code: "404",
            status_text: "Not Found",
            headers: {
                let mut h = HashMap::new();
                h.insert("Content-Type", "text/html");
                Some(h)
            },
            body: Some("xxxx".into()),
        };
        assert_eq!(response_actual, response_expected);
    }

    #[test]
    fn test_http_response_creation() {
        let response_expected = HttpResponse {
            version: "HTTP/1.1",
            status_code: "404",
            status_text: "Not Found",
            headers: {
                let mut h = HashMap::new();
                h.insert("Content-Type", "text/html");
                Some(h)
            },
            body: Some("xxxx".into()),
        };
        let http_string: String = response_expected.into();
        let actual_string =
            "HTTP/1.1 404 Not Found\r\nContent-Type:text/html\r\nContent-Length: 4\r\n\r\nxxxx";
        assert_eq!(http_string, actual_string);
    }
}

測(cè)試

s1 on  master [?] via ?? 1.67.1 via ?? base 
? cargo test -p http
   Compiling http v0.1.0 (/Users/qiaopengjun/rust/s1/http)
    Finished test [unoptimized + debuginfo] target(s) in 0.70s
     Running unittests src/lib.rs (target/debug/deps/http-ca93876a5b13f05e)

running 6 tests
test httprequest::tests::test_version_into ... ok
test httprequest::tests::test_method_into ... ok
test httpresponse::tests::test_response_struct_creation_200 ... ok
test httpresponse::tests::test_response_struct_creation_404 ... ok
test httpresponse::tests::test_http_response_creation ... ok
test httprequest::tests::test_read_http ... ok

test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests http

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s


s1 on  master [?] via ?? 1.67.1 via ?? base 
? 

四、構(gòu)建 server 模塊

目錄

s1 on  master [?] via ?? 1.67.1 via ?? base 
? tree
.
├── Cargo.lock
├── Cargo.toml
├── http
│   ├── Cargo.toml
│   └── src
│       ├── httprequest.rs
│       ├── httpresponse.rs
│       └── lib.rs
├── httpserver
│   ├── Cargo.toml
│   └── src
│       ├── handler.rs
│       ├── main.rs
│       ├── router.rs
│       └── server.rs
├── src
│   └── main.rs
├── target
│   ├── CACHEDIR.TAG
│   └── debug
│       ├── build
│       ├── deps
│       ├── examples
│       ├── httpserver
│       ├── httpserver.d
│       ├── incremental
│       ├── libhttp.d
│       ├── libhttp.rlib
│       ├── tcpclient
│       ├── tcpclient.d
│       ├── tcpserver
│       └── tcpserver.d
├── tcpclient
│   ├── Cargo.toml
│   └── src
│       └── main.rs
└── tcpserver
    ├── Cargo.toml
    └── src
        └── main.rs

58 directories, 532 files

s1 on  master [?] via ?? 1.67.1 via ?? base 
? 

httpserver/Cargo.toml

[package]
name = "httpserver"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
http = {path = "../http"}

httpserver/main.rs

mod handler;
mod router;
mod server;

use server::Server;

fn main() {
    let server = Server::new("localhost:3000");
    server.run();
}

httpserver/server.rs

use super::router::Router;
use http::httprequest::HttpRequest;
use std::io::prelude::*;
use std::net::TcpListener;
use std::str;

pub struct Server<'a> {
    socket_addr: &'a str,
}

impl<'a> Server<'a> {
    pub fn new(socket_addr: &'a str) -> Self {
        Server { socket_addr }
    }

    pub fn run(&self) {
        let connection_listener = TcpListener::bind(self.socket_addr).unwrap();
        println!("Running on {}", self.socket_addr);

        for stream in connection_listener.incoming() {
            let mut stream = stream.unwrap();
            println!("Connection established");

            let mut read_buffer = [0; 200];
            stream.read(&mut read_buffer).unwrap();

            let req: HttpRequest = String::from_utf8(read_buffer.to_vec()).unwrap().into();
            Router::route(req, &mut stream);
        }
    }
}

五、構(gòu)建 router 和 handler 模塊

目錄

s1 on  master [?] via ?? 1.67.1 via ?? base 
? tree
.
├── Cargo.lock
├── Cargo.toml
├── http
│   ├── Cargo.toml
│   └── src
│       ├── httprequest.rs
│       ├── httpresponse.rs
│       └── lib.rs
├── httpserver
│   ├── Cargo.toml
│   ├── data
│   │   └── orders.json
│   ├── public
│   │   ├── 404.html
│   │   ├── health.html
│   │   ├── index.html
│   │   └── styles.css
│   └── src
│       ├── handler.rs
│       ├── main.rs
│       ├── router.rs
│       └── server.rs
├── src
│   └── main.rs
├── target
│   ├── CACHEDIR.TAG
│   └── debug
│       ├── build
│       ├── deps
│       ├── libhttp.d
│       ├── libhttp.rlib
│       ├── tcpclient
│       ├── tcpclient.d
│       ├── tcpserver
│       └── tcpserver.d
├── tcpclient
│   ├── Cargo.toml
│   └── src
│       └── main.rs
└── tcpserver
    ├── Cargo.toml
    └── src
        └── main.rs

74 directories, 805 files

s1 on  master [?] via ?? 1.67.1 via ?? base 
? 

httpserver/Cargo.toml

[package]
name = "httpserver"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
http = {path = "../http"}
serde = {version="1.0.163", features = ["derive"]}
serde_json = "1.0.96"

httpserver/src/handler.rs

use http::{httprequest::HttpRequest, httpresponse::HttpResponse};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::env;
use std::fs;

pub trait Handler {
    fn handle(req: &HttpRequest) -> HttpResponse;
    fn load_file(file_name: &str) -> Option<String> {
        let default_path = format!("{}/public", env!("CARGO_MANIFEST_DIR"));
        let public_path = env::var("PUBLIC_PATH").unwrap_or(default_path);
        let full_path = format!("{}/{}", public_path, file_name);

        let contents = fs::read_to_string(full_path);
        contents.ok()
    }
}

pub struct StaticPageHandler;
pub struct PageNotFoundHandler;
pub struct WebServiceHandler;

#[derive(Serialize, Deserialize)]
pub struct OrderStatus {
    order_id: i32,
    order_date: String,
    order_status: String,
}

impl Handler for PageNotFoundHandler {
    fn handle(_req: &HttpRequest) -> HttpResponse {
        HttpResponse::new("404", None, Self::load_file("404.html"))
    }
}

impl Handler for StaticPageHandler {
    fn handle(req: &HttpRequest) -> HttpResponse {
        let http::httprequest::Resource::Path(s) = &req.resource;
        let route: Vec<&str> = s.split("/").collect();
        match route[1] {
            "" => HttpResponse::new("200", None, Self::load_file("index.html")),
            "health" => HttpResponse::new("200", None, Self::load_file("health.html")),
            path => match Self::load_file(path) {
                Some(contents) => {
                    let mut map: HashMap<&str, &str> = HashMap::new();
                    if path.ends_with(".css") {
                        map.insert("Content-Type", "text/css");
                    } else if path.ends_with(".js") {
                        map.insert("Content-Type", "text/javascript");
                    } else {
                        map.insert("Content-Type", "text/html");
                    }
                    HttpResponse::new("200", Some(map), Some(contents))
                }
                None => HttpResponse::new("404", None, Self::load_file("404.html")),
            },
        }
    }
}

impl WebServiceHandler {
    fn load_json() -> Vec<OrderStatus> {
        let default_path = format!("{}/data", env!("CARGO_MANIFEST_DIR"));
        let data_path = env::var("DATA_PATH").unwrap_or(default_path);
        let full_path = format!("{}/{}", data_path, "orders.json");
        let json_contents = fs::read_to_string(full_path);
        let orders: Vec<OrderStatus> =
            serde_json::from_str(json_contents.unwrap().as_str()).unwrap();
        orders
    }
}

impl Handler for WebServiceHandler {
    fn handle(req: &HttpRequest) -> HttpResponse {
        let http::httprequest::Resource::Path(s) = &req.resource;
        let route: Vec<&str> = s.split("/").collect();
        // localhost:3000/api/shipping/orders
        match route[2] {
            "shipping" if route.len() > 2 && route[3] == "orders" => {
                let body = Some(serde_json::to_string(&Self::load_json()).unwrap());
                let mut headers: HashMap<&str, &str> = HashMap::new();
                headers.insert("Content-Type", "application/json");
                HttpResponse::new("200", Some(headers), body)
            }
            _ => HttpResponse::new("404", None, Self::load_file("404.html")),
        }
    }
}

httpserver/src/server.rs

use super::router::Router;
use http::httprequest::HttpRequest;
use std::io::prelude::*;
use std::net::TcpListener;
use std::str;

pub struct Server<'a> {
    socket_addr: &'a str,
}

impl<'a> Server<'a> {
    pub fn new(socket_addr: &'a str) -> Self {
        Server { socket_addr }
    }

    pub fn run(&self) {
        let connection_listener = TcpListener::bind(self.socket_addr).unwrap();
        println!("Running on {}", self.socket_addr);

        for stream in connection_listener.incoming() {
            let mut stream = stream.unwrap();
            println!("Connection established");

            let mut read_buffer = [0; 200];
            stream.read(&mut read_buffer).unwrap();

            let req: HttpRequest = String::from_utf8(read_buffer.to_vec()).unwrap().into();
            Router::route(req, &mut stream);
        }
    }
}

httpserver/src/router.rs

use super::handler::{Handler, PageNotFoundHandler, StaticPageHandler, WebServiceHandler};
use http::{httprequest, httprequest::HttpRequest, httpresponse::HttpResponse};
use std::io::prelude::*;

pub struct Router;

impl Router {
    pub fn route(req: HttpRequest, stream: &mut impl Write) -> () {
        match req.method {
            httprequest::Method::Get => match &req.resource {
                httprequest::Resource::Path(s) => {
                    let route: Vec<&str> = s.split("/").collect();
                    match route[1] {
                        "api" => {
                            let resp: HttpResponse = WebServiceHandler::handle(&req);
                            let _ = resp.send_response(stream);
                        }
                        _ => {
                            let resp: HttpResponse = StaticPageHandler::handle(&req);
                            let _ = resp.send_response(stream);
                        }
                    }
                }
            },
            _ => {
                let resp: HttpResponse = PageNotFoundHandler::handle(&req);
                let _ = resp.send_response(stream);
            }
        }
    }
}

httpserver/data/orders.json

[
    {
        "order_id": 1,
        "order_date": "21 Jan 2023",
        "order_status": "Delivered"
    },
    {
        "order_id": 2,
        "order_date": "2 Feb 2023",
        "order_status": "Pending"
    }
]

httpserver/public/404.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Not Found!</title>
</head>

<body>
    <h1>404 Error</h1>
    <p>Sorry the requested page does not exist</p>
</body>

</html>

httpserver/public/health.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Health!</title>
</head>

<body>
    <h1>Hello welcome to health page!</h1>
    <p>This site is perfectly fine</p>
</body>

</html>

httpserver/public/index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="styles.css">
    <title>Index!</title>
</head>

<body>
    <h1>Hello, welcome to home page</h1>
    <p>This is the index page for the web site</p>
</body>

</html>

httpserver/public/styles.css

h1 {
    color: red;
    margin-left: 25px;
}

測(cè)試該項(xiàng)目

運(yùn)行文章來源地址http://www.zghlxwxcb.cn/news/detail-461963.html

s1 on  master [?] via ?? 1.67.1 via ?? base 
? cargo run -p httpserver
   Compiling ryu v1.0.13
   Compiling itoa v1.0.6
   Compiling serde v1.0.163
   Compiling serde_json v1.0.96
   Compiling httpserver v0.1.0 (/Users/qiaopengjun/rust/s1/httpserver)
    Finished dev [unoptimized + debuginfo] target(s) in 2.48s
     Running `target/debug/httpserver`
Running on localhost:3000
Connection established
Connection established
Connection established
Connection established
Connection established
Connection established
Connection established

到了這里,關(guān)于Rust Web 全棧開發(fā)之自建TCP、HTTP Server的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • Rust Web 全棧開發(fā)之 Actix 嘗鮮并構(gòu)建REST API

    actix-web v4.3.1 actix-rt v2.8.0 目錄 Cargo.toml webservice/Cargo.toml webservice/src/bin/server1.rs 運(yùn)行 客戶端瀏覽器 互聯(lián)網(wǎng) Actix HTTP Server Actix的并發(fā)(concurrency) Actix支持兩類并發(fā): 異步I/O:給定的OS原生線程在等待I/O時(shí)執(zhí)行其他任務(wù)(例如偵聽網(wǎng)絡(luò)連接) 多線程并行:默認(rèn)情況下啟動(dòng)OS原生

    2024年02月06日
    瀏覽(18)
  • 〖Web全棧開發(fā)③〗—HTTP協(xié)議和靜態(tài)web服務(wù)器

    〖Web全棧開發(fā)③〗—HTTP協(xié)議和靜態(tài)web服務(wù)器

    ??????個(gè)人簡(jiǎn)介:以山河作禮。 ??????: Python領(lǐng)域新星創(chuàng)作者,CSDN實(shí)力新星認(rèn)證,阿里云社區(qū)專家博主 ????:Web全棧開發(fā)專欄:《Web全棧開發(fā)》免費(fèi)專欄,歡迎閱讀! TCP (Transmission Control Protocol) 是在互聯(lián)網(wǎng)協(xié)議(IP)上的一種基于連接(面向連接)的傳輸層協(xié)議 。數(shù)據(jù)

    2024年02月05日
    瀏覽(21)
  • 本地開發(fā) npm 好用的http server、好用的web server、靜態(tài)服務(wù)器

    本地開發(fā) npm 好用的http server、好用的web server、靜態(tài)服務(wù)器

    有時(shí)需要快速啟動(dòng)一個(gè)web 服務(wù)器(http服務(wù)器)來伺服靜態(tài)網(wǎng)頁(yè),安裝nginx又太繁瑣,那么可以考慮使用npm serve、http-server、webpack-dev-server。 npm 的serve可以提供給http server功能, 如果你想提供 靜態(tài)站點(diǎn) 、 單頁(yè)面應(yīng)用 或者 靜態(tài)文件 甚至羅列文件夾的內(nèi)容服務(wù),那么npm serve 是

    2024年02月14日
    瀏覽(27)
  • SpringBoot 系列 web 篇之自定義返回 Http Code 的 n 種姿勢(shì)

    雖然 http 的提供了一整套完整、定義明確的狀態(tài)碼,但實(shí)際的業(yè)務(wù)支持中,后端并不總會(huì)遵守這套規(guī)則,更多的是在返回結(jié)果中,加一個(gè) code 字段來自定義業(yè)務(wù)狀態(tài),即便是后端 5xx 了,返回給前端的 http code 依然是 200 那么如果我想遵守 http 的規(guī)范,不同的 case 返回不同的

    2024年04月12日
    瀏覽(17)
  • 30天拿下Rust之實(shí)戰(zhàn)Web Server

    30天拿下Rust之實(shí)戰(zhàn)Web Server

    概述 ????????隨著互聯(lián)網(wǎng)技術(shù)的飛速發(fā)展,Web服務(wù)器作為承載網(wǎng)站與應(yīng)用的核心組件,其性能、穩(wěn)定性和安全性都顯得至關(guān)重要。Rust語(yǔ)言憑借其獨(dú)特的內(nèi)存安全保證、高效的性能以及豐富的生態(tài)系統(tǒng),成為了構(gòu)建現(xiàn)代Web服務(wù)器的理想選擇。 新建項(xiàng)目 ????????首先,使

    2024年04月09日
    瀏覽(33)
  • 〖Web全棧開發(fā)⑤〗— CSS基礎(chǔ)

    〖Web全棧開發(fā)⑤〗— CSS基礎(chǔ)

    ??????個(gè)人簡(jiǎn)介:以山河作禮。 ??????: Python領(lǐng)域新星創(chuàng)作者,CSDN實(shí)力新星認(rèn)證,阿里云社區(qū)專家博主 ????:Web全棧開發(fā)專欄:《Web全棧開發(fā)》免費(fèi)專欄,歡迎閱讀! CSS 的意思為 Cascading Style Sheets,中文名是層疊樣式表。 CSS 是由大名鼎鼎的 W3C 中 CSS 工作組來發(fā)布以

    2024年02月09日
    瀏覽(25)
  • 【 Python 全棧開發(fā) - WEB開發(fā)篇 - 26 】Javascript 基礎(chǔ)

    Javascript 是一種動(dòng)態(tài)的、基于對(duì)象的編程語(yǔ)言,通常用于網(wǎng)頁(yè)的客戶端腳本編程。它可以在網(wǎng)頁(yè)上實(shí)現(xiàn)交互效果、動(dòng)態(tài)效果、表單驗(yàn)證、數(shù)據(jù)處理等功能。 學(xué)習(xí) Javascript 可以通過以下途徑: 在線教程:像 w3schools、MDN 等網(wǎng)站提供了詳細(xì)的 Javascript 教程和示例代碼。 書籍:可以

    2024年02月08日
    瀏覽(16)
  • 【 Python 全棧開發(fā) - WEB開發(fā)篇 - 21 】進(jìn)程與線程

    進(jìn)程和線程都是計(jì)算機(jī)中用來實(shí)現(xiàn)多任務(wù)并發(fā)的機(jī)制,但它們有區(qū)別和聯(lián)系。 區(qū)別: 定義不同:進(jìn)程是操作系統(tǒng)分配資源的基本單位,是程序執(zhí)行時(shí)的一個(gè)實(shí)例,包括代碼、數(shù)據(jù)和資源,可以看成是程序的一次執(zhí)行過程。而線程是進(jìn)程內(nèi)的一個(gè)執(zhí)行單元,是程序執(zhí)行流的最

    2024年02月08日
    瀏覽(21)
  • 〖Web全棧開發(fā)②〗—網(wǎng)絡(luò)編程基礎(chǔ)(下)

    〖Web全棧開發(fā)②〗—網(wǎng)絡(luò)編程基礎(chǔ)(下)

    ??????個(gè)人簡(jiǎn)介:以山河作禮。 ??????: Python領(lǐng)域新星創(chuàng)作者,CSDN實(shí)力新星認(rèn)證,阿里云社區(qū)專家博主 ????:Web全棧開發(fā)專欄:《Web全棧開發(fā)》免費(fèi)專欄,歡迎閱讀! ?? 學(xué)習(xí)目標(biāo) 能夠知道TCP客戶端程序的開發(fā)流程 1. TCP 網(wǎng)絡(luò)應(yīng)用程序開發(fā)流程的介紹 TCP 網(wǎng)絡(luò)應(yīng)用程

    2024年02月04日
    瀏覽(23)
  • 〖Web全棧開發(fā)①〗—網(wǎng)絡(luò)編程基礎(chǔ)(上)

    〖Web全棧開發(fā)①〗—網(wǎng)絡(luò)編程基礎(chǔ)(上)

    ??????個(gè)人簡(jiǎn)介:以山河作禮。 ??????: Python領(lǐng)域新星創(chuàng)作者,CSDN實(shí)力新星認(rèn)證,阿里云社區(qū)專家博主 ?? 計(jì)算機(jī)網(wǎng)絡(luò) : ????計(jì)算機(jī)網(wǎng)絡(luò)是指將地理位置不同的具有獨(dú)立功能的多臺(tái)計(jì)算機(jī)及其外部設(shè)備,通過通信線路連接起來,在網(wǎng)絡(luò)操作系統(tǒng),網(wǎng)絡(luò)管理軟件及網(wǎng)

    2024年02月05日
    瀏覽(24)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包