1use crate::config::{Config};
2use crate::encoding::Encoding;
3use chrono::{DateTime, Local};
4use json::{array, object, JsonValue};
5use log::{error, info, warn};
6use std::io::{Error, Write};
7use std::path::{Path, PathBuf};
8use std::{env, fs, io, thread};
9use std::fs::OpenOptions;
10use std::sync::{Arc, Mutex};
11use std::time::Instant;
12use crate::stream::{Protocol, Scheme};
13
14#[derive(Clone, Debug)]
16pub struct Request {
17 pub config: Config,
18 pub protocol: Protocol,
20 pub method: Method,
22 pub uri: Uri,
24 pub header: JsonValue,
26 pub cookie: JsonValue,
28 pub body: Body,
30 pub authorization: Authorization,
32 pub handle_time: String,
34 pub datetime: String,
36 pub timestamp: i64,
38 pub client_ip: String,
40 pub proxy_ip: String,
42 pub server_ip: String,
44 pub upgrade: Upgrade,
46 pub connection: Connection,
48 pub accept_encoding: Encoding,
50 start_time: Instant,
52 pub raw_data: Vec<u8>,
54 pub header_data: Vec<u8>,
56 pub body_data: Vec<u8>,
58 pub scheme: Arc<Mutex<Scheme>>,
59}
60impl Request {
61 #[must_use]
62 pub fn default(config: Config, scheme: Arc<Mutex<Scheme>>) -> Self {
63
64 let client_ip = scheme.lock().unwrap().client_ip();
66 let server_ip = scheme.lock().unwrap().server_ip();
68 let local: DateTime<Local> = Local::now();
69 Self {
70 config,
71 protocol: Protocol::None,
72 method: Method::None,
73 uri: Uri::default(),
74 header: object! {},
75 cookie: object! {},
76 body: Body::default(),
77 authorization: Authorization::None,
78 handle_time: String::new(),
79 scheme,
80 start_time: Instant::now(),
81 datetime: local.format("%Y-%m-%d %H:%M:%S").to_string(),
82 timestamp: local.timestamp(),
83 client_ip,
84 server_ip,
85 proxy_ip: String::new(),
86 upgrade: Upgrade::Http,
87 connection: Connection::None,
88 accept_encoding: Encoding::None,
89 raw_data: vec![],
90 header_data: vec![],
91 body_data: vec![],
92 }
93 }
94
95 pub fn handle(&mut self) -> io::Result<()> {
96 loop {
98 let data = self.scheme.lock().unwrap().read()?;
99 self.raw_data.extend(data);
100 if let Some(pos) = self.raw_data.windows(4).position(|window| window == [13, 10, 13, 10]) {
101 self.header_data = self.raw_data[..pos].to_vec();
102 self.body_data.extend(self.raw_data[pos + 4..].to_vec());
103 break;
104 }
105 }
106 self.handle_header()?;
107 if self.body.content_length > 0 {
108 loop {
109 if self.body_data.len() == self.body.content_length {
110 break;
111 }
112 let data = self.scheme.lock().unwrap().read()?;
113 self.raw_data.extend(data.clone());
114 self.body_data.extend(data);
115 }
116 self.body.set_content(self.body_data.clone());
117 }
118 self.handle_time = format!("{:?}", self.start_time.elapsed());
119 Ok(())
120 }
121 pub fn handle_header(&mut self) -> io::Result<()> {
122 if self.config.debug {
123 let text = unsafe { String::from_utf8_unchecked(self.header_data.clone()) };
124 info!("\r\n=================请求信息 {:?}=================\r\n{text}\r\n========================================",thread::current().id());
125 }
126 let headers = String::from_utf8_lossy(self.header_data.as_slice());
127 let line = headers.lines().next().unwrap_or("");
128 let lines = line.split_whitespace().collect::<Vec<&str>>();
129 if lines.len() != 3 {
130 return Err(Error::other("请求行错误"));
131 }
132 self.protocol = Protocol::from(lines[2]);
133 match self.protocol {
134 Protocol::HTTP1_0 | Protocol::HTTP1_1 => {
135 self.method = Method::from(lines[0]);
136 self.uri = Uri::from(lines[1]);
137 for item in headers.lines().skip(1) {
138 match item.trim().find(':') {
139 None => continue,
140 Some(e) => {
141 let key = item[..e].trim().to_lowercase().clone();
142 let value = item[e + 1..].trim().to_string();
143 self.header[key] = value.into();
144 }
145 };
146 }
147 }
148 Protocol::HTTP2 => {
149 let res = self.header_data[..8].to_vec();
150 if !res.eq(b"\r\nSM\r\n\r\n") {
151 return Err(Error::other("HTTP2格式错误"));
152 }
153 }
154 Protocol::None => {
155 return Err(Error::other("格式错误"));
156 }
157 }
158 if self.header.has_key("content-length") {
159 self.body.content_length = self.header["content-length"].to_string().parse::<usize>().unwrap_or(0);
160 }
161 if self.header.has_key("content-type") {
162 let t = self.header["content-type"].as_str().unwrap().split_whitespace().collect::<Vec<&str>>();
163 match t[0] {
164 _ if t[0].contains("multipart/form-data") => {
165 self.body.boundary = t[1].trim().trim_start_matches("boundary=").to_string();
166 self.body.content_type = ContentType::from("multipart/form-data");
167 }
168 _ => {
169 self.body.content_type = ContentType::from(t[0]);
170 }
171 }
172 let _ = self.header.insert("content-type", self.body.content_type.str());
173 }
174 if self.header.has_key("authorization") {
175 self.authorization = Authorization::from(self.header["authorization"].as_str().unwrap_or(""));
176 }
177
178 if self.header.has_key("upgrade") {
179 self.upgrade = Upgrade::from(self.header["upgrade"].as_str().unwrap_or(""));
180 }
181 if self.header.has_key("connection") {
182 self.connection = Connection::from(self.header["connection"].as_str().unwrap_or(""));
183 }
184 if self.header.has_key("accept-encoding") {
185 self.accept_encoding = Encoding::from(self.header["accept-encoding"].as_str().unwrap_or(""));
186 }
187 if self.header.has_key("cookie") {
188 let _ = self.header["cookie"].to_string().split(';').collect::<Vec<&str>>().iter().map(|&x| {
189 match x.find('=') {
190 None => {}
191 Some(index) => {
192 let key = x[..index].trim().to_string();
193 let val = x[index + 1..].trim().to_string();
194 let _ = self.cookie.insert(key.as_str(), val);
195 }
196 }
197 ""
198 }).collect::<Vec<&str>>();
199 }
200 if self.header.has_key("x-forwarded-for") {
201 self.proxy_ip = self.header["x-forwarded-for"].as_str().unwrap_or("").to_string();
202 }
203 if self.header.has_key("x-real-ip") {
204 self.client_ip = self.header["x-real-ip"].as_str().unwrap_or("").to_string();
205 }
206 self.uri.handle(self.header["host"].as_str().unwrap_or(""));
207 self.uri.scheme = if self.config.https { "https" } else { "http" }.to_string();
208 Ok(())
209 }
210
211 pub fn set_http2_headers(&mut self, key: &str, value: &str) {
212 match key {
213 "content-type" => match value {
214 _ if value.contains("multipart/form-data") => {
215 let boundarys = value.split("boundary=").collect::<Vec<&str>>();
216 self.body.boundary = boundarys[1..].join("");
217 self.body.content_type = ContentType::from("multipart/form-data");
218 self.header.insert(key, "multipart/form-data").unwrap();
219 }
220 _ => {
221 let value = match value.find(';') {
222 None => value,
223 Some(e) => &*value[..e].trim().to_string(),
224 };
225 self.body.content_type = ContentType::from(value);
226 let _ = self.header.insert(key, self.body.content_type.str());
227 }
228 },
229 "content-length" => {
230 self.body.content_length = value.to_string().parse::<usize>().unwrap_or(0);
231 }
232 "authorization" => {
233 self.authorization = Authorization::from(value);
234 }
235 "cookie" => {
236 let _ = value.split(';').collect::<Vec<&str>>().iter().map(|&x| {
237 match x.find('=') {
238 None => {}
239 Some(index) => {
240 let key = x[..index].trim().to_string();
241 let val = x[index + 1..].trim().to_string();
242 let _ = self.cookie.insert(key.as_str(), val);
243 }
244 }
245 ""
246 }).collect::<Vec<&str>>();
247 }
248 "upgrade" => {
249 self.upgrade = Upgrade::from(value);
250 }
251 "connection" => {
252 self.connection = Connection::from(value);
253 }
254 "accept-encoding" => {
255 self.accept_encoding = Encoding::from(value);
256 }
257 _ => {
258 self.header.insert(key, value).unwrap();
259 }
260 }
261 }
262
263 pub fn save_log(&mut self) -> io::Result<()> {
265 if !self.config.log {
266 return Ok(());
267 }
268 let local: DateTime<Local> = Local::now();
269 let time_dir = local.format("%Y-%m-%d-%H").to_string();
270 let time_dir = time_dir.split('-').collect::<Vec<&str>>();
271
272 let mut res = self.config.root_path.join(self.config.runtime.clone()).join("log");
273 for item in &time_dir {
274 res.push(item);
275 }
276 fs::create_dir_all(res.parent().unwrap())?;
277 let log_file = format!("{}.log", res.to_str().unwrap());
278 let mut file = OpenOptions::new()
279 .append(true) .create(true) .open(log_file)?;
283 let data = format!(
284 "[{}] {} ClientIP: {} {} {} ContentLength: {} ContentType: {} Time: {:?}\r\n",
285 self.datetime,
286 self.protocol.str(),
287 self.client_ip,
288 self.method.str(),
289 self.uri.url,
290 self.body.content_length,
291 self.body.content_type.str(),
292 self.handle_time
293 );
294 file.write_all(data.as_bytes())?;
295 Ok(())
296 }
297 pub fn read_resource(&mut self) -> io::Result<PathBuf> {
299 if self.uri.path != "/" {
300 let file = self.config.root_path.join(self.config.public.clone()).join(self.uri.path.trim_start_matches('/'));
301 if file.is_file() {
302 return Ok(file);
303 }
304 }
305 if let Method::GET = self.method {
306 if self.uri.path == "/" {
307 let file = self.config.root_path.join("webpage").join(self.config.webpage.clone()).join("index.html");
308 if file.is_file() {
309 return Ok(file);
310 }
311 } else {
312 let file = self.config.root_path.join("webpage").join(self.config.webpage.clone()).join(self.uri.path.trim_start_matches('/'));
313 if file.is_file() {
314 return Ok(file);
315 }
316 }
317 }
318 Err(Error::other("Not a file"))
319 }
320}
321
322
323#[derive(Clone, Debug)]
325pub enum Method {
326 POST,
327 GET,
328 HEAD,
329 PUT,
330 DELETE,
331 OPTIONS,
332 PATCH,
333 TRACE,
334 VIEW,
335 PROPFIND,
336 PRI,
338 None,
339}
340
341impl Method {
342 #[must_use]
343 pub fn from(name: &str) -> Self {
344 match name.to_lowercase().as_str() {
345 "post" => Self::POST,
346 "get" => Self::GET,
347 "head" => Self::HEAD,
348 "put" => Self::PUT,
349 "delete" => Self::DELETE,
350 "options" => Self::OPTIONS,
351 "patch" => Self::PATCH,
352 "trace" => Self::TRACE,
353 "view" => Self::VIEW,
354 "propfind" => Self::PROPFIND,
355 "pri" => Self::PRI,
356 _ => Self::None,
357 }
358 }
359 pub fn str(&mut self) -> &str {
360 match self {
361 Method::POST => "POST",
362 Method::GET => "GET",
363 Method::HEAD => "HEAD",
364 Method::PUT => "PUT",
365 Method::DELETE => "DELETE",
366 Method::OPTIONS => "OPTIONS",
367 Method::PATCH => "PATCH",
368 Method::TRACE => "TRACE",
369 Method::VIEW => "VIEW",
370 Method::PROPFIND => "PROPFIND",
371 Method::PRI => "PRI",
372 Method::None => "",
373 }
374 }
375}
376#[derive(Clone, Debug)]
378pub struct Uri {
379 pub url: String,
381 pub query: String,
383 pub query_params: JsonValue,
385 pub fragment: String,
387 pub path: String,
389 pub path_segments: Vec<String>,
391 pub scheme: String,
393 pub host: String,
395 pub port: String,
397}
398impl Uri {
399 #[must_use]
400 pub fn from(url: &str) -> Self {
401 let mut decoded_url = Uri::decode(url).unwrap_or(url.to_string());
402 let fragment = match decoded_url.rfind('#') {
403 None => String::new(),
404 Some(index) => decoded_url.drain(index..).collect::<String>(),
405 };
406 let mut query = String::new();
407 let query_params = match decoded_url.rfind('?') {
408 None => object! {},
409 Some(index) => {
410 let text = decoded_url.drain(index..).collect::<String>();
411 query = text.trim_start_matches('?').parse().unwrap();
412 let text = query.split('&').collect::<Vec<&str>>();
413 let mut params = object! {};
414 for &item in &text {
415 if let Some(index) = item.find('=') {
416 let key = item[..index].to_string();
417 let value = item[index + 1..].to_string();
418 let _ = params.insert(
419 Uri::decode(&key).unwrap_or(key.to_string()).as_str(),
420 Uri::decode(&value).unwrap_or(key.to_string()),
421 );
422 }
423 }
424 params
425 }
426 };
427 let path_segments = decoded_url.split('/').collect::<Vec<&str>>();
428 let path_segments = path_segments.into_iter().filter(|&x| !x.is_empty()).collect::<Vec<&str>>().iter().map(|&x| x.to_string()).collect::<Vec<String>>();
429 Self {
430 url: url.to_string(),
431 query,
432 query_params,
433 fragment,
434 path: decoded_url.clone(),
435 path_segments,
436 scheme: String::new(),
437 host: String::new(),
438 port: String::new(),
439 }
440 }
441 pub fn decode(input: &str) -> Result<String, String> {
443 let mut decoded = String::new();
444 let bytes = input.as_bytes();
445 let mut i = 0;
446
447 while i < bytes.len() {
448 if bytes[i] == b'%' {
449 if i + 2 >= bytes.len() {
450 return Err("Incomplete percent-encoding".into());
451 }
452 let hex = &input[i + 1..i + 3];
453 match u8::from_str_radix(hex, 16) {
454 Ok(byte) => decoded.push(byte as char),
455 Err(_) => return Err(format!("Invalid percent-encoding: %{hex}")),
456 }
457 i += 3;
458 } else if bytes[i] == b'+' {
459 decoded.push(' ');
460 i += 1;
461 } else {
462 decoded.push(bytes[i] as char);
463 i += 1;
464 }
465 }
466
467 Ok(decoded)
468 }
469 pub fn handle(&mut self, host: &str) {
470 if !host.is_empty() {
471 let hostport = host.split(':').collect::<Vec<&str>>();
472 if hostport.len() > 1 {
473 self.host = hostport[0].to_string();
474 self.port = hostport[1].to_string();
475 } else {
476 self.host = hostport[0].to_string();
477 }
478 }
479 }
480 #[must_use]
481 pub fn to_json(&self) -> JsonValue {
482 object! {
483 url: self.url.clone(),
484 query: self.query.clone(),
485 query_params: self.query_params.clone(),
486 fragment: self.fragment.clone(),
487 path: self.path.clone(),
488 path_segments: self.path_segments.clone(),
489 scheme: self.scheme.clone(),
490 host: self.host.clone(),
491 port: self.port.clone(),
492 }
493 }
494}
495
496impl Default for Uri {
497 fn default() -> Self {
498 Self {
499 url: String::new(),
500 query: String::new(),
501 query_params: object! {},
502 fragment: String::new(),
503 path: String::new(),
504 scheme: String::new(),
505 path_segments: Vec::new(),
506 host: String::new(),
507 port: String::new(),
508 }
509 }
510}
511
512#[derive(Debug, Clone)]
514pub enum ContentType {
515 FormData,
516 FormUrlencoded,
517 Json,
518 Xml,
519 Javascript,
520 Text,
521 Html,
522 Other(String),
523}
524impl ContentType {
525 #[must_use]
526 pub fn from(name: &str) -> Self {
527 match name {
528 "multipart/form-data" => Self::FormData,
529 "application/x-www-form-urlencoded" => Self::FormUrlencoded,
530 "application/json" => Self::Json,
531 "application/xml" | "text/xml" => Self::Xml,
532 "application/javascript" => Self::Javascript,
533 "text/html" => Self::Html,
534 "text/plain" => Self::Text,
535 _ => Self::Other(name.to_string()),
536 }
537 }
538 pub fn str(&mut self) -> String {
539 match self {
540 Self::FormData => "multipart/form-data",
541 Self::FormUrlencoded => "application/x-www-form-urlencoded",
542 Self::Json => "application/json",
543 Self::Xml => "application/xml",
544 Self::Javascript => "application/javascript",
545 Self::Text => "text/plain",
546 Self::Html => "text/html",
547 Self::Other(name) => name,
548 }.to_string()
549 }
550}
551#[derive(Clone, Debug)]
553pub enum Authorization {
554 Basic(String, String),
555 Bearer(String),
556 Digest(JsonValue),
557 None,
558}
559impl Authorization {
560 #[must_use]
561 pub fn from(data: &str) -> Self {
562 let authorization = data.split_whitespace().collect::<Vec<&str>>();
563 let mode = authorization[0].to_lowercase();
564 match mode.as_str() {
565 "basic" => {
566 let text = br_crypto::base64::decode(&authorization[1].to_string().clone());
567 let text: Vec<&str> = text.split(':').collect();
568 Self::Basic(text[0].to_string(), text[1].to_string())
569 }
570 "bearer" => Self::Bearer(authorization[1].to_string()),
571 "digest" => {
572 let text = authorization[1..].concat().clone();
573 let text = text.split(',').collect::<Vec<&str>>();
574 let mut params = object! {};
575 for item in &text {
576 let index = match item.find('=') {
577 None => continue,
578 Some(e) => e,
579 };
580 let key = item[..index].to_string();
581 let value = item[index + 2..item.len() - 1].to_string();
582 let _ = params.insert(key.as_str(), value);
583 }
584
585 Self::Digest(params)
586 }
587 _ => {
588 warn!("未知认证模式: {mode}");
589 Self::None
590 }
591 }
592 }
593 pub fn str(&mut self) -> JsonValue {
594 match self {
595 Authorization::Basic(key, value) => {
596 let mut data = object! {};
597 data[key.as_str()] = value.clone().into();
598 data
599 }
600 Authorization::Bearer(e) => e.clone().into(),
601 Authorization::Digest(e) => e.clone(),
602 Authorization::None => "".into(),
603 }
604 }
605}
606
607#[derive(Debug, Clone)]
608pub struct Body {
609 pub content_type: ContentType,
610 pub boundary: String,
611 pub content_length: usize,
612 pub content: JsonValue,
613}
614impl Body {
615 pub fn set_content(&mut self, data: Vec<u8>) {
616 if self.content_length == 0 {
617 return;
618 }
619 match self.content_type {
620 ContentType::FormData => {
621 let mut fields = object! {};
622 let boundary_marker = format!("--{}", self.boundary);
623
624
625 let parts = split_vec_u8(&data, boundary_marker.as_bytes());
626
627 for part in parts {
628 if part.eq(b"\r\n") || part.is_empty() || part.eq(b"--") || part.eq(b"--\r\n") || part.eq(format!("--{}", self.boundary).as_bytes()) {
629 continue; }
631
632 let headers_and_body = split_vec_u8(&part, b"\r\n\r\n");
633 if headers_and_body.len() == 1 {
634 let headers = unsafe { String::from_utf8_unchecked(headers_and_body[0].clone()) };
635 error!(">>>>>>>>>>>>{headers:?}");
636 continue;
637 }
638
639 let headers = unsafe { String::from_utf8_unchecked(headers_and_body[0].clone()) };
640 let body = headers_and_body[1].clone();
641 {
642 let headers = headers.split("\r\n");
644
645 let mut field_name = "";
646 let mut filename = "";
647 let mut content_type = ContentType::Text;
648
649 for header in headers {
650 if header.to_lowercase().starts_with("content-disposition:") {
651 match header.find("filename=\"") {
652 None => {}
653 Some(filename_start) => {
654 let filename_len = filename_start + 10;
655 let filename_end = header[filename_len..].find('"').unwrap() + filename_len;
656 filename = &header[filename_len..filename_end];
657 }
658 }
659 match header.find("name=\"") {
660 None => {}
661 Some(name_start) => {
662 let name_start = name_start + 6;
663 let name_end = header[name_start..].find('"').unwrap() + name_start;
664 field_name = &header[name_start..name_end];
665 }
666 }
667 }
668 if header.to_lowercase().starts_with("content-type:") {
669 content_type = ContentType::from(
670 header.to_lowercase().trim_start_matches("content-type:").trim(),
671 );
672 }
673 }
674 if filename.is_empty() {
675 let text = String::from_utf8_lossy(&body);
676 fields[field_name.to_string()] = JsonValue::from(text.lines().next().unwrap());
677 } else {
678 let extension = Path::new(filename).extension() .and_then(|ext| ext.to_str()); let suffix = extension.unwrap_or("txt");
681 let filename = if extension.is_none() {
682 format!("{filename}.txt")
683 } else {
684 filename.to_string()
685 };
686 let mut temp_dir = env::temp_dir();
688 temp_dir.push(filename.clone());
690 let Ok(mut temp_file) = fs::File::create(&temp_dir) else { continue };
692
693 if temp_file.write(body.as_slice()).is_ok() {
694 if fields[field_name.to_string()].is_empty() {
695 fields[field_name.to_string()] = array![];
696 }
697
698 fields[field_name.to_string()].push(object! {
699 id:br_crypto::sha256::encrypt_hex(&body.clone()),
700 name:filename,
701 suffix:suffix,
702 size:body.len(),
703 type:content_type.str(),
704 file:temp_dir.to_str()
705 }).unwrap();
706 }
707 }
708 }
709 }
710 self.content = fields;
711 }
712 ContentType::FormUrlencoded => {
713 let text = unsafe { String::from_utf8_unchecked(data) };
714 let params = text.split('&').collect::<Vec<&str>>();
715 let mut list = object! {};
716 for param in ¶ms {
717 let t = param.split('=').collect::<Vec<&str>>().iter().map(|&x| Uri::decode(x).unwrap_or(x.to_string())).collect::<Vec<String>>();
718 list[t[0].to_string()] = t[1].clone().into();
719 }
720 self.content = list;
721 }
722 ContentType::Json => {
723 let text = unsafe { String::from_utf8_unchecked(data) };
724 self.content = json::parse(text.as_str()).unwrap_or(object! {});
725 }
726 ContentType::Xml | ContentType::Html | ContentType::Text | ContentType::Javascript | ContentType::Other(_) => {
727 let text = unsafe { String::from_utf8_unchecked(data) };
728 self.content = text.into();
729 }
730 }
731 }
732}
733fn split_vec_u8(data: &[u8], delimiter: &[u8]) -> Vec<Vec<u8>> {
734 let mut parts = Vec::new();
735 let mut start = 0;
736 let mut i = 0;
737
738 while i <= data.len().saturating_sub(delimiter.len()) {
739 if &data[i..i + delimiter.len()] == delimiter {
740 parts.push(data[start..i].to_vec());
741 i += delimiter.len();
742 start = i;
743 } else {
744 i += 1;
745 }
746 }
747
748 if start <= data.len() {
750 parts.push(data[start..].to_vec());
751 }
752
753 parts
754}
755impl Default for Body {
756 fn default() -> Self {
757 Self {
758 content_type: ContentType::Other("text/plain".to_string()),
759 boundary: String::new(),
760 content_length: 0,
761 content: object! {},
762 }
763 }
764}
765#[derive(Clone, Debug)]
767pub enum Content {
768 FormUrlencoded(JsonValue),
769 FormData(JsonValue),
770 Json(JsonValue),
771 Text(JsonValue),
772 Xml(JsonValue),
773 None,
774}
775impl Content {}
776#[derive(Clone, Debug)]
777pub enum FormData {
778 File(String, PathBuf),
779 Field(JsonValue),
780}
781
782#[derive(Clone, Debug)]
783pub enum Upgrade {
784 Websocket,
785 Http,
786 None,
787}
788impl Upgrade {
789 #[must_use]
790 pub fn from(name: &str) -> Self {
791 match name.to_lowercase().as_str() {
792 "websocket" => Self::Websocket,
793 "http" => Self::Http,
794 _ => Self::None,
795 }
796 }
797 pub fn str(&mut self) -> String {
798 match self {
799 Self::Websocket => "websocket",
800 Self::Http => "http",
801 Self::None => "",
802 }.to_string()
803 }
804}
805
806#[derive(Clone, Debug)]
807pub enum Connection {
808 KeepAlive,
809 Close,
810 Upgrade,
811 None,
812}
813impl Connection {
814 #[must_use]
815 pub fn from(value: &str) -> Self {
816 match value.to_lowercase().as_str() {
817 "upgrade" => Self::Upgrade,
818 "keep-alive" => Self::KeepAlive,
819 "close" => Self::Close,
820 _ => Self::None,
821 }
822 }
823}