成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

保護(hù) Node.js 項(xiàng)目的源代碼

Steven / 2103人閱讀

摘要:而對(duì)于應(yīng)用越來越廣泛的而言,運(yùn)行的則是源代碼。通過查閱的相關(guān)代碼,可以發(fā)現(xiàn)字節(jié)碼的頭部保存著這些信息其中第項(xiàng)就是源代碼長(zhǎng)度。本文同時(shí)發(fā)表于作者個(gè)人博客保護(hù)項(xiàng)目的源代碼

SaaS(Software as a Service,軟件即服務(wù)),是一種通過互聯(lián)網(wǎng)提供軟件服務(wù)的模式。服務(wù)提供商會(huì)全權(quán)負(fù)責(zé)軟件服務(wù)的搭建、維護(hù)和管理,使得他們的客戶從這些繁瑣的工作中解放出來。對(duì)于許多中小型企業(yè)而言,SaaS 是采用先進(jìn)技術(shù)的最好途徑。

然而,對(duì)于大型企業(yè)而言,情況有所不同。出于產(chǎn)品定制、功能穩(wěn)定以及掌握自身數(shù)據(jù)資產(chǎn)等方面的考慮,即使成本增加,他們也更樂意把相關(guān)服務(wù)部署在企業(yè)自己的硬件設(shè)備上,也就是常說的私有化部署。

在私有化部署的過程中,服務(wù)提供商首先要確保自己的源代碼不被泄露,否則產(chǎn)品就可以隨意復(fù)制和更改,得不償失。傳統(tǒng)的后端運(yùn)行環(huán)境,如 Java、.NET,其源代碼是經(jīng)過編譯才部署到服務(wù)器上運(yùn)行的,不存在泄露的風(fēng)險(xiǎn)。而對(duì)于應(yīng)用越來越廣泛的 Node.js 而言,運(yùn)行的則是源代碼。即使經(jīng)過壓縮混淆,也可以很大程度地還原。

本文介紹一種可用于 Node.js 端的代碼保護(hù)方案,使得 Node.js 項(xiàng)目也可以放心地進(jìn)行私有化部署。

原理

當(dāng) V8 編譯 JavaScript 代碼時(shí),解析器將生成一個(gè)抽象語法樹,進(jìn)一步生成字節(jié)碼。Node.js 有一個(gè)叫做 vm 的內(nèi)置模塊,創(chuàng)建 vm.Script 的實(shí)例時(shí),只要在構(gòu)造函數(shù)中傳入 produceCachedData 屬性,并設(shè)為 true,就可以獲取對(duì)應(yīng)代碼的字節(jié)碼。例如:

const vm = require("vm");
const CODE = "console.log("Hello world");"; // 源代碼
const script = new vm.Script(CODE, {
  produceCachedData: true
});
const bytecodeBuffer = script.cachedData; // 字節(jié)碼

并且,這段字節(jié)碼可以脫離源代碼運(yùn)行:

const anotherScript = new vm.Script(" ".repeat(CODE.length), {
  cachedData: bytecodeBuffer
});
anotherScript.runInThisContext(); // "Hello world"

這段代碼看起來不那么容易理解,主要體現(xiàn)在創(chuàng)建 vm.Script 實(shí)例時(shí)傳入的第一個(gè)參數(shù):

既然源代碼的字節(jié)碼已經(jīng)在 bytecodeBuffer 中,為何還要傳入第一個(gè)參數(shù)?

為何傳入與源代碼長(zhǎng)度相同的空格?

首先,創(chuàng)建 vm.Script 實(shí)例時(shí),V8 會(huì)檢查字節(jié)碼(cachedData)是否與源代碼(第一個(gè)參數(shù)傳入的代碼)匹配,所以第一個(gè)參數(shù)不能省略。其次,這個(gè)檢查非常簡(jiǎn)單,它只會(huì)對(duì)比代碼長(zhǎng)度是否一致,所以只要使用與源代碼長(zhǎng)度相同的空格,就可以“欺騙”這個(gè)檢查。

細(xì)心的讀者會(huì)發(fā)現(xiàn),這樣一來,其實(shí)字節(jié)碼并沒有完全脫離源代碼運(yùn)行,因?yàn)樾枰玫皆创a長(zhǎng)度這項(xiàng)數(shù)據(jù)。而實(shí)際上,還有其他方法可以解決這個(gè)問題。試想一下,既然有源代碼長(zhǎng)度檢查,那就說明字節(jié)碼中也必然保存著源代碼的長(zhǎng)度信息,否則就無法對(duì)比了。通過查閱 V8 的相關(guān)代碼,可以發(fā)現(xiàn)字節(jié)碼的頭部保存著這些信息:

// The data header consists of uint32_t-sized entries:
// [0] magic number and (internally provided) external reference count
// [1] version hash
// [2] source hash
// [3] cpu features
// [4] flag hash

其中第 [2] 項(xiàng) source hash 就是源代碼長(zhǎng)度。但因?yàn)?Node.js 的 buffer 是 Uint8Array 類型的數(shù)組,所以 uint32 數(shù)組中的 [2],相當(dāng)于 uint8 數(shù)組中的 [8, 9, 10, 11]。

接著把上述位置的數(shù)據(jù)提取出來:

const lengthBytes = bytecodeBuffer.slice(8, 12);

其結(jié)果類似于:

這是一種叫做 Little-Endian 的字節(jié)序,低位字節(jié)排放在內(nèi)存的低地址端,高位字節(jié)排放在內(nèi)存的高地址端。

即為 0x0000001b,也就是十進(jìn)制的 27。計(jì)算方法如下:

firstByte + (secondByte  256) + (thirdByte  256**2) + (forthByte * 256**3)

寫成代碼如下:

const length = lengthBytes.reduce((sum, number, power) => {
  return sum += number * Math.pow(256, power);
}, 0); // 27

此外,還有一種更簡(jiǎn)單的方法:

const length = bytecodeBuffer.readIntLE(8, 4); // 27

綜上所述,運(yùn)行字節(jié)碼的代碼可以優(yōu)化為:

const length = bytecodeBuffer.readIntLE(8, 4);
const anotherScript = new vm.Script(" ".repeat(length), {
  cachedData: bytecodeBuffer
});
anotherScript.runInThisContext();
編譯文件

講清楚原理之后,下面就嘗試編譯一個(gè)很簡(jiǎn)單的項(xiàng)目,目錄結(jié)構(gòu)如下:

src/

lib.js

index.js

dist/

compile.js

src 目錄內(nèi)的兩個(gè)文件為源代碼,內(nèi)容分別為:

// lib.js
console.log("I am lib");
exports.add = function(a, b) {
  return a + b;
};
// index.js
console.log("I am index");
const lib = require("./lib");
console.log(lib.add(1, 2));

dist 目錄用于放置編譯后的代碼。compile.js 即為執(zhí)行編譯操作的文件,其流程也非常簡(jiǎn)單,讀取源文件內(nèi)容,編譯為字節(jié)碼后保存為文件(dist/*.jsc):

const path = require("path");
const fs = require("fs");
const vm = require("vm");
const glob = require("glob"); // 第三方依賴包

const srcPath = path.resolve(__dirname, "./src");
const destPath = path.resolve(__dirname, "./dist");

glob.sync("**/*.js", { cwd: srcPath }).forEach((filePath) => {
  const fullPath = path.join(srcPath, filePath);
  const code = fs.readFileSync(fullPath, "utf8");
  const script = new vm.Script(code, {
    produceCachedData: true
  });
  fs.writeFileSync(
    path.join(destPath, filePath).replace(/.js$/, ".jsc"),
    script.cachedData
  );
});

運(yùn)行 node compile 后,就可以在 dist 目錄內(nèi)生成源代碼對(duì)應(yīng)的字節(jié)碼文件,接下來就是運(yùn)行字節(jié)碼文件。然而,直接執(zhí)行 node index.jsc 是無法運(yùn)行的,因?yàn)?Node.js 在默認(rèn)情況下會(huì)把目標(biāo)文件當(dāng)做 JavaScript 源代碼來執(zhí)行。

此時(shí),就需要對(duì) jsc 文件使用特殊的加載邏輯。在 dist 目錄內(nèi)新建文件 main.js,內(nèi)容如下:

const Module = require("module");
const path = require("path");
const fs = require("fs");
const vm = require("vm");

// 加載 jsc 文件的擴(kuò)展
Module._extensions[".jsc"] = function(module, filename) {
  const bytecodeBuffer = fs.readFileSync(filename);
  const length = bytecodeBuffer.readIntLE(8, 4);
  const script = new vm.Script(" ".repeat(length), {
    cachedData: bytecodeBuffer
  });
  script.runInThisContext();
};

// 調(diào)用字節(jié)碼文件
require("./index");

執(zhí)行 node dist/main,雖然 jsc 文件可以加載進(jìn)來了,但是就出現(xiàn)了另一段異常信息:

ReferenceError: require is not defined

這是個(gè)奇怪的問題,在 Node.js 中,require 是個(gè)很基礎(chǔ)的函數(shù),怎么會(huì)未定義呢?原來,Node.js 在編譯 js 文件的過程中會(huì)對(duì)其內(nèi)容進(jìn)行包裝。以 index.js 為例,包裝后的代碼如下:

(function (exports, require, module, __filename, __dirname) {
  console.log("I am index");
  const lib = require("./lib");
  console.log(lib.add(1, 2));
});

包裝這個(gè)操作并不在編譯字節(jié)碼這個(gè)步驟里面,而是在之前執(zhí)行。所以,要在 compile.js 補(bǔ)上包裝(Module.wrap)操作:

const script = new vm.Script(Module.wrap(code), {
  produceCachedData: true
});

加上包裝之后,script.runInThisContext 就會(huì)返回一個(gè)函數(shù),執(zhí)行這個(gè)函數(shù)才能運(yùn)行模塊,修改代碼如下:

Module._extensions[".jsc"] = function(module, filename) {
  // 省略 N 行代碼

  const compiledWrapper = script.runInThisContext();
  return compiledWrapper.apply(module.exports, [
    module.exports,
    id => module.require(id),
    module,
    filename,
    path.dirname(filename),
    process,
    global
  ]);
};

再次執(zhí)行 node dist/main.js,出現(xiàn)了另一條錯(cuò)誤信息:

SyntaxError: Unexpected end of input

這是一個(gè)讓人一臉懵逼,不知道從何查起的錯(cuò)誤。但是,仔細(xì)觀察控制臺(tái)又可以發(fā)現(xiàn),在錯(cuò)誤信息之前,兩條日志已經(jīng)打印出來了:

I am index  
I am lib

由此可見,錯(cuò)誤信息是執(zhí)行 lib.add 時(shí)產(chǎn)生的。所以,結(jié)論就是,函數(shù)以外的邏輯可以正常執(zhí)行,函數(shù)內(nèi)部的邏輯執(zhí)行失敗。

回想 V8 編譯的流程。它解析 JavaScript 代碼的過程中,Toplevel 部分會(huì)被解釋器完全解析,生成抽象語法樹以及字節(jié)碼。Non Toplevel 部分僅僅被預(yù)解析(語法檢查),不會(huì)生成語法樹,更不會(huì)生成字節(jié)碼。Non Toplevel 部分,即函數(shù)體部分,只有在函數(shù)被調(diào)用的時(shí)候才會(huì)被編譯。

所以問題也就一目了然了:函數(shù)體沒有編譯成字節(jié)碼。幸好,這種行為也是可以更改的:

const v8 = require("v8");
v8.setFlagsFromString("--no-lazy");

設(shè)置了 no-lazy 標(biāo)志后再執(zhí)行 node compile 進(jìn)行編譯,函數(shù)體也可以被完全解析了。最終 compile.js 代碼如下:

const path = require("path");
const fs = require("fs");
const vm = require("vm");
const Module = require("module");
const glob = require("glob");
const v8 = require("v8");
v8.setFlagsFromString("--no-lazy");

const srcPath = path.resolve(__dirname, "./src");
const destPath = path.resolve(__dirname, "./dist");

glob.sync("**/*.js", { cwd: srcPath }).forEach((filePath) => {
  const fullPath = path.join(srcPath, filePath);
  const code = fs.readFileSync(fullPath, "utf8");
  const script = new vm.Script(Module.wrap(code), {
    produceCachedData: true
  });
  fs.writeFileSync(
    path.join(destPath, filePath).replace(/.js$/, ".jsc"),
    script.cachedData
  );
});

dist/main.js 代碼如下:

const Module = require("module");
const path = require("path");
const fs = require("fs");
const vm = require("vm");
const v8 = require("v8");
v8.setFlagsFromString("--no-lazy");

Module._extensions[".jsc"] = function(module, filename) {
  const bytecodeBuffer = fs.readFileSync(filename);
  const length = bytecodeBuffer.readIntLE(8, 4);
  const script = new vm.Script(" ".repeat(length), {
    cachedData: bytecodeBuffer
  });

  const compiledWrapper = script.runInThisContext();
  return compiledWrapper.apply(module.exports, [
    module.exports,
    id => module.require(id),
    module,
    filename,
    path.dirname(filename),
    process,
    global
  ]);
};

require("./index");
bytenode

實(shí)際上,如果你真的需要把 JavaScript 源代碼編譯成字節(jié)碼,并不需要自己去編寫這么多的代碼。npm 平臺(tái)上已經(jīng)有一個(gè)叫做 bytenode 的包可以完成這些事情,并且它在細(xì)節(jié)和兼容性上做得更好。

字節(jié)碼的問題

雖然編譯成字節(jié)碼后可以保護(hù)源代碼,但字節(jié)碼也會(huì)存在一些問題:

JavaScript 源代碼可以在任何平臺(tái)的 Node.js 環(huán)境中運(yùn)行,但字節(jié)碼是平臺(tái)相關(guān)的,在何種平臺(tái)下編譯,就只能在何種平臺(tái)下運(yùn)行(比如在 Windows 下編譯的字節(jié)碼不能在 macOS 下運(yùn)行)。

修改源代碼后要再次編譯為字節(jié)碼,較為繁瑣。對(duì)于一些如數(shù)據(jù)庫服務(wù)器地址、端口號(hào)等配置信息,建議不要編譯成字節(jié)碼,仍使用源文件運(yùn)行,方便隨時(shí)修改。

后記

作為一名聰明的讀者,你必定能猜到,本文是以倒敘的方式寫的。筆者是先使用 bytenode 完成了需求,再研究其原理。

本文同時(shí)發(fā)表于作者個(gè)人博客:《保護(hù) Node.js 項(xiàng)目的源代碼》

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/105412.html

相關(guān)文章

  • Node Hero】8. 使用 Passport.js 進(jìn)行 Node.js 身份驗(yàn)證

    摘要:本文轉(zhuǎn)載自眾成翻譯譯者網(wǎng)絡(luò)埋伏紀(jì)事鏈接原文本教程中將學(xué)習(xí)如何使用和實(shí)現(xiàn)一個(gè)本地身份驗(yàn)證策略。我們將有一個(gè)用戶頁,一個(gè)備注頁,和一些與身份驗(yàn)證相關(guān)的功能。下一步下一章主要涉及應(yīng)用程序的單元測(cè)試。你會(huì)學(xué)習(xí)單元測(cè)試測(cè)試金字塔測(cè)試替代等概念。 本文轉(zhuǎn)載自:眾成翻譯譯者:網(wǎng)絡(luò)埋伏紀(jì)事鏈接:http://www.zcfy.cc/article/1755原文:https://blog.risings...

    CoderStudy 評(píng)論0 收藏0
  • 新上課程推薦:TypeScript完全解讀(總26課時(shí))

    摘要:本套課程包含兩大部分,第一部分是基礎(chǔ)部分,也是重要部分,參考官方文檔結(jié)構(gòu),針對(duì)內(nèi)容之間的關(guān)聯(lián)性和前后順序進(jìn)行合理調(diào)整。 showImg(https://segmentfault.com/img/bVbpBA0?w=1460&h=400); 講師簡(jiǎn)介: iview 核心開發(fā)者,iview-admin 作者,百萬級(jí)虛擬渲染表格組件 vue-bigdata-table 作者。目前就職于知名互...

    caozhijian 評(píng)論0 收藏0
  • [譯]保持Node.js速度-創(chuàng)建高性能Node.js Servers工具、技術(shù)和提示

    摘要:本文翻譯自原文地址中文標(biāo)題保持的速度創(chuàng)建高性能的工具技術(shù)和提示快速摘要是一個(gè)非常多彩的平臺(tái),而創(chuàng)建服務(wù)就是其非常重要的能力之一。在目錄下,我們執(zhí)行譯者注現(xiàn)在的話可以使用新的形式的命令語法會(huì)在剖析完畢后,創(chuàng)建文件并自動(dòng)打開瀏覽器。 pre-tips 本文翻譯自: Keeping Node.js Fast: Tools, Techniques, And Tips For Making Hi...

    Lavender 評(píng)論0 收藏0
  • 阿里云容器服務(wù)區(qū)塊鏈解決方案全新升級(jí) 支持Hyperledger Fabric v1.1

    摘要:阿里云容器服務(wù)區(qū)塊鏈解決方案第一時(shí)間同步升級(jí),在新功能的基礎(chǔ)上,提供了彈性裸金屬服務(wù)器神龍內(nèi)置容器化集成阿里云日志服務(wù)等方面的增強(qiáng)。 摘要: 全球開源區(qū)塊鏈領(lǐng)域影響最為廣泛的Hyperledger Fabric日前宣布了1.1版本的正式發(fā)布,帶來了一系列豐富的新功能以及在安全性、性能與擴(kuò)展性等方面的顯著提升。阿里云容器服務(wù)區(qū)塊鏈解決方案第一時(shí)間同步升級(jí),在v1.1新功能的基礎(chǔ)上,提供了...

    vvpale 評(píng)論0 收藏0
  • 如果有人問你爬蟲抓取技術(shù)門道,請(qǐng)叫他來看這篇文章

    摘要:未授權(quán)的爬蟲抓取程序是危害原創(chuàng)內(nèi)容生態(tài)的一大元兇,因此要保護(hù)網(wǎng)站的內(nèi)容,首先就要考慮如何反爬蟲。反爬蟲的銀彈目前的反抓取機(jī)器人檢查手段,最可靠的還是驗(yàn)證碼技術(shù)。機(jī)器人協(xié)議除此之外,在爬蟲抓取技術(shù)領(lǐng)域還有一個(gè)白道的手段,叫做協(xié)議。 本文首發(fā)于我的個(gè)人博客,同步發(fā)布于SegmentFault專欄,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處,商業(yè)轉(zhuǎn)載請(qǐng)閱讀原文鏈接里的法律聲明。 web是一個(gè)開放的平臺(tái),這也奠定了...

    raoyi 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<