網站搜尋

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