编程技术分享平台

网站首页 > 技术教程 正文

Rust socket编程之HTTP协议基础实现

xnh888 2025-01-23 22:03:58 技术教程 202 ℃ 0 评论

前言

本章节将带你实现一个简单的HTTP服务器,它能够响应基本的HTTP请求。我们将从零开始构建,并逐步完善功能,最终实现一个能够响应静态文件和处理简单动态路由的HTTP服务器。

项目目标

HTTP服务器功能

  • 监听特定端口,接受HTTP请求。
  • 能够解析请求头并生成响应。
  • 提供静态文件服务,例如HTML文件。
  • 支持简单的动态路由,例如返回JSON响应。

创建项目

首先我们创建一个名为simple_http的项目,项目目录结构如下所示:

?  simple_http git:(master) ? tree .
.
├── Cargo.toml
├── src
│   ├── http
│   │   ├── mod.rs
│   │   ├── request.rs
│   │   └── response.rs
│   ├── main.rs
│   └── server.rs
└── static
    ├── about.html
    └── index.html

4 directories, 8 files

添加所需的项目依赖,具体操作如下所示:

cargo add serde --features derive
cargo add serde_json
cargo add log
cargo add env_logger
cargo add tokio --features full

HTTP模块实现

HTTP请求解析

编辑文件:src/http/request.rs

use std::collections::HashMap;

// 定义http 请求结构
#[derive(Debug)]
pub struct Request {
    pub method: String,
    pub path: String,
    pub headers: HashMap,
    pub body: Option,
}

impl Request {
    pub fn parse(request: &str) -> Option {
        let mut lines = request.lines();

        // 解析请求行
        let request_line = lines.next()?;
        let mut parts = request_line.split_whitespace();
        let method = parts.next()?.to_string();
        let path = parts.next()?.to_string();

        // 解析http header
        let mut headers = HashMap::new();
        for line in lines {
            if line.is_empty() {
                break;
            }
            if let Some((name, value)) = line.split_once(':') {
                headers.insert(name.to_string(), value.to_string());
            }
        }
        // 解析body
        let body = parts.next().map(|s| s.to_string());

        Some(Request{
            method,
            path,
            headers,
            body,
        })
    }
}

HTTP响应构建

编辑文件:src/http/response.rs

use std::collections::HashMap;

#[derive(Debug)] 
pub struct Response {
    pub status_code: u16,
    pub headers: HashMap,
    pub body: Option,
}

impl Response {
    pub fn new(status_code: u16, body: Option) -> Response {
        let mut headers = HashMap::new();
        if let Some(ref body) = body {
            headers.insert("Content-Length".to_string(), body.len().to_string());
            headers.insert("Content-Type".to_string(), "text/html".to_string());
        }
        Response{
            status_code,
            headers,
            body,
        }
    }

    pub fn to_string(&self) -> String {
        let mut response = format!("HTTP/1.1 {} OK\r\n\r\n", self.status_code);
        for (key, value) in &self.headers {
            response.push_str(&format!("{}: {}\r\n", key, value));
        }
        response.push_str("\r\n");
        if let Some(body) = &self.body {
            response.push_str(body);
        }
        response
    }
}

HTTP模块入口

编辑文件:src/http/mod.rs

pub mod request;
pub mod response;

HTTP服务器逻辑

编辑文件:src/server.rs

use std::fs;
use log::info;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;
use crate::http::request::Request;
use crate::http::response::Response;
use std::sync::Arc;

pub struct Server {
    pub address: String,
}

impl Server {
    pub fn new(address: &str) -> Server {
        Server{address: address.to_string()}
    }

    pub async fn run(self) {
        let listener = TcpListener::bind(&self.address).await.unwrap();
        info!("Server listening on {}", &self.address);

        let value = Arc::new(self);

        loop {
            let (mut socket, _) = listener.accept().await.unwrap();
            let handler = value.clone();
            tokio::spawn(async move {
                let mut buf = vec![0; 1024];
                let n = socket.read(&mut buf).await.unwrap();
                let request = String::from_utf8_lossy(&buf[..n]);

                if let Some(http_request) = Request::parse(&request) {
                    let response = handler.handle_request(http_request).await;
                    socket.write_all(response.to_string().as_bytes()).await.unwrap();
                }
            });
        }
    }

    async fn handle_request(&self, request: Request) -> Response {
        match request.path.as_str() {
            "/" => {
                let content = fs::read_to_string("static/index.html").unwrap_or_else(|_| {
                    "

welcome to rust http server

".to_string() }); Response::new(200, Some(content)) } "/about" => { let content = fs::read_to_string("static/about.html").unwrap_or_else(|_| { "

about page

".to_string() }); Response::new(200, Some(content)) } "/api" => { let content = serde_json::to_string(&serde_json::json!({ "msg": "success", "status": "ok" })).unwrap(); Response::new(200, Some(content)) } _ => { Response::new(404, Some(format!("404 not found {}", request.path))) } } } }

主程序入口

编辑文件:src/main.rs

mod http;
mod server;

#[tokio::main]
async fn main() {
    env_logger::init_from_env(env_logger::Env::new().filter_or("RUST_LOG", "INFO"));
    let server = server::Server::new("0.0.0.0:9527");
    server.run().await;
}

运行项目

确保static/目录下包含index.html和about.html。

运行服务器:

cargo run

在浏览器访问:

  • http://127.0.0.1:9527/ 查看主页。
  • http://127.0.0.1:9527/about 查看关于页面。
  • http://127.0.0.1:9527/api 查看JSON API。


总结

  • 本章节实现了一个简单的HTTP服务器,包含静态文件服务和动态路由支持。
  • 学习了Rust中使用tokio实现异步网络操作。
  • 掌握了基本的HTTP请求解析和响应构建技术,为构建更复杂的Web服务器打下基础。


如果你觉得本文对你有所帮助,欢迎点赞,收藏,关注,或者分享给更多的 Rust 开发者!感谢你的收看,我们明年见!!!

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表