まず、この 用に作成した GitHub リポジトリを使用して、このコードを自分で試すことができます。リポジトリをクローンして実行するだけnode header
です。
(スポイラー、これを読んでいて、何かを機能させるために時間のプレッシャーにさらされていて、学ぶ気分ではない場合( :( )、最後にもっと簡単な解決策があります)
一般的な考え方
これは素晴らしい質問です。あなたが求めていることは非常に可能であり、クライアント側は必要ありません。node.jsがどのように機能するかを示しながら、HTTPプロトコルがどのように機能するかをより深く理解するだけです:)
これは、基盤となるTCP プロトコルを 1 レベル深く掘り下げて、この特定のケースの HTTP リクエストを自分で処理することで、簡単に行うことができます。Node.js では、組み込みのnet モジュールを使用してこれを簡単に行うことができます。
HTTP プロトコル
まず、HTTP リクエストがどのように機能するかを見てみましょう。
HTTP 要求は、CRLF ( \r\n
) で区切られた key:value ペアの一般的な形式のヘッダー セクションで構成されます。二重の CRLF (つまり\r\n\r\n
) に到達すると、ヘッダー セクションが終了したことがわかります。
典型的な HTTP GET リクエストは次のようになります。
GET /resource HTTP/1.1
Cache-Control: no-cache
User-Agent: Mozilla/5.0
Hello=World&stuff=other
「空行」の前の上部はヘッダー セクションで、下部はリクエストの本文です。リクエストは でエンコードされているため、本文セクションでは少し異なって見えますmultipart/form-data
が、ヘッダーは同じままです。これがどのように適用されるかを見てみましょう。
Node.js の TCP
TCP で生のリクエストをリッスンし、取得したパケットを読み取ることができます。これは、先ほど説明した二重の crlf を読み取るまで続きます。次に、必要な検証のために既に持っている短いヘッダー セクションをチェックします。それを行った後、検証が通らなかった場合 (たとえば、単純に TCP 接続を終了することによって) 要求を終了するか、通過させることができます。これにより、リクエスト本文を受信したり読み取ったりするのではなく、はるかに小さいヘッダーのみを受信または読み取ることができます。
これを既存のアプリケーションに組み込む簡単な方法の 1 つは、特定のユース ケースでアプリケーションから実際の HTTP サーバーにリクエストをプロキシすることです。
実装の詳細
このソリューションは、必要最小限のものです。それは単なる提案です。
ワークフローは次のとおりです。
net
node.js で tcp サーバーを作成できるようにする node.jsのモジュールが必要です。
net
データをリッスンするモジュールを 使用して TCP サーバーを作成しますvar tcpServer = net.createServer(function (socket) {...
。正しいポートをリッスンするように指示することを忘れないでください
socket.on("data",function(data){
そのコールバック内で、パケットが到着するたびにトリガーされるdata events をリッスンします。
- 「data」イベントから渡されたバッファのデータを読み取り、それを変数に格納します
- 二重の CRLF をチェックします。これにより、リクエストの HEADER セクションがHTTP プロトコルに従って終了したことが保証されます
- 検証がヘッダー (あなたの言葉ではトークン) であると仮定すると、ヘッダーのみを解析した後にチェックします (つまり、二重の CRLF を取得しました)。これは、コンテンツ長ヘッダーをチェックするときにも機能します。
- ヘッダーがチェックアウトされていないことに気付いた場合は
socket.end()
、接続を閉じる呼び出しを行います。
ここに私たちが使用するいくつかのものがあります
ヘッダーを読み取る方法:
function readHeaders(headers) {
var parsedHeaders = {};
var previous = "";
headers.forEach(function (val) {
// check if the next line is actually continuing a header from previous line
if (isContinuation(val)) {
if (previous !== "") {
parsedHeaders[previous] += decodeURIComponent(val.trimLeft());
return;
} else {
throw new Exception("continuation, but no previous header");
}
}
// parse a header that looks like : "name: SP value".
var index = val.indexOf(":");
if (index === -1) {
throw new Exception("bad header structure: ");
}
var head = val.substr(0, index).toLowerCase();
var value = val.substr(index + 1).trimLeft();
previous = head;
if (value !== "") {
parsedHeaders[head] = decodeURIComponent(value);
} else {
parsedHeaders[head] = null;
}
});
return parsedHeaders;
};
データ イベントで取得したバッファ内の二重 CRLF をチェックし、オブジェクト内に存在する場合はその場所を返すメソッド:
function checkForCRLF(data) {
if (!Buffer.isBuffer(data)) {
data = new Buffer(data,"utf-8");
}
for (var i = 0; i < data.length - 1; i++) {
if (data[i] === 13) { //\r
if (data[i + 1] === 10) { //\n
if (i + 3 < data.length && data[i + 2] === 13 && data[i + 3] === 10) {
return { loc: i, after: i + 4 };
}
}
} else if (data[i] === 10) { //\n
if (data[i + 1] === 10) { //\n
return { loc: i, after: i + 2 };
}
}
}
return { loc: -1, after: -1337 };
};
そして、この小さなユーティリティメソッド:
function isContinuation(str) {
return str.charAt(0) === " " || str.charAt(0) === "\t";
}
実装
var net = require("net"); // To use the node net module for TCP server. Node has equivalent modules for secure communication if you'd like to use HTTPS
//Create the server
var server = net.createServer(function(socket){ // Create a TCP server
var req = []; //buffers so far, to save the data in case the headers don't arrive in a single packet
socket.on("data",function(data){
req.push(data); // add the new buffer
var check = checkForCRLF(data);
if(check.loc !== -1){ // This means we got to the end of the headers!
var dataUpToHeaders= req.map(function(x){
return x.toString();//get buffer strings
}).join("");
//get data up to /r/n
dataUpToHeaders = dataUpToHeaders.substring(0,check.after);
//split by line
var headerList = dataUpToHeaders.trim().split("\r\n");
headerList.shift() ;// remove the request line itself, eg GET / HTTP1.1
console.log("Got headers!");
//Read the headers
var headerObject = readHeaders(headerList);
//Get the header with your token
console.log(headerObject["your-header-name"]);
// Now perform all checks you need for it
/*
if(!yourHeaderValueValid){
socket.end();
}else{
//continue reading request body, and pass control to whatever logic you want!
}
*/
}
});
}).listen(8080); // listen to port 8080 for the sake of the example
ご不明な点がございましたら、お気軽にお問い合わせください:)
わかりました、私は嘘をつきました、もっと簡単な方法があります!
しかし、それの何が楽しいのですか?最初にここをスキップした場合、HTTP の仕組みを理解できません :)
Node.js にはhttp
モジュールが組み込まれています。node.js では、リクエストは本質的にチャンク化されているため、特に長いリクエストは、プロトコルの高度な理解がなくても同じことを実装できます。
今回はhttp
モジュールを使ってhttpサーバーを作ってみましょう
server = http.createServer( function(req, res) { //create an HTTP server
// The parameters are request/response objects
// check if method is post, and the headers contain your value.
// The connection was established but the body wasn't sent yet,
// More information on how this works is in the above solution
var specialRequest = (req.method == "POST") && req.headers["YourHeader"] === "YourTokenValue";
if(specialRequest ){ // detect requests for special treatment
// same as TCP direct solution add chunks
req.on('data',function(chunkOfBody){
//handle a chunk of the message body
});
}else{
res.end(); // abort the underlying TCP connection, since the request and response use the same TCP connection this will work
//req.destroy() // destroy the request in a non-clean matter, probably not what you want.
}
}).listen(8080);
request
これは、nodejsモジュールのハンドルが、デフォルトでヘッダーが送信された後に実際にフックされるという事実に基づいていhttp
ます (他には何も実行されませんでした)。(これはサーバーモジュールで、これはパーサーモジュールで)
ユーザーigorw100 Continue
は、対象のブラウザーがヘッダーをサポートしていると仮定して、ヘッダーを使用したややクリーンなソリューションを提案しました。100 Continue は、あなたがしようとしていることを正確に実行するように設計されたステータス コードです。
100 (Continue) ステータス (セクション 10.1.1 を参照) の目的は、リクエスト本文を含むリクエスト メッセージを送信するクライアントが、オリジン サーバーがリクエストを受け入れるかどうかを (リクエスト ヘッダーに基づいて) 判断できるようにすることです。クライアントがリクエストボディを送信する前。場合によっては、サーバーが本文を確認せずにメッセージを拒否する場合、クライアントが本文を送信することは不適切または非常に非効率的である可能性があります。
ここにあります :
var http = require('http');
function handle(req, rep) {
req.pipe(process.stdout); // pipe the request to the output stream for further handling
req.on('end', function () {
rep.end();
console.log('');
});
}
var server = new http.Server();
server.on('checkContinue', function (req, rep) {
if (!req.headers['x-foo']) {
console.log('did not have foo');
rep.writeHead(400);
rep.end();
return;
}
rep.writeContinue();
handle(req, rep);
});
server.listen(8080);
サンプルの入力/出力はこちらで確認できます。Expect:
これには、適切なヘッダーでリクエストを発行する必要があります。