关于javascript:使用Node.js HTTP服务器获取并设置单个Cookie

Get and Set a Single Cookie with Node.js HTTP Server

我希望能够设置一个cookie,并在对nodejs服务器实例的每个请求中读取该cookie。 是否可以通过几行代码来完成,而无需引入第三方库?

1
2
3
4
5
6
7
8
9
var http = require('http');

http.createServer(function (request, response) {
  response.writeHead(200, {'Content-Type': 'text/plain'});
  response.end('Hello World\
'
);
}).listen(8124);

console.log('Server running at http://127.0.0.1:8124/');

只是尝试直接从nodejs.org中获取上述代码,然后在其中工作一个cookie。


无法快速获取/设置Cookie的功能,因此我想到了以下技巧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var http = require('http');

function parseCookies (request) {
    var list = {},
        rc = request.headers.cookie;

    rc && rc.split(';').forEach(function( cookie ) {
        var parts = cookie.split('=');
        list[parts.shift().trim()] = decodeURI(parts.join('='));
    });

    return list;
}


http.createServer(function (request, response) {

  // To Read a Cookie
  var cookies = parseCookies(request);

  // To Write a Cookie
  response.writeHead(200, {
    'Set-Cookie': 'mycookie=test',
    'Content-Type': 'text/plain'
  });
  response.end('Hello World\
'
);
}).listen(8124);

console.log('Server running at http://127.0.0.1:8124/');

这会将所有cookie存储到cookie对象中,并且在编写头部时需要设置cookie。


如果您使用Express库,就像许多node.js开发人员一样,则有一种更简单的方法。查看Express.js文档页面以获取更多信息。

上面的解析示例有效,但是express给您提供了一个很好的函数来解决这一问题:

1
app.use(express.cookieParser());

设置Cookie:

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

清除Cookie:

1
res.clearCookie('cookiename');


RevNoah对于使用Express的cookie解析器的建议是最好的答案。但是,这个答案现在已经3年了,已经过时了。

使用Express,您可以按以下方式读取Cookie

1
2
3
4
5
6
7
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,以替换相应的相对最新版本。

1
2
3
4
"dependencies": {
   "express":"4.12.3",
   "cookie-parser":"1.4.0"
  },

此处介绍了更多操作,例如设置和解析Cookie
和这里


为了增强@Corey Hart的答案,我使用以下命令重写了parseCookies()

  • RegExp.prototype.exec-使用正则表达式解析"名称=值"字符串

这是工作示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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\
'
);
} ).listen(1234);

我还注意到OP使用http模块。
如果OP使用restify,则他可以使用restify-cookie:

1
2
3
4
5
6
7
8
9
10
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);


您可以使用" cookies" npm模块,该模块具有一组全面的功能。

文档和示例,请访问:
https://github.com/jed/cookies


要使Cookie拆分器与Cookie值中包含" ="的Cookie一起使用,请执行以下操作:

1
2
3
4
5
6
7
8
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:

1
get_cookies(request)['my_cookie']

Cookie通过HTTP标头传输
您只需要解析请求标头并放入响应标头。


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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


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

Can it be done in a few lines of code, without the need to pull in a third party lib?


阅读饼干

从具有Cookie标头的请求中读取Cookie。它们仅包含namevalue。由于路径的工作方式,可以发送多个相同名称的cookie。在NodeJS中,所有Cookie均以一个字符串的形式发送到Cookie标头中。您用;拆分了它们。拥有cookie后,等号左边(如果存在)的所有内容均为name,后面的所有内容均为value。某些浏览器会接受不带等号的Cookie,并假定名称为空。空格不算作Cookie的一部分。值也可以用双引号(")引起来。值也可以包含=。例如,formula=5+3=8是有效的cookie。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
 * @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()是为了避免数组发生突变。)


设定Cookie

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
 * @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 protected]是有效的cookie,但是这些库会将其编码为email=name%40domain.com。如果您在Cookie中使用%,则解码可能会出现问题。它会被扭曲。例如,以前为secretagentlevel=50%007and50%006的cookie变为secretagentlevel=507and506。这是一个极端的情况,但是如果切换库,则需要注意。

同样,在这些库上,cookie设置为默认值path=/,这意味着它们会在每个url请求上发送到主机。

如果要自己编码或解码这些值,可以分别使用encodeURIComponentdecodeURIComponent


参考文献:

  • Cookie语法
  • Set-Cookie语法

附加信息:

  • https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Cookie
  • https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Set-Cookie

我写了这个简单的函数
req.headers.cookie和cookie名称

1
2
3
4
5
6
7
8
9
10
11
const getCookieByName =(cookies,name)=>{
    const arrOfCookies = cookies.split(' ')
    let yourCookie = null

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

使用一些ES5 / 6魔法和RegEx Magic

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

注意:如果该值中包含=,则无需担心。如果密钥中有一个=,则天堂中的麻烦。

更多说明:有些人可能会争辩可读性,因此请根据需要对其进行分解。

我喜欢注释:添加错误处理程序(尝试捕获)不会有问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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}`)
}

enter image description here

到底是怎么回事?

iLikeCookies()将按;(;后的空格)分割Cookie:

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

然后,我们映射该数组,并使用正则表达式捕获parens按第一次出现的=进行拆分:

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

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

Nooice。


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

1
2
3
4
5
6
7
8
9
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
  });
});


首先需要创建cookie(我将令牌包装在cookie中作为示例),然后进行响应设置。要以以下方式使用cookie,请安装cookieParser

1
app.use(cookieParser());

浏览器会将其保存在其"资源"标签中,并将随后的每个请求(以初始URL为基础)用于每个请求

1
2
3
4
5
var token = student.generateToken('authentication');
        res.cookie('token', token, {
            expires: new Date(Date.now() + 9999999),
            httpOnly: false
        }).status(200).send();

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

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

1
2
3
4
5
6
7
8
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);
}