sa_token_plugin_actix_web/
adapter.rs

1// Author: 金书记
2//
3//! Actix-web请求/响应适配器
4
5use actix_web::{HttpRequest, HttpResponse};
6use sa_token_adapter::context::{SaRequest, SaResponse, CookieOptions};
7use serde::Serialize;
8use std::collections::HashMap;
9
10/// Actix-web请求适配器
11pub struct ActixRequestAdapter<'a> {
12    request: &'a HttpRequest,
13}
14
15impl<'a> ActixRequestAdapter<'a> {
16    pub fn new(request: &'a HttpRequest) -> Self {
17        Self { request }
18    }
19}
20
21impl<'a> SaRequest for ActixRequestAdapter<'a> {
22    fn get_header(&self, name: &str) -> Option<String> {
23        self.request.headers().get(name)
24            .and_then(|v| v.to_str().ok())
25            .map(|s| s.to_string())
26    }
27    
28    fn get_cookie(&self, name: &str) -> Option<String> {
29        self.request.cookie(name)
30            .map(|c| c.value().to_string())
31    }
32    
33    fn get_param(&self, name: &str) -> Option<String> {
34        // Actix-web 中 match_info 用于路径参数
35        // 对于查询参数,需要手动解析
36        self.request.match_info().get(name)
37            .map(|s| s.to_string())
38            .or_else(|| {
39                // 解析查询字符串
40                parse_query_string(self.request.query_string())
41                    .get(name)
42                    .cloned()
43            })
44    }
45    
46    fn get_path(&self) -> String {
47        self.request.path().to_string()
48    }
49    
50    fn get_method(&self) -> String {
51        self.request.method().to_string()
52    }
53    
54    fn get_client_ip(&self) -> Option<String> {
55        self.request.peer_addr()
56            .map(|addr| addr.ip().to_string())
57    }
58}
59
60/// Actix-web响应适配器
61pub struct ActixResponseAdapter {
62    status: actix_web::http::StatusCode,
63    headers: Vec<(String, String)>,
64    cookies: Vec<actix_web::cookie::Cookie<'static>>,
65    body: Option<String>,
66}
67
68impl ActixResponseAdapter {
69    pub fn new() -> Self {
70        Self {
71            status: actix_web::http::StatusCode::OK,
72            headers: Vec::new(),
73            cookies: Vec::new(),
74            body: None,
75        }
76    }
77    
78    /// 构建 HttpResponse
79    pub fn build(self) -> HttpResponse {
80        let mut builder = HttpResponse::build(self.status);
81        
82        for (name, value) in self.headers {
83            builder.insert_header((name, value));
84        }
85        
86        for cookie in self.cookies {
87            builder.cookie(cookie);
88        }
89        
90        if let Some(body) = self.body {
91            builder.body(body)
92        } else {
93            builder.finish()
94        }
95    }
96}
97
98impl Default for ActixResponseAdapter {
99    fn default() -> Self {
100        Self::new()
101    }
102}
103
104impl SaResponse for ActixResponseAdapter {
105    fn set_header(&mut self, name: &str, value: &str) {
106        self.headers.push((name.to_string(), value.to_string()));
107    }
108    
109    fn set_cookie(&mut self, name: &str, value: &str, options: CookieOptions) {
110        use actix_web::cookie::{Cookie, SameSite};
111        
112        let mut cookie = Cookie::new(name.to_string(), value.to_string());
113        
114        if let Some(domain) = options.domain {
115            cookie.set_domain(domain);
116        }
117        if let Some(path) = options.path {
118            cookie.set_path(path);
119        }
120        if let Some(max_age) = options.max_age {
121            cookie.set_max_age(actix_web::cookie::time::Duration::seconds(max_age));
122        }
123        cookie.set_http_only(options.http_only);
124        cookie.set_secure(options.secure);
125        
126        if let Some(same_site) = options.same_site {
127            use sa_token_adapter::context::SameSite as SaSameSite;
128            let ss = match same_site {
129                SaSameSite::Strict => SameSite::Strict,
130                SaSameSite::Lax => SameSite::Lax,
131                SaSameSite::None => SameSite::None,
132            };
133            cookie.set_same_site(ss);
134        }
135        
136        self.cookies.push(cookie);
137    }
138    
139    fn set_status(&mut self, status: u16) {
140        if let Ok(status_code) = actix_web::http::StatusCode::from_u16(status) {
141            self.status = status_code;
142        }
143    }
144    
145    fn set_json_body<T: Serialize>(&mut self, body: T) -> Result<(), serde_json::Error> {
146        let json = serde_json::to_string(&body)?;
147        self.body = Some(json);
148        self.headers.push(("Content-Type".to_string(), "application/json".to_string()));
149        Ok(())
150    }
151}
152
153/// 解析查询字符串
154fn parse_query_string(query: &str) -> HashMap<String, String> {
155    let mut params = HashMap::new();
156    for pair in query.split('&') {
157        if let Some((key, value)) = pair.split_once('=') {
158            if let Ok(decoded_value) = urlencoding::decode(value) {
159                params.insert(key.to_string(), decoded_value.to_string());
160            }
161        }
162    }
163    params
164}