还能把浏览器当作 Web 服务器?骚操作,学废了~

5,370 阅读3分钟

这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战

楔子

什么?还能把浏览器当作 Web 服务器?

闲话少说,直接干货!

整体思路:PWA 中用于缓存文件的 server workers 可以动态生成新文件,并通过 fetch 事件,将它们发送至浏览器!

  • 不熟悉 PWA 的朋友们可简单了解如下:

PWA(Progressive Web Apps) 翻译为 渐进式网页应用,它是一种构建 Web 应用程序的新理念,涉及 一些 特定的模式,API 和其他功能。

它能实现传统 web 所不能做到的:离线工作、可安装、易于同步、可以发送推送通知等;

  • 不熟悉 server workers 的朋友们可简单了解如下:

server workers 就是一个服务器与浏览器之间的中间人角色,如果网站中注册了service worker,那么它可以拦截当前网站所有的请求,进行判断(需要编写相应的判断程序),如果需要向服务器发起请求的就转给服务器,如果可以直接使用缓存的就直接返回缓存不再转给服务器。从而大大提高浏览体验。

所以,通过 server workers 可以发送文件至浏览器!

工具

somorphic-git 是 git 的纯 JavaScript 实现,适用于 Node 和浏览器环境(包括WebWorkers 和 ServiceWorkers);

它可以用于读写 git 库,以及从 Github 获取和推送。

BrowserFS 与 Webpack 类似,也是模块打包工具;

它的特点:

  1. 基于流式(stream)思想设计
  2. 可以通过command line,也可以通过API来使用
  3. 仅处理javascript
  4. 模块化的逆过程,但是推动着模块化的更好发展
  5. 内置了一些 node core module
  6. node模块可以在浏览器端使用,是 同构应用 的有力武器

它让浏览器文件读写更加快速,快如闪电⚡~

这个相信大家并不陌生,indexedDB 可以实现在客户端存储大量的结构化数据~~

实现

代码实现:

/**@license
 *   ___ ___ _____  __      __   _      _____              _           _
 *  / __|_ _|_   _| \ \    / /__| |__  |_   _|__ _ _ _ __ (_)_ _  __ _| |
 * | (_ || |  | |    \ // / -_) '_ \   | |/ -_) '_| '  | | ' / _` | |
 *  ___|___| |_|     _/_/___|_.__/   |_|___|_| |_|_|_|_|_||___,_|_|
 *
 * this is service worker and it's part of GIT Web terminal
 *
 * Copyright (c) 2018 Jakub Jankiewicz <http://jcubic.pl/me>
 * Released under the MIT license
 *
 */
/* global BrowserFS, Response, setTimeout, fetch, Blob, Headers */
self.importScripts('https://cdn.jsdelivr.net/npm/browserfs');

self.addEventListener('install', self.skipWaiting);

self.addEventListener('activate', self.skipWaiting);

self.addEventListener('fetch', function (event) {
    let path = BrowserFS.BFSRequire('path');
    let fs = new Promise(function(resolve, reject) {
        BrowserFS.configure({ fs: 'IndexedDB', options: {} }, function (err) {
            if (err) {
                reject(err);
            } else {
                resolve(BrowserFS.BFSRequire('fs'));
            }
        });
    });
    event.respondWith(fs.then(function(fs) {
        return new Promise(function(resolve, reject) {
            function sendFile(path) {
                fs.readFile(path, function(err, buffer) {
                    if (err) {
                        err.fn = 'readFile(' + path + ')';
                        return reject(err);
                    }
                    var ext = path.replace(/.*./, '');
                    var mime = {
                        'html': 'text/html',
                        'json': 'application/json',
                        'js': 'application/javascript',
                        'css': 'text/css'
                    };
                    var headers = new Headers({
                        'Content-Type': mime[ext]
                    });
                    resolve(new Response(buffer, {headers}));
                });
            }
            var url = event.request.url;
            var m = url.match(/__browserfs__(.*)/);
            function redirect_dir() {
                return resolve(Response.redirect(url + '/', 301));
            }
            function serve() {
                fs.stat(path, function(err, stat) {
                    if (err) {
                        return resolve(textResponse(error404Page(path)));
                    }
                    if (stat.isFile()) {
                        sendFile(path);
                    } else if (stat.isDirectory()) {
                        if (path.substr(-1, 1) !== '/') {
                            return redirect_dir();
                        }
                        fs.readdir(path, function(err, list) {
                            if (err) {
                                err.fn = 'readdir(' + path + ')';
                                return reject(err);
                            }
                            var len = list.length;
                            if (list.includes('index.html')) {
                                sendFile(path + '/index.html');
                            } else {
                                listDirectory({fs, path, list}).then(function(list) {
                                    resolve(textResponse(fileListingPage(path, list)));
                                }).catch(reject);
                            }
                        });
                    }
                });
            }
            if (m) {
                var path = m[1];
                if (path === '') {
                    return redirect_dir();
                }
                console.log('serving ' + path + ' from browserfs');
                serve();
            } else {
                if (event.request.cache === 'only-if-cached' && event.request.mode !== 'same-origin') {
                    return;
                }
                //request = credentials: 'include'
                fetch(event.request).then(resolve).catch(reject);
            }
        });
    }));
});
// -----------------------------------------------------------------------------
function listDirectory({fs, path, list}) {
    return new Promise(function(resolve, reject) {
        var items = [];
        (function loop() {
            var item = list.shift();
            if (!item) {
                return resolve(items);
            }
            fs.stat(path + '/' + item, function(err, stat) {
                if (err) {
                    err.fn = 'stat(' + path + '/' + item + ')';
                    return reject(err);
                }
                items.push(stat.isDirectory() ? item + '/' : item);
                loop();
            });
        })();
    });
}

// -----------------------------------------------------------------------------
function textResponse(string, filename) {
    var blob = new Blob([string], {
        type: 'text/html'
    });
    return new Response(blob);
}

// -----------------------------------------------------------------------------
function fileListingPage(path, list) {
    var output = [
        '<!DOCTYPE html>',
        '<html>',
        '<body>',
        `<h1>BrowserFS ${path}</h1>`,
        '<ul>'
    ];
    if (path.match(/^/(.*/)/)) {
        output.push('<li><a href="..">..</a></li>');
    }
    list.forEach(function(name) {
        output.push('<li><a href="' + name + '">' + name + '</a></li>');
    });
    output = output.concat(['</ul>', '</body>', '</html>']);
    return output.join('\n');
}

// -----------------------------------------------------------------------------
function error404Page(path) {
    var output = [
        '<!DOCTYPE html>',
        '<html>',
        '<body>',
        '<h1>404 File Not Found</h1>',
        `<p>File ${path} not found in browserfs`,
        '</body>',
        '</html>'
    ];
    return output.join('\n');
}

初始化 service worker 如下:

if ('serviceWorker' in navigator) {
var scope = location.pathname.replace(/\/[^\/]+$/, '/');
if (!scope.match(/__browserfs__/)) {
navigator.serviceWorker.register('sw.js', {scope})
.then(function(reg) {
reg.addEventListener('updatefound', function() {
var installingWorker = reg.installing;
console.log('A new service worker is being installed:',
installingWorker);
});
// registration worked
console.log('Registration succeeded. Scope is ' +
reg.scope);
}).catch(function(error) {
// registration failed
console.log('Registration failed with ' + error);
});
}
}

测试

原作者大佬 Jakub T. Jankiewicz 还搞了个在线测试 Demo GIT Web Terminal,除了 NB 我还能说什么呢?

调用测试:

vi test.txt

i Hello World :wq

view test.txt

查看返回:

image.png

有一说一,这个方向的尝试还是很顶的~~

增强 Web 能力,吾辈义不容辞!(●'◡'●)


OK,以上就是本篇分享~ 撰文不易,点赞鼓励👍👍👍

本篇参考:

我是掘金安东尼,公众号同名,日拱一卒、日掘一金,再会~