序文Axios は、ブラウザと Node.js 環境の両方をサポートする Promise ベースの HTTP クライアントです。これは優れた HTTP クライアントであり、多数の Web プロジェクトで広く使用されています。 上の図からわかるように、Axios プロジェクトのスターの数は「77.9K」、フォークの数は「7.3K」と非常に多くなっています。非常に優れたオープンソース プロジェクトなので、次に Abao が Axios プロジェクトの学ぶ価値のある側面をいくつか分析します。 この記事を読むと、次のことがわかります。
まずは最も簡単なものから始めて、Axios について学びましょう。 1. Axiosの紹介Axios は、次の機能を備えた Promise ベースの HTTP クライアントです。
ブラウザ側では、Axios は Chrome、Firefox、Safari、IE 11 など、ほとんどの主要ブラウザをサポートしています。さらに、Axios には独自のエコシステムもあります。 (データソース - https://github.com/axios/axios/blob/master/ECOSYSTEM.md) Axios について簡単に紹介した後、Axios が提供するコア機能の 1 つであるインターセプターを分析してみましょう。 2. HTTPインターセプターの設計と実装2.1 インターセプターの紹介ほとんどの SPA アプリケーションでは、通常、トークンを使用してユーザーが認証されます。これには、認証に合格した後、各リクエストで認証情報を伝える必要があります。この要件を満たすには、各リクエストを個別に処理することを避けるために、統合されたリクエスト関数をカプセル化して、各リクエストにトークン情報を追加します。 しかし、後で、一部の GET リクエストのキャッシュ時間を設定したり、一部のリクエストの呼び出し頻度を制御したりする必要がある場合は、リクエスト関数を継続的に変更して、対応する機能を拡張する必要があります。この時点で、統一された応答処理を考慮すると、リクエスト関数はますます大きくなり、保守が困難になります。では、この問題をどう解決すればよいのでしょうか? Axios はインターセプターというソリューションを提供します。 Axios は Promise ベースの HTTP クライアントであり、HTTP プロトコルはリクエストとレスポンスに基づいています。 そのため、Axios はリクエストとレスポンスをそれぞれ処理するためのリクエスト インターセプターとレスポンス インターセプターを提供します。それらの機能は次のとおりです。
Axios でインターセプターを設定するのは非常に簡単です。axios.interceptors.request および axios.interceptors.response オブジェクトによって提供される use メソッドを使用して、それぞれリクエスト インターセプターとレスポンス インターセプターを設定できます。 // リクエストインターセプターを追加 axios.interceptors.request.use(function (config) { config.headers.token = 'インターセプターによって追加されました'; 設定を返します。 }); // レスポンスインターセプターを追加 axios.interceptors.response.use(function (data) { data.data = data.data + '-インターセプターによって変更されました'; データを返します。 }); では、インターセプターはどのように機能するのでしょうか?具体的なコードを見る前に、まずはその設計思想を分析してみましょう。 Axios は HTTP リクエストを送信するために使用され、リクエスト インターセプターとレスポンス インターセプターの本質は特定の機能を実装する関数です。 HTTP リクエストの送信は、リクエスト構成オブジェクトを処理するサブタスク、HTTP リクエストを送信するサブタスク、応答オブジェクトを処理するサブタスクなど、機能に応じてさまざまな種類のサブタスクに分類できます。これらのサブタスクを指定された順序で実行すると、完全な HTTP リクエストを完了できます。 これらを理解した後、タスク登録、タスクスケジューリング、タスクスケジューリングの 3 つの側面から Axios インターセプターの実装を分析します。 2.2 タスク登録これまでのインターセプターの使用例を通じて、リクエスト インターセプターとレスポンス インターセプターを登録する方法はすでにわかっています。リクエスト インターセプターはリクエスト構成オブジェクトのサブタスクを処理するために使用され、レスポンス インターセプターはレスポンス オブジェクトのサブタスクを処理するために使用されます。タスクがどのように登録されるかを理解するには、axios および axios.interceptors オブジェクトを理解する必要があります。 // lib/axios.js 関数createInstance(defaultConfig) { var context = new Axios(defaultConfig); var インスタンス = bind(Axios.prototype.request, コンテキスト); // axios.prototype をインスタンスにコピーする utils.extend(インスタンス、Axios.prototype、コンテキスト); // コンテキストをインスタンスにコピー utils.extend(インスタンス、コンテキスト); インスタンスを返します。 } // エクスポートするデフォルトのインスタンスを作成する var axios = createInstance(デフォルト); Axios のソース コードには、axios オブジェクトの定義が見つかりました。明らかに、デフォルトの axios インスタンスは createInstance メソッドによって作成され、最終的に Axios.prototype.request 関数オブジェクトを返します。同時に、Axios のコンストラクターも見つかりました。 // lib/core/Axios.js 関数 Axios(instanceConfig) { this.defaults = インスタンス構成; this.interceptors = { リクエスト: 新しい InterceptorManager()、 レスポンス: 新しい InterceptorManager() }; } コンストラクターでは、axios.interceptors オブジェクトの定義が見つかり、interceptors.request オブジェクトと interceptors.response オブジェクトの両方が InterceptorManager クラスのインスタンスであることもわかりました。したがって、InterceptorManager コンストラクターと関連する使用方法をさらに分析すると、タスクがどのように登録されるかがわかります。 // lib/core/InterceptorManager.js 関数InterceptorManager() { this.handlers = []; } InterceptorManager.prototype.use = 関数 use(fulfilled, declined) { this.handlers.push({ 達成された: 達成された、 拒否: 拒否 }); // 登録されたインターセプターを削除するために使用される現在のインデックスを返します。 return this.handlers.length - 1; }; use メソッドを観察すると、登録されたインターセプターが InterceptorManager オブジェクトの handlers プロパティに保存されることがわかります。以下に、図を使用して、Axios オブジェクトと InterceptorManager オブジェクト間の内部構造と関係をまとめます。 2.3 タスクのスケジュールこれでインターセプター タスクを登録する方法がわかりましたが、タスクを登録するだけでは十分ではありません。タスクの実行順序を保証するために、登録されたタスクをスケジュールする必要もあります。ここでは、完全な HTTP リクエストの完了を、リクエスト構成オブジェクトの処理、HTTP リクエストの開始、およびレスポンス オブジェクトの処理という 3 つの段階に分けます。 次に、Axios がリクエストを送信する方法を見てみましょう。 アクシオス({ URL: '/hello', メソッド: 'get'、 }).then(res =>{ console.log('axios res: ', res) console.log('axios res.data: ', res.data) }) これまでの分析から、axios オブジェクトが Axios.prototype.request 関数オブジェクトに対応していることはすでにわかっています。この関数の具体的な実装は次のとおりです。 // lib/core/Axios.js Axios.prototype.request = 関数リクエスト(config) { config = mergeConfig(this.defaults、config); // 一部のコードを省略 var chain = [dispatchRequest, undefined]; var promise = Promise.resolve(config); // タスクのスケジュール this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { インターセプターが満たされた場合、チェーンはシフト解除されます。 }); this.interceptors.response.forEach(関数 pushResponseInterceptors(インターセプター) { チェーンをプッシュします(インターセプターが満たされ、インターセプターが拒否されます)。 }); // タスクのスケジュール while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); } 返却約束; }; タスク スケジューリングのコードは比較的シンプルです。タスク スケジューリングの前後の比較チャートを見てみましょう。 2.4 タスクのスケジュールタスクのスケジュールが完了したら、HTTP リクエストを開始するには、スケジュールされた順序でタスクのスケジュールを実行する必要もあります。 Axios の具体的なディスパッチ メソッドは、以下に示すように非常にシンプルです。 // lib/core/Axios.js Axios.prototype.request = 関数リクエスト(config) { // 一部のコードを省略 var promise = Promise.resolve(config); while (チェーンの長さ) { promise = promise.then(chain.shift(), chain.shift()); } } チェーンは配列なので、while 文を通じて設定されたタスクを連続的に取り出し、Promise 呼び出しチェーンに組み立ててタスクのスケジュールを実装できます。対応する処理フローを下図に示します。 Axios インターセプターの完全な使用プロセスを確認しましょう。 // リクエストインターセプターを追加 - リクエスト構成オブジェクトを処理する axios.interceptors.request.use(function (config) { config.headers.token = 'インターセプターによって追加されました'; 設定を返します。 }); // レスポンスインターセプターを追加 - レスポンスオブジェクトを処理 axios.interceptors.response.use(function (data) { data.data = data.data + '-インターセプターによって変更されました'; データを返します。 }); アクシオス({ URL: '/hello', メソッド: 'get'、 }).then(res =>{ console.log('axios res.data: ', res.data) }) Axios のインターセプターを紹介した後、その利点をまとめてみましょう。 Axios は、開発者がリクエスト ライフサイクル内のさまざまな処理ロジックを簡単にカスタマイズできるようにするインターセプター メカニズムを提供します。 さらに、Axios エコシステムにリストされている 2 つのライブラリ axios-response-logger と axios-debug-log などのインターセプター メカニズムを通じて、Axios の機能を柔軟に拡張することもできます。 Axios インターセプターの設計モデルを参照すると、次のような一般的なタスク処理モデルを抽出できます。 3. HTTPアダプタの設計と実装3.1 デフォルトの HTTP アダプタAxios は、ブラウザ環境と Node.js 環境の両方をサポートしています。ブラウザ環境では、XMLHttpRequest または fetch API を介して HTTP リクエストを送信でき、Node.js 環境では、Node.js の組み込み http または https モジュールを介して HTTP リクエストを送信できます。 さまざまな環境をサポートするために、Axios はアダプターを導入します。 HTTP インターセプターの設計セクションでは、HTTP リクエストの送信に使用される dispatchRequest メソッドについて説明しました。具体的な実装は次のとおりです。 // lib/core/dispatchRequest.js module.exports = 関数dispatchRequest(config) { // 一部のコードを省略 var adaptor = config.adapter || defaults.adapter; アダプタ(config)を返します。その後(関数onAdapterResolution(response) { // 一部のコードを省略します。return response; }, 関数 onAdapterRejection(理由) { // 一部のコードを省略 return Promise.reject(reason); }); }; 上記の dispatchRequest メソッドを見ると、Axios がカスタム アダプターをサポートし、デフォルトのアダプターも提供していることがわかります。ほとんどのシナリオでは、カスタム アダプターは必要ありませんが、デフォルトのアダプターを直接使用します。したがって、デフォルトのアダプターには、ブラウザーと Node.js 環境の適応コードが含まれます。具体的な適応ロジックは次のとおりです。 // lib/defaults.js var デフォルト = { アダプタ: getDefaultAdapter(), xsrfCookieName: 'XSRF-TOKEN', xsrfヘッダー名: 'X-XSRF-TOKEN', //... } 関数 getDefaultAdapter() { var アダプタ; if (typeof XMLHttpRequest !== 'undefined') { // ブラウザの場合はXHRアダプタを使用する アダプタ = require('./adapters/xhr'); } そうでない場合 (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[オブジェクト プロセス]') { // ノードの場合はHTTPアダプタを使用する アダプタ = require('./adapters/http'); } リターンアダプター; } getDefaultAdapter メソッドでは、まずプラットフォーム内の特定のオブジェクトによって異なるプラットフォームを区別し、次に異なるアダプタをインポートします。具体的なコードは比較的単純なので、ここでは紹介しません。 3.2 カスタムアダプタ実際、デフォルトのアダプターに加えて、アダプターをカスタマイズすることもできます。では、アダプターをどのようにカスタマイズするのでしょうか?ここでは、Axios が提供する例を参照できます。 var settle = require('./../core/settle'); module.exports = 関数 myAdapter(config) { // 現在のタイミング: // - config オブジェクトがデフォルトのリクエスト設定とマージされました // - リクエストコンバータが実行されました // - リクエストインターセプターが実行されました // 提供された config オブジェクトを使用してリクエストを作成します // レスポンスオブジェクトに基づいて Promise の状態を処理します return new Promise(function(resolve, deny) { var レスポンス = { データ: レスポンスデータ、 ステータス: リクエストステータス、 ステータステキスト: request.statusText、 ヘッダー: responseHeaders、 設定: 設定、 リクエスト: リクエスト }; 解決(解決、拒否、応答) // この後: // - レスポンスコンバーターが実行されます // - レスポンスインターセプターが実行されます }); } 上記の例では、コンバーター、インターセプターの実行時タイミング、およびアダプターの基本要件に焦点を当てました。たとえば、カスタム アダプターが呼び出されると、Promise オブジェクトが返される必要があります。これは、Axios が内部的に Promise チェーン呼び出しを使用してリクエストのスケジュールを完了するためです。この点がよくわからない場合は、「インターセプターの設計と実装」セクションを再度お読みください。 アダプターをカスタマイズする方法がわかったところで、カスタム アダプターは何に役立つのでしょうか? Axios エコシステムで、Abao は axios-mock-adapter ライブラリを発見しました。これにより、開発者はカスタム アダプターを通じてリクエストを簡単にシミュレートできるようになります。対応する使用例は次のとおりです。 var axios = require("axios"); var MockAdapter = require("axios-mock-adapter"); // デフォルトの Axios インスタンスにモック アダプターを設定します。var mock = new MockAdapter(axios); // GET /users リクエストをシミュレートする mock.onGet("/users").reply(200, { ユーザー: [{ id: 1, name: "John Smith" }], }); axios.get("/users").then(関数 (応答) { console.log(応答データ); }); MockAdapter に興味のある友人は、axios-mock-adapter ライブラリについて学ぶことができます。これまで、Axios インターセプターとアダプターを紹介してきました。以下は、Axios がリクエスト インターセプターとレスポンス インターセプターを使用した後のリクエスト処理フローをまとめた図です。 4. CSRF防御4.1 CSRF の概要「クロスサイトリクエストフォージェリ」は、「CSRF」または「XSRF」と略されることが多く、現在ログインしている Web アプリケーションでユーザーに意図しないアクションを実行させる攻撃方法です。 簡単に言えば、クロスサイト リクエスト攻撃とは、攻撃者が何らかの技術的手段を使用して、ユーザーのブラウザーを騙し、攻撃者が認証した Web サイトにアクセスさせて、何らかの操作 (電子メールやメッセージの送信、送金や商品の購入などの金融操作など) を実行させることです。ブラウザが認証されているため、訪問した Web サイトは正規のユーザー操作であると想定して実行します。 上記の内容をよりよく理解できるように、アバオ兄弟はクロスサイト リクエスト攻撃の例の図を描きました。 上図では、攻撃者は Web 上のユーザー認証の脆弱性を悪用しました。「単純な認証では、リクエストが特定のユーザーのブラウザから送信されたことしか保証できませんが、リクエスト自体がユーザーによって自発的に発行されたものであるかどうかは保証できません。」上記のような脆弱性が存在する場合、私たちはどのように身を守るべきでしょうか?次に、一般的な CSRF 防御対策をいくつか紹介します。 4.2 CSRF防御対策4.2.1 リファラーフィールドを確認するHTTP ヘッダーには Referer フィールドがあり、リクエストの送信元のアドレスを示すために使用されます。 「機密データのリクエストを処理する場合、一般的に、Referer フィールドは、リクエストされたアドレスと同じドメイン名の下に配置する必要があります。」 この例のモール操作では、Referer フィールドのアドレスは通常、モールの Web アドレスであり、www.semlinker.com の下に配置されます。リクエストが CSRF 攻撃からのものである場合、Referer フィールドに悪意のある URL のアドレスが含まれ、www.semlinker.com の下には配置されません。この時点で、サーバーは悪意のあるアクセスを識別できます。 この方法はシンプルで実装が簡単で、主要なアクセス ポイントで追加のチェック手順を実行するだけで済みます。しかし、このアプローチには限界もあります。正しい Referer フィールドの送信はブラウザに完全に依存しているからです。 HTTP プロトコルにはこのフィールドの内容に関する明確な規定がありますが、アクセスしているブラウザの特定の実装を保証することはできず、またブラウザにこのフィールドに影響するセキュリティ上の脆弱性がないことも保証できません。攻撃者が特定のブラウザを攻撃し、Referer フィールドを改ざんする可能性もあります。 4.2.2 同期フォームCSRFチェックサーバーが通常のリクエストと攻撃リクエストを区別できないため、CSRF 攻撃が成功します。この問題に対処するには、すべてのユーザー リクエストに、CSRF 攻撃者が取得できないトークンを付与することを要求できます。 CSRF サンプル図のフォーム攻撃に対しては、「同期フォーム CSRF 検証」防御策を使用できます。 「同期フォーム CSRF 検証」とは、ページを返すときにページ上にトークンをレンダリングし、フォームが送信されるときに隠しフィールドまたはクエリ パラメータを通じて CSRF トークンをサーバーに送信することを意味します。たとえば、ページを同期的にレンダリングする場合は、ユーザーがフォームを送信したときに CSRF トークンが送信されるように、フォーム リクエストに _csrf クエリ パラメータを追加します。 <form method="POST" action="/upload?_csrf={{サーバーによって生成}}" enctype="multipart/form-data"> ユーザー名: <input name="name" /> アバターを選択: <input name="file" type="file" /> <button type="submit">送信</button> </フォーム> 4.2.3 ダブルクッキー防御「二重 Cookie 防御」とは、Cookie にトークンを設定し、(POST、PUT、PATCH、DELETE) などのリクエストを送信するときに Cookie を送信し、Cookie に設定されたトークンをリクエスト ヘッダーまたはリクエスト ボディを通じて伝達することです。サーバーはリクエストを受信後、比較と検証を実行します。 jQuery を例に、CSRF トークンを設定する方法を見てみましょう。 csrfToken = Cookies.get('csrfToken'); とします。 関数csrfSafeMethod(メソッド) { // 次の HTTP メソッドは CSRF 保護を必要としません return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); } $.ajaxSetup({ 送信前: 関数(xhr, 設定) { if (!csrfSafeMethod(settings.type) && !this.crossDomain) { xhr.setRequestHeader('x-csrf-token', csrfToken); } }, }); CSRF 攻撃の手法と防御策を紹介した後、Axios が CSRF 攻撃に対してどのように防御するかを見てみましょう。 4.3 Axios CSRF防御Axios には、CSRF クッキー名と HTTP リクエスト ヘッダー名をそれぞれ設定するための xsrfCookieName と xsrfHeaderName という 2 つのプロパティが用意されています。それぞれのデフォルト値は次のとおりです。 // lib/defaults.js var デフォルト = { アダプタ: getDefaultAdapter(), // 一部のコードを省略 xsrfCookieName: 'XSRF-TOKEN', xsrfヘッダー名: 'X-XSRF-TOKEN', }; Axios は異なるプラットフォームで HTTP リクエストを送信するために異なるアダプタを使用することはすでにわかっています。ここではブラウザ プラットフォームを例に、Axios が CSRF 攻撃に対してどのように防御するかを見ていきます。 // lib/アダプタ/xhr.js module.exports = 関数 xhrAdapter(config) { 新しいPromise(function dispatchXhrRequest(resolve, deny) を返す { var requestHeaders = config.headers; var リクエスト = 新しい XMLHttpRequest(); // 一部のコードを省略 // xsrf ヘッダーを追加 if (utils.isStandardBrowserEnv()) { var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && config.xsrfCookieName ? クッキーを読み込む(config.xsrfCookieName): 未定義; if (xsrfValue) { リクエストヘッダー[config.xsrfHeaderName] = xsrfValue; } } リクエストを送信します。(リクエストデータ) }); }; 上記のコードを読んだら、すでに答えがわかっていると思います。Axios は CSRF 攻撃を防御するために「二重 Cookie 防御」ソリューションを使用していることがわかります。 さて、この記事の主な内容はここで紹介しました。実は、Axios プロジェクトには、CancelToken の設計や例外処理の仕組みなど、学ぶ価値のあるものがまだいくつかあります。興味のある方は、自分で学んでみてください。 5. 参考資料
要約するGitHub 上の 77.9K の Axios プロジェクトから学ぶ価値のあることについての記事はこれで終わりです。Axios プロジェクトのより詳細な分析については、123WORDPRESS.COM の以前の記事を検索するか、以下の関連記事を引き続き参照してください。皆様が今後とも 123WORDPRESS.COM を応援していただければ幸いです。 以下もご興味があるかもしれません:
|
>>: Linux で PCIe のバージョンと速度を確認する方法
フォーム要素はたくさんあります。簡単にまとめると、次のようになります。私のやり方では、主にテキスト ...
1. 位置情報の利用状況の概要ロケーションは、さまざまな処理方法に対してさまざまな種類のリクエストを...
目次1. はじめに2. コードの実装2.1 目的分析2.2 実装プロセス2.2.1 エントリーコード...
Tomcatをインストールする前に、まずJDK環境をインストールしてくださいLinux サーバー上で...
0. タグとは何ですか? XML/HTML コードコンテンツをクリップボードにコピー<入力 t...
最近、たまたま vue+springboot のフロントエンドとバックエンドの分離プロジェクトに触れ...
1. ファイルの権限と所有権の概要1. アクセス権Read r: ファイルの内容を表示し、ディレクト...
MySQL DATE_ADD(date,INTERVAL expr type) 関数と ADDDA...
写真といえば、まず背景画像が思い浮かびます。私たちの装飾の多くは背景画像を使用して実現されているから...
概要Binlog2sql は、Python で開発されたオープンソースの MySQL Binlog ...
日々の最適化プロセス中に、奇妙なことに気付きました。同じ SQL にまったく異なる 2 つの実行プラ...
導入前の記事で述べたように、NodeJS には 2 種類のスレッドがあります。1 つは、ユーザー リ...
目次1. 冷蔵庫に入りきらない象2. シャドウクローン文字列3. 実際に見た「奇妙なボール」 4. ...
解決策 1: HEAD に次のコードを挿入するなど、HTML ドキュメントで条件付きインポートを使用...
目次LinuxでTCPを作成する手順サーバクライアントTCP確立プロセスサンプルコードLinuxでT...