当前位置:网站首页>Use service worker to preferentially request resources - continuous update
Use service worker to preferentially request resources - continuous update
2022-06-13 04:37:00 【Let let】
Preface
See my personal blog for long-term updates , Because I can't synchronize updates on so many platforms https://blog.imlete.cn/article/Service-Worker-Preferred-Request-Resource.html
When your site or blog has multiple deployment sites , The access speed deployed on a platform is relatively fast , So you resolve your domain name to this platform , But sometimes it gets very slow , At this time, other sites may become a little faster than what you currently use , Is there any way to resolve domain names back and forth ? It's too troublesome
Is there any way to directly return to the fastest website resources ?
- Use the domain name management platform , Some platforms can parse sites in different networks or regions
for example Tencent cloud It can distinguish and analyze the three major domestic operators 、 Territory 、 overseas 、 And other parsing options ( Not very easy to use , You also need to test yourself , Is it difficult to ask friends who use mobile phones from other operators to help you test your speed ~) - Use js Block all requests from the website , And tamper with sending requests to all your sites , Among these sites, if which site returns the fastest , Then use the fastest returned information , At the same time, cut off all other requests
Text
This article details how to Use Service Worker Preferred request resource Make your website faster than before , A more stable
Service Worker In the following content, they are uniformly called sw
Service Worker
For more details, please see https://developer.mozilla.org/zh-CN/docs/Web/API/Service_Worker_API
Service workers Act essentially as Web Applications 、 Browser and Web ( When available ) Proxy server between
Service worker Running on the worker Context , So it can't access DOM. Relative to the main driver of the application JavaScript Threads , It runs in other threads , So it won't block .
For safety reasons ,Service workers Only by HTTPS bearing , After all, the ability to modify network requests can be very dangerous when exposed to man in the middle attacks .
register
register sw It's simple , Just one line of code , If the registration is successful , be sw Will be downloaded to the client and installed and activated , This step is just registration , The whole thing is : download —> install —> Activate
Be careful : sw The registration log of is recorded in Chrome In the browser, you can access chrome://serviceworker-internals see
navigator.serviceWorker.register('/Service-Worker.js')
among /Service-Worker.js
Must be under the current domain js file , He cannot be a member of another domain , Even if js The contents of the file are exactly the same , That won 't do either
If you only want to write in a certain path, use sw Words , You can use scope
Options , Of course /Service-Worker.js
The location of can also be customized ( As long as it is homologous and https The agreement will do ), As follows, only in /article/
Article page sw To launch , Other paths are written sw No processing
navigator.serviceWorker.register('/sw-test/Service-Worker.js', {
scope: '/article/' })
And must be https agreement , If it's local 127.0.0.1
or localhost
It's allowed
This is a complete registration code
Place the installation code in <head>
after
<script> ;(function () {
if ('serviceWorker' in navigator) {
navigator.serviceWorker .register('/sw.js') .then((result) => {
// Determine if... Is installed sw if (!localStorage.getItem('installSW')) {
localStorage.setItem('installSW', true) // There is no need to clean up here setInterval 了 , Because there will be no after the page is refreshed setInterval(() => {
// Judge sw After installation , Is it active , Refresh the page after activation if (result && result.active && result.active.state === 'activated') {
location.reload() } }, 100) } }) .catch((err) => {
console.log(err) }) } })() </script>
Life cycle
installing state
When the registration is successful, it will trigger install event , Then the trigger activate event , At this point, if you refresh the page again , Neither of them will be triggered
until /sw.js
It has changed , It will trigger once install ( It's not just code changes , Even if it is one more space or one less space , Or writing a comment will be triggered ), But only implemented install event , It's not implemented activate event
activing state
Why? activate The event is not triggered ? Because there is already one sw 了 , It is in a waiting state , As for when it will be triggered , That's what happened before we waited sw It will not trigger until it stops activate event
Is there any way not to let it wait ? The answer is : Yes
Use skipWaiting()
Skip waiting , It returns a promise object ( Asynchronous ), Prevention is still being implemented skipWaiting()
When I get there, I just jump to activate event , We need to use async/await
, You can also use event.waitUntil(skipWaiting())
Method to skipWaiting()
Put it in there , and async/await
The effect is the same
// sw.js
// stay sw Can be used in this or self Express oneself
self.addEventListener('install', async (event) => {
// event.waitUntil(self.skipWaiting())
await self.skipWaiting()
})
Trigger activate After the event , This time the web page will not be sw Managed , The next page refresh is required sw management , So how to make it manage pages immediately ?
For more details, please see :
https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/skipWaiting
https://developer.mozilla.org/en-US/docs/Web/API/Clients/claim
self.addEventListener('activate', async (event) => {
// event.waitUntil(self.clients.claim())
await self.clients.claim() // Manage pages now
})
install
The above life cycle has been described in detail
// sw.js
self.addEventListener('install', async (event) => {
// event.waitUntil(self.skipWaiting())
await self.skipWaiting()
})
Capture request
// sw.js
self.addEventListener('fetch', async (event) => {
// All requests have to go to handleRequest In function processing
handleRequest(event.request)
// If handleRequest If the request is successful, the data will be responded to the web page
.then((result) => event.respondWith(result))
// If handleRequest request was aborted , Don't do anything? ( This is the actual request of the web page )
.catch(() => 0)
})
// Processing requests
function handleRequest(req) {
return fetch(req.url)
}
event.respondWith()
: Give the browser a response , Because we have used Fetch API
Sent a request for the browser , And get the result and return , Then it is natural to return to the browser
Fetch API You can talk to XMLHttpRequest
equally , You can send any request
It is worth mentioning that Fetch API There will be cross domain problems when sending requests , Once it is intercepted across domains , Then there is no return from the above , The page will not display the requested content ( For example, the image is blocked across domains ), and img、script Label that they do not have cross domain request problems , So the above catch Catch an exception 0 and null almost
Since it is not used event.respondWith Of course, there is no data returned to the browser , Then the browser requests itself ( This problem is well avoided )
Tamper request
We can all use Fetch API
Sent a request for the browser , Can it be tampered with ?
// Processing requests
function handleRequest(req) {
// Just an example , More wonderful uses are waiting for you to explore
const str = 'https://cdn.jsdelivr.net/npm/xhr-ajax/dist/'
const url = req.url.replace(str + 'ajax.js', str + 'ajax.min.js')
return fetch(url)
}
Code above , We can ajax Third party Library of requests js File requests become compressed requests , And return to browser ( Tampering succeeded )
// Batch request
function handleRequest(req) {
// It can be more than one
const urls = [
"https://cdn.jsdelivr.net/npm/xhr-ajax/dist/ajax.min.js",
"https://unpkg.com/xhr-ajax/dist/ajax.min.js",
];
// Interrupt one or more requests
const controller = new AbortController();
// The request can be interrupted
// https://developer.mozilla.org/zh-CN/docs/Web/API/AbortController#%E5%B1%9E%E6%80%A7
const signal = controller.signal;
// Traversal converts all request addresses to promise
const PromiseAll = urls.map((url) => {
return new Promise(async (resolve, reject) => {
fetch(url, {
signal })
.then(
(res) =>
new Response(await res.arrayBuffer(), {
status: res.status,
headers: res.headers,
})
)
.then((res) => {
if (res.status !== 200) reject(null);
// As long as one request returns successfully, disconnect all requests
controller.abort(); // interrupt
resolve(res);
})
.catch(() => reject(null));
});
});
// Use Promise.any Send bulk requests , It receives an iteratable object , For example, an array is an iteratable object
return Promise.any(PromiseAll)
.then((res) => res)
.catch(() => null);
}
Promise.any Specific please see : https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/any
AbortController Specific please see : https://developer.mozilla.org/zh-CN/docs/Web/API/AbortController
As long as any one of the iteration objects passed in promise
Return to success (resolve) state , Then it returns the success status , If all of them promises
All failed , Then all the failures will be returned
So as long as Promise.any
There is a successful status data return , Then we will respond this data to the browser , Others All requests are cut off , In this way, you can efficiently provide users with the fastest response resources in different regions ~
This is also the problem we need to solve before the text begins
complete sw.js file
This is a summary I wrote sw.js file , You just need to put the following origin
Just change the array to your blog address , Others can be left untouched , If you want to add something new , It's up to you ~ Ha ha ha
const origin = ['https://blog.imlete.cn', 'https://lete114.github.io']
const cdn = {
gh: {
jsdelivr: 'https://cdn.jsdelivr.net/gh',
fastly: 'https://fastly.jsdelivr.net/gh',
gcore: 'https://gcore.jsdelivr.net/gh',
testingcf: 'https://testingcf.jsdelivr.net/gh',
test1: 'https://test1.jsdelivr.net/gh',
tianli: 'https://cdn1.tianli0.top/gh'
},
combine: {
jsdelivr: 'https://cdn.jsdelivr.net/combine',
fastly: 'https://fastly.jsdelivr.net/combine',
gcore: 'https://gcore.jsdelivr.net/combine',
testingcf: 'https://testingcf.jsdelivr.net/combine',
test1: 'https://test1.jsdelivr.net/combine',
tianli: 'https://cdn1.tianli0.top/combine'
},
npm: {
jsdelivr: 'https://cdn.jsdelivr.net/npm',
fastly: 'https://fastly.jsdelivr.net/npm',
gcore: 'https://gcore.jsdelivr.net/npm',
testingcf: 'https://testingcf.jsdelivr.net/npm',
test1: 'https://test1.jsdelivr.net/npm',
eleme: 'https://npm.elemecdn.com',
unpkg: 'https://unpkg.com',
tianli: 'https://cdn1.tianli0.top/npm'
}
}
self.addEventListener('install', async () => {
await self.skipWaiting()
})
self.addEventListener('activate', async () => {
await self.clients.claim()
})
self.addEventListener('fetch', async (event) => {
try {
event.respondWith(handleRequest(event.request))
} catch (e) {
}
})
// Return response
async function progress(res) {
return new Response(await res.arrayBuffer(), {
status: res.status,
headers: res.headers
})
}
function handleRequest(req) {
const urls = []
const urlStr = req.url
let urlObj = new URL(urlStr)
// In order to obtain cdn type
// For example, get gh (https://cdn.jsdelivr.net/gh)
const path = urlObj.pathname.split('/')[1]
// matching cdn
for (const type in cdn) {
if (type === path) {
for (const key in cdn[type]) {
const url = cdn[type][key] + urlObj.pathname.replace('/' + path, '')
urls.push(url)
}
}
}
// If it's above cdn Traverse Match to cdn Send the request directly and uniformly ( It won't go down )
if (urls.length) return fetchAny(urls)
// Merge the current web site visited by the user with all source sites
let origins = [location.origin, ...origin]
// Traverse to determine whether the current request is the origin host
const is = origins.find((i) => {
const {
hostname } = new URL(i)
const reg = new RegExp(hostname)
return urlStr.match(reg)
})
// If it is the origin , Then race to get ( It won't go down )
if (is) {
origins = origins.map((i) => i + urlObj.pathname + urlObj.search)
return fetchAny(origins)
}
// The exception is thrown to make sw Don't intercept requests
throw new Error(' Not the origin ')
}
// Promise.any Of polyfill
function createPromiseAny() {
Promise.any = function (promises) {
return new Promise((resolve, reject) => {
promises = Array.isArray(promises) ? promises : []
let len = promises.length
let errs = []
if (len === 0) return reject(new AggregateError('All promises were rejected'))
promises.forEach((p) => {
if (!p instanceof Promise) return reject(p)
p.then(
(res) => resolve(res),
(err) => {
len--
errs.push(err)
if (len === 0) reject(new AggregateError(errs))
}
)
})
})
}
}
// Send all requests
function fetchAny(urls) {
// Interrupt one or more requests
const controller = new AbortController()
const signal = controller.signal
// Traversal converts all request addresses to promise
const PromiseAll = urls.map((url) => {
return new Promise((resolve, reject) => {
fetch(url, {
signal })
.then(progress)
.then((res) => {
const r = res.clone()
if (r.status !== 200) reject(null)
controller.abort() // interrupt
resolve(r)
})
.catch(() => reject(null))
})
})
// Determine whether the browser supports Promise.any
if (!Promise.any) createPromiseAny()
// Who will go back first " Success status " Whose content is returned , If they all return " Failure status " Then return to null
return Promise.any(PromiseAll)
.then((res) => res)
.catch(() => null)
}
边栏推荐
- [automated test] what you need to know about unittest
- 2022 ICML | Pocket2Mol: Efficient Molecular Sampling Based on 3D Protein Pockets
- Basic syntax example for go
- Solve the problem of running server nodemon reporting errors
- php安全开发15用户密码修改模块
- Blockly learning ----1 Work area, block, toolbox
- Latex operation
- Message scrolling JS implementation
- 一款開源的Markdown轉富文本編輯器的實現原理剖析
- PHP development 16 exit module
猜你喜欢
Express framework knowledge - Art template template, cookie, session
ACM ICPC
一致性哈希的简单认识
120. triangle minimum path sum - Dynamic Planning
Day 007: go language string
Ultra quicksort reverse sequence pair
Redis
Colab使用教程(超级详细版)及Colab Pro/Pro+评测
The data obtained from mongodb query data command is null
Li Kou brush question 338 Bit count
随机推荐
力扣刷题647.回文子串
Collection of some compatibility issues
Develop go using vscode
[chapter 67 of the flutter problem series] the solution to the problem that the get plug-in cannot jump to the route twice in the dialog pop-up window in flutter
2022年建筑架子工(建筑特殊工种)特种作业证考试题库及在线模拟考试
Webpack system learning (VIII) how contenthash can prevent browsers from using cache files
This Sedata uses multiple methods to dynamically modify objects and values in arrays. Object calculation properties
2022 ICML | Pocket2Mol: Efficient Molecular Sampling Based on 3D Protein Pockets
Small program input element moving up
PHP syntax
一致性哈希的简单认识
PHP development 16 exit module
Ctfshow SQL injection (231-253)
Implementation of article list function on PHP 18 home page
Redis master-slave replication, sentinel mode, cluster
PowerShell plus domain add computer module
工业互联网通用通信协议
Crawler scrapy framework learning 1
2022氯化工艺操作证考试题库及模拟考试
[automated test] what you need to know about unittest