XMLHttpRequestの置換、または大量の完成したjs-codeに触れることなく、すべてのajaxリクエストの動作を変更する

こんにちは、この小さな投稿で、JSのOOP、XMLHttpRequestオブジェクト、プロキシパターン、およびこの点でのJavaScriptの使いやすさについて少し説明します。



今日私はそのような仕事をしていました-ajaxリクエストを非常に積極的に使用するプロジェクトがありますが、ここに問題があります-私たちのバックエンドは、ユーザーが例えば30分間アクティブでない場合、ユーザーを認証しないように設計されています。 その結果、ユーザーがajaxを使用する何らかのアクションを実行しようとしても実行できなかった(トートロジーについてはすみません)ため、この問題を解決する必要がありました。





自分で設定したTK


ajaxリクエストが行われ、*ユーザーがセッションを完了したことを示すメッセージが返された場合、ユーザーログインフォームを表示し(通常のオーバーレイを使用し、ipframeを使用せずに)、ユーザーに認証を許可する(再度) ayaksu、ページの状態を失うことができないため。 さらに、認証に合格した場合、合格しなかったajaxリクエストは再度送信する必要があります。 しかし、ここに最初の問題があります-この要求に依存するjs-codeが何も感じないように、これを行う必要があります。つまり、必要に応じて動作するためにすべてのコールバックが必要です(必要なことが判明したときに動作しないはずです)認証が、それが渡されたときにする必要があります)。 そして、2番目の問題は非同期リクエストに起因します-それらの多くが存在する可能性があり、複数のリクエストが一度にこの問題に遭遇することが判明する可能性があります。 そして、はい、*ユーザーがセッションを終了したと言うこと*-私たちの場合、これは応答コード「403」であり、応答本文は「401」(401、それは精神的に近いが、必要ではないため) WWW-Authenticateでは使用できますが、一般的には認証とはまったく関係がないため、少なくとも403は不可能です。



javascriptの愛


ためらうことなく、私は決断に至りました-プロキシパターンを使用して、実際にプロキシオブジェクトを作成し、それをXMLHttpRequest(XHR)に置き換えます。このプロキシ自体ではすでにXHRと直接通信しています。 そして、はい、Javascriptでクラスを別のクラスに置き換えることができることを想像してください。実際、ここでクラスは同じオブジェクト(または、用語ではプロトタイプではありません:私は強くありません:())。

それではまず、新しいClassName()でインスタンス化できる一般的なクラスをどのように作成し、そこにメソッドとプロパティを追加するのですか? Googleでできることすべてを見るにはいくつかの方法がありますが、おそらく最も簡単な方法を使用しました。ここにクラスの定義があります:

(function () { "use strict"; window.SomeClass = function () { var randNumber = Math.random(); this.someMethod = function () { console.log(randNumber); }; this.randomized = randNumber; }; })();
      
      







気づいたら、私はすぐに呼び出した関数にすべてのコードをすぐに詰めました。これはJSの一般的な慣行であり、最初はコードのクリーン化に使用されます(グローバルスコープを詰まらせません)、次にパフォーマンスのために(これはまず、1つのスコープ(この場合はグローバル)で多くの変数を作成し、それらにアクセスすると、インタープリターは変数を検索する時間が長くなります。最も近いスコープから始まり、グローバルに進みます)。 私もuse strictを使用しています 。これについてはここで読むことができます。特にIDEを使用している場合(特にJsLint / JsHintを使用している場合)、予期しない状況を避けるのに役立ちます。 コードについて-ご覧のとおり、グローバルスコープで「クラス」SomeClassを作成しました。実際、このクラスのコンストラクターは関数内のすべてのコードです。 その結果、クラス(より正確には、そのインスタンス)内からのみ表示されるrandNumber変数、同じクラスインスタンスに対して常に同じ番号をコンソールに送信するsomeMethod()メソッド、および同じ番号に等しいランダムプロパティがあります。 。

置換を行う:

 (function () { "use strict"; //   , ..       var XHR = window.XMLHttpRequest; window.XMLHttpRequest = function () { //    var o = new XHR(), t = this, reassignAllProperties = function reassign() { t.readyState = o.readyState; t.responseText = o.responseText; t.responseXML = o.responseXML; t.status = o.status; t.statusText = o.statusText; }; t.readyState = 0; t.responseText = ""; t.responseXML = null; t.status = null; t.statusText = ""; //    ,     //    reassignAllProperties() ..  //        - - t.open = function open() { o.open.apply(o, arguments); reassignAllProperties(); }; t.send = function send() { o.send.apply(o, arguments); reassignAllProperties(); }; t.abort = function abort() { o.abort(); reassignAllProperties(); }; t.setRequestHeader = o.setRequestHeader; t.overrideMimeType = o.overrideMimeType; t.getResponseHeader = o.getResponseHeader; t.getAllResponseHeaders = o.getAllResponseHeaders; t.onreadystatechange = function () {}; o.onreadystatechange = function onReady() { reassignAllProperties(); t.onreadystatechange(); }; }; })();
      
      







コードからわかるように、元のXMLHttpRequest(ロシア語版ウィキペディアのページで確認できるメソッドの概要/ sv)を完全に繰り返しています 。 さらに多くが必要です-サーバーの応答を追跡する必要があります。本文に403番目の回答と401が表示されている場合は、緊急にログインフォームを開きます。 ただし、カスタムコールバックは呼び出さないでください。 さらに、「中絶」の後、要求を再開して応答を受け取る機会があります。 したがって、いくつかのセッターメソッド(openおよびsendを含む)に渡されたすべてのデータをプロキシオブジェクトに格納する必要があり、リクエストが再開されると、これらすべてのメソッドを再度呼び出す必要があります。 しかし、問題は残ります-認証が原因で失敗したリクエストが行われ、ユーザーがログインした後にのみリクエストを再開し、onreadystatechangeイベントを起動する必要がある状況を想像してくださいが、このイベントはリクエストの前にトリガーされるべきではありません再起動されます。 解決策は簡単です-実際には、onreadystatechangeイベントが少なくとも4回発生し、readyStateプロパティがインクリメントされます(ここにすべての値があります )。そのため、答えが正当であることが確実な場合にのみカスタムコールバックを呼び出す必要があります。 ただし、「完了」以外の状態がどこかで使用されている場合、これを考慮する必要があります。最も簡単な方法は、ループ内で最後の3つのreadyState(2から4)でイベントを3回起動することです。 また、失敗したすべてのリクエストを保存する必要があります。その後、リクエストを再起動する必要があります。



最後の仕上げ


 (function () { "use strict"; //   , ..       var XHR = window.XMLHttpRequest, //       ,    failedRequestsPool = [], authenticationWindow = function () { $("#auth-overlay").show(); }; $("#auth-overlay form").submit(function () { $.ajax({ type: "post", url: "/login", data: { login: $("#auth-login").val(), password: $("#auth-password").val() }, dataType: "json", success: function (data) { if (data.state === "OK") { $("#auth-overlay").hide(); //   ,      for (var i in failedRequestsPool) { if (failedRequestsPool.hasOwnProperty(i)) { failedRequestsPool[i].retry(); } } failedRequestsPool = []; } } }); return false; }); window.XMLHttpRequest = function () { //    var o = new XHR(), t = this, //   ,       callback- aborted = false, reassignAllProperties = function reassign() { t.readyState = o.readyState; t.responseText = o.responseText; t.responseXML = o.responseXML; t.status = o.status; t.statusText = o.statusText; }, //      ,       data = { open: null, send: null, setRequestHeader: [], overrideMimeType: null }; t.readyState = 0; t.responseText = ""; t.responseXML = null; t.status = null; t.statusText = ""; t.retry = function retry() { aborted = false; //     o.open.apply(o, data.open); reassignAllProperties(); for (var i in data.setRequestHeader) { if (data.setRequestHeader.hasOwnProperty(i)) { o.setRequestHeader.apply(o, data.setRequestHeader[i]); } } if ("overrideMimeType" in o && data.overrideMimeType !== null) { o.overrideMimeType(data.overrideMimeType); } o.send(data.send); reassignAllProperties(); }; //    ,     //    reassignAllProperties() ..  //        - - t.open = function open() { data.open = arguments; // ,    o.open.apply(o, arguments); reassignAllProperties(); }; t.send = function send(body) { data.send = body; o.send(body); reassignAllProperties(); }; t.abort = function abort() { o.abort(); reassignAllProperties(); }; t.setRequestHeader = function setRequestHeader() { data.setRequestHeader.push(arguments); o.setRequestHeader.apply(o, arguments); }; //    IE     ,   if ("overrideMimeType" in o) { t.overrideMimeType = function (mime) { data.overrideMimeType = mime; o.overrideMimeType(mime); }; } t.getResponseHeader = o.getResponseHeader; t.getAllResponseHeaders = o.getAllResponseHeaders; t.onreadystatechange = function () {}; o.onreadystatechange = function onReady() { reassignAllProperties(); //          ,  ,   if (!aborted && o.state === 403 && o.responseText.indexOf("401") !== -1) { aborted = true; o.abort(); failedRequestsPool.push(t); authenticationWindow(); } //        ,    if (!aborted && o.readyState === 4) { for (var i = 1; i < 5; ++i) { t.readyState = i; t.onreadystatechange(); } } }; }; })();
      
      







そのため、非常に単純に、XHRをプロキシに置き換えました。これにより、ユーザーが「認証されていない」後に送信された要求を見逃すことがなくなります。



PSエラーに気付きました。オペラのgetAllResponseHeaders()メソッドはWRONG_THIS_ERRをスローしますが、それがどこから来たのかは完全に不明です。

PPP、元の問題を解決する最善の方法については議論しません。 事実は、このソリューションが認証解除の場合だけでなく便利になることです



UPDあなたが本当にそのような置換を行う必要がある場合(そして、コメントで多くの人が気づいているように、ルートライブラリを置き換えることはあまり良くないので、以前にそれについて考えることをお勧めします)、このソリューションを使用してくださいgithub.com/ilinsky/xmlhttprequest



All Articles