javascript - 使用 Node.js HTTP 伺服器獲取和設定單一 Cookie
我希望能夠設置一個單一的 cookie,並在每次對 nodejs 伺服器實例的請求中讀取該單一 cookie。這可以用幾行代碼完成,而不需要引入第三方庫嗎?
var http = require('http');
http.createServer(function (request, response) {
response.writeHead(200, {'Content-Type': 'text/plain'});
response.end('Hello World\n');
}).listen(8124);
console.log('Server running at http://127.0.0.1:8124/');
只是試著直接從 nodejs.org 拿上面的程式碼,並加入一個 cookie。
答案#1
沒有快速的方法可以存取或設定 cookies,所以我想出了以下的破解方法:
const http = require('http');
function parseCookies (request) {
const list = {};
const cookieHeader = request.headers?.cookie;
if (!cookieHeader) return list;
cookieHeader.split(`;`).forEach(function(cookie) {
let [ name, ...rest] = cookie.split(`=`);
name = name?.trim();
if (!name) return;
const value = rest.join(`=`).trim();
if (!value) return;
list[name] = decodeURIComponent(value);
});
return list;
}
const server = http.createServer(function (request, response) {
// To Read a Cookie
const cookies = parseCookies(request);
// To Write a Cookie
response.writeHead(200, {
"Set-Cookie": `mycookie=test`,
"Content-Type": `text/plain`
});
response.end(`Hello World\n`);
}).listen(8124);
const {address, port} = server.address();
console.log(`Server running at http://${address}:${port}`);
這將把所有的 cookie 存入 cookies 物件中,而且在你寫入 head 時需要設定 cookie。
答案#2
如果你像許多 Node.js 開發者一樣使用 Express 庫,有一種更簡單的方法。請查看 Express.js 文件頁面以獲取更多資訊。
上面的解析範例可以運作,但 Express 提供了一個很好的函數來處理這個問題:
app.use(express.cookieParser());
設置 Cookie:
res.cookie('cookiename', 'cookievalue', { maxAge: 900000, httpOnly: true });
清除 Cookie:
res.clearCookie('cookiename');
答案#3
RevNoah 提出的使用 Express 的 cookie 解析器的建議是最好的答案。但那個答案現在已經三年了,已經過時了。
使用 Express,你可以如下方式讀取 cookie
var express = require('express');
var cookieParser = require('cookie-parser');
var app = express();
app.use(cookieParser());
app.get('/myapi', function(req, resp) {
console.log(req.cookies['Your-Cookie-Name-Here']);
})
並使用以下內容更新你的 package.json,並替換為適當的相對最新版本。
"dependencies": {
"express": "4.12.3",
"cookie-parser": "1.4.0"
},
更多像設置和解析 cookie 的操作在這裡和這裡有描述
答案#4
作為對 @Corey Hart 回答的補充,我已經使用以下方式重寫了 parseCookies():
- RegExp.prototype.exec - 使用正則表達式解析 "name=value" 字串
這裡有一個可運作的範例:
let http = require('http');
function parseCookies(str) {
let rx = /([^;=\s]*)=([^;]*)/g;
let obj = { };
for ( let m ; m = rx.exec(str) ; )
obj[ m[1] ] = decodeURIComponent( m[2] );
return obj;
}
function stringifyCookies(cookies) {
return Object.entries( cookies )
.map( ([k,v]) => k + '=' + encodeURIComponent(v) )
.join( '; ');
}
http.createServer(function ( request, response ) {
let cookies = parseCookies( request.headers.cookie );
console.log( 'Input cookies: ', cookies );
cookies.search = 'google';
if ( cookies.counter )
cookies.counter++;
else
cookies.counter = 1;
console.log( 'Output cookies: ', cookies );
response.writeHead( 200, {
'Set-Cookie': stringifyCookies(cookies),
'Content-Type': 'text/plain'
} );
response.end('Hello World\n');
} ).listen(1234);
我也注意到原作者使用了 http 模組。如果原作者使用的是 restify,他可以使用 restify-cookies:
var CookieParser = require('restify-cookies');
var Restify = require('restify');
var server = Restify.createServer();
server.use(CookieParser.parse);
server.get('/', function(req, res, next){
var cookies = req.cookies; // Gets read-only cookies from the request
res.setCookie('my-new-cookie', 'Hi There'); // Adds a new cookie to the response
res.send(JSON.stringify(cookies));
});
server.listen(8080);
答案#5
讓我重複這部分的問題,這裡的答案都忽略了:
可以在幾行程式碼中完成,而不需要引入第三方庫嗎?
讀取 Cookie
Cookie 是從請求的 Cookie 標頭讀取的。它們只包含 name 和 value。由於路徑的工作方式,相同名稱的多個 Cookie 可以被發送。在 NodeJS 中,所有 Cookie 都作為一個字串發送,如同在 Cookie 標頭中一樣。你可以用 ; 將它們分開。一旦你有了一個 Cookie,等號左邊的部分(如果有)是 name,等號右邊的部分是 value。一些瀏覽器會接受沒有等號的 Cookie,並假設名稱為空白。空白字元不算作 Cookie 的一部分。值也可以用雙引號包起來 (")。值中也可以包含 =。例如,formula=5+3=8 是一個有效的 Cookie。
/**
* @param {string} [cookieString='']
* @return {[string,string][]} String Tuple
*/
function getEntriesFromCookie(cookieString = '') {
return cookieString.split(';').map((pair) => {
const indexOfEquals = pair.indexOf('=');
let name;
let value;
if (indexOfEquals === -1) {
name = '';
value = pair.trim();
} else {
name = pair.substr(0, indexOfEquals).trim();
value = pair.substr(indexOfEquals + 1).trim();
}
const firstQuote = value.indexOf('"');
const lastQuote = value.lastIndexOf('"');
if (firstQuote !== -1 && lastQuote !== -1) {
value = value.substring(firstQuote + 1, lastQuote);
}
return [name, value];
});
}
const cookieEntries = getEntriesFromCookie(request.headers.Cookie);
const object = Object.fromEntries(cookieEntries.slice().reverse());
如果你不期望名稱重複,那麼你可以將其轉換為一個物件,這樣會更方便。然後你可以像 object.myCookieName 這樣訪問以取得值。如果你預期會有重複,則需要遍歷 cookieEntries。瀏覽器會按降序優先級提供 cookie,因此反轉順序可以確保物件中出現的是最高優先級的 cookie。(.slice() 是為了避免陣列被修改。)
設定 Cookies
「寫入」cookies 是透過在你的回應中使用 Set-Cookie 標頭來完成的。response.headers['Set-Cookie'] 對象實際上是一個陣列,因此你會向它推送值。它接受字串,但除了 name 和 value 外,還有更多的值。最困難的部分是撰寫字串,但這可以在一行中完成。
/**
* @param {Object} options
* @param {string} [options.name='']
* @param {string} [options.value='']
* @param {Date} [options.expires]
* @param {number} [options.maxAge]
* @param {string} [options.domain]
* @param {string} [options.path]
* @param {boolean} [options.secure]
* @param {boolean} [options.httpOnly]
* @param {'Strict'|'Lax'|'None'} [options.sameSite]
* @return {string}
*/
function createSetCookie(options) {
return (`${options.name || ''}=${options.value || ''}`)
+ (options.expires != null ? `; Expires=${options.expires.toUTCString()}` : '')
+ (options.maxAge != null ? `; Max-Age=${options.maxAge}` : '')
+ (options.domain != null ? `; Domain=${options.domain}` : '')
+ (options.path != null ? `; Path=${options.path}` : '')
+ (options.secure ? '; Secure' : '')
+ (options.httpOnly ? '; HttpOnly' : '')
+ (options.sameSite != null ? `; SameSite=${options.sameSite}` : '');
}
const newCookie = createSetCookie({
name: 'cookieName',
value: 'cookieValue',
path:'/',
});
response.headers['Set-Cookie'].push(newCookie);
請記住,你可以設定多個 cookies,因為你實際上可以在請求中設定多個 Set-Cookie 標頭。這就是為什麼它是一個陣列的原因。
關於外部函式庫的說明:
如果你決定使用 express、cookie-parser 或 cookie,請注意它們有非標準的默認行為。解析的 Cookie 始終會進行 URI 解碼(百分號解碼)。這意味著,如果你使用的名稱或值包含以下任何字符:!#$%&'()*+/:<=>?@[]^`{|},這些函式庫會以不同方式處理它們。如果你在設置 Cookie,它們會被編碼為 %{HEX}。而如果你在讀取 Cookie,你必須對它們進行解碼。
例如,雖然 [email 是一個有效的 cookie,但這些庫會將其編碼為 email=name%40domain.com。如果你在 cookie 中使用了 %,解碼可能會出現問題。它會被破壞。例如,你的 cookie 原本是:secretagentlevel=50%007and50%006,會變成 secretagentlevel=507and506。這是一個邊緣情況,但在切換庫時需要注意。
此外,在這些庫中,Cookie 被設置了預設的 path=/,這意味著它們會在對主機的每個 URL 請求時被發送。
如果你想自己編碼或解碼這些值,你可以分別使用 encodeURIComponent 或 decodeURIComponent。
參考文獻:
- Cookie 語法
- Set-Cookie 語法
附加資訊:
- https://developer.mozilla.org/zh-TW/docs/Web/HTTP/Headers/Cookie
- https://developer.mozilla.org/zh-TW/docs/Web/HTTP/Headers/Set-Cookie
