express-session 在设置 cookie 时会导致服务器崩溃?
Posted
技术标签:
【中文标题】express-session 在设置 cookie 时会导致服务器崩溃?【英文标题】:express-session crashes server on setting cookie? 【发布时间】:2022-01-12 21:53:55 【问题描述】:基本上这是另一个帖子Express-session does not set cookie?,我正在关注 Ben Awad 的全栈教程。 cookie 被创建,但服务器崩溃,这是错误
node:internal/errors:464
ErrorCaptureStackTrace(err);
^
TypeError [ERR_INVALID_ARG_TYPE]: The "chunk" argument must be of type string or an instance of Buffer or Uint8Array. Received an instance of Array
at new NodeError (node:internal/errors:371:5)
at _write (node:internal/streams/writable:312:13)
at Socket.Writable.write (node:internal/streams/writable:334:10)
at RedisSocket.writeCommand (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/@node-redis/client/dist/lib/client/socket.js:57:130)
at Commander._RedisClient_tick (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/@node-redis/client/dist/lib/client/index.js:415:64)
at Commander._RedisClient_sendCommand (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/@node-redis/client/dist/lib/client/index.js:396:82)
at Commander.commandsExecutor (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/@node-redis/client/dist/lib/client/index.js:160:154)
at Commander.BaseClass.<computed> [as set] (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/@node-redis/client/dist/lib/commander.js:8:29)
at RedisStore.set (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/connect-redis/lib/connect-redis.js:65:21)
at Session.save (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/express-session/session/session.js:72:25)
at Session.save (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/express-session/index.js:406:15)
at ServerResponse.end (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/express-session/index.js:335:21)
at ServerResponse.send (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/express/lib/response.js:221:10)
at /home/kuratar/github/milestone-4-Kuratar/server/node_modules/apollo-server-express/dist/ApolloServer.js:89:25
code: 'ERR_INVALID_ARG_TYPE'
我注意到 user.ts 中有这行特定的代码:
req.session.userId = user.id
当它被注释掉时,错误不会发生,但 cookie 没有设置。响应标头中没有 set-cookie 选项。 我的文件与我链接的帖子中的其他人几乎相同。
index.ts
import "reflect-metadata";
import MikroORM from "@mikro-orm/core";
import __prod__ from "./constants";
import microConfig from "./mikro-orm.config";
import express from "express";
import ApolloServer from "apollo-server-express";
import ApolloServerPluginLandingPageGraphQLPlayground from "apollo-server-core";
import buildSchema from "type-graphql";
import HelloResolver from "./resolvers/hello";
import PostResolver from "./resolvers/post";
import UserResolver from "./resolvers/user";
import * as redis from "redis";
import session from "express-session";
import connectRedis from "connect-redis";
import MyContext from "./types";
// start postgresql server on wsl - sudo service postgresql start
// stop - sudo service postgresql stop
// start redis server on wsl - redis-server
// sudo /etc/init.d/redis-server restart
// stop, start
// watch ts changes - npm run watch
// run server - npm run dev
const main = async () =>
const orm = await MikroORM.init(microConfig); // initialize database
await orm.getMigrator().up(); // run migrations before anything else
const app = express();
app.set("trust proxy", 1); // trust first proxy
// this comes before applyMiddleware since use session middleware inside apollo
const RedisStore = connectRedis(session);
const redisClient = redis.createClient(); // TODO: TypeError: Cannot read properties of undefined (reading 'createClient')
redisClient.on("error", (err) => console.log("Redis Client Error", err));
await redisClient.connect();
app.use(
session(
name: "qid",
// touch - make request to redis to reset the user's session
// if user does something, it means they are active and should reset the timer of automatically logging them out
// after 24 hours for example
// disableTouch: true - keep session forever, can change this later to timed sessions
store: new RedisStore( client: redisClient, disableTouch: true ), // tell express session using redis
cookie:
maxAge: 1000 * 60 * 60 * 24 * 365 * 10, // 10 years
httpOnly: true,
sameSite: "lax", // csrf
secure: __prod__, // only works in https
,
saveUninitialized: false,
secret: "askljdhfjkalshdjlf", // want to keep this secret separately
resave: true,
rolling: true,
)
);
// app.use(function (req, res, next)
// res.header(
// "Access-Control-Allow-Origin",
// "https://studio.apollographql.com"
// );
// res.header("Access-Control-Allow-Credentials", "true");
// next();
// );
const apolloServer = new ApolloServer(
schema: await buildSchema(
resolvers: [HelloResolver, PostResolver, UserResolver],
validate: false,
),
// object that is accessible by resolvers, basically pass the database itself
context: ( req, res ): MyContext => ( em: orm.em, req, res ),
plugins: [
ApolloServerPluginLandingPageGraphQLPlayground(
settings: "request.credentials": "include" ,
),
],
);
await apolloServer.start();
const corsOptions =
origin: new RegExp("/*/"),
credentials: true,
;
apolloServer.applyMiddleware( app, cors: corsOptions ); // create graphql endpoint on express
app.listen(4000, () =>
console.log("Server started on localhost:4000");
);
;
main().catch((error) =>
console.log("----------MAIN CATCHED ERROR----------");
console.error(error);
console.log("-----------------END------------------");
);
user.ts
import
Resolver,
Arg,
Mutation,
InputType,
Field,
Ctx,
ObjectType,
from "type-graphql";
import User from "../entities/User";
import MyContext from "../types";
import argon2 from "argon2";
// another way to implementing arguments for methods instead of @Arg()
@InputType()
class UsernamePasswordInput
@Field()
username: string;
@Field()
password: string;
@ObjectType()
class FieldError
@Field()
field: string;
@Field()
message: string;
@ObjectType()
class UserResponse
@Field(() => [FieldError], nullable: true )
errors?: FieldError[];
@Field(() => User, nullable: true )
user?: User;
@Resolver()
export class UserResolver
@Mutation(() => UserResponse)
async register(
@Arg("options") options: UsernamePasswordInput,
@Ctx() em : MyContext
): Promise<UserResponse>
if (options.username.length <= 2)
return
errors: [
field: "username", message: "length must be greater than 2" ,
],
;
if (options.password.length <= 2)
return
errors: [
field: "password", message: "length must be greater than 2" ,
],
;
// argon2 is a password hasher package
const hashedPassword = await argon2.hash(options.password);
const user = em.create(User,
username: options.username,
password: hashedPassword,
);
try
await em.persistAndFlush(user);
catch (error)
// duplicate username error
if (error.code === "23505")
// || error.detail.includes("already exists")
return
errors: [ field: "username", message: "Username already taken" ],
;
// return user in an object since response is now a response object - UserResponse
return user ;
@Mutation(() => UserResponse)
async login(
@Arg("options") options: UsernamePasswordInput,
@Ctx() em, req : MyContext
): Promise<UserResponse>
// argon2 is a password hasher package
const user = await em.findOne(User,
username: options.username,
);
// can give same field error message like invalid login
if (!user)
return
errors: [ field: "username", message: "That username doesn't exist" ],
;
const valid = await argon2.verify(user.password, options.password);
if (!valid)
return
errors: [ field: "password", message: "Incorrect password" ],
;
// mutation
// login(options: username: "eric", password: "eric")
// errors
// field
// message
//
// user
// id
// username
//
//
//
console.log(req.session)
console.log(user.id)
req.session.userId = user.id
console.log(req.session)
console.log(req.session.id)
// console.log(req.session.userId)
// return user in an object since response is now a response object - UserResponse
return user ;
types.ts
import EntityManager, IDatabaseDriver, Connection from "@mikro-orm/core";
import Request, Response from "express";
import Session, SessionData from "express-session";
// this is the type of orm.em from index.ts
// extracted to make code look cleaner in post.ts
export type MyContext =
em: EntityManager<any> & EntityManager<IDatabaseDriver<Connection>>;
req: Request &
session: Session & Partial<SessionData> & userId?: number ;
;
res: Response;
;
【问题讨论】:
【参考方案1】:为了更具体地了解 Bernardo,Ben 还在 github 存储库中将其更改为 ioredis。所以需要安装ioredis并添加这些行
import Redis from "ioredis";
const redis = new Redis(process.env.REDIS_URL);
并删除/注释掉旧的 redisClient 代码行。
【讨论】:
【参考方案2】:我遇到了同样的错误。在我的情况下,我可以通过将 redis 客户端更改为 ioredis(我使用的是 redis)来修复它。
【讨论】:
正如目前所写,您的答案尚不清楚。请edit 添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以找到更多关于如何写好答案的信息in the help center。以上是关于express-session 在设置 cookie 时会导致服务器崩溃?的主要内容,如果未能解决你的问题,请参考以下文章
如何通过express-session在node.js上设置持久性cookie?
什么时候应该将 cookie-parser 与 express-session 一起使用?
express-session 和 cookie-session 有啥区别?
在带有 express-session 的 NodeJS 中使用安全 cookie 时会话不持久
Node.js、Angular、express-session:由于 cookie 策略(sameSite cookie),Chrome 80 不保存会话