首先,您可以使用我为此创建的 GitHub 存储库自己尝试此代码。只需克隆存储库并运行node header
.
(剧透,如果你正在阅读这篇文章,并且在时间压力下要完成一些工作并且没有心情学习(:(),最后有一个更简单的解决方案)
总体思路
这是一个很好的问题。您所要求的非常有可能,并且不需要客户端,只需更深入地了解 HTTP 协议的工作原理,同时展示 node.js 是如何运作的 :)
如果我们更深入地了解底层TCP 协议并针对这种特定情况自己处理 HTTP 请求,这将变得容易。Node.js 让您可以使用内置的net 模块轻松完成此操作。
HTTP 协议
首先,让我们看看 HTTP 请求是如何工作的。
HTTP 请求包含一个头部部分,其一般格式为键:值对,由 CRLF ( \r\n
) 分隔。我们知道,当我们到达双 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
但标头将保持相似让我们来探索这如何适用于我们。
nodejs中的TCP
我们可以在 TCP 中侦听原始请求并读取我们得到的数据包,直到我们读取我们谈到的双 crlf。然后我们将检查我们已经拥有的短标题部分以进行我们需要的任何验证。在我们这样做之后,如果验证没有通过(例如通过简单地结束 TCP 连接),我们可以结束请求,或者通过它。这允许我们不接收或读取请求正文,而只是接收或读取更小的标头。
将其嵌入到现有应用程序中的一种简单方法是将来自应用程序的请求代理到特定用例的实际 HTTP 服务器。
实施细节
这个解决方案是最简单的。这只是一个建议。
这是工作流程:
我们需要 node.js 中的net
模块,它允许我们在 node.js 中创建 tcp 服务器
net
使用将监听数据 的模块创建一个 TCP 服务器: var tcpServer = net.createServer(function (socket) {...
. 不要忘记告诉它监听正确的端口
- 在该回调中,监听数据事件
socket.on("data",function(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
,nodejshttp
模块中的句柄在发送标头后(但未执行其他任何操作)实际上挂钩的事实。(服务器模块中的这个,解析器模块中的这个)
假设您的目标浏览器支持它,用户igorw建议使用标题更简洁的解决方案。100 Continue
100 Continue 是一个状态代码,旨在完全按照您的尝试:
100(继续)状态(参见第 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:
标头触发请求。