• Service Worker 离线无法缓存Post请求的问题解决


    许多非REST API甚至可以用于读取数据的POST请求:典型的例子是graphql、soap和其他rpcpapi。但是,Post请求不能在一个现成的渐进式Web应用程序中缓存和脱机使用。浏览器的缓存API不会接受它们。下面是一个在IncedB中使用自定义缓存的解决方案。

    幸运的是Service Worker可以截获任何异步请求,因此我们处理POST请求没有问题。缺少的是在离线时缓存它们并检索相应的响应的可能性。

    在下面的示例中,我们将在indexeddb中创建一个简单的缓存,并将其用作回退(networkfirst策略)。注意,我们将使用整个请求的字符串化版本作为缓存键。这是非常安全的,但会为具有长主体和/或头的请求生成大密钥。如果这对您来说是一个问题,您可以选择另一种方法来构建键,或者只使用lz字符串或类似的库来压缩它。

    一句警告

    当然,有一些原因可以解释为什么post请求不属于当前Service Worker规范的一部分。以下是其中一些:

    从理论上讲,POSTs将修改数据。当然,您不能使用正在尝试修改的数据缓存版本。所以不要缓存任何类型的修改请求!

    这些API使用文章进行阅读,通常不会为每个可能的结果提供单独的URL。相反,它们只有一个URL来检索不同的数据集,并接受请求主体中某种类型的查询。这意味着,您不能缓存基于URL的响应(就像缓存API一样),而是需要使用自己的组合键,包括主体和可能的一些头。

    所以在将post请求缓存到您的数据之前,请仔细考虑!如果您正在寻找一种使修改post请求离线工作的方法,我建议您查看用于延迟发布的Mozilla代码配方。

    如何在服务工作进程中缓存发布请求

    对于缓存所有post请求的工作示例,请将此代码放在serviceworker.js中。我在这里使用dexie.js访问indexeddb,但是任何其他方法也可以。

    https://dexie.org/

    importScripts('your/path/to/dexie/dist/dexie.min.js');
    
    // Listen to fetch requests
    self.addEventListener('fetch', function(event) {
    // We will cache all POST requests, but in the real world, you will probably filter for
    // specific URLs like if(... || event.request.url.href.match(...))
    if(event.request.method === "POST"){
    
    // Init the cache. We use Dexie here to simplify the code. You can use any other
    // way to access IndexedDB of course.
    var db = new Dexie("post_cache");
    db.version(1).stores({
    post_cache: 'key,response,timestamp'
    })
    
    event.respondWith(
    // First try to fetch the request from the server
    fetch(event.request.clone())
    .then(function(response) {
    // If it works, put the response into IndexedDB
    cachePut(event.request.clone(), response.clone(), db.post_cache);
    return response;
    })
    .catch(function() {
    // If it does not work, return the cached response. If the cache does not
    // contain a response for our request, it will give us a 503-response
    return cacheMatch(event.request.clone(), db.post_cache);
    })
    );
    }
    })
    
    /**
    * Serializes a Request into a plain JS object.
    * 
    * @param request
    * @returns Promise
    */ 
    function serializeRequest(request) {
    var serialized = {
    url: request.url,
    headers: serializeHeaders(request.headers),
    method: request.method,
    mode: request.mode,
    credentials: request.credentials,
    cache: request.cache,
    redirect: request.redirect,
    referrer: request.referrer
    };
    
    // Only if method is not `GET` or `HEAD` is the request allowed to have body.
    if (request.method !== 'GET' && request.method !== 'HEAD') {
    return request.clone().text().then(function(body) {
    serialized.body = body;
    return Promise.resolve(serialized);
    });
    }
    return Promise.resolve(serialized);
    }
    
    /**
    * Serializes a Response into a plain JS object
    * 
    * @param response
    * @returns Promise
    */ 
    function serializeResponse(response) {
    var serialized = {
    headers: serializeHeaders(response.headers),
    status: response.status,
    statusText: response.statusText
    };
    
    return response.clone().text().then(function(body) {
    serialized.body = body;
    return Promise.resolve(serialized);
    });
    }
    
    /**
    * Serializes headers into a plain JS object
    * 
    * @param headers
    * @returns object
    */ 
    function serializeHeaders(headers) {
    var serialized = {};
    // `for(... of ...)` is ES6 notation but current browsers supporting SW, support this
    // notation as well and this is the only way of retrieving all the headers.
    for (var entry of headers.entries()) {
    serialized[entry[0]] = entry[1];
    }
    return serialized;
    }
    
    /**
    * Creates a Response from it's serialized version
    * 
    * @param data
    * @returns Promise
    */ 
    function deserializeResponse(data) {
    return Promise.resolve(new Response(data.body, data));
    }
    
    /**
    * Saves the response for the given request eventually overriding the previous version
    * 
    * @param data
    * @returns Promise
    */
    function cachePut(request, response, store) {
    var key, data;
    getPostId(request.clone())
    .then(function(id){
    key = id;
    return serializeResponse(response.clone());
    }).then(function(serializedResponse) {
    data = serializedResponse;
    var entry = {
    key: key,
    response: data,
    timestamp: Date.now()
    };
    store
    .add(entry)
    .catch(function(error){
    store.update(entry.key, entry);
    });
    });
    }
    
    /**
    * Returns the cached response for the given request or an empty 503-response for a cache miss.
    * 
    * @param request
    * @return Promise
    */
    function cacheMatch(request) {
    return getPostId(request.clone())
    .then(function(id) {
    return store.get(id);
    }).then(function(data){
    if (data) {
    return deserializeResponse(data.response);
    } else {
    return new Response('', {status: 503, statusText: 'Service Unavailable'});
    }
    });
    }
    
    /**
    * Returns a string identifier for our POST request.
    * 
    * @param request
    * @return string
    */
    
    function getPostId(request) {
    
                            return new Promise((resolve, reject) => {
    
                                        return JSON.stringify(serializeRequest(request.clone()));
    
                            });
    
     }
    

      

  • 相关阅读:
    kitkat-s5p4418drone 记录
    Android USER 版本与ENG 版本的差异
    Android中的Apk的加固(加壳)原理解析和实现(转)
    Android悬浮窗实现 使用WindowManager
    Android系统的开机画面显示过程分析
    【PMP】变更流程图与说明
    【PMP】十五至尊图
    【Excle】一个比VLOOKUP牛的函数LOOKUP
    【DB2】慎用nickname,可能会引起效率较低
    【读书】人生向前-读书笔记
  • 原文地址:https://www.cnblogs.com/Cavalry/p/10579349.html
Copyright © 2020-2023  润新知