Apollo-link: networkError при ошибке apollo-link-error не имеет атрибута статуса

Созданный на 2 дек. 2017  ·  35Комментарии  ·  Источник: apollographql/apollo-link

Как предмет. Спасибо

const errorLink = onError(({ networkError }) => {
  if (networkError.status === 401) {
    logout();
  }
})

screen shot 2017-12-03 at 4 49 20 am

Самый полезный комментарий

Так что для тех, кто все еще ищет ...

Значение statusCode возвращается как свойство в networkError, и здесь добавлена ​​обновленная типизация для networkError: https://github.com/apollographql/apollo-link/pull/530

Поскольку networkError теперь является типом объединения Error | ServerError | ServerParseError , Typescript позволит нам получить доступ только к свойствам, общим для всех типов в объединении. statusCode не существует для типа Error . Мы не сможем получить к нему доступ через networkError.statusCode без жалоб на Typescript; нам нужно использовать защиту типа или что-то еще, чтобы различать типы.

В руководстве по Typescript предлагается несколько методов: https://www.typescriptlang.org/docs/handbook/advanced-types.html#type -guards-and-дифференцирующие-типы

В своих целях я использовал оператор in чтобы все

  onError: ({ networkError }: ErrorResponse) => {
    if (
      networkError &&
      'statusCode' in networkError &&
      networkError.statusCode === 401
    ) {
      // perform logout stuff
    }
  },

@JoviDeCroock Думаете, эту проблему можно безопасно закрыть?

Все 35 Комментарий

Насколько я знаю, «не удалось получить» происходит, если нет ответа (например, сервер не работает). Это потому, что вы не увидите никакого кода состояния. Пожалуйста, поправьте меня, если я ошибаюсь.

Но я думаю, что в таких ситуациях не лучшая идея иметь только текстовое сообщение - должно быть что-то, что можно было бы обработать более легко (например, код ошибки).

Я действительно видел, что запрос отправлялся и находился в (401) в сетевом теге инспектора Chrome.

Я думаю, это связано с № 218 (не уверен ..)

Также есть эта проблема. networkError.status или networkError.statusCode отсутствуют, а объект ответа отсутствует. Нет возможности обрабатывать коды состояния HTTP на сетевом уровне.

У меня тоже сейчас эта проблема. Предположительно это было исправлено в последнем выпуске, но у меня все еще проблема.

какие-нибудь мысли по этому поводу @jbaxleyiii ?

Сожалею, что у вас, @akrigline и @bogdansoare , все еще есть проблемы с этим! Это обязательно нужно исправить.

Сейчас прорабатываю №364. А пока не могли бы вы создать или изменить этот код для воспроизведения ошибки? Или еще лучше открыть PR с провалом теста?

любые новости?

Также возникает эта проблема, объект ответа пуст, а в объекте networkError нет statusCode.

Я даже не получаю свойство networkError :
screen shot 2018-05-07 at 13 01 56

РЕДАКТИРОВАТЬ: Скорее всего, # 475

Я исправил эту проблему, проигнорировав тип Error , но это полностью временное решение, и нам нужно дождаться исправлений в будущем. @evans

Вот это проблема!

Текущая ошибка networkError о которой мы писали, не была реальной ошибкой на стороне сервера. Таким образом, в этих случаях у нас вообще не будет statusCode и на самом деле нам нужно создать или получить настоящую сетевую ошибку на сервере graphql. (_Если у вас просто проблема со связью с сервером, вы не можете получить доступ к statusCode _).

screen shot 2018-05-07 at 20 38 09

Ошибка гарнитуры все еще сохраняется!

Если вы используете TypeScript, вы можете увидеть, что networkError имеет следующий тип:

export interface ErrorResponse {
  graphQLErrors?: GraphQLError[];
  networkError?: Error;
  response?: ExecutionResult;
  operation: Operation;
}

А Error определяется как:

interface Error {
    name: string;
    message: string;
    stack?: string;
}

Мы видим, что statusCode здесь не определено, поэтому мы не можем вызвать что-то вроде networkError.statusCode в TypeScript.

Мое временное решение

Мы должны передать 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 и теперь можем получить доступ к этому коду состояния:

screen shot 2018-05-07 at 20 28 03

Вот мое определение 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)
})

Есть новости по этому поводу? Та же проблема. В моем коде javascript нет networkError.statusCode .

@goldenbearkin может мы неправильно его используем?

Мы должны разрешить код статуса сети в ошибке, в настоящее время нет возможности получить доступ к статусу сети. Это networkError на самом деле является объектом типа HttpErrorResponse который имеет свойство status которое всегда равно 0 где исходный код состояния в консоли - 401 или 403.

@lvlohammadi
Можно ли такое же обходное решение для graphQLErrors? Я могу увидеть код состояния для networkErrors, используя ваш обходной путь - проверял это с помощью 504. Но когда аутентификация не удалась, я ожидаю, что код состояния graphQLError будет 401. Но в настоящее время нет возможности получить к нему доступ. Я попробовал вариант 'graphQLErrors = {} as any', но безрезультатно.

Это странно, но на самом деле у меня есть свойство statusCode на networkError . Однако в интерфейсе такого свойства нет, поэтому 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 не определен?

Это ошибка машинописного текста или действительно неопределенная? Вы можете воспроизвести это?

Кстати, голосование против моего комментария не помогает решить эту ошибку, никогда не было никакого способа воспроизвести эту проблему, плюс проблемы, которые необходимо было устранить, чтобы улучшить способ, которым мы можем помочь.

У меня такая же проблема, но это может быть связано с тем, как я отправляю статус с сервера. Я в основном пытаюсь сократить любую обработку, специфичную для graphQL, и просто отвечаю, например:
res.status(404).send("The resource you are looking for cannot be found")

Однако я получаю только TypeError: Failed To Fetch . Это из-за деталей реализации в Apollo-Server-Express?

@JoviDeCroock На самом деле, я немного напортачил с кодом graphql, когда пытался реализовать обработку ошибок на стороне клиента для 401 или 403. Мы используем промежуточное ПО, и я просто возвращал всегда 401 для каждого запроса. Таким образом, также запрос OPTION получил 401, чего не должно происходить, и, вероятно, из-за этого я вообще не мог видеть статус. После обновления кода я могу видеть коды состояния

Спасибо за обновление @ dimitriy-k рад слышать, что эта проблема решена.

Что касается @AlbertoPL , я рассмотрю вашу проблему сегодня вечером. Не стесняйтесь давать больше комментариев по этому поводу, если можете.

@JoviDeCroock Спасибо, что заглянули. Итак, конкретный код, который я пытаюсь заставить работать, следующий:
res.status(426).send()

На вкладке сети Chrome я вижу, что конечная точка / graphql отвечает кодом состояния 426 и соответствующим ответом (требуется обновление). Однако я все еще вижу только TypeError: Failed to Fetch при использовании ссылки onError из apollo-link-error . Если бы я мог получить истинный код состояния, это было бы здорово (или даже сообщения было бы достаточно).

Хорошо, это отличная информация. Мы должны иметь возможность как-то отладить это, вы чувствуете себя готовым к этому или?

Да, я могу попробовать. Буду признателен за любую помощь / советы по этому поводу.

Итак, просто пройдя код в отладчике 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». Код состояния отсутствует.

Мы должны проверить, здесь ли что-то не так:

https://github.com/apollographql/apollo-link/blob/master/packages/apollo-link-http-common/src/index.ts#L118

Я постараюсь сделать репродукцию в эти выходные, у меня на работе довольно плотный график.

РЕДАКТИРОВАТЬ:

Основная проблема заключается в том, что каждый раз, когда я тестирую это, я использую apollo-server и все идет хорошо, но могут быть привязки или что-то другое. Так что мне сложно протестировать это без сервера в качестве репродукции.

Если это поможет, вот наш Apollo-server (обратите внимание, что мы используем 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 , а не клиента Apollo.

Обещание fetch () будет отклонено с ошибкой TypeError при обнаружении сетевой ошибки или неправильной конфигурации CORS на стороне сервера.

При отклонении запроса убедитесь, что вы возвращаете правильные заголовки. В моем случае я использовал 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-risks-of-setting-access-control-allow-origin

Так что для тех, кто все еще ищет ...

Значение statusCode возвращается как свойство в networkError, и здесь добавлена ​​обновленная типизация для networkError: https://github.com/apollographql/apollo-link/pull/530

Поскольку networkError теперь является типом объединения Error | ServerError | ServerParseError , Typescript позволит нам получить доступ только к свойствам, общим для всех типов в объединении. statusCode не существует для типа Error . Мы не сможем получить к нему доступ через networkError.statusCode без жалоб на Typescript; нам нужно использовать защиту типа или что-то еще, чтобы различать типы.

В руководстве по Typescript предлагается несколько методов: https://www.typescriptlang.org/docs/handbook/advanced-types.html#type -guards-and-дифференцирующие-типы

В своих целях я использовал оператор in чтобы все

  onError: ({ networkError }: ErrorResponse) => {
    if (
      networkError &&
      'statusCode' in networkError &&
      networkError.statusCode === 401
    ) {
      // perform logout stuff
    }
  },

@JoviDeCroock Думаете, эту проблему можно безопасно закрыть?

@mrkrlli Спасибо. Достойное решение.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги