express-session 设置 cookie 时服务器崩溃?
express-session crashes server on setting cookie?
基本上是另一个 post Express-session does not set cookie?,我正在关注 Ben Awad 的 Fullstack 教程。 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。 response-header 中没有 set-cookie 选项。
我的文件与我链接的 post 中的其他人几乎相同。
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;
};
我遇到了同样的错误。在我的情况下,我能够通过将 redis 客户端更改为 ioredis(我使用的是 redis)来修复它。
为了更具体地说明 Bernardo,Ben 还在 github 存储库中将其更改为 ioredis。所以你需要安装 ioredis 并添加这些行
import Redis from "ioredis";
const redis = new Redis(process.env.REDIS_URL);
和 delete/comment 删除旧的 redisClient 代码行。
基本上是另一个 post Express-session does not set cookie?,我正在关注 Ben Awad 的 Fullstack 教程。 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。 response-header 中没有 set-cookie 选项。 我的文件与我链接的 post 中的其他人几乎相同。
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;
};
我遇到了同样的错误。在我的情况下,我能够通过将 redis 客户端更改为 ioredis(我使用的是 redis)来修复它。
为了更具体地说明 Bernardo,Ben 还在 github 存储库中将其更改为 ioredis。所以你需要安装 ioredis 并添加这些行
import Redis from "ioredis";
const redis = new Redis(process.env.REDIS_URL);
和 delete/comment 删除旧的 redisClient 代码行。