Web第一步用戶先登錄

開(kāi)始試用Rust的Web開(kāi)發(fā)組件actix-web

1. 使用cargo new新建一個(gè)項(xiàng)目rust_login用于實(shí)現(xiàn)用戶登錄功能肌括。

2. 在Cargo.toml文件中配置需要的依賴

```

[package]

name = "rust_login"

version = "0.1.0"

authors = ["Tianlang <tianlangstuido@aliyun.com>"]

edition = "2018"

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

[dependencies]

actix-web="2" #使用的actix-web 提供web服務(wù)器溃睹、request解析筝蚕、response生成等功能

actix-rt="1" #actix-rt actix的運(yùn)行時(shí)运悲,用于運(yùn)行異步函數(shù)等它呀,可以理解為Java concurrent下的Executor

#serde用于序列化和反序列化對(duì)象的海诲,比如把對(duì)象轉(zhuǎn)換成一個(gè)Json字符串,就是序列化页屠;

#把Json字符串轉(zhuǎn)換為一個(gè)對(duì)象阴幌,就是反序列化

serde="1"

```

3.? 在src/main.rs文件中敲入以下代碼

```rust

use actix_web::{post, web, App, HttpServer, Responder};

use serde::Deserialize;

//用于表示請(qǐng)求傳來(lái)的Json對(duì)象

#[derive(Deserialize)]

struct LoginInfo {

? username: String,

? password: String,

}

#[post("/login")] //聲明請(qǐng)求方式和請(qǐng)求路徑,接受post方式請(qǐng)求/login路徑

async fn index(login_info: web::Json<LoginInfo>) -> impl Responder {

? ? format!("Hello {}! password:{}",login_info.username , login_info.password)

}

#[actix_rt::main]

async fn main() -> std::io::Result<()> {

 //啟動(dòng)http服務(wù)器

? ? HttpServer::new(|| App::new().service(index))

? ? ? ? .bind("127.0.0.1:8088")?

? ? ? ? .run()

? ? ? ? .await

}

```

4. 使用cargo run 運(yùn)行程序

5. 執(zhí)行curl請(qǐng)求我們編寫的login路徑

```bash

curl -v -H "Content-Type:application/json" -X POST --data '{"username":"tianalng", password:"tianlang"}' http://127.0.0.1:8088/login

```

沒(méi)有訪問(wèn)成功:

```

* TCP_NODELAY set

* Connected to 127.0.0.1 (127.0.0.1) port 8088 (#0)

> POST /login HTTP/1.1

> Host: 127.0.0.1:8088

> User-Agent: curl/7.58.0

> Accept: */*

> Content-Type:application/json

> Content-Length: 44

>

* upload completely sent off: 44 out of 44 bytes

< HTTP/1.1 400 Bad Request

< content-length: 0

< date: Sat, 16 May 2020 23:20:07 GMT

<

* Connection #0 to host 127.0.0.1 left intact

```

從返回的錯(cuò)誤信息

> 400 Bad Request

可以看出這是因?yàn)榭蛻舳苏?qǐng)求不滿足服務(wù)端也就是我們寫的login服務(wù)要求造成的

一般看到4開(kāi)始的http錯(cuò)誤碼卷中,我們可以認(rèn)為是客戶端沒(méi)寫好。如果是5開(kāi)頭的可以認(rèn)為是服務(wù)端沒(méi)寫好渊抽。

也可以搜索下:

```

在 ajax 請(qǐng)求后臺(tái)數(shù)據(jù)時(shí)比較常見(jiàn)蟆豫。產(chǎn)生 HTTP 400 錯(cuò)誤的原因有:

1、前端提交數(shù)據(jù)的字段名稱或者是字段類型和后臺(tái)的實(shí)體類不一致懒闷,導(dǎo)致無(wú)法封裝十减;

2、前端提交的到后臺(tái)的數(shù)據(jù)應(yīng)該是 json 字符串類型愤估,而前端沒(méi)有將對(duì)象轉(zhuǎn)化為字符串類型

```

接下來(lái)我們檢查下curl命令帮辟,可以看到password缺少雙引號(hào),把雙引號(hào)加上玩焰,再執(zhí)行下:

```

curl -v -H "Content-Type:application/json" -X POST --data '{"username":"tianalng", "password":"tianlang"}' http://127.0.0.1:8088/login

Note: Unnecessary use of -X or --request, POST is already inferred.

*? Trying 127.0.0.1...

* TCP_NODELAY set

* Connected to 127.0.0.1 (127.0.0.1) port 8088 (#0)

> POST /login HTTP/1.1

> Host: 127.0.0.1:8088

> User-Agent: curl/7.58.0

> Accept: */*

> Content-Type:application/json

> Content-Length: 46

>

* upload completely sent off: 46 out of 46 bytes

< HTTP/1.1 200 OK

< content-length: 33

< content-type: text/plain; charset=utf-8

< date: Sat, 16 May 2020 23:22:48 GMT

<

* Connection #0 to host 127.0.0.1 left intact

Hello tianalng! password:tianlang

```

這次就成功了

現(xiàn)在我們可以獲取到用戶提交的用戶名密碼了由驹,簡(jiǎn)單起見(jiàn),接下來(lái)我們判斷用戶名是不是等于密碼昔园,如果相等就返回OK告訴客戶端登錄成功了蔓榄,如果不相等就返回Error告訴客戶端登錄失敗了并炮。

在index函數(shù)中使用if語(yǔ)句判斷用戶名是否跟密碼一致,如果一致就返回成功如果不一致就返回失敗甥郑,當(dāng)然這里也可以使用match,代碼如下:

```rust

#[post("/login")]

async fn index(login_info: web::Json<LoginInfo>) -> impl Responder {

? ? if login_info.username == login_info.password {

? ? ? ? HttpResponse::Ok().json("success")

? ? } else {

? ? ? ? HttpResponse::Forbidden().json("password error")

? ? }


}

```

>其中HttpResponse::Ok設(shè)置結(jié)果成功也就是對(duì)應(yīng)http的狀態(tài)碼200

>HttpResponse::Forbidden設(shè)置結(jié)果為拒絕請(qǐng)求也就是對(duì)應(yīng)http的狀態(tài)碼403

你可以繼續(xù)使用curl分別使用與用戶名一致的密碼和不一致的密碼測(cè)試:

```bash

curl -v -H "Cication/json" -X POST --data '{"username":"tianlang", "password":"tianlang"}' http://127.0.0.1:8088/login

```

```

Note: Unnecessary use of -X or --request, POST is already inferred.

*? Trying 127.0.0.1...

* TCP_NODELAY set

* Connected to 127.0.0.1 (127.0.0.1) port 8088 (#0)

> POST /login HTTP/1.1

> Host: 127.0.0.1:8088

> User-Agent: curl/7.58.0

> Accept: */*

> Content-Type:application/json

> Content-Length: 46

>

* upload completely sent off: 46 out of 46 bytes

**< HTTP/1.1 200 OK**

< content-length: 9

< content-type: application/json

< date: Sat, 23 May 2020 11:36:30 GMT

<

* Connection #0 to host 127.0.0.1 left intact

**"success"**

```

```bash

curl -v -H "Content-ication/json" -X POST --data '{"username":"tianlang", "password":"wrong"}' http://127.0.0.1:8088/login

```

```

*? Trying 127.0.0.1...

* TCP_NODELAY set

* Connected to 127.0.0.1 (127.0.0.1) port 8088 (#0)

> POST /login HTTP/1.1

**> Host: 127.0.0.1:8088**

> User-Agent: curl/7.58.0

> Accept: */*

> Content-Type:application/json

> Content-Length: 43

>

* upload completely sent off: 43 out of 43 bytes

< HTTP/1.1 403 Forbidden

< content-length: 16

< content-type: application/json

< date: Sat, 23 May 2020 11:37:27 GMT

<

* Connection #0 to host 127.0.0.1 left intact

**"password error"**

```

也可以使用postman構(gòu)造一個(gè)post請(qǐng)求:

![rust login success](https://img-blog.csdnimg.cn/20200523204743217.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3RpYW5sYW5nc3R1ZGlv,size_16,color_FFFFFF,t_70)

這樣就可以根據(jù)客戶端提供的數(shù)據(jù)返回不同的結(jié)果了逃魄,代碼已提交[github](https://github.com/TianLangStudio/rust_login)

## 現(xiàn)在還存在個(gè)問(wèn)題:

雖然是調(diào)用的json設(shè)置的返回結(jié)果,但返回結(jié)果仍然是一個(gè)普通的字符串澜搅,在前端頁(yè)面是不能調(diào)用JSON.parse()轉(zhuǎn)換為json對(duì)象的伍俘。接下來(lái)我們要定義個(gè)struct統(tǒng)一表示返回的數(shù)據(jù)樣式,這樣客戶端可以統(tǒng)一轉(zhuǎn)換成json方便解析處理勉躺。

首先我們定義一個(gè)struct用來(lái)表示http接口返回的數(shù)據(jù)癌瘾,按照傳統(tǒng)命名為AjaxResult.

```rust

#[derive(Deserialize)]

#[derive(Serialize)]

struct AjaxResult<T> {

? ? msg: String,

? ? data: Option<Vec<T>>,

}

```

需要把它序列化成json,所以需要給它添加

>#[derive(Serialize)]

注解

字段msg用來(lái)存儲(chǔ)接口執(zhí)行的結(jié)果信息,接口執(zhí)行成功統(tǒng)一設(shè)置為 success,接口執(zhí)行失敗就設(shè)置為失敗信息赂蕴。

字段data用來(lái)存儲(chǔ)返回的數(shù)據(jù)柳弄,數(shù)據(jù)不是必須的,比如在接口執(zhí)行失敗的時(shí)候就沒(méi)有數(shù)據(jù)返回概说,所以data字段是Option類型碧注。

為了方便創(chuàng)建AjaxResut對(duì)象我們?cè)偬砑有╆P(guān)聯(lián)函數(shù):

```rust

const MSG_SUCCESS: &str = "success";

impl<T> AjaxResult<T> {

? ? pub fn success(data_opt: Option<Vec<T>>) -> Self{

? ? ? ? Self {

? ? ? ? ? ? msg: MSG_SUCCESS.to_string(),

? ? ? ? ? ? data: data_opt

? ? ? ? }

? ? }

? ? pub fn success_without_data() -> Self {

? ? ? ? Self::success(Option::None)

? ? }

? ? pub fn success_with_single(single: T) -> Self{

? ? ? ? Self {

? ? ? ? ? ? msg:? MSG_SUCCESS.to_string(),

? ? ? ? ? ? data: Option::Some(vec![single])

? ? ? ? }

? ? }

? ? pub fn fail(msg: String) -> Self {

? ? ? ? Self {

? ? ? ? ? ? msg,

? ? ? ? ? ? data: None

? ? ? ? }

? ? }

}

```

接下來(lái)修改login函數(shù),不再返回一個(gè)字符串而是返回AjaxRsult對(duì)象:

```rust

#[post("/login")]

async fn index(login_info: web::Json<LoginInfo>) -> impl Responder {

? ? if login_info.username == login_info.password {

? ? ? ? HttpResponse::Ok().json(AjaxResult::<bool>::success_without_data())

? ? } else {

? ? ? ? HttpResponse::Forbidden().json(AjaxResult::<bool>::fail("password must match username".to_string()))

? ? }

}

```

>AjaxResult::<bool> 這里的bool不是設(shè)置返回值數(shù)據(jù)類型因?yàn)槲覀円矝](méi)有返回?cái)?shù)據(jù)而是為了告訴Rust編譯器我們使用的泛型T的類型糖赔,不然它推導(dǎo)不出來(lái)就編譯出錯(cuò)了萍丐。這里的bool可以換成i32、String等

在執(zhí)行下接口調(diào)用:

![retun json object](https://img-blog.csdnimg.cn/20200525112021841.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3RpYW5sYW5nc3R1ZGlv,size_16,color_FFFFFF,t_70)

這時(shí)返回的數(shù)據(jù)就是標(biāo)準(zhǔn)的json對(duì)象了放典,方便前端解析處理逝变。

以前我們?cè)O(shè)計(jì)AjaxResult對(duì)象時(shí),也會(huì)包含一個(gè)數(shù)字類型的code字段用于區(qū)分不同的執(zhí)行結(jié)果錯(cuò)誤類型奋构。我們這里直接復(fù)用http的狀態(tài)碼壳影,就不需要定義這個(gè)字段了。

這也是設(shè)計(jì)Restful API的指導(dǎo)思想:

> 不是把所有的參數(shù)都盡量放到path里就是Resulful了弥臼,Restful是盡量復(fù)用已有的http規(guī)范宴咧。

> **純屬個(gè)人言論,如有誤導(dǎo)概不負(fù)責(zé)**?

代碼已提交[github](https://github.com/TianLangStudio/rust_login)

## 現(xiàn)在還有個(gè)問(wèn)題:

如果用戶已經(jīng)登錄過(guò)了就不需要再判斷用戶名密碼了径缅,浪費(fèi)資源掺栅,直接返回就可以了奏属,怎么實(shí)現(xiàn)呢法焰? 也就是如果用戶已經(jīng)登錄過(guò)了,我們?cè)趺粗烙脩粢呀?jīng)登錄過(guò)了呢蚜锨?

這個(gè)我們可以借助Session實(shí)現(xiàn)氏堤,Session一般代表從用戶打開(kāi)瀏覽器訪問(wèn)網(wǎng)站到關(guān)閉瀏覽器無(wú)論中間瀏覽過(guò)多少次網(wǎng)頁(yè)一般都屬于一個(gè)Session沙绝。 ~~注意這里說(shuō)的一般情況,有的瀏覽器可能行為不一樣~~ 可以在用戶第一次登錄成功后把用戶的登錄信息放入到Session中,判斷用戶名密碼之前先在Session中找有沒(méi)有用戶信息如果有就代表用戶已經(jīng)登錄過(guò)了宿饱,如果沒(méi)有再接著判斷用戶名密碼是否一致熏瞄。要使用Session需要在Cargo.toml文件中配置actix-session依賴:

```toml

[dependencies]

actix-web="2"

actix-rt="1"

actix-session="0.3"

```

修改login函數(shù)中的代碼如下:

```rust

const SESSION_USER_KEY: &str = "user_info";

#[post("/login")]

async fn index(session: Session, login_info: web::Json<LoginInfo>) -> impl Responder {

? ? match session.get::<String>(SESSION_USER_KEY) {

? ? ? ? Ok(Some(user_info)) if user_info == login_info.username => {

? ? ? ? ? ? println!("already logged in");

? ? ? ? ? ? HttpResponse::Ok().json(AjaxResult::<bool>::success_without_data())

? ? ? ? }

? ? ? ? _ => {

? ? ? ? ? ? println!("login now");

? ? ? ? ? ? if login_info.username == login_info.password {

? ? ? ? ? ? ? ? session.set::<String>(SESSION_USER_KEY, login_info.username.clone());

? ? ? ? ? ? ? ? HttpResponse::Ok().json(AjaxResult::<bool>::success_without_data())

? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? HttpResponse::Forbidden().json(AjaxResult::<bool>::fail("password must match username".to_string()))

? ? ? ? ? ? }

? ? ? ? }

? ? }

}

```

另外需要在創(chuàng)建Server時(shí)配置Session中間件

```rust

#[actix_rt::main]

async fn main() -> std::io::Result<()> {

? ? HttpServer::new(|| App::new()

? ? ? ? .wrap(

? ? ? ? ? ? CookieSession::signed(&[0; 32]) // <- create cookie based session middleware

? ? ? ? ? ? ? ? .secure(false),

? ? ? ? ).service(index))

? ? ? ? .bind("127.0.0.1:8088")?

? ? ? ? .run()

? ? ? ? .await

}

```

現(xiàn)在我們?cè)偈褂肞ostman訪問(wèn)登錄接口,第一次控制臺(tái)會(huì)輸出:

> login now

第二次就會(huì)輸出:

> already logged in

在Postman中也可以看到多了一個(gè)cookie,細(xì)看你細(xì)看這就是我們放入Session的用戶信息:

![cookie](https://img-blog.csdnimg.cn/2020052920055647.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3RpYW5sYW5nc3R1ZGlv,size_16,color_FFFFFF,t_70)

當(dāng)前的actix session中間件只支持cookie存儲(chǔ)方式谬以,也可以自己實(shí)現(xiàn)基于Redis的存儲(chǔ)方式强饮。

## 現(xiàn)在還有個(gè)問(wèn)題

如果一個(gè)用戶看到了我們的cookie,從cookie的內(nèi)容就可以看出我們這里就是用戶名,那他是不是只要知道了別人的用戶名就可以偽造這個(gè)cookie模仿其他用戶登錄?

代碼已提交[github](https://github.com/TianLangStudio/rust_login)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末为黎,一起剝皮案震驚了整個(gè)濱河市邮丰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌铭乾,老刑警劉巖剪廉,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異炕檩,居然都是意外死亡斗蒋,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門笛质,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)泉沾,“玉大人,你說(shuō)我怎么就攤上這事妇押□尉浚” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵敲霍,是天一觀的道長(zhǎng)俊马。 經(jīng)常有香客問(wèn)我,道長(zhǎng)肩杈,這世上最難降的妖魔是什么柴我? 我笑而不...
    開(kāi)封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮扩然,結(jié)果婚禮上屯换,老公的妹妹穿的比我還像新娘。我一直安慰自己与学,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布嘉抓。 她就那樣靜靜地躺著索守,像睡著了一般。 火紅的嫁衣襯著肌膚如雪抑片。 梳的紋絲不亂的頭發(fā)上卵佛,一...
    開(kāi)封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼截汪。 笑死疾牲,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的衙解。 我是一名探鬼主播阳柔,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蚓峦!你這毒婦竟也來(lái)了舌剂?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤暑椰,失蹤者是張志新(化名)和其女友劉穎霍转,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體一汽,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡避消,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了召夹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片岩喷。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖戳鹅,靈堂內(nèi)的尸體忽然破棺而出均驶,到底是詐尸還是另有隱情,我是刑警寧澤枫虏,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布妇穴,位于F島的核電站,受9級(jí)特大地震影響隶债,放射性物質(zhì)發(fā)生泄漏腾它。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一死讹、第九天 我趴在偏房一處隱蔽的房頂上張望瞒滴。 院中可真熱鬧,春花似錦赞警、人聲如沸妓忍。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)世剖。三九已至,卻和暖如春笤虫,著一層夾襖步出監(jiān)牢的瞬間旁瘫,已是汗流浹背祖凫。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留酬凳,地道東北人惠况。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像宁仔,于是被迫代替她去往敵國(guó)和親稠屠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354