javascript - هل Safari على iOS 6 يخبئ $.ajax النتائج؟




jquery caching (17)

آمل أن يكون هذا مفيدًا للمطورين الآخرين الذين يقرعون رؤوسهم ضد الجدار على هذا الجهاز. لقد اكتشفت أن أيًا مما يلي يمنع Safari على iOS 6 من التخزين المؤقت لاستجابة POST:

  • إضافة [cache-control: no-cache] في رؤوس الطلبات
  • إضافة معلمة URL متغير مثل الوقت الحالي
  • إضافة [pragma: no-cache] في رؤوس الاستجابة
  • إضافة [cache-control: no-cache] في رؤوس الاستجابة

كان الحل الخاص بي هو التالي في Javascript الخاص بي (جميع طلبات AJAX الخاصة بي هي POST).

$.ajaxSetup({
    type: 'POST',
    headers: { "cache-control": "no-cache" }
});

وأضيف أيضًا رأس [pragma: no-cache] للعديد من استجابات الخادم.

إذا كنت تستخدم الحل أعلاه ، فكن على دراية بأن أية مكالمات $ .ajax () يتم إجراؤها والتي تم تعيينها على العمومية: false لن تستخدم الإعدادات المحددة في $ .ajaxSetup () ، لذا ستحتاج إلى إضافة الرؤوس مرة أخرى.

منذ الترقية إلى نظام التشغيل iOS 6 ، نرى أن مشاهدة الويب الخاصة $.ajax بحرية التخزين المؤقت $.ajax . هذا في سياق تطبيق PhoneGap لذا فهو يستخدم WebView سفاري. مكالمات $.ajax هي طرق POST ويتم تعيين ذاكرة التخزين المؤقت على false {cache:false} ، ولكن لا يزال هذا يحدث. لقد حاولنا يدويًا إضافة " TimeStamp إلى الرؤوس ولكنه لم يساعد.

أجرينا المزيد من البحث ووجدنا أن Safari لا يعرض سوى النتائج المخزنة مؤقتًا لخدمات الويب التي لها توقيع دالة ثابت ولا يتغير من الاتصال إلى المكالمة. على سبيل المثال ، تخيل وظيفة تسمى شيئًا مثل:

getNewRecordID(intRecordType)

وتتلقى هذه الوظيفة نفس معلمات الإدخال مرارًا وتكرارًا ، ولكن البيانات التي يتم إرجاعها يجب أن تكون مختلفة في كل مرة.

يجب أن تكون في سرعة شركة Apple لجعل نظام iOS 6 zip ممتعًا بشكل مبهر عندما يكونوا سعداء جدًا باستخدام إعدادات ذاكرة التخزين المؤقت. هل شاهد أي شخص آخر هذا السلوك على iOS 6؟ إذا كان الأمر كذلك ، فما الذي يسببها بالضبط؟

كان الحل الذي وجدناه هو تعديل التوقيع الوظيفي ليكون شيئًا كالتالي:

getNewRecordID(intRecordType, strTimestamp)

ثم تمريرها دائمًا في معلمة TimeStamp ، وكذلك تجاهل تلك القيمة على جانب الخادم. هذا يعمل حول هذه القضية. آمل أن يساعد هذا بعض الفقراء الآخرين الذين يقضون 15 ساعة في هذه القضية كما فعلت!


أخيرًا ، لدي حل لمشكلة التحميل.

في JavaScript:

var xhr = new XMLHttpRequest();
xhr.open("post", 'uploader.php', true);
xhr.setRequestHeader("pragma", "no-cache");

في PHP :

header('cache-control: no-cache');

أقترح حلاً لتعديل توقيع الدالة ليكون شيئًا كالتالي:

getNewRecordID (intRecordType ، strTimestamp) ومن ثم تمرير دوماً في معلمة TimeStamp وكذلك فقط تجاهل تلك القيمة على جانب الملقم. هذا يعمل حول هذه القضية.


إن العمل السريع حول خدمات GWT-RPC هو إضافة هذا إلى جميع الطرق البعيدة:

getThreadLocalResponse().setHeader("Cache-Control", "no-cache");

بالنسبة لأولئك الذين يستخدمون Struts 1 ، فإليك الطريقة التي أصلحت بها المشكلة.

web.xml

<filter>
    <filter-name>SetCacheControl</filter-name>
    <filter-class>com.example.struts.filters.CacheControlFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>SetCacheControl</filter-name>
    <url-pattern>*.do</url-pattern>
    <http-method>POST</http-method>
</filter-mapping>

com.example.struts.filters.CacheControlFilter.js

package com.example.struts.filters;

import java.io.IOException;
import java.util.Date;
import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;

public class CacheControlFilter implements Filter {

        public void doFilter(ServletRequest request, ServletResponse response,
                     FilterChain chain) throws IOException, ServletException {

        HttpServletResponse resp = (HttpServletResponse) response;
        resp.setHeader("Expires", "Mon, 18 Jun 1973 18:00:00 GMT");
        resp.setHeader("Last-Modified", new Date().toString());
        resp.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0");
        resp.setHeader("Pragma", "no-cache");

        chain.doFilter(request, response);
    }

    public void init(FilterConfig filterConfig) throws ServletException {
    }

    public void destroy() {
    }

}

بعد قليل من التحقيق ، تبين أن Safari على iOS6 سيقوم بتخزين POSTs التي لا تحتوي على رؤوس Cache-Control أو حتى Cache-Control: max-age = 0.

الطريقة الوحيدة التي وجدتها لمنع هذا التخزين المؤقت من الحدوث على المستوى العالمي بدلاً من الاضطرار إلى اختراق عمليات الاستدعاء العشوائية في نداءات نهاية الخدمة هي تعيين "Cache-Control: no-cache".

وبالتالي:

  • لا توجد رؤوس Cache-Control أو Expires = iOS6 Safari will cache
  • Cache-Control max-age = 0 and an Expires Expires = iOS6 Safari cache
  • Cache-Control: no-cache = iOS6 Safari will NOT cache

أظن أن أبل تستفيد من هذا من مواصفات HTTP في القسم 9.5 حول POST:

الردود على هذه الطريقة غير قابلة للتخزين المؤقت ، ما لم تتضمن الاستجابة حقول رأسية Cache-Control أو Expires مناسبة. ومع ذلك ، يمكن استخدام استجابة 303 (انظر غير ذلك) لتوجيه وكيل المستخدم لاسترداد مورد قابل للتخزين المؤقت.

لذا من الناحية النظرية ، يمكنك تخزين ردود POST ... التي عرفتها. ولكن لم يعتقد أي صانع متصفح آخر أنها ستكون فكرة جيدة حتى الآن. ولكن هذا لا يمثل التخزين المؤقت عندما لا يتم تعيين رؤوس Cache-Control أو Expires ، فقط عندما يكون هناك مجموعة معينة. لذلك يجب أن يكون علة.

في ما يلي ما أستخدمه في الجزء الصحيح من تهيئة Apache الخاصة بي لاستهداف واجهة برمجة التطبيقات الخاصة بي بالكامل ، لأنه عندما يحدث ذلك ، لا أريد تخزين كل شيء مؤقتًا ، حتى لو حصل. ما لا أعرفه هو كيفية ضبط هذا فقط لـ POSTs.

Header set Cache-Control "no-cache"

تحديث: لاحظت فقط أنني لم أشير إلى أنه فقط عندما يكون POST هو نفسه ، لذا قم بتغيير أي من بيانات POST أو عنوان URL وأنت بخير. لذلك يمكنك كما ذكر في مكان آخر إضافة بعض البيانات العشوائية إلى عنوان URL أو جزء صغير من بيانات POST.

تحديث: يمكنك تحديد "no-cache" فقط إلى POSTs إذا كنت ترغب في ذلك في Apache:

SetEnvIf Request_Method "POST" IS_POST
Header set Cache-Control "no-cache" env=IS_POST

بينما تعمل صفحات تسجيل الدخول والتسجيل مثل سحر في Firefox و IE و Chrome ... أنا أعاني من هذه المشكلة في Safari لـ IOS و OSX ، قبل بضعة أشهر ، وجدت حلًا على SO.

<body onunload="">

أو عبر javascript

<script type="text/javascript">
window.onunload = function(e){
    e.preventDefault();
    return;
};
</script>   

هذا شيء قبيح كيندا ولكن يعمل لفترة من الوقت.

لا أعرف سبب ذلك ، ولكن مع إعادة القيمة الفارغة إلى حدث onunload لا يتم تخزين الصفحة مؤقتًا في Safari.


حل بسيط لجميع طلبات خدمة الويب ، على افتراض أنك تستخدم jQuery:

$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
    // you can use originalOptions.type || options.type to restrict specific type of requests
    options.data = jQuery.param($.extend(originalOptions.data||{}, { 
      timeStamp: new Date().getTime()
    }));
});

اقرأ المزيد حول استدعاء pre-fq jQuery here .

إذا كنت لا تستخدم jQuery ، فراجع المستندات الخاصة بمكتبتك المختارة. قد يكون لديهم وظائف مماثلة.


في حين أن إضافة معلمات ذاكرة التخزين المؤقت لجعل الطلب يبدو مختلفًا يبدو وكأنه حلًا صامدًا ، فإنني أنصحه ضده ، حيث إنه سيؤذي أي تطبيق يعتمد على التخزين المؤقت الفعلي. يعد جعل واجهات برمجة التطبيقات إخراج الرؤوس الصحيحة هو أفضل حل ممكن ، حتى لو كان ذلك أصعب قليلاً من إضافة أجهزة ضبط ذاكرة التخزين المؤقت إلى المتصلين.


في روبي سيناترا

before '*' do
  if env['REQUEST_METHOD'] == 'POST'
    headers 'Cache-Control' => 'no-cache, no-store, must-revalidate'
  end
end

لحل هذه المشكلة من أجل WebApps المضافة إلى الشاشة الرئيسية ، يجب اتباع كلا من الحلول العليا المصوتة. يجب إيقاف تشغيل التخزين المؤقت على خادم الويب لمنع تخزين الطلبات الجديدة مؤقتًا من الآن فصاعدًا ، كما يجب إضافة بعض المدخلات العشوائية إلى كل طلب نشر من أجل الطلبات التي تم تخزينها مؤقتًا بالفعل. يرجى الرجوع إلى منصبي:

iOS6 - هل هناك طريقة لمسح طلبات PAG المخبأة في PAG التي تمت إضافتها إلى الشاشة الرئيسية؟

تحذير: لأي شخص قام بتطبيق حل بديل عن طريق إضافة طابع زمني إلى طلباته دون إيقاف تشغيل التخزين المؤقت على الخادم. إذا تمت إضافة تطبيقك إلى الشاشة الرئيسية ، فسيتم تخزين كل رد ظاهر مؤقتًا الآن ، ولا يؤدي مسح ذاكرة التخزين المؤقت الخاصة بسفاري التخزين إلى مسحها ، ولا يبدو أنها تنتهي صلاحيتها. ما لم يكن لدى شخص طريقة لإزالته ، يبدو هذا وكأنه تسرب للذاكرة محتمل!


لقد تمكنت من إصلاح مشكلتي باستخدام مجموعة من $ .ajaxSetup وإلحاق طابع زمني إلى عنوان url الخاص بي (وليس إلى معلمات / نصوص المشاركة). هذا يعتمد على توصيات الإجابات السابقة

$(document).ready(function(){
    $.ajaxSetup({ type:'POST', headers: {"cache-control","no-cache"}});

    $('#myForm').submit(function() {
        var data = $('#myForm').serialize();
        var now = new Date();
        var n = now.getTime();
        $.ajax({
            type: 'POST',
            url: 'myendpoint.cfc?method=login&time='+n,
            data: data,
            success: function(results){
                if(results.success) {
                    window.location = 'app.cfm';
                } else {
                    console.log(results);
                    alert('login failed');
                }
            }
        });
    });
});

لقد واجهت هذه المشكلة أيضًا في تطبيق PhoneGap . أنا حلها باستخدام وظيفة جافا سكريبت getTime() بالطريقة التالية:

var currentTime = new Date();
var n = currentTime.getTime();
postUrl = "http://www.example.com/test.php?nocache="+n;
$.post(postUrl, callbackFunction);

لقد أضعت بضع ساعات في فهم ذلك. كان من الرائع لشركة أبل إخطار مطوري مشكلة التخزين المؤقت هذه.


لقد وجدت أحد الحلول التي تجعلني أشعر بالفضول بشأن سبب نجاحها. قبل قراءة إجابة Tadej بخصوص خدمة ASP.NET على الويب ، كنت أحاول أن أحقق شيئًا قد ينجح.

وأنا لا أقول إنه حل جيد ، لكني أردت فقط توثيقه هنا.

الصفحة الرئيسية: تتضمن وظيفة JavaScript ، checkStatus (). تستدعي الطريقة طريقة أخرى تستخدم استدعاء jQuery AJAX لتحديث محتوى html. اعتدت setInterval للاتصال checkStatus (). بالطبع ، واجهت مشكلة التخزين المؤقت.

الحل: استخدم صفحة أخرى للاتصال بالتحديث.

في الصفحة الرئيسية ، أضبط متغيرًا منطقيًا ، و runUpdate ، وأضفنا ما يلي إلى علامة النص الأساسي:

<iframe src="helper.html" style="display: none; visibility: hidden;"></iframe>

في من helper.html:

<meta http-equiv="refresh" content="5">
<script type="text/javascript">
    if (parent.runUpdate) { parent.checkStatus(); }
</script>

لذلك ، إذا تم استدعاء checkStatus () من الصفحة الرئيسية ، فسوف أحصل على المحتوى المخزن مؤقتًا. إذا اتصلت checkStatus من الصفحة الفرعية ، فأنا أحصل على محتوى محدث.


هذا تحديث من إجابة Baz1nga. نظرًا لأن options.data ليس كائنًا ولكن سلسلة لجأت فقط إلى تسلسل الطابع الزمني:

$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
  if (originalOptions.type == "post" || options.type == "post") {

    if (options.data && options.data.length)
      options.data += "&";
    else
      options.data = "";

    options.data += "timeStamp=" + new Date().getTime();
  }
});

هذا هو العمل حول GWT-RPC

class AuthenticatingRequestBuilder extends RpcRequestBuilder 
{
       @Override
       protected RequestBuilder doCreate(String serviceEntryPoint) 
       {
               RequestBuilder requestBuilder = super.doCreate(serviceEntryPoint);           
               requestBuilder.setHeader("Cache-Control", "no-cache");

               return requestBuilder;
       }
}

AuthenticatingRequestBuilder builder = new AuthenticatingRequestBuilder();
((ServiceDefTarget)myService).setRpcRequestBuilder(builder);    

يمكنك أيضًا إصلاح هذه المشكلة عن طريق تعديل وظيفة jQuery Ajax عن طريق القيام بما يلي (بدءًا من 1.7.1) إلى أعلى الدالة Ajax (تبدأ الدالة في السطر 7212). سيؤدي هذا التغيير إلى تنشيط ميزة مكافحة ذاكرة التخزين المؤقت المضمنة في jQuery لجميع طلبات POST.

(النص الكامل متاح على http://dl.dropbox.com/u/58016866/jquery-1.7.1.js .)

أدخل أدناه خط 7221:

if (options.type === "POST") {
    options.cache = false;
}

ثم تعديل ما يلي (ابتداء من السطر ~ 7497).

if (!s.hasContent) {
    // If data is available, append data to URL
    if (s.data) {
        s.url += (rquery.test(s.url) ? "&" : "?") + s.data;
        // #9682: remove data so that it's not used in an eventual retry
        delete s.data;
    }

    // Get ifModifiedKey before adding the anti-cache parameter
    ifModifiedKey = s.url;

    // Add anti-cache in URL if needed
    if (s.cache === false) {
        var ts = jQuery.now(),
        // Try replacing _= if it is there
        ret = s.url.replace(rts, "$1_=" + ts);

        // If nothing was replaced, add timestamp to the end.
        s.url = ret + ((ret === s.url) ? (rquery.test(s.url) ? "&" : "?") + "_=" + ts : "");
    }
}

إلى:

// More options handling for requests with no content
if (!s.hasContent) {
    // If data is available, append data to URL
    if (s.data) {
        s.url += (rquery.test(s.url) ? "&" : "?") + s.data;
        // #9682: remove data so that it's not used in an eventual retry
        delete s.data;
    }

    // Get ifModifiedKey before adding the anti-cache parameter
    ifModifiedKey = s.url;
}

// Add anti-cache in URL if needed
if (s.cache === false) {
    var ts = jQuery.now(),
    // Try replacing _= if it is there
    ret = s.url.replace(rts, "$1_=" + ts);

    // If nothing was replaced, add timestamp to the end.
    s.url = ret + ((ret === s.url) ? (rquery.test(s.url) ? "&" : "?") + "_=" + ts : "");
}




mobile-safari