이 기사는 Node.js 학습 과정에서 개발 한 최근 아이디어이며, 귀하와 논의하겠습니다.
node.js http 서버
Node.js를 사용하면 HTTP 서비스를 매우 쉽게 구현할 수 있습니다. 가장 간단한 예는 공식 웹 사이트의 예와 같습니다.
코드 사본은 다음과 같습니다.
var http = 요구 ( 'http');
http.createserver (function (req, res) {
res.writehead (200, { 'content-type': 'text/plain'});
res.end ( 'Hello World/n');
}). 듣기 (1337, '127.0.0.1');
이는 포트 1337의 모든 HTTP 요청을 듣는 웹 서비스를 신속하게 구축합니다.
그러나 실제 생산 환경에서는 일반적으로 Node.js를 사용자를위한 프론트 엔드 웹 서버로 직접 사용하지 않습니다. 주된 이유는 다음과 같습니다.
1. Node.js의 단일 스레드 기능을 기반으로 견고성 보증은 개발자에게 상대적으로 높습니다.
2. 서버의 다른 HTTP 서비스는 이미 포트 80을 차지할 수 있으며 포트 80이 아닌 웹 서비스는 사용자에게 충분히 사용자 친화적이지 않습니다.
3.node.js는 파일 IO 처리에서 큰 이점이 없습니다. 예를 들어, 정기적 인 웹 사이트는 사진과 같은 파일 리소스에 동시에 응답해야 할 수도 있습니다.
4. 분산로드 시나리오도 도전입니다.
따라서 Node.js를 웹 서비스로 사용하는 것은 게임 서버 인터페이스 및 기타 유사한 시나리오 일 가능성이 높으며, 대부분 직접 사용자 액세스가 필요하지 않고 데이터 교환 만 수행하는 서비스를 처리 할 수 있습니다.
Node.js 웹 서비스는 Nginx를 프론트 엔드 시스템으로 기반으로합니다.
위의 이유에 따라 Node.js로 구축 된 웹 사이트 모양의 제품인 경우 기존의 사용 방법은 Node.js의 웹 서비스의 프론트 엔드에 다른 성숙한 HTTP 서버를 배치하는 것입니다.
그런 다음 Node.js 기반 웹 서비스에 액세스하기 위해 Nginx를 리버스 프록시로 사용하십시오. 좋다:
코드 사본은 다음과 같습니다.
섬기는 사람{
80;
Server_name yekai.me;
루트/홈/앤디/wwwroot/yekai;
위치 / {
proxy_pass http://127.0.0.1:1337;
}
위치 ~ /.
루트/홈/앤디/wwwroot/yekai/static;
}
}
이것은 위에서 제기 된 몇 가지 문제를 더 잘 해결할 것입니다.
FASTCGI 프로토콜을 사용한 통신
그러나 위의 프록시 방법에 대해서는별로 좋지 않은 것들이 있습니다.
하나는 나중에 제어 해야하는 node.js 웹 서비스에 직접 HTTP 액세스 해야하는 가능한 시나리오입니다. 그러나 문제를 해결하려면 자신의 서비스를 사용하거나 방화벽에서 의존하여 차단할 수도 있습니다.
또 다른 이유는 프록시 방법이 결국 네트워크 응용 프로그램 계층의 솔루션이며, Keep-Alive, 트렁크 및 쿠키 처리와 같은 클라이언트 HTTP와 직접 데이터를 상호 작용하는 것이 편리하지 않습니다. 물론 이것은 또한 프록시 서버 자체의 기능 및 기능적 완벽 함과 관련이 있습니다.
그래서 나는 그것을 다루기 위해 또 다른 방법을 시도 할 생각이었다. 내가 생각한 첫 번째는 PHP 웹 애플리케이션에서 일반적으로 사용되는 FastCGI 방법입니다.
Fastcgi는 무엇입니까?
FASTCGI (FAST Common Gateway Interface)는 대화식 프로그램이 웹 서버와 통신 할 수있는 프로토콜입니다.
FASTCGI에 의해 생성 된 배경은 CGI 웹 응용 프로그램의 대안으로 사용됩니다. 가장 명백한 기능 중 하나는 FASTCGI 서비스 프로세스를 사용하여 일련의 요청을 처리 할 수 있다는 것입니다. 웹 서버는 FASTCGI 프로세스와 같은 소켓을 통해 환경 변수 와이 페이지 요청을 웹 서버에 연결합니다. 연결은 UNIX 도메인 소켓 또는 TCP/IP 연결을 통해 웹 서버에 연결할 수 있습니다. 더 많은 배경 지식은 Wikipedia의 진입을 참조하십시오.
Node.js의 FastCGI 구현
따라서 이론적으로 우리는 FASTCGI 프로세스를 생성하기 위해 Node.js를 사용한 다음 Nginx의 청취 요청 이이 프로세스로 전송되도록 지정하면됩니다. Nginx와 Node.js는 모두 이벤트 중심의 서비스 모델이므로 세계와 일치하는 "이론적"솔루션이어야합니다. 직접 해보자.
Node.js에서는 네트 모듈을 사용하여 소켓 서비스를 설정할 수 있습니다. 편의를 위해 UNIX 소켓 방법을 선택합니다.
Nginx 구성의 약간 수정으로 :
코드 사본은 다음과 같습니다.
...
위치 / {
fastcgi_pass unix : /tmp/node_fcgi.sock;
}
...
다음 내용으로 새 파일 Node_FCGI.JS를 만듭니다.
코드 사본은 다음과 같습니다.
var net = 요구 사항 ( 'net');
var server = net.createserver ();
server.listen ( '/tmp/node_fcgi.sock');
server.on ( '연결', function (sock) {
Console.log ( 'Connection');
sock.on ( 'data', function (data) {
Console.log (데이터);
});
});
그런 다음 실행 (권한으로 인해 NGINX 및 노드 스크립트가 동일한 사용자 또는 상호 권한이있는 계정으로 실행되는지 확인하십시오. 그렇지 않으면 양말 파일을 읽고 쓸 때 권한 문제가 발생합니다.
Node node_fcgi.js
브라우저에 액세스 할 때 노드 스크립트를 실행하는 터미널은 일반적으로 다음과 같은 데이터 컨텐츠를 수신합니다.
코드 사본은 다음과 같습니다.
연결
<버퍼 01 01 00 01 00 08 00 00 00 01 00 00 00 00 00 00 01 04 00 01 01 87 01 ...>
이것은 우리의 이론적 기초가 첫 번째 단계를 달성했음을 증명합니다. 다음 으로이 버퍼의 내용을 구문 분석하는 방법 만 알아야합니다.
FASTCGI 프로토콜 재단
FASTCGI 레코드는 고정 길이 접두사와 가변 수의 컨텐츠 및 패딩 바이트로 구성됩니다. 레코드 구조는 다음과 같습니다.
코드 사본은 다음과 같습니다.
typedef struct {
서명되지 않은 숯 버전;
서명되지 않은 숯 유형;
서명되지 않은 Char requestIdb1;
서명되지 않은 Char requestIdb0;
서명되지 않은 char contentlengthb1;
서명되지 않은 char contentlengthb0;
서명되지 않은 문자 패드 길이;
서명되지 않은 문자 예약;
서명되지 않은 char contentData [contentLength];
서명되지 않은 char paddingdata [paddinglength];
} fcgi_record;
버전 : FASTCGI 프로토콜 버전, 이제 기본적으로 1을 사용하십시오
유형 : 레코드 유형은 실제로 다른 상태로 간주 될 수 있으며 나중에 자세히 설명합니다.
requestId : 요청 ID, 반환 할 때 해당해야합니다. 멀티플렉싱 동시성의 경우가 아닌 경우 여기에서 1을 사용하십시오.
ContentLength : 콘텐츠 길이, 여기서 최대 길이는 65535입니다
패딩 길이 : 패딩 길이는 긴 데이터를 전체 8 바이트의 정수 배수로 채우는 데 사용됩니다. 주로 성능 고려 사항을 위해보다 효과적으로 정렬 된 데이터를 처리하는 데 주로 사용됩니다.
예약 : 후속 확장을위한 예약 바이트
ContentData : 실제 컨텐츠 데이터, 나중에 자세히 이야기합시다.
PaddingData : 데이터를 채우십시오. 어쨌든 0이면 직접 무시하십시오.
특정 구조 및 설명은 공식 웹 사이트 문서 (http://www.fastcgi.com/devkit/doc/fcgi-spec.html#s3.3)를 참조하십시오.
요청 부분
매우 간단 해 보이고, 한 번에 데이터를 구문 분석하고 얻습니다. 그러나 여기에는 구덩이가 있습니다. 즉, 여기서 정의되는 것은 전체 버퍼 구조가 아니라 데이터 장치 (레코드)의 구조입니다. 전체 버퍼는 하나의 레코드와 하나의 레코드로 구성됩니다. 처음에는 프론트 엔드 개발에 익숙한 학생들에게는 쉽지 않을 수도 있지만, 이것이 FASTCGI 프로토콜을 이해하기위한 기초이므로 나중에 더 많은 예를 볼 수 있습니다.
따라서 우리는 레코드를 구문 분석하고 이전에 얻은 유형에 따라 레코드를 구별해야합니다. 다음은 모든 레코드를 얻는 간단한 기능입니다.
코드 사본은 다음과 같습니다.
함수 getrcds (data, cb) {
var rcds = [],
시작 = 0,
길이 = data.length;
return function () {
if (start> = length) {
CB && CB (RCDS);
rcds = null;
반품;
}
var end = start + 8,
header = data.slice (시작, 끝),
버전 = 헤더 [0],
유형 = 헤더 [1],
requestId = (헤더 [2] << 8) + 헤더 [3],
contentlength = (헤더 [4] << 8) + 헤더 [5],
PaddingLength = 헤더 [6];
start = end + contentlength + paddinglength;
var body = contentlength? data.slice (끝, contentlength) : null;
rcds.push ([type, body, requestId]);
return arguments.callee ();
}
}
//사용
sock.on ( 'data', function (data) {
getrcds (data, function (rcds) {
}) ();
}
이것은 단순한 프로세스 일뿐입니다. 파일 업로드와 같은 복잡한 상황이있는 경우이 기능은 가장 간단한 데모에 적합하지 않습니다. 동시에, requestId 매개 변수도 무시됩니다. 다중화 인 경우 무시할 수 없으며 처리는 훨씬 더 복잡해야합니다.
다음으로, 유형에 따라 다른 레코드를 처리 할 수 있습니다. 유형의 정의는 다음과 같습니다.
코드 사본은 다음과 같습니다.
#define fcgi_begin_request 1
#define fcgi_abort_request 2
#define fcgi_end_request 3
#define fcgi_params 4
#define fcgi_stdin 5
#define fcgi_stdout 6
#define fcgi_stderr 7
#define fcgi_data 8
#define fcgi_get_values 9
#define fcgi_get_values_result 10
#define fcgi_unknown_type 11
#define fcgi_maxtype (fcgi_unknown_type)
다음으로 기록 된 유형에 따라 실제 데이터를 구문 분석 할 수 있습니다. 가장 일반적으로 사용되는 fcgi_params, fcgi_get_values 및 fcgi_get_values_result 만 사용하여 설명합니다. 다행히 그들의 분석 방법은 일관성이 있습니다. 다른 유형 레코드의 구문 분석에는 고유 한 규칙이 있으며,이를 구현하기 위해 사양의 정의를 참조 할 수 있습니다. 나는 여기에서 자세히 설명하지 않을 것입니다.
fcgi_params, fcgi_get_values, fcgi_get_values_result는 모두 "인코딩 된 Name-value"유형 데이터입니다. 표준 형식은 다음과 같습니다. 이름 길이의 형태로 전송 된 다음 값의 길이, 이름이 뒤 따른 다음 값이 뒤 따릅니다. 그 뒤에는 127 바이트 이하의 값이 1 바이트로 인코딩 될 수 있지만 더 긴 길이는 항상 4 개의 바이트로 인코딩됩니다. 길이의 첫 바이트의 높은 비트는 길이가 어떻게 인코딩되는지를 나타냅니다. 높은 비트 0은 바이트 인코딩 방법을 의미하고 1은 4 바이트 인코딩 방법을 의미합니다. 긴 이름 및 짧은 값의 경우와 같은 포괄적 인 예를 살펴 보겠습니다.
코드 사본은 다음과 같습니다.
typedef struct {
서명되지 않은 char namelengthb3; / * namelengthb3 >> 7 == 1 */
서명되지 않은 char namelengthb2;
서명되지 않은 char namelengthb1;
서명되지 않은 char namelengthb0;
서명되지 않은 char valuelengrengthb0; / * valuelengthb0 >> 7 == 0 */
서명되지 않은 char namedata [namelength
((B3 & 0x7f) << 24) + (B2 << 16) + (B1 << 8) + B0];
서명되지 않은 char valuedata [Valuelengrength];
} fcgi_namevaluepair41;
해당 구현 JS 메소드 예 :
코드 사본은 다음과 같습니다.
기능 파세 파람 (Body) {
var j = 0,
params = {},
길이 = 바디. 길이;
while (j <길이) {
var 이름,
값,
namelenger,
valuelengrength;
if (body [j] >> 7 == 1) {
namelength = ((body [j ++] & 0x7f) << 24)+(body [j ++] << 16)+(body [j ++] << 8)+body [j ++];
} 또 다른 {
namelength = body [j ++];
}
if (body [j] >> 7 == 1) {
valuelength = ((body [j ++] & 0x7f) << 24)+(body [j ++] << 16)+(body [j ++] << 8)+body [j ++];
} 또 다른 {
Valuelength = body [j ++];
}
var ret = body.asciislice (j, j + namelength + valuelength);
name = ret.substring (0, namelength);
값 = ret.substring (namelength);
params [name] = value;
j + = (namelength + valuelength);
}
반환 매개 변수;
}
이것은 다양한 매개 변수와 환경 변수를 얻는 간단한 방법을 구현합니다. 이전 코드를 개선하고 클라이언트 IP를 얻을 수있는 방법을 보여줍니다.
코드 사본은 다음과 같습니다.
sock.on ( 'data', function (data) {
getrcds (data, function (rcds) {
for (var i = 0, l = rcds.length; i <l; i ++) {
var bodydata = rcds [i],
type = bodydata [0],
Body = BodyData [1];
if (body && (type === type.fcgi_params || type === type.fcgi_get_values || type === type.fcgi_get_values_result)) {
var params = parseparams (Body);
console.log (params.remote_addr);
}
}
}) ();
}
지금까지 우리는 FastCGI 요청 부분의 기본 사항을 이해 한 다음 응답 부분을 구현하고 마지막으로 간단한 Echo 답장 서비스를 완료합니다.
응답 부분
응답 부분은 비교적 간단합니다. 가장 간단한 경우, 두 개의 레코드, 즉 FCGI_STDOUT 및 FCGI_END_REQUEST 만 보내면됩니다.
엔티티의 특정 내용을 설명하지 않고 코드를 살펴 보겠습니다.
코드 사본은 다음과 같습니다.
var res = (function () {
var maxlength = math.pow (2, 16);
함수 buffer0 (len) {
새 버퍼를 반환합니다 ((새 배열 (Len + 1)). join ( '/u0000'));
};
함수 writestdout (data) {
var rcdstdouthd = 새로운 버퍼 (8),
contentLength = data.length,
PaddingLength = 8 -Contentlength % 8;
rcdstdouthd [0] = 1;
rcdstdouthd [1] = type.fcgi_stdout;
rcdstdouthd [2] = 0;
rcdstdouthd [3] = 1;
RCDSTDOURD [4] = POTENDLENGTH >> 8;
rcdstdouthd [5] = 경쟁 길이;
rcdstdouthd [6] = PaddingLength;
rcdstdouthd [7] = 0;
return buffer.concat ([rcdstdouthd, data, buffer0 (paddinglength)]);
};
함수 writehttphead () {
Return WritestDout (새 버퍼 ( "HTTP/1.1 200 OK/R/NCONTENT-TYPE : TEXT/HTML; charSet = UTF-8/R/NConnection : Close/R/N/R/N");
}
함수 writehttpbody (bodystr) {
var bodybuffer = [],
Body = New Buffer (bodyst);
for (var i = 0, l = body.length; i <l; i + = maxlength + 1) {
bodybuffer.push (writestdout (body.slice (i, i + maxlength)));
}
return buffer.concat (보디 버퍼);
}
함수 쓰기 () {
var rcdendhd = 새로운 버퍼 (8);
rcdendhd [0] = 1;
rcdendhd [1] = type.fcgi_end_request;
rcdendhd [2] = 0;
rcdendhd [3] = 1;
rcdendhd [4] = 0;
rcdendhd [5] = 8;
rcdendhd [6] = 0;
rcdendhd [7] = 0;
return buffer.concat ([rcdendhd, buffer0 (8)];
}
반환 함수 (데이터) {
return buffer.concat ([writehttphead (), writeHttpBody (data), writeend ()];
};
}) ();
가장 간단한 경우, 전체 응답을 보낼 수 있습니다. 최종 코드 변경 :
코드 사본은 다음과 같습니다.
var 방문자 = 0;
server.on ( '연결', function (sock) {
방문자 ++;
sock.on ( 'data', function (data) {
...
var querys = querystring.parse (params.query_string);
var ret = res ( 'welcome,' + (querys.name || 'dear friend') + '! 당신은 숫자' + 방문자 + 'document ~');
SOCK.WRITE (RET);
ret = null;
sock.end ();
...
});
브라우저를 열고 http : // domain/? name = yekai를 방문하면 "환영, yekai! 당신은이 사이트의 7 번째 사용자입니다 ~"와 같은 것을 볼 수 있습니다.
이 시점에서 우리는 node.js를 사용하여 가장 간단한 FastCGI 서비스를 성공적으로 구현했습니다. 실제 서비스로 사용해야하는 경우 프로토콜 사양을 비교하여 논리를 개선하면됩니다.
비교 테스트
마지막으로, 우리가 고려해야 할 질문은이 솔루션이 구체적으로 실현 가능한지 여부입니다. 일부 학생들은 문제를 보았을 수 있으므로 간단한 압력 테스트 결과를 먼저 올릴 것입니다.
코드 사본은 다음과 같습니다.
// FASTCGI 메소드 :
500 명의 고객, 10 초
속도 = 27678 페이지/분, 63277 바이트/초.
요청 : 3295 SUSOVER, 1318은 실패했습니다.
500 명의 고객, 20 초
속도 = 22131 페이지/분, 63359 바이트/초.
요청 : 6523 SUSOVER, 854 실패.
// 프록시 방법 :
500 명의 고객, 10 초
속도 = 28752 페이지/분, 73191 바이트/초.
요청 : 3724 SUSOVER, 1068은 실패했습니다.
500 명의 고객, 20 초
속도 = 26508 페이지/분, 66267 바이트/초.
요청 : 6716 SUSOVER, 2120은 실패했습니다.
// node.js 서비스 방법을 직접 액세스합니다.
500 명의 고객, 10 초
속도 = 101154 페이지/분, 264247 바이트/초.
요청 : 15729 SUSEVERED, 1130은 실패했습니다.
500 명의 고객, 20 초
속도 = 43791 페이지/분, 115962 바이트/초.
요청 : 13898 SUSEVERED, 699 실패.
프록시 방법이 FastCGI 방법보다 더 나은 이유는 무엇입니까? 프록시 체계에 따라 백엔드 서비스는 node.js 기본 모듈에 의해 직접 실행되며 FASTCGI 체계는 JavaScript를 사용하여 스스로 구현되기 때문입니다. 그러나 두 솔루션 사이에 효율성이 큰 차이가 없다는 것을 알 수 있습니다 (물론 여기서 비교는 단순한 상황입니다. 실제 비즈니스 시나리오에서는 차이가 더 커지면 Node.js가 FASTCGI 서비스를 지원하는 경우 효율성이 더 좋을 것입니다.
추신
계속 재생에 관심이 있으시면이 기사에서 구현 한 예제의 소스 코드를 확인할 수 있습니다. 지난 이틀 동안 프로토콜 사양을 연구했지만 어렵지 않습니다.
동시에, 나는 UWSGI와 함께 돌아가서 놀아 주지만, 공무원은 V8이 이미 직접 지원할 준비가되어 있다고 말했다.
나는 매우 얕은 게임이 있습니다. 실수가 있으면 저를 수정하고 의사 소통하십시오.