🎃

「actix-web」入門#4 CORS

2022/05/30に公開

初めに

CORS(コルス)の概念は以下の考え方です。

actix-webのexamplesで紹介されているのは、フロントエンドが「Vue.js」、バックエンドが「actix-web」って感じです。
以下がソースのURLです。
https://github.com/actix/examples/tree/master/cors

以下の説明をしていきます。

  1. CORSの概念
  2. CORSとFacadeパターン
  3. フロントエンド側のソース確認&動作確認
  4. バックエンド側のソース確認&動作確認

1. CORSの概念

CORSは「Cross-origin resource sharing」の略です。

wikiを確認していただければわかりますが、CORSの大きな目的として、セキュリティ対策があげられます。
https://en.wikipedia.org/wiki/Cross-origin_resource_sharing

大雑把には門番(トランジスタでいうところのゲート)がいて、その門番が特定のサイトからのみのリクエストを特定のオリジン(リソースが異なるサービス)に接続する仕組みです。

CORSの概念はQiitaにもわかりやすくまとめられてました。
https://qiita.com/att55/items/2154a8aad8bf1409db2b

2. CORSとFacadeパターン

CORSの概念をみて、私が思い出したのはFacade パターンでした。
https://ja.wikipedia.org/wiki/Facade_パターン

CORSとFacadeパターンの似ている部分

Facadeパターンは、あくまでクラス単位で適用される概念ですが、クラスをオリジン(リソースが異なるサービス)に読み替えればかなり近しい概念に思えます。
以下はWiki抜粋「Facadeパターンのクラス図」
wiki抜粋

CORSとFacadeパターンの違い

FacadeパターンとCORSの徹底的な違いはセキュリティに重きを置くかどうかです。
Facadeパターンは独立性を重視するため、門(Facade)からのみサブシステムに接続できるかどうか等を決定していません。

3. フロントエンド側のソース確認&動作確認

今回のexampleは以下です。
https://github.com/actix/examples/tree/master/cors/frontend

フロントエンド側は「Vue.js」で実装しているようです。
「package.json」について、私の環境ではvueのバージョンが「2.6.14」なので合わせておきます。

  "dependencies": {
    "vue": "2.6.14"
  },

ソース確認

ソースの構成はこんな感じです。シンプルな「Vue.js」です。

「main.js」はかなりシンプルでid「app」に対して、「app.vue」を割り当ててます。

main.js
import Vue from 'vue'
import App from './app'

new Vue({
  render: h => h(App)
}).$mount('#app')

「app.vue」ではログイン画面のテンプレートを埋めるようです。「Sign up」で他のオリジンへのリクエストが飛ぶようになってます。

app.vue
<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の説明通り、コマンド実行します。
https://github.com/actix/examples/tree/master/cors

npm install
npm run serve

ブラウザで確認

もちろん、フロントエンド側だけの実装なので「Sign up」を押しても動作はしません。

4. バックエンド側のソース確認&動作確認

今回のexampleは以下です。
https://github.com/actix/examples/tree/master/cors/backend
バックエンド側は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の説明通り、コマンド実行します。
https://github.com/actix/examples/tree/master/cors

cargo run

ブラウザで確認

今度はちゃんと「Sign up」したときに動作してくれます。

5. フロントエンドで「Vue.js」を使う強み

フロントエンドは「Vue.js」で実装されているので、HMRが効きます。そのため、リアルタイムでソースを変更するだけで画面表示に反映されるので考え方としてかなりありだなと感じます。

余談

最初、デフォルトでそのまま説明通り実行すると真っ白な画面になって戸惑ってました(笑)。
なんで、ポートちゃんと分けなかったんだろ?

体験談になりますが、少し古いですが、Apatchはaxis2でリソース間のやり取りしてる実装を見たことあるので、その同機ずれなどの対応がどれだけ大変かわかっているつもりです。なので、今回のCORSの概念はすごく重要な概念だというのが身に染みてわかります。

次回

次回は「セッション」を使ってみます。

次回以降は以下のいずれかをやっていく感じです。
・db周りとかの解説
・test周りとかの解説
・tls周りとかの解説

蛇足

Facadeパターンを確認し直して、Facadeパターンの定義って広いなと思いました。
門番(表)があって、裏方に処理を回すってことさえできてればいいので、例えば、PythonにおけるNumpy(Cython(C言語)で実装されているモジュールを読み込んで、操作はpythonで行うということがなされているため)とかも当てはまってしまいそうです。

Discussion