BigPipe는 웹 페이지로드 속도를 최적화하기 위해 Facebook에서 개발 한 기술입니다. 인터넷에서 node.js와 함께 구현 된 기사는 거의 없습니다. 사실, 그것은 단지 node.js가 아닙니다. 다른 언어로 된 BigPipe 구현은 인터넷에서는 드 rare니다. 이 기술이 나타난 후 얼마 지나지 않아 전체 웹 페이지 프레임 워크가 먼저 전송 된 후에는 다른 AJAX 요청을 사용하여 페이지의 모듈을 요청했습니다. 얼마 전까지까지는 BigPipe의 핵심 개념이 하나의 HTTP 요청 만 사용하는 것이 아니라 페이지 요소를 순서대로 전송한다는 것을 알게되었습니다.
이 핵심 개념을 이해하기 쉽습니다. node.js의 비동기식 기능 덕분에 node.js를 사용하여 BigPipe를 쉽게 구현할 수 있습니다. 이 기사는 예제를 단계별로 사용하여 Bigpipe 기술의 원인과 Node.js를 기반으로 간단한 구현을 설명합니다.
나는 Express를 사용하여 시연 할 것입니다. 단순화를 위해 템플릿 엔진으로 Jade를 선택하고 엔진의 하위 테마 (부분) 기능을 사용하지 않고 하위 템플릿을 사용하여 HTML을 부모 템플릿 데이터로 렌더링합니다.
먼저 nodejs-bigpipe 폴더를 만들고 다음과 같이 package.json 파일을 작성하십시오.
코드 사본은 다음과 같습니다.
{
"이름": "Bigpipe-Experiment"
, "버전": "0.1.0"
, "개인": 참
, "종속성": {
"Express": "3.xx"
, "통합": "최신"
, "Jade": "최신"
}
}
이 세 가지 라이브러리를 설치하려면 NPM 설치를 실행하십시오. 통합은 Jade를 부르는 데 용이하게 사용됩니다.
가장 간단한 시도를 먼저 시도해 봅시다.
app.js :
코드 사본은 다음과 같습니다.
var express = 요구 사항 ( 'Express')
, cons = require ( 'consolidate')
, jade = 요구 ( 'jade')
, path = 요구 사항 ( 'Path')
var app = Express ()
app.engine ( 'jade', cons.jade)
app.set ( 'views', path.join (__ dirname, 'views'))))
app.set ( 'view engine', 'jade')
app.use (function (req, res) {
res.render ( '레이아웃', {
S1 : "안녕하세요, 저는 첫 번째 섹션입니다."
, S2 : "안녕하세요, 저는 두 번째 섹션입니다."
})
})
App.Listen (3000)
보기/레이아웃
코드 사본은 다음과 같습니다.
Doctype HTML
머리
제목 안녕하세요, 세계!
스타일
부분 {
마진 : 20px 자동;
테두리 : 1px 점원 회색;
너비 : 80%;
높이 : 150px;
}
섹션#S1! = S1
섹션#S2! = S2
효과는 다음과 같습니다.
다음으로 두 개의 섹션 템플릿을 두 개의 다른 템플릿 파일에 넣습니다.
보기/s1.jade :
코드 사본은 다음과 같습니다.
H1 부분 1
.Content! = 컨텐츠
Views/s2.jade :
코드 사본은 다음과 같습니다.
H1 부분 2
.Content! = 컨텐츠
레이아웃에 스타일을 추가하십시오
코드 사본은 다음과 같습니다.
섹션 H1 {
글꼴 크기 : 1.5;
패딩 : 10px 20px;
여백 : 0;
국경-바닥 : 1px 점원 회색;
}
섹션 div {
여백 : 10px;
}
app.use () app.js의 부분을 다음과 같이 변경하십시오.
코드 사본은 다음과 같습니다.
var temp = {
S1 : jade.compile (fs.readfilesync (path.join (__ dirname, 'views', 's1.jade')))))
, s2 : jade.compile (fs.readfilesync (path.join (__ dirname, 'views', 's2.jade'))))
}
app.use (function (req, res) {
res.render ( '레이아웃', {
S1 : Temp.S1 ({내용 : "안녕하세요, 첫 섹션입니다."})
, s2 : temp.S2 ({내용 : "안녕하세요, 두 번째 섹션입니다."})
})
})
우리는 이전에 "렌더링 후 HTML은 부모 템플릿의 데이터로 자식 템플릿으로 완료되었습니다"는 두 개의 메소드 Temp.S1과 Temp.S2가 두 파일 S1.jade 및 S2.Jade에 대한 HTML 코드를 생성 한 다음 Layout.jade의 두 변수 S1과 S2의 값 으로이 두 코드를 사용한다는 것을 의미합니다.
이제 페이지는 다음과 같습니다.
일반적으로, 두 섹션의 데이터는 데이터베이스를 쿼리하거나 RESTFUL 요청을 통해 별도로 얻어집니다. 두 가지 기능을 사용하여 동기화 된 작업을 시뮬레이션합니다.
코드 사본은 다음과 같습니다.
var getData = {
D1 : 기능 (fn) {
settimeout (fn, 3000, null, {내용 : "안녕하세요, 첫 섹션입니다."})
}
, d2 : 함수 (fn) {
settimeout (fn, 5000, null, {content : "안녕하세요, 두 번째 섹션입니다."})
}
}
이러한 방식으로 app.use ()의 논리가 더 복잡해지며 가장 간단한 방법은 다음과 같습니다.
코드 사본은 다음과 같습니다.
app.use (function (req, res) {
getData.d1 (function (err, s1data) {
getData.d2 (function (err, s2data) {
res.render ( '레이아웃', {
S1 : Temp.S1 (S1Data)
, S2 : Temp.S2 (S2Data)
})
})
})
})
이것은 또한 우리가 원하는 결과를 얻을 수 있지만이 경우 반환하는 데 8 초가 걸립니다.
실제로, 구현 로직은 getData.d1의 결과가 반환 된 후 getData.d2가 호출을 시작한다는 것을 보여 주며 그러한 종속성이 없습니다. 우리는 JavaScript Asynchronous 호출을 처리 하여이 문제를 해결하기 위해 Async와 같은 라이브러리를 사용할 수 있지만 여기에 간단히 작성해 봅시다.
코드 사본은 다음과 같습니다.
app.use (function (req, res) {
var n = 2
, 결과 = {}
getData.d1 (function (err, s1data) {
result.s1data = s1data
--n || Writeresult ()
})
getData.d2 (function (err, s2data) {
result.s2data = s2data
--n || Writeresult ()
})
함수 writeresult () {
res.render ( '레이아웃', {
S1 : Temp.S1 (result.s1data)
, S2 : Temp.S2 (result.s2data)
})
}
})
5 초 밖에 걸리지 않습니다.
다음 최적화 전에 jQuery 라이브러리를 추가하고 CSS 스타일을 외부 파일에 넣습니다. 그건 그렇고, 우리는 나중에 사용할 jade 템플릿을 사용하는 데 필요한 runtime.js 파일을 추가하고 app.js를 포함하는 디렉토리에서 실행합니다.
코드 사본은 다음과 같습니다.
mkdir 정적
CD 정적
CURL http://code.jquery.com/jquery-1.8.3.min.js -o jquery.js
ln -s ../node_modules/jade/runtime.min.js jade.js
그리고 스타일 태그에서 Layout.jade에서 코드를 꺼내서 static/style.css로 넣고 헤드 태그를 다음으로 변경하십시오.
코드 사본은 다음과 같습니다.
머리
제목 안녕하세요, 세계!
link (href = "/static/style.css", rel = "스타일 시트")
스크립트 (src = "/static/jquery.js")
스크립트 (src = "/static/jade.js")
App.js에서는 다운로드 속도를 2 초로 시뮬레이션하고 App.use (function (req, res) {: 전에 추가합니다.
코드 사본은 다음과 같습니다.
var static = express.static (path.join (__ dirname, 'static')))
app.use ( '/static', function (req, res, next) {
settimeout (static, 2000, req, res, next)
})
외부 정적 파일로 인해 우리 페이지의로드 시간은 약 7 초입니다.
HTTP 요청을받는 즉시 헤드 부품을 반환 한 다음 두 섹션이 반환 전에 비동기 작업이 완료 될 때까지 기다리면 HTTP의 차단 된 전송 인코딩 메커니즘을 사용합니다. node.js에서 res.write () 메소드를 사용하는 한 전송 인코딩 : 청크 헤더가 자동으로 추가됩니다. 이러한 방식으로 브라우저가 정적 파일을로드하는 동안 노드 서버는 비동기 호출 결과를 기다리고 있습니다. 먼저 Layout.jade에서 섹션의 두 줄을 삭제하겠습니다.
코드 사본은 다음과 같습니다.
섹션#S1! = S1
섹션#S2! = S2
따라서 res.render () {s1 :……, s2 :……} 에이 객체를 제공 할 필요는 없으며, res.render ()는 기본적으로 res.end ()가 render를 완료 한 후 콜백 함수를 수동으로 설정하고 res.write () 메소드를 사용해야합니다. Layout.jade의 내용은 Writeresult () 콜백 함수에있을 필요가 없습니다. 이 요청을 받으면 반환 할 수 있습니다. 콘텐츠 유형 헤더를 수동으로 추가했습니다.
코드 사본은 다음과 같습니다.
app.use (function (req, res) {
res.render ( '레이아웃', 함수 (err, str) {
if (err) return res.req.next (err)
res.setheader ( 'content-type', 'text/html; charset = utf-8')
res.write (str)
})
var n = 2
getData.d1 (function (err, s1data) {
res.write ( '<섹션 id = "S1">' + temp.s1 (s1data) + '</section>')
--n || res.end ()
})
getData.d2 (function (err, s2data) {
res.write ( '<<section id = "s2">' + temp.s2 (s2data) + '</section>')
--n || res.end ()
})
})
이제 최종 로딩 속도는 약 5 초로 돌아갑니다. 실제 작동에서 브라우저는 먼저 헤드 파트 코드를 수신하고 3 개의 정적 파일을로드합니다. 2 초가 걸립니다. 그런 다음 세 번째 두 번째로 부분 1이 나타나고 부분 2가 5 초에 나타나고 웹 페이지로드가 끝납니다. 스크린 샷을주지 않으면 스크린 샷 효과는 지난 5 초 동안 스크린 샷과 동일합니다.
그러나 getData.d1이 getData.d2보다 빠르기 때문에이 효과를 달성 할 수 있습니다. 즉, 웹 페이지의 블록이 먼저 반환되는 것은 그 뒤에있는 인터페이스의 비동기 호출의 결과를 반환하는 사람에 따라 다릅니다. GetData.d1을 8 초 안에 반환하도록 변경하면 먼저 부분 2를 반환합니다. S1과 S2의 순서가 반전되고 웹 페이지의 최종 결과는 기대치와 일치하지 않습니다.
이 문제는 궁극적으로 BigPipe로 이어집니다.이 문제는 데이터 페이지의 각 부분의 디스플레이 순서를 데이터의 전송 순서에서 분리 할 수있는 기술입니다.
기본 아이디어는 먼저 전체 웹 페이지의 일반적인 프레임 워크를 전송하는 것입니다. 나중에 전송 해야하는 부분은 빈 DIV (또는 기타 태그)로 표시됩니다.
코드 사본은 다음과 같습니다.
res.render ( '레이아웃', 함수 (err, str) {
if (err) return res.req.next (err)
res.setheader ( 'content-type', 'text/html; charset = utf-8')
res.write (str)
res.write ( '<섹션 id = "S1"> </section> <섹션 id = "S2"> </section>')
})
그런 다음 반환 된 데이터를 JavaScript에 작성하십시오
코드 사본은 다음과 같습니다.
getData.d1 (function (err, s1data) {
res.write ( '<cript> $ ( "#s1"). html ( "' + temp.s1 (s1data) .replace (/"/g, '// "' + '") </script>')
--n || res.end ()
})
S2의 처리는 이것과 유사합니다. 이 시점에서 웹 페이지를 요청 한 두 번째 순간에는 2 초에 두 개의 빈 점선 상자가 나타나고, 5 초에 부분 2가 나타나고 8 번째로 부분 1이 나타나고 웹 페이지 요청이 완료됩니다.
이 시점에서 BigPipe Technology에서 구현 한 가장 간단한 웹 페이지를 완료했습니다.
작성할 웹 페이지 조각에 S1.jade 변경과 같은 스크립트 태그가있는 경우 :
코드 사본은 다음과 같습니다.
H1 부분 1
.Content! = 컨텐츠
스크립트
경고 ( "S1.jade의 경고")
그런 다음 웹 페이지를 새로 고치면 경고 문장이 실행되지 않으며 웹 페이지에 오류가 발생합니다. 소스 코드를 확인하고 <cript>의 문자열로 인한 오류인지 확인하십시오. <// 스크립트>로 바꾸십시오
코드 사본은 다음과 같습니다.
res.write ( '<cript> $ ( "#s1"). html ( "' + temp.s1 (s1data) .replace (/"/g, '//"”.replace(/</script>/g,'<// script> ') </script>')
위에서 우리는 bigpipe의 원칙과 node.js를 사용하여 BigPipe를 구현하는 기본 방법을 설명합니다. 그리고 실제로 어떻게 사용되어야합니까? 다음은 벽돌과 제이드를 던지는 간단한 방법입니다. 코드는 다음과 같습니다.
코드 사본은 다음과 같습니다.
var resproto = require ( 'Express/lib/response')
resproto.pipe = function (선택기, html, 교체) {
this.write ( '<cript>' + '$ ( "' + selector + '").' +
(대체 === true? 'ReplaceWith': 'html') +
'( "' + html.replace (/"/g, '//"') .replace(/<//script>/g, '<// script>') +
' ") </script>')
}
함수 pipename (res, name) {
res.pipecount = res.pipecount || 0
res.pipemap = res.pipemap || {}
if (res.pipemap [name]) 반환
res.pipecount ++
res.pipemap [name] = this.id = [ 'pipe', math.random (). tostring (). substring (2), (new date ()). valueof ()]. join ( '_')
this.res = res
this.name = 이름입니다
}
resproto.pipename = function (name) {
새로운 pipename 리턴 (this, name)
}
resproto.pipelayout = function (보기, 옵션) {
var res = 이것
Object.keys (옵션) .foreach (function (key) {
if (옵션 [key] pipename의 인스턴스) 옵션 [key] = '<span id = "' + 옵션 [key] .id + '"> </span>'
})
res.render (보기, 옵션, 함수 (err, str) {
if (err) return res.req.next (err)
res.setheader ( 'content-type', 'text/html; charset = utf-8')
res.write (str)
if (! res.pipecount) res.end ()
})
}
resproto.pipepartial = function (이름,보기, 옵션) {
var res = 이것
res.render (보기, 옵션, 함수 (err, str) {
if (err) return res.req.next (err)
res.pipe ( '#'+res.pipemap [name], str, true)
-res.pipecount || res.end ()
})
}
app.get ( '/', function (req, res) {
res.pipelayout ( '레이아웃', {
S1 : res.pipename ( 's1name')
, s2 : res.pipename ( 's2name')
})
getData.d1 (function (err, s1data) {
res.pipepartial ( 's1name', 's1', s1data)
})
getData.d2 (function (err, s2data) {
res.pipepartial ( 's2name', 's2', s2data)
})
})
Layout.jade에 두 섹션을 추가하십시오.
코드 사본은 다음과 같습니다.
섹션#S1! = S1
섹션#S2! = S2
여기서 아이디어는 파이프의 내용을 먼저 스팬 태그와 함께 배치하고 데이터를 비동기 적으로 얻고 브라우저로 출력하기 전에 해당 HTML 코드를 렌더링하고 자리 표시기 SPAN 요소를 jQuery의 REPLACEWITH 메소드로 바꾸어야한다는 것입니다.
이 기사의 코드는 https://github.com/undozen/bigpipe-on-node에 있습니다. 나는 각 단계를 커밋으로 만들었습니다. 실제로 현지에서 실행하여 해킹 할 수 있기를 바랍니다. 다음 몇 단계에는 로딩 순서가 포함되기 때문에 실제로 브라우저를 직접 열어야하며 스크린 샷에서 볼 수 없습니다 (실제로는 GIF 애니메이션으로 구현해야하지만 너무 게으르지 않아야합니다).
BigPipe 연습에 대한 최적화의 여지가 여전히 많습니다. 예를 들어, 파이프 내용의 트리거 된 시간 값을 설정하는 것이 가장 좋습니다. 비동기로 호출되는 데이터가 빠르게 돌아 오면 BigPipe을 사용할 필요가 없습니다. 웹 페이지를 직접 생성하여 보낼 수 있습니다. BigPipe를 사용하기 전에 데이터 요청이 일정 시간을 초과 할 때까지 기다릴 수 있습니다. ajax와 비교하여 BigPipe를 사용하면 브라우저에서 Node.js 서버로의 요청 수가 저장 될뿐만 아니라 Node.js 서버에서 데이터 소스로 요청 수를 저장합니다. 그러나 Snowball Network가 BigPipe을 사용한 후 특정 최적화 및 실습 방법을 공유해 봅시다.