使用 Node.js HTTP 服务器获取和设置单个 Cookie

Posted

技术标签:

【中文标题】使用 Node.js HTTP 服务器获取和设置单个 Cookie【英文标题】:Get and Set a Single Cookie with Node.js HTTP Server 【发布时间】:2011-03-24 13:24:23 【问题描述】:

我希望能够设置一个 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】:

没有快速访问获取/设置 cookie 的功能,所以我想出了以下 hack:

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`);

这会将所有cookies存储到cookies对象中,需要在写head的时候设置cookies。

【讨论】:

cookie 值是否必须经过 URL 编码/百分比编码? = 在这种情况下,doockie 值是无效的,对吧? 在这种情况下,由; 分割,然后由= 的第一个实例分割。左为关键,右为价值。 我遇到了与@Eye 相同的问题,我没有走@iLoch 的路线,而是将parts[0] 切换到parts.shift()parts[1] 切换到parts.join('=') 给出的代码有严重的错误,人们不厌其烦地复制它。查看我的最新更新【参考方案2】:

如果您使用 express 库,就像许多 node.js 开发人员所做的那样,有一种更简单的方法。查看 Express.js 文档页面了解更多信息。

上面的解析示例有效,但 express 为您提供了一个很好的函数来处理它:

app.use(express.cookieParser());

设置 cookie:

res.cookie('cookiename', 'cookievalue',  maxAge: 900000, httpOnly: true );

清除 cookie:

res.clearCookie('cookiename');

【讨论】:

cookie库其实是来自底层库connect;你不需要采取所有的快递来获得cookie助手。 其实cookie库不是connect的一部分(不能设置cookie) cookie-parser 不再是 express 和/或 connect 的一部分,但可以作为中间件使用:github.com/expressjs/cookie-parser 在本例中如何“获取”cookie?为了完整性和回答问题。 由于需要安装 express 来使用 cookie 而被否决【参考方案3】:

RevNoah 给出了最好的答案,建议使用 Express 的 cookie parser。但是,这个答案现在已经 3 岁了,而且已经过时了。

使用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"
  ,

更多设置和解析cookies的操作描述here 和here

【讨论】:

问题询问如何设置。设置使用这个:res.cookie('cookieName', cookieValue, maxAge: 900000, httpOnly: true ); 你能在每个会话中设置和获取 cookie(无需知道和处理密钥)吗? 对一个问题的大量基于快递的答案显示了使用普通 Node http 模块的代码......【参考方案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);

我还注意到 OP 使用 http 模块。 如果 OP 使用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);

【讨论】:

现在(3.5 年后)为未来的观众提供一些建议。使用let/conststringifyCookies 可以使用map,而不是那样构建数组。【参考方案5】:

让我重复一下这里的答案忽略的这部分问题:

是否可以在几行代码中完成,而不需要拉入第三方库?


读取 Cookies

Cookie 是从带有 Cookie 标头的请求中读取的。它们仅包括 namevalue。由于路径的工作方式,可以发送多个同名 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

“写入”cookie 是通过在您的响应中使用 Set-Cookie 标头来完成的。 response.headers['Set-Cookie'] 对象实际上是一个数组,因此您将向它推送。它接受一个字符串,但具有比namevalue 更多的值。最难的部分是写字符串,但这可以在一行中完成。

/**
 * @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);

请记住,您可以设置多个 cookie,因为您实际上可以在请求中设置多个 Set-Cookie 标头。这就是为什么它是一个数组。


关于外部库的注意事项:

如果您决定使用expresscookie-parsercookie,请注意它们具有非标准的默认值。解析的 Cookie 始终是 URI 解码(百分比解码)。这意味着如果您使用具有以下任何字符的名称或值:!#$%&'()*+/:<=>?@[]^`|,这些库的处理方式将有所不同。如果您正在设置 cookie,它们将使用%HEX 进行编码。如果您正在读取 cookie,则必须对其进行解码。

例如,虽然email=name@domain.com 是一个有效的cookie,但这些库会将其编码为email=name%40domain.com。如果您在 cookie 中使用 %,解码可能会出现问题。它会被破坏。例如,您的 cookie:secretagentlevel=50%007and50%006 变为 secretagentlevel=507and506。这是一个边缘情况,但如果切换库需要注意。

此外,在这些库中,cookie 设置为默认 path=/,这意味着它们会在每次向主机发送 url 请求时发送。

如果您想自己对这些值进行编码或解码,可以分别使用encodeURIComponentdecodeURIComponent


参考资料:

Cookie Syntax Set-Cookie Syntax

附加信息:

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cookie https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie

【讨论】:

如果我们是“干净”的现代代码,至少还要将 createSetCookie 转换为 return [`$options.name ?? ``=$options.value ?? ```, options.expires !== undefined && `Expires=$options.expires.toUTCString()`, options.maxAge != undefined && Max-Age=$options.maxAge`,...].filter(Boolean).join(`;`);(当然还有相关的缩进)。请注意,未指定的对象属性解析为undefined,而不是null @Mike'Pomax'Kamermans != null 检查nullundefined 其中!== undefined 将接受null。使用您的代码,如果expires 为空,当您调用options.expires.toUTCString() 时它会崩溃。 JSDoc 表示法意味着您可以毫无问题地传递 null。如果不是,则参数必须是!string|undefined。此外,根据规范,分号后应该有一个空格。 单个字符串也比两次迭代数组更快(joinfilter),尽管我们对易读性的看法可能不同。作为单个字符串模板的整个事情可能会更快,但我认为它变得太难以理解了。 哦,等等,你使用了强制!=。这是一种糟糕的形式,但可以肯定的是,确实可以做到这一点。 @Mike'Pomax'Kamermans 这形式不错。例如,eslint 并不认为这很糟糕。大多数流行的实现都使用它,包括 NodeJS 本身。因为==nullundefined 一起工作,所以没有强制。您也不应该与undefined 进行比较,因为这是您要比较的globalThis 中的对象可以被覆盖(尝试:'undefined' in globalThis)。你应该做typeof object !== 'undefined' && object !== null。但是,使用!= null 更干净。见262.ecma-international.org/5.1/#sec-11.9.3【参考方案6】:

您可以使用“cookies”npm 模块,它具有一套全面的功能。

文档和示例位于:https://github.com/jed/cookies

【讨论】:

看起来该模块旨在用于 http 服务器。是否有用于处理 http 客户端中的 cookie 的 cookiejar 工具?基本上我不想告诉 http 客户端库:如果您获得任何 Set-Cookie 标头,请自动记住它们,然后酌情传递后续出站请求(当域匹配时)。 这将是您选择的 http 客户端库的一个功能。我可以建议 superagent 作为一个很好的例子。 放弃尝试让这个库在几个小时后快速工作...改用 connect。【参考方案7】:

要让 cookie 拆分器处理 cookie 值中包含“=”的 cookie:

var get_cookies = function(request) 
  var cookies = ;
  request.headers && request.headers.cookie.split(';').forEach(function(cookie) 
    var parts = cookie.match(/(.*?)=(.*)$/)
    cookies[ parts[1].trim() ] = (parts[2] || '').trim();
  );
  return cookies;
;

然后获取单独的 cookie:

get_cookies(request)['my_cookie']

【讨论】:

【参考方案8】:

Cookie 已转移through HTTP-Headers 您只需解析请求标头并放入响应标头。

【讨论】:

***不容易【参考方案9】:

这是一个简洁的复制粘贴补丁,用于管理节点中的 cookie。为了美观,我将在 CoffeeScript 中执行此操作。

http = require 'http'

http.IncomingMessage::getCookie = (name) ->
  cookies = 
  this.headers.cookie && this.headers.cookie.split(';').forEach (cookie) ->
    parts = cookie.split '='
    cookies[parts[0].trim()] = (parts[1] || '').trim()
    return

  return cookies[name] || null

http.IncomingMessage::getCookies = ->
  cookies = 
  this.headers.cookie && this.headers.cookie.split(';').forEach (cookie) ->
    parts = cookie.split '='
    cookies[parts[0].trim()] = (parts[1] || '').trim()
    return

  return cookies

http.OutgoingMessage::setCookie = (name, value, exdays, domain, path) ->
  cookies = this.getHeader 'Set-Cookie'
  if typeof cookies isnt 'object'
    cookies = []

  exdate = new Date()
  exdate.setDate(exdate.getDate() + exdays);
  cookieText = name+'='+value+';expires='+exdate.toUTCString()+';'
  if domain
    cookieText += 'domain='+domain+';'
  if path
    cookieText += 'path='+path+';'

  cookies.push cookieText
  this.setHeader 'Set-Cookie', cookies
  return

现在您可以按预期处理 cookie:

server = http.createServer (request, response) ->
  #get individually
  cookieValue = request.getCookie 'testCookie'
  console.log 'testCookie\'s value is '+cookieValue

  #get altogether
  allCookies = request.getCookies()
  console.log allCookies

  #set
  response.setCookie 'newCookie', 'cookieValue', 30

  response.end 'I luvs da cookies';
  return

server.listen 8080

【讨论】:

只需将该代码复制粘贴到 coffeescript.org 的 TRY COFFESCRIPT 选项卡中即可。您的回答确实对我有所帮助,如果您了解 javascript,coffeescript 并不难阅读。【参考方案10】:

如果您不关心 cookie 中的内容而只想使用它,请尝试使用 request(流行的节点模块)的这种干净方法:

var request = require('request');
var j = request.jar();
var request = request.defaults(jar:j);
request('http://www.google.com', function () 
  request('http://images.google.com', function (error, response, body)
     // this request will will have the cookie which first request received
     // do stuff
  );
);

【讨论】:

抱歉,这段代码对我来说毫无意义。 jar() 是不是一些可爱的方法名来获取当前的 cookie 列表?两次调用请求有什么意义?这主要是一个广告,而不是一个答案。 不接受客户的回答。接受服务器答案。【参考方案11】:
var cookie = 'your_cookie';
var cookie_value;
var i = request.headers.indexOf(cookie+'=');
if (i != -1) 
  var eq = i+cookie.length+1;
  var end = request.headers.indexOf(';', eq);
  cookie_value = request.headers.substring(eq, end == -1 ? undefined : end);

【讨论】:

【参考方案12】:

使用一些 ES5/6 巫术和正则表达式魔法

这是一个读取 cookie 并将其转换为 Key、Value 对的对象的选项,供客户端使用,也可以在服务器端使用。

注意:如果值中有=,不用担心。如果键中有=,天堂麻烦了。

更多注释:有些人可能会争论可读性,所以你可以随意分解。

我喜欢笔记:添加错误处理程序(try catch)不会有什么坏处。

const iLikeCookies = () => 
    return Object.fromEntries(document.cookie.split('; ').map(v => v.split(/=(.+)/))); 


const main = () => 
    // Add Test Cookies
    document.cookie = `name=Cookie Monster;expires=false;domain=localhost`
    document.cookie = `likesCookies=yes=withARandomEquals;expires=false;domain=localhost`;

    // Show the Objects
    console.log(document.cookie)
    console.log('The Object:', iLikeCookies())

    // Get a value from key
    console.log(`Username: $iLikeCookies().name`)
    console.log(`Enjoys Cookies: $iLikeCookies().likesCookies`)

发生了什么事?

iLikeCookies() 将用; 分割cookie(; 后面的空格):

["name=Cookie Monster", "likesCookies=yes=withARandomEquals"]

然后我们映射该数组,并使用 regex capturing parens 按第一次出现的= 进行拆分:

[["name", "Cookie Monster"], ["likesCookies", "yes=withARandomEquals"]]

然后使用我们的朋友 `Object.fromEntries 使其成为 key、val 对的对象。

Nooice。

【讨论】:

使用 /="?([^"]+)"?/ 拆分以支持引用值(例如:name="value")。【参考方案13】:

我写了这个简单的函数只是通过 req.headers.cookiecookie 名称

const getCookieByName =(cookies,name)=>
    const arrOfCookies = cookies.split(' ')
    let yourCookie = null

    arrOfCookies.forEach(element => 
        if(element.includes(name))
            yourCookie = element.replace(name+'=','')
        
    );
    return yourCookie

【讨论】:

【参考方案14】:

第一个需要创建cookie(我已经将令牌包装在cookie中作为示例),然后在响应中设置它。按照以下方式使用cookie安装cookieParser

app.use(cookieParser());

浏览器会将其保存在其“资源”选项卡中,并将用于此后以初始 URL 为基础的每个请求

var token = student.generateToken('authentication');
        res.cookie('token', token, 
            expires: new Date(Date.now() + 9999999),
            httpOnly: false
        ).status(200).send();

在服务器端从请求中获取 cookie 也很简单。您必须通过调用请求对象的 'cookie' 属性从请求中提取 cookie。

var token = req.cookies.token; // Retrieving Token stored in cookies

【讨论】:

以上是关于使用 Node.js HTTP 服务器获取和设置单个 Cookie的主要内容,如果未能解决你的问题,请参考以下文章

[转] Node.js的线程和进程

Node.js 实现第一个应用以及HTTP模块和URL模块应用

Node.js服务器开发(上)

如何使用适用于 node.js 的单实例弹性负载均衡器设置 HTTPS?

Node.js的线程和进程

使用 node.js 运行许多并行 http 请求