「actix-web」入門#4 CORS
初めに
CORS(コルス)の概念は以下の考え方です。
actix-webのexamplesで紹介されているのは、フロントエンドが「Vue.js」、バックエンドが「actix-web」って感じです。
以下がソースのURLです。
以下の説明をしていきます。
- CORSの概念
- CORSとFacadeパターン
- フロントエンド側のソース確認&動作確認
- バックエンド側のソース確認&動作確認
1. CORSの概念
CORSは「Cross-origin resource sharing」の略です。
wikiを確認していただければわかりますが、CORSの大きな目的として、セキュリティ対策があげられます。
大雑把には門番(トランジスタでいうところのゲート)がいて、その門番が特定のサイトからのみのリクエストを特定のオリジン(リソースが異なるサービス)に接続する仕組みです。
CORSの概念はQiitaにもわかりやすくまとめられてました。
2. CORSとFacadeパターン
CORSの概念をみて、私が思い出したのはFacade パターンでした。
CORSとFacadeパターンの似ている部分
Facadeパターンは、あくまでクラス単位で適用される概念ですが、クラスをオリジン(リソースが異なるサービス)に読み替えればかなり近しい概念に思えます。
以下はWiki抜粋「Facadeパターンのクラス図」
CORSとFacadeパターンの違い
FacadeパターンとCORSの徹底的な違いはセキュリティに重きを置くかどうかです。
Facadeパターンは独立性を重視するため、門(Facade)からのみサブシステムに接続できるかどうか等を決定していません。
3. フロントエンド側のソース確認&動作確認
今回のexampleは以下です。
フロントエンド側は「Vue.js」で実装しているようです。
「package.json」について、私の環境ではvueのバージョンが「2.6.14」なので合わせておきます。
"dependencies": {
"vue": "2.6.14"
},
ソース確認
ソースの構成はこんな感じです。シンプルな「Vue.js」です。
「main.js」はかなりシンプルでid「app」に対して、「app.vue」を割り当ててます。
import Vue from 'vue'
import App from './app'
new Vue({
render: h => h(App)
}).$mount('#app')
「app.vue」ではログイン画面のテンプレートを埋めるようです。「Sign up」で他のオリジンへのリクエストが飛ぶようになってます。
<template>
<div id="app">
<div id="content">
<div id="title">
<a to="#">SignUp</a>
</div>
<input type="text" name="username" placeholder="Username" v-model="Username" required />
<input type="text" name="email" placeholder="E-mail" v-model="Email" required />
<input type="password" name="password" placeholder="Password" v-model="Password" required/>
<input type="password" name="confirm_password" placeholder="Confirm password" v-model="ConfirmPassword" required/><br/>
<button id="submit" @click="signup">Sign up</button>
<div id="user-info">
<p>Click Above 'Sign up' Button <br> Then Get Your Signup Info!</p>
<p>email : {{ email }}</p>
<p>username : {{ username }}</p>
<p>password : {{ password }}</p>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'app',
data () {
return {
Username: '',
Email: '',
Password: '',
ConfirmPassword: '',
email: '',
username: '',
password: ''
}
},
methods: {
signup () {
let username = this.Username
let email = this.Email
let password = this.Password
let confirm_password = this.ConfirmPassword
let data = {
username: username,
email: email,
password: password,
confirm_password: confirm_password
}
fetch('http://localhost:8000/user/info', {
body: JSON.stringify(data),
headers: {
'content-type': 'application/json'
},
method: 'POST',
}).then(response => response.json())
.then(json => {
console.log(json)
this.email = json.email
this.username = json.username
this.password = json.password
})
.catch((e) => {
console.log(e)
})
}
}
}
</script>
ソース修正
「app.vue」について、なぜかバックエンドは「http://localhost:8000/user/info 」に対して、POSTしているのですが、私はフロントエンド側はポート「8080」使って、バックエンド側はポート「8081」を使うという構成にしたかったので「http://localhost:8081/user/info 」に対して、POSTするように修正します。
以下URLの説明通り、コマンド実行します。
npm install
npm run serve
ブラウザで確認
もちろん、フロントエンド側だけの実装なので「Sign up」を押しても動作はしません。
4. バックエンド側のソース確認&動作確認
今回のexampleは以下です。
バックエンド側はactix-webで実装されてます。ソース確認
バックエンド側のソース構成はこんな感じです。
main.rsではCorsの定義があるのがわかります。
use actix_cors::Cors;
use actix_web::{http::header, middleware::Logger, App, HttpServer};
mod user;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));
HttpServer::new(move || {
App::new()
.wrap(
Cors::default()
.allowed_origin("http://localhost:8080")
.allowed_methods(vec!["GET", "POST"])
.allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT])
.allowed_header(header::CONTENT_TYPE)
.supports_credentials()
.max_age(3600),
)
.wrap(Logger::default())
.service(user::info)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
Corsの関数の構成でどのORIGINとかリクエストからの処理をOKとするか定められてるのがわかります。
「allowed_origin」・・・どのORIGINからのリクエストを許可するかの記述があります。
「allowed_methods」・・・GETとPOSTを許可する記述があります。
ソース修正
main.rsにあるポートはなぜか「8080」に割り当てられているので、「8081」に修正します。
以下URLの説明通り、コマンド実行します。
cargo run
ブラウザで確認
今度はちゃんと「Sign up」したときに動作してくれます。
5. フロントエンドで「Vue.js」を使う強み
フロントエンドは「Vue.js」で実装されているので、HMRが効きます。そのため、リアルタイムでソースを変更するだけで画面表示に反映されるので考え方としてかなりありだなと感じます。
余談
最初、デフォルトでそのまま説明通り実行すると真っ白な画面になって戸惑ってました(笑)。
なんで、ポートちゃんと分けなかったんだろ?
体験談になりますが、少し古いですが、Apatchはaxis2でリソース間のやり取りしてる実装を見たことあるので、その同機ずれなどの対応がどれだけ大変かわかっているつもりです。なので、今回のCORSの概念はすごく重要な概念だというのが身に染みてわかります。
次回
次回は「セッション」を使ってみます。
次回以降は以下のいずれかをやっていく感じです。
・db周りとかの解説
・test周りとかの解説
・tls周りとかの解説
蛇足
Facadeパターンを確認し直して、Facadeパターンの定義って広いなと思いました。
門番(表)があって、裏方に処理を回すってことさえできてればいいので、例えば、PythonにおけるNumpy(Cython(C言語)で実装されているモジュールを読み込んで、操作はpythonで行うということがなされているため)とかも当てはまってしまいそうです。
Discussion