我有一個使用 Netlify 上托管的 NextJS 構建的應用程式。API 托管在 Heroku 上(這是一個運行 GraphQL 的 NestJS 專案)
在本地開發模式下,我的任何 SSR 頁面都沒有問題。但是,在生產中,我不斷收到 500 個錯誤,在 Netlify 函式面板中產生以下日志:
ERROR ApolloError: request to https://api.paladindeck.com/graphql failed, reason: Client network socket disconnected before secure TLS connection was established
at new ApolloError (/var/task/node_modules/@apollo/client/errors/errors.cjs:34:28)
at /var/task/node_modules/@apollo/client/core/core.cjs:1598:19
at both (/var/task/node_modules/@apollo/client/utilities/utilities.cjs:986:53)
at /var/task/node_modules/@apollo/client/utilities/utilities.cjs:979:72
at new Promise (<anonymous>)
at Object.then (/var/task/node_modules/@apollo/client/utilities/utilities.cjs:979:24)
at Object.error (/var/task/node_modules/@apollo/client/utilities/utilities.cjs:987:49)
at notifySubscription (/var/task/node_modules/zen-observable/lib/Observable.js:140:18)
at onNotify (/var/task/node_modules/zen-observable/lib/Observable.js:179:3)
at SubscriptionObserver.error (/var/task/node_modules/zen-observable/lib/Observable.js:240:7) {
graphQLErrors: [],
clientErrors: [],
networkError: FetchError: request to https://api.paladindeck.com/graphql failed, reason: Client network socket disconnected before secure TLS connection was established
at ClientRequest.<anonymous> (/var/task/node_modules/next/dist/compiled/node-fetch/index.js:1:64142)
at ClientRequest.emit (events.js:412:35)
at ClientRequest.emit (domain.js:475:12)
at TLSSocket.socketErrorListener (_http_client.js:475:9)
at TLSSocket.emit (events.js:400:28)
at TLSSocket.emit (domain.js:475:12)
at emitErrorNT (internal/streams/destroy.js:106:8)
at emitErrorCloseNT (internal/streams/destroy.js:74:3)
at processTicksAndRejections (internal/process/task_queues.js:82:21) {
type: 'system',
errno: 'ECONNRESET',
code: 'ECONNRESET'
},
extraInfo: undefined
}
我已將 Sentry 附加到應用程式,它正在捕獲一些類似的資訊:
http
POST https://api.paladindeck.com/graphql [[undefined]]
Info
09:15:05
console
ApolloError: request to https://api.paladindeck.com/graphql failed, reason: Client network socket disconnected before secure TLS connection was established
at new ApolloError (/var/task/node_modules/@apollo/client/errors/errors.cjs:34:28)
at /var/task/node_modules/@apollo/client/core/core.cjs:1598:19
at both (/var/task/node_modules/@apollo/client/utilities/utilities.cjs:986:53)
at /var/task/node_modules/@apollo/client/utilities/utilities.cjs:979:72
at new Promise (<anonymous>)
at Object.then (/var/task/node_modules/@apollo/client/utilities/utilities.cjs:979:24)
at Object.error (/var/task/node_modules/@apollo/client/utilities/utilities.cjs:987:49)
at notifySubscription (/var/task/node_modules/zen-observable/lib/Observable.js:140:18)
at onNotify (/var/task/node_modules/zen-observable/lib/Observable.js:179:3)
at SubscriptionObserver.error (/var/task/node_modules/zen-observable/lib/Observable.js:240:7) {
graphQLErrors: [],
clientErrors: [],
networkError: FetchError: request to https://api.paladindeck.com/graphql failed, reason: Client network socket disconnected before secure TLS connection was established
at ClientRequest.<anonymous> (/var/task/node_modules/next/dist/compiled/node-fetch/index.js:1:64142)
at ClientRequest.emit (events.js:412:35)
at ClientRequest.emit (domain.js:475:12)
at TLSSocket.socketErrorListener (_http_client.js:475:9)
at TLSSocket.emit (events.js:400:28)
at TLSSocket.emit (domain.js:475:12)
at emitErrorNT (internal/streams/destroy.js:106:8)
at emitErrorCloseNT (internal/streams/destroy.js:74:3)
at processTicksAndRejections (internal/process/task_queues.js:82:21) {
type: 'system',
errno: 'ECONNRESET',
code: 'ECONNRESET'
},
extraInfo: undefined
}
Error
09:15:06
console
[GET] /_next/data/hHiW6IT3wpykwmCV9Cdhe/collections/d45ebedf-d7f1-4208-bfbf-e7aa1af43bd3/e54b8945-6ed0-4094-8c54-fbd42e755e97.json?cardInCollectionId=e54b8945-6ed0-4094-8c54-fbd42e755e97&collectionId=d45ebedf-d7f1-4208-bfbf-e7aa1af43bd3 (SSR)
Info
09:15:06
所有其他頁面(不使用 SSR,但查詢 API)按預期作業。
我看過其他類似的問題,但到目前為止,沒有任何解決方案有幫助。
當我無法找到解決此類問題的方法時,我傾向于認為我在做一些非常愚蠢的事情并且沒有意識到這一點。所以,完全有可能我只是錯過了一些如此基本的東西,我什至沒有考慮它。
uj5u.com熱心網友回復:
呼……這花了我幾天的時間。
所以,事實證明這不是一件容易診斷的事情(至少對我來說不是)。
對我的問題的簡短回答是:不要將背景關系標頭傳遞getServerSideProps給 Apollo 客戶端。出于某種原因,即使authorization附加了標頭,這些標頭也會導致某些內容中斷。
這就是我現在正在做的事情:
// graphql-client.ts
export class GraphQLClient {
private readonly logger = new Logger(GraphQLClient.name);
get value(): ApolloClient<NormalizedCacheObject> {
if (!this._client) {
this._client = this.createClient();
}
if (this._client === undefined)
throw new Error(`Error when creating graphql client`);
return this._client;
}
constructor(
private readonly user?: User | null,
private _client?: ApolloClient<NormalizedCacheObject>,
) {}
private createClient(): ApolloClient<NormalizedCacheObject> {
const isSsrMode = typeof window === 'undefined';
const httpLink = createHttpLink({ uri: apolloConfig.uri });
const authLink = setContext(async (_, context) => {
let token: string | undefined;
if (context?.headers?.cookie) {
try {
token = getCookie(TOKEN_COOKIE_NAME, context.headers.cookie);
} catch (err) {
this.logger.error(err);
token = await this.user?.getIdToken();
}
} else {
token = await this.user?.getIdToken();
}
const headers = {
// HERE IS HOW I FIXED THINGS
// If this is SSR, DO NOT PASS THE REQUEST HEADERS.
// Just send along the authorization headers.
// The **correct** headers will be supplied by the `getServerSideProps` invocation of the query.
...(!isSsrMode ? context.headers : []),
authorization: token ? `Bearer ${token}` : ``,
};
return { headers };
});
return new ApolloClient({
link: authLink.concat(httpLink),
credentials: 'include',
cache: new InMemoryCache({
possibleTypes: generatedIntrospection.possibleTypes,
}),
ssrMode: isSsrMode,
});
}
}
// mypage.tsx
...
...
...
export const getServerSideProps: GetServerSideProps = async (context) => {
if (!isCardDetailsPageQueryType(context.query))
return {
props: {},
};
const logger = new Logger(
`${CardDetailsPage.name}_${getServerSideProps.name}`,
);
const client = new GraphQLClient();
const GET_CARD_DETAILS_QUERY = gql`
// query
`;
const results = await client.value.query({
query: GET_CARD_DETAILS_QUERY,
variables: { id: context.query.cardInCollectionId },
context: {
headers: {
...context.req.headers, // <-- just pass the context headers, the client will automatically append the authorization header
},
},
});
const GET_OTHER_PRINTINGS_BY_NAME_QUERY = gql`
// query
`;
const otherPrintingResults = await client.value.query({
query: GET_OTHER_PRINTINGS_BY_NAME_QUERY,
variables: {
name: results.data.cardsInCollection.card.name,
collectionId: context.query.collectionId,
},
context: {
headers: {
...context.req.headers, // <-- same as above
},
},
});
return {
props: {
cardsInCollection: results.data.cardsInCollection,
otherPrintings: otherPrintingResults.data.otherPrintings,
allCardsInCollection: otherPrintingResults.data.allCardsInCollection,
},
};
};
對于我的特定用例來說,這可能是一個非常具體的問題,但我確實希望有一天,有人會覺得這很有幫助。
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/451128.html
