์ฃผ์ ๋ก. ๊ฐ์ฌ ํด์
const errorLink = onError(({ networkError }) => {
if (networkError.status === 401) {
logout();
}
})
๋ด๊ฐ ์๋ ๋ฐ์ ๊ฐ์ด "failed to fetch"๋ ์๋ต์ด ์์ผ๋ฉด(์: ์๋ฒ๊ฐ ๋ค์ด๋ ๊ฒฝ์ฐ) ๋ฐ์ํฉ๋๋ค. ์ํ ์ฝ๋๊ฐ ํ์๋์ง ์๊ธฐ ๋๋ฌธ์ ๋๋ค. ๋ด๊ฐ ํ๋ ธ๋ค๋ฉด ์ ๋ฅผ ์์ ํ์ญ์์ค.
๊ทธ๋ฌ๋ ๊ทธ๋ฐ ์ํฉ์์ ๋ฌธ์ ๋ฉ์์ง๋ง ๊ฐ๋ ๊ฒ์ ์ต์ ์ ์์ด๋์ด๊ฐ ์๋๋ผ๊ณ ์๊ฐํฉ๋๋ค. ์ค๋ฅ ์ฝ๋์ ๊ฐ์ด ๋ ์ฝ๊ฒ ์ฒ๋ฆฌํ ์ ์๋ ๋ฌด์ธ๊ฐ๊ฐ ์์ด์ผ ํฉ๋๋ค.
๋๋ ์์ฒญ์ด ํฌ๋กฌ ์ธ์คํํฐ์ ๋คํธ์ํฌ ํ๊ทธ์์ (401)๋ก ๋ณด๋ด๊ณ ์๋ ๊ฒ์ ๋ณด์์ต๋๋ค.
#218๊ณผ ๊ด๋ จ์ด ์๋ ๊ฒ ๊ฐ์์(ํ์คํ์ง ์์..)
๋ํ์ด ๋ฌธ์ ๊ฐ ์์ต๋๋ค. networkError.status
๋๋ networkError.statusCode
์ด(๊ฐ) ์๊ณ ์๋ต ๊ฐ์ฒด๊ฐ ์์ต๋๋ค. ๋คํธ์ํฌ ๊ณ์ธต์์ HTTP ์ํ ์ฝ๋๋ฅผ ์ฒ๋ฆฌํ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค.
๋๋ ๋ํ ํ์ฌ์ด ๋ฌธ์ ๊ฐ ์์ต๋๋ค. ์ต์ ๋ฆด๋ฆฌ์ค์์ ์์ ๋ ๊ฒ์ผ๋ก ์ถ์ ๋์ง๋ง ์ฌ์ ํ ์ด ๋ฌธ์ ๊ฐ ์์ต๋๋ค.
@jbaxleyiii ์ ๋ํ ์๊ฐ์ด
@akrigline ๊ณผ @bogdansoare ๊ฐ ์ฌ์ ํ ๋ฌธ์ ๋ฅผ ๊ฒช๊ณ ์๋ค๋ ์์์ ๋ค์ผ๋ ์ ๊ฐ์ ๋๋ค! ์ด๊ฒ์ ํ์คํ ๊ณ ์ณ์ ธ์ผ ํฉ๋๋ค.
๋๋ ์ง๊ธ #364๋ฅผ ํตํด ์ผํ๊ณ ์๋ค. ๊ทธ๋์ ์ด ์ฝ๋ ์๋๋ณด์ค ๋ฅผ ์์ฑํ๊ฑฐ๋ ์์ ํ์ฌ ์ค๋ฅ๋ฅผ ์ฌํํ ์ ์์ต๋๊น? ์๋๋ฉด ์คํจํ ํ ์คํธ๋ก PR์ ์ฌ๋ ๊ฒ์ด ๋ ๋ซ์ต๋๊น?
์ด๋ค ์์?
๋ํ ์ด ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ฉด ์๋ต ๊ฐ์ฒด๊ฐ ๋น์ด ์๊ณ networkError ๊ฐ์ฒด์ statusCode๊ฐ ์์ต๋๋ค.
networkError
์์ฑ๋ ์ป์ง ๋ชปํฉ๋๋ค.
ํธ์ง: ์๋ง๋ #475
Error
์ ํ์ ๋ฌด์ํ์ฌ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์ง๋ง ์ด๊ฒ์ ์์ ํ ์์์ ์ธ ํด๊ฒฐ ๋ฐฉ๋ฒ์ด๋ฉฐ ํฅํ ์์ ์ฌํญ์ ๊ธฐ๋ค๋ ค์ผ ํฉ๋๋ค. @์๋ฐ์ค
์ฐ๋ฆฌ๊ฐ ๊ทธ๋ค์ ๋ํด ์ด networkError
์ ํ์ฌ๋ ์ค์ ์๋ฒ ์ธก ์ค๋ฅ๊ฐ ์๋๋๋ค. ์์ฝํ๋ฉด ์ด๋ฌํ ๊ฒฝ์ฐ์๋ statusCode
๊ฐ ์ ํ ๋ฐ์ํ์ง ์์ผ๋ฉฐ ์ค์ ๋ก graphql ์๋ฒ์์ ์ค์ ๋คํธ์ํฌ ์ค๋ฅ๋ฅผ ์์ฑํ๊ฑฐ๋ ์์ ํด์ผ ํฉ๋๋ค. (_์๋ฒ์์ ํต์ ๋ฌธ์ ๋ง ์๋ ๊ฒฝ์ฐ statusCode
_์ ์ ๊ทผํ ์ ์์ต๋๋ค.)
TypeScript๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ networkError
์ ๋ค์๊ณผ ๊ฐ์ ์ ํ์ด ์์์ ์ ์ ์์ต๋๋ค.
export interface ErrorResponse {
graphQLErrors?: GraphQLError[];
networkError?: Error;
response?: ExecutionResult;
operation: Operation;
}
๊ทธ๋ฆฌ๊ณ Error
๋ ๋ค์๊ณผ ๊ฐ์ด ์ ์๋ฉ๋๋ค.
interface Error {
name: string;
message: string;
stack?: string;
}
์ฌ๊ธฐ์ statusCode
๊ฐ ์ ์๋์ด ์์ง ์์์ ์ ์ ์์ผ๋ฏ๋ก TypeScript์์ networkError.statusCode
์ ๊ฐ์ ๊ฒ์ ํธ์ถํ ์ ์์ต๋๋ค.
networkError ๋ฅผ any
๋ก ์บ์คํธํด์ผ ํ๋ฉฐ ๋ํ ์ด๋ฅผ ๋น object
๋ก ๋ง๋ค์ด์ผ ํฉ๋๋ค.
const afterWareLink = onError(({operation, response, graphQLErrors = {}, networkError = {} as any}) => {
const status: number = networkError && networkError.statusCode ? networkError.statusCode : null;
debugHelper.error('apolloError', {
operation,
response,
graphQLErrors,
networkError,
status,
});
// Do your job
// if (status && HTTPExceptions[status])
// redirectService.redirectWithReload(`/error/${status}`);
});
์์ afterWareLink
์ฝ๋์ ๋ฐ๋ฅด๋ฉด 400 ์ํ ์ฝ๋์ ํจ๊ป ์๋ฒ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ผ๋ฉฐ ์ด์ ์ด ์ํ ์ฝ๋์ ์ก์ธ์คํ ์ ์์ต๋๋ค.
๋ค์์ ๋ด ApolloClient ์ ์์ ๋๋ค.
import {ApolloLink, from} from 'apollo-link';
import {application} from '../../constants/application';
import {ApolloClient} from 'apollo-client';
import {InMemoryCache} from 'apollo-cache-inmemory';
import {HttpLink} from 'apollo-link-http';
import {tokenStorage} from '../middleware';
import {debugHelper} from '../helpers/debugHelper';
import {onError} from 'apollo-link-error';
import {apolloCache} from './apolloCache';
import fetch from 'unfetch';
const API = new HttpLink({
uri: application.API.URL,
fetch: fetch
});
const cache = new InMemoryCache({
dataIdFromObject: (object: any) => apolloCache.getID(object)
});
const afterWareLink = onError(({operation, response, graphQLErrors = {}, networkError = {} as any}) => {
const status: number = networkError && networkError.statusCode ? networkError.statusCode : null;
debugHelper.error('apolloError', {
operation,
response,
graphQLErrors,
networkError,
status,
});
// Do your job
// if (status && HTTPExceptions[status])
// redirectService.redirectWithReload(`/error/${status}`);
});
const middleWareLink = new ApolloLink((operation, forward) => {
const token = tokenStorage.get();
operation.setContext(context => ({
...context,
headers: {
...context.headers,
token: token ? token.token : '',
},
}));
return forward(operation);
});
export const apolloClient = new ApolloClient({
link: from([
middleWareLink,
afterWareLink,
API
]),
cache: cache,
});
์ ์ฉํ๊ธฐ๋ฅผ ๋ฐ๋๋๋ค.
๋
ธ๋ ์๋ฒ๋ฅผ ์ฌ์ฉ ์ค์ด๊ณ ์ปจํ
์คํธ์์ ์๋ต ๊ฐ์ฒด์ ์ก์ธ์คํ ์ ์๋ ๊ฒฝ์ฐ ๋ค์์ ์ถ๊ฐํ์ฌ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์์ต๋๋ค. ctx.res.status(401);
์๋ต์ ๋ฐํํ๊ธฐ ์ ์.
์ด์ apollo-link-errors ๋ฐ apollo-link-retry ๋ ๋ค networkErrors ๊ฐ์ฒด์์ ์ค๋ฅ๋ฅผ ์ ํํ ์ ์์ต๋๋ค.
๋ค์์ ์ฌ์ฉ์๊ฐ ์ธ์ฆ๋์๋์ง ํ์ธํ๊ธฐ ์ํด ์ฌ์ฉ์ ์ง์ ์คํค๋ง ์ง์๋ฌธ์ ์ฌ์ฉํ๋ ์์ ๋๋ค.
import { SchemaDirectiveVisitor } from "graphql-tools";
import { defaultFieldResolver } from "graphql";
import { createError } from "apollo-errors";
const AuthError = createError("AuthError", {
message: "Not authorized"
});
export class IsAuthenticatedDirective extends SchemaDirectiveVisitor {
visitObject(type) {
this.ensureFieldsWrapped(type);
}
visitFieldDefinition(field, details) {
this.ensureFieldsWrapped(details.objectType);
}
ensureFieldsWrapped(objectType) {
if (objectType._authFieldsWrapped) return;
objectType._authFieldsWrapped = true;
const fields = objectType.getFields();
Object.keys(fields).forEach(fieldName => {
const field = fields[fieldName];
const { resolve = defaultFieldResolver } = field;
field.resolve = async function(...args) {
const ctx = args[2];
const result = await resolve.apply(this, args);
if (ctx.req.user) {
return result;
} else {
ctx.res.status(401);
return null;
}
};
});
}
}
๊ณ ์ณ์ก๋ ์๋์ด? ๋๋ ์ฌ์ ํ networkError ๊ฐ์ฒด์ ์ํ ์ฝ๋์ ์ก์ธ์คํ ์ ์์ต๋๋ค.
@artsiompeshko
TypeScript์์ ๋ค์๊ณผ ๊ฐ์ด ์ก์ธ์คํ ์ ์์ต๋๋ค.
const networkErrorLink = onError(({networkError, operation, forward}: ErrorResponse) => {
if (networkError) {
switch (networkError['statusCode']) {
case 401:
case 422:
if (window.location.pathname !== '/login') {
Logger.error('Unauthorized or stale Authorization Session. Reloading page...')
window.history.go()
}
break
}
}
return forward(operation)
})
์ด๊ฒ์ ๋ํ ์์์ด ์์ต๋๊น? ๊ฐ์ ๋ฌธ์ . ๋ด ์๋ฐ์คํฌ๋ฆฝํธ ์ฝ๋์์ ์ฌ์ฉํ ์ ์๋ networkError.statusCode
์์ต๋๋ค.
@goldenbearkin ์ด์ฉ๋ฉด ์ฐ๋ฆฌ๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์ฌ์ฉํ๋ ๊ฒ์ด ์๋ชป ๋์์ต๋๊น?
์ค๋ฅ์์ ๋คํธ์ํฌ ์ํ ์ฝ๋๋ฅผ ํ์ฉํด์ผ ํฉ๋๋ค. ํ์ฌ ๋คํธ์ํฌ ์ํ์ ์ก์ธ์คํ ์ ์๋ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค. networkError
๋ ์ค์ ๋ก status
์์ฑ์ด ์๋ HttpErrorResponse
์ ํ ๊ฐ์ฒด์ด๋ฉฐ ํญ์ 0
์ฌ๊ธฐ์ ์ฝ์์ ์๋ ์ํ ์ฝ๋๋ 401 ๋๋ 403์
๋๋ค.
@lvlohammadi
graphQLErrors์ ๋ํด์๋ ๋์ผํ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์ํํ ์ ์์ต๋๊น? ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ์ฌ networkErrors์ ๋ํ ์ํ ์ฝ๋๋ฅผ ๋ณผ ์ ์์ต๋๋ค. ์ด๋ฅผ 504๋ก ํ
์คํธํ์ต๋๋ค. ๊ทธ๋ฌ๋ ์ธ์ฆ์ ์คํจํ๋ฉด graphQLError ์ํ ์ฝ๋ 401์ด ์์๋ฉ๋๋ค. ํ์ง๋ง ํ์ฌ ์ก์ธ์คํ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค. 'graphQLErrors = {} as any' ์ต์
์ ์๋ํ์ง๋ง ์๋ฌด ์์ฉ์ด ์์์ต๋๋ค.
์ด๊ฒ์ ์ด์ํ์ง๋ง ์ค์ ๋ก networkError
์ statusCode
์์ฑ์ด ์์ต๋๋ค. ๊ทธ๋ฌ๋ ์ธํฐํ์ด์ค์๋ ์ด๋ฌํ ์์ฑ์ด ์์ผ๋ฏ๋ก TS๋ ์ฌ๊ธฐ์ ์ค๋ฅ๋ฅผ ์ถ๋ ฅํฉ๋๋ค.
const errorLink = onError(({ networkError, graphQLErrors, response }: ErrorResponse) => {
if (graphQLErrors) {
graphQLErrors.map(({ message, locations, path }: GraphQLError) => {
showError(message);
});
}
// @ts-ignore
console.log('statusCode', networkError.statusCode); // outputs 401
if (networkError) {
showError(networkError.message);
}
});
์๋ฒ์์ koa
๋ฐ koa-passport
๋ฅผ ์ฌ์ฉํ๊ณ ํด๋น ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
app.use(mount(graphQlPath, passport.authenticate('jwt', { session: false })));
์ด ๋ฌธ์ ๋ ์ค์ ๋ก ์ค๋๋์์ผ๋ฏ๋ก ๋ซ์ต๋๋ค. ๊ทธ๋ฌ๋ ์ฌ์ ํ ์ด์ ๋ํด ์ฐ๋ คํ๋ ๊ฒฝ์ฐ ์ธ์ ๋ ์ง ๋ค์ ์ด์ด์ ์ต๋ํ ๋นจ๋ฆฌ ์ฐ๋ฝ๋๋ฆฌ๊ฒ ์ต๋๋ค.
@JoviDeCroock ์ ์ง๊ธ ๊ทธ ๋ฌธ์ ์ ์ง๋ฉดํด ์์ต๋๋ค... ๊ทธ๋์, ํด๊ฒฐ์ฑ ์ ๋ฌด์์ ๋๊น? networkError์์ ์ค๋ฅ ์ํ๋ฅผ ์ป์ผ๋ ค๋ฉด ์ด๋ป๊ฒ ํด์ผ ํฉ๋๊น?
์ข์ต๋๋ค. ๊ทธ๋์ ๊ทธ๊ฒ์ด networkError(graphqlError๊ฐ ์์ต๋๊น?)์ด๊ณ statusCode/status๊ฐ ์ ์๋์ง ์์ ๊ฒ์ด ํ์คํฉ๋๊น?
typescript ์ค๋ฅ์ ๋๊น ์๋๋ฉด ์ค์ ๋ก ์ ์๋์ง ์์ ๊ฒ์ ๋๊น? ์ฌํํ ์ ์์ต๋๊น?
Btw ๋ด ์๊ฒฌ์ ๋ฐ๋ํ๋ ๊ฒ์ ์ด ์ค๋ฅ๋ฅผ ํด๊ฒฐํ๋ ๋ฐ ๋์์ด ๋์ง ์์ต๋๋ค. ์ด ๋ฌธ์ ๋ฅผ ์ฌํํ ๋ฐฉ๋ฒ์ด ์์๊ณ ์ฐ๋ฆฌ๊ฐ ๋์ธ ์ ์๋ ๋ฐฉ๋ฒ์ ๊ฐ์ ํ๊ธฐ ์ํด ๋ค๋ฌ์ด์ผ ํ๋ ๋ฌธ์ ๊ฐ ์์์ต๋๋ค.
๋์ผํ ๋ฌธ์ ๊ฐ ์์ง๋ง ์๋ฒ์์ ์ํ๋ฅผ ๋ณด๋ด๋ ๋ฐฉ๋ฒ ๋๋ฌธ์ผ ์ ์์ต๋๋ค. ์ ๋ ๊ธฐ๋ณธ์ ์ผ๋ก graphQL ํน์ ์ฒ๋ฆฌ๋ฅผ ๋ฐ๋ก ๊ฐ๊ธฐ ์ํด ๋
ธ๋ ฅํ๊ณ ์์ผ๋ฉฐ ์๋ฅผ ๋ค์ด ๋ค์๊ณผ ๊ฐ์ด ๊ฐ๋จํ ์๋ตํฉ๋๋ค.
res.status(404).send("The resource you are looking for cannot be found")
๊ทธ๋ฌ๋ ๋๋ TypeError: Failed To Fetch
๋ง ์ป์ต๋๋ค. Apollo-Server-Express์ ๊ตฌํ ์ธ๋ถ ์ ๋ณด ๋๋ฌธ์
๋๊น?
@JoviDeCroock ์ฌ์ค, 401 ๋๋ 403์ ๋ํ ํด๋ผ์ด์ธํธ ์ธก ์ค๋ฅ ์ฒ๋ฆฌ๋ฅผ ๊ตฌํํ๋ ค๊ณ ํ ๋ graphql ์ฝ๋๋ก ์ฝ๊ฐ ์๋ง์ด๋์์ต๋๋ค. ์ฐ๋ฆฌ๋ ๋ฏธ๋ค์จ์ด๋ฅผ ์ฌ์ฉํ๊ณ ๋ด๊ฐ ํ ๊ฒ์ ๋ชจ๋ ์์ฒญ์ โโ๋ํด ํญ์ 401์ ๋ฐํํ๋ ๊ฒ์ ๋๋ค. ๊ทธ๋ ๊ฒ ํ๋ฉด OPTION ์์ฒญ์๋ 401์ด ๋ฐ์ํ๋๋ฐ, ๋ฐ์ํด์๋ ์ ๋๋ ์ผ์ด๋ฉฐ ์๋ง๋ ๊ทธ ๋๋ฌธ์ ์ํ๋ฅผ ์ ํ ๋ณผ ์ ์์์ ๊ฒ์ ๋๋ค. ์ฝ๋๋ฅผ ์ ๋ฐ์ดํธํ ํ ์ํ ์ฝ๋๋ฅผ ๋ณผ ์ ์์ต๋๋ค.
@dimitriy-k ์ ๋ฐ์ดํธ์ ๊ฐ์ฌ๋๋ฆฝ๋๋ค. ์ด ๋ฌธ์ ๊ฐ ํด๊ฒฐ๋์๋ค๋ ์์์ ๋ค์ผ๋ ๊ธฐ์๊ฒ ์๊ฐํฉ๋๋ค.
@AlbertoPL์ ๊ฒฝ์ฐ ์ค๋ ๋ฐค ๊ทํ์ ๋ฌธ์ ๋ฅผ ์กฐ์ฌํ๊ฒ ์ต๋๋ค. ๊ฐ๋ฅํ๋ฉด ์ด์ ๋ํด ๋ ๋ง์ ์๊ฒฌ์ ๋ณด๋ด์ฃผ์ญ์์ค.
@JoviDeCroock ๋ด ์ฃผ์
์ ๊ฐ์ฌํฉ๋๋ค. ๋ฐ๋ผ์ ์๋์ํค๋ ค๋ ํน์ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
res.status(426).send()
Chrome์ ๋คํธ์ํฌ ํญ์์ /graphql ์๋ํฌ์ธํธ๊ฐ 426 ์ํ ์ฝ๋์ ์ ์ ํ ์๋ต(์
๊ทธ๋ ์ด๋ ํ์)์ผ๋ก ์๋ตํ๋ ๊ฒ์ ๋ด
๋๋ค. ๊ทธ๋ฌ๋ apollo-link-error
์์ onError ๋งํฌ๋ฅผ ์ฌ์ฉํ ๋ ์ฌ์ ํ TypeError: Failed to Fetch
๋ง ํ์๋ฉ๋๋ค. ์ง์ ํ ์ํ ์ฝ๋๋ฅผ ์ป์ ์ ์๋ค๋ฉด ํ๋ฅญํ ๊ฒ์
๋๋ค(๋๋ ๋ฉ์์ง๋ก๋ ์ถฉ๋ถํ ๊ฒ์
๋๋ค).
์๊ฒ ์ต๋๋ค. ์์ฃผ ์ข์ ์ ๋ณด์ ๋๋ค. ์ฐ๋ฆฌ๋ ์ด๊ฒ์ ์ด๋ป๊ฒ๋ ๋๋ฒ๊น ํ ์ ์์ด์ผ ํฉ๋๋ค.
๋ค, ํ์คํ ์๋ํ ์ ์์ต๋๋ค. ๋์/ํ์ ์ฃผ์๋ฉด ๊ฐ์ฌํ๊ฒ ์ต๋๋ค.
๋ฐ๋ผ์ Chrome์ ๋๋ฒ๊ฑฐ์์ ์ฝ๋๋ฅผ ๋จ๊ณ๋ณ๋ก ์คํํ๋ฉด apollo-link-batch-http
๋ฐ apollo-link-http
๋ชจ๋ ๊ฐ์ ธ์ค๊ธฐ ๊ธฐ๋ฅ์ ์ฌ์ฉํ์ฌ API ํธ์ถ์ ์ํํ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค(๋งํฌ ์ต์
์ผ๋ก ์ ๋ฌ๋๊ฑฐ๋ ์ผ๋ถ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ์ ๋ฌ๋จ, ๋ ๋ค ์๋ํจ).
fetcher(chosenURI, options)
.then(function (response) {
operations.forEach(function (operation) { return operation.setContext({ response: response }); });
return response;
}).then(parseAndCheckHttpResponse(operations))
then ๋ธ๋ก์ผ๋ก ์ด๋ํ์ง ์๊ณ catch ๋ธ๋ก์ผ๋ก ์ด๋ํฉ๋๋ค. ๊ทธ๋๊น์ง ์ค๋ฅ๋ ์ด๋ฏธ "Failed to Fetch"๋ก ์ค์ ๋์ด ์์ต๋๋ค. ์ํ ์ฝ๋๊ฐ ์์ต๋๋ค.
์ฌ๊ธฐ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋์ง ํ์ธํด์ผ ํฉ๋๋ค.
์ง์ฅ์์ ๊ฝค ๋ฐ์ ์ผ์ ์ผ๋ก ์ด๋ฒ ์ฃผ๋ง์ ์ฌํํด ๋ณด๋ ค๊ณ ํฉ๋๋ค.
ํธ์งํ๋ค:
์ฃผ์ ๋ฌธ์ ๋ ๋ด๊ฐ ์ด๊ฒ์ ํ
์คํธํ ๋๋ง๋ค apollo-server
ํ๊ณ ์๊ณ ์ ์งํ๋์ง๋ง ๋ฐ์ธ๋ฉ์ด๋ ๋ฌด์ธ๊ฐ๊ฐ ์ฝ๊ฐ ๋ค๋ฅผ ์ ์๋ค๋ ๊ฒ์
๋๋ค. ๊ทธ๋์ ์ฌ์์ฐ ์๋ฒ ์์ด๋ ํ
์คํธํ๊ธฐ ์ด๋ ต์ต๋๋ค.
๋์์ด ๋๋ค๋ฉด ์ฌ๊ธฐ Apollo ์๋ฒ๊ฐ ์์ต๋๋ค(Apollo-server-express๋ฅผ ์ฌ์ฉํ๊ณ ์์ต๋๋ค). CreateGraphQLSchema๋ ์ฐ๋ฆฌ๊ฐ ์ฌ์ฉํ๋ Apollo Server์ ์ธ์คํด์ค๋ฅผ ๋ฐํํฉ๋๋ค:
const { ApolloServer, gql } = require("apollo-server-express");
const collectFragments = container => {
return container
.$list()
.map(component => container[component])
.join("\n");
};
const mergeObjects = container => {
const types = container.$list().map(k => container[k]);
return _.merge(...types);
};
function CreateGraphQLSchema(
schema,
queries,
mutations,
subscriptions,
resolvers,
graphqlFormatError,
) {
const graphQlSchema = gql`
${collectFragments(schema.enum)}
${collectFragments(schema.type)}
type Query {
${schema.Query}
}
${collectFragments(schema.input)}
type Mutation {
${schema.Mutation}
}
type Subscription {
${schema.Subscription}
}
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
`;
const resolverMap = {};
resolverMap.Query = mergeObjects(queries);
resolverMap.Mutation = mergeObjects(mutations);
resolverMap.Subscription = mergeObjects(subscriptions);
resolvers.$list().forEach(type => (resolverMap[type] = resolvers[type]));
return new ApolloServer({
typeDefs: graphQlSchema,
resolvers: resolverMap,
context: ({ req, res }) => {
return { req, res, user: req.user };
},
formatError: graphqlFormatError,
playground: process.env.NODE_ENV !== "production",
});
}
@AlbertoPL ๋๋์ด ๋ฌธ์ ์ CORS ๋ฌธ์ ๋ก ๋ฐํ์ก์ต๋๋ค.
๋คํธ์ํฌ ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ฑฐ๋ CORS๊ฐ ์๋ฒ ์ธก์์ ์๋ชป ๊ตฌ์ฑ๋๋ฉด fetch() ์ฝ์์ด TypeError์ ํจ๊ป ๊ฑฐ๋ถ๋ฉ๋๋ค.
์์ฒญ์ ๊ฑฐ๋ถํ ๋ ์ ์ ํ ํค๋๋ฅผ ๋ฐํํ๋์ง ํ์ธํ์ญ์์ค. ์ ๊ฒฝ์ฐ์๋ API Gateway๋ฅผ ์ฌ์ฉํ๊ณ ์์๊ณ ์ฌ์ฉ์ ์ง์ ๊ถํ ๋ถ์ฌ์๊ฐ ์์ฒญ์ ๊ฑฐ๋ถํ์ง๋ง Access-Control-Allow-Origin
ํค๋๋ฅผ ์ฒจ๋ถํ์ง ์์์ต๋๋ค. ์ ๋ ์๋ฒ๋ฆฌ์ค๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐฐํฌ๋ฅผ ๊ด๋ฆฌํ์ง๋ง ์ด๊ฒ์ด ์ฌ์ฉ๋๋ ๊ฒฝ์ฐ CloudFormation ํ
ํ๋ฆฟ์ผ๋ก ๋ณํํ ์ ์์ต๋๋ค: https://serverless.com/blog/cors-api-gateway-survival-guide/#cors -with-custom-authorizers.
@chris-feist๋ฅผ ์ ์ฐพ์์ต๋๋ค!
์๋ต์ Access-Control-Allow-Origin
ํค๋๋ฅผ ์ถ๊ฐํ๋ฉด apollo-client์์ ์ํ ์ฝ๋๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
express.js์์ ๋ค์์ ์ํํ์ต๋๋ค.
res.set('Access-Control-Allow-Origin', '*').status(401).send()
Access-Control-Allow-Origin์ ์ฌ์ฉํ๋ฉด ์ ์ฌ์ ์ธ ๋ณด์ ์ํ์ด ๋ฐ์ํ์ง ์์ต๋๊น? https://stackoverflow.com/questions/12001269/what-are-the-security-risk-of-setting-access-control-allow-origin
๊ทธ๋์ ์์ง๋ ์ฐพ๊ณ ์๋ ๋๊ตฐ๊ฐ๋ฅผ ์ํด...
statusCode ๊ฐ์ networkError์ ์์ฑ์ผ๋ก ๋ฐํ๋๊ณ networkError์ ๋ํ ์ ๋ฐ์ดํธ๋ ์ ํ์ด ์ฌ๊ธฐ์ ์ถ๊ฐ๋์์ต๋๋ค. https://github.com/apollographql/apollo-link/pull/530
networkError
๋ ์ด์ Error | ServerError | ServerParseError
์ ๊ณต์ฉ์ฒด ์ ํ์ด๋ฏ๋ก Typescript๋ ๊ณต์ฉ์ฒด์ ๋ชจ๋ ์ ํ์ ๊ณตํต์ ์ธ ์์ฑ์๋ง ์ก์ธ์คํ ์ ์๋๋ก ํ์ฉํฉ๋๋ค. statusCode
๋ Error
์ ํ์ ์กด์ฌํ์ง ์์ต๋๋ค. Typescript๊ฐ ๋ถํํ์ง ์๊ณ ๋ networkError.statusCode
๋ฅผ ํตํด ์ก์ธ์คํ ์ ์์ต๋๋ค. ์ ํ์ ๊ตฌ๋ณํ๊ธฐ ์ํด ์ ํ ๊ฐ๋ ๋๋ ๋ค๋ฅธ ๊ฒ์ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
Typescript ํธ๋๋ถ์๋ ๋ค์๊ณผ ๊ฐ์ ๋ช ๊ฐ์ง ๋ฐฉ๋ฒ์ด ์ ์๋์ด ์์ต๋๋ค. https://www.typescriptlang.org/docs/handbook/advanced-types.html#type -guards-and-differentiating-types
๋ด ๋ชฉ์ ์ ์ํด in
์ฐ์ฐ์๋ฅผ ์ฌ์ฉํ์ฌ ์์
์ ์ํํ์ต๋๋ค.
onError: ({ networkError }: ErrorResponse) => {
if (
networkError &&
'statusCode' in networkError &&
networkError.statusCode === 401
) {
// perform logout stuff
}
},
@JoviDeCroock ์ด ๋ฌธ์ ๋ฅผ ๋ซ์๋ ์์ ํ๋ค๊ณ ์๊ฐํ์ญ๋๊น?
@mrkrlli ๊ฐ์ฌํฉ๋๋ค. ๊ด์ฐฎ์ ์๋ฃจ์ .
๊ฐ์ฅ ์ ์ฉํ ๋๊ธ
๊ทธ๋์ ์์ง๋ ์ฐพ๊ณ ์๋ ๋๊ตฐ๊ฐ๋ฅผ ์ํด...
statusCode ๊ฐ์ networkError์ ์์ฑ์ผ๋ก ๋ฐํ๋๊ณ networkError์ ๋ํ ์ ๋ฐ์ดํธ๋ ์ ํ์ด ์ฌ๊ธฐ์ ์ถ๊ฐ๋์์ต๋๋ค. https://github.com/apollographql/apollo-link/pull/530
networkError
๋ ์ด์ Error | ServerError | ServerParseError
์ ๊ณต์ฉ์ฒด ์ ํ์ด๋ฏ๋ก Typescript๋ ๊ณต์ฉ์ฒด์ ๋ชจ๋ ์ ํ์ ๊ณตํต์ ์ธ ์์ฑ์๋ง ์ก์ธ์คํ ์ ์๋๋ก ํ์ฉํฉ๋๋ค.statusCode
๋Error
์ ํ์ ์กด์ฌํ์ง ์์ต๋๋ค. Typescript๊ฐ ๋ถํํ์ง ์๊ณ ๋networkError.statusCode
๋ฅผ ํตํด ์ก์ธ์คํ ์ ์์ต๋๋ค. ์ ํ์ ๊ตฌ๋ณํ๊ธฐ ์ํด ์ ํ ๊ฐ๋ ๋๋ ๋ค๋ฅธ ๊ฒ์ ์ฌ์ฉํด์ผ ํฉ๋๋ค.Typescript ํธ๋๋ถ์๋ ๋ค์๊ณผ ๊ฐ์ ๋ช ๊ฐ์ง ๋ฐฉ๋ฒ์ด ์ ์๋์ด ์์ต๋๋ค. https://www.typescriptlang.org/docs/handbook/advanced-types.html#type -guards-and-differentiating-types
๋ด ๋ชฉ์ ์ ์ํด
in
์ฐ์ฐ์๋ฅผ ์ฌ์ฉํ์ฌ ์์ ์ ์ํํ์ต๋๋ค.@JoviDeCroock ์ด ๋ฌธ์ ๋ฅผ ๋ซ์๋ ์์ ํ๋ค๊ณ ์๊ฐํ์ญ๋๊น?