Flutter GraphQL中如何拦截获取HttpCode

  今天继续补上早一阵聊Graphql_flutter的后遗症,上一篇文章《Flutter中使用GraphQL进行数据请求》

前言

  在《Flutter中使用GraphQL进行数据请求》文章中我们说了Graphql的诞生历史,它的目的、它的使用方法等。虽然说在查询接口多变一的状况下使用还是挺方便的,减少了接口的调用,本来一个页面需要好几个接口的,这下因Graphql,变为只需要使用一个接口搞定,真香是真香。但是,它还有点问题,因为Graphql它封装了Http StatusCode,也就是说它不给你返回Http状态码了,它给封装成它自己的Exception了。这就尴尬了,比如说token过期返回401,你捕获不了;比如说服务器异常250它哥500,你捕获不了;一曲凉凉~~

  那这里我通过Google,和自己研究了下Graphql_flutter里的源码,发现其实可以通过自定义Link来捕获Http状态码,其实通过查看HttpLink可以发现,它对于http的异常状态码,其实是做了一层封装的。

  而我们以下的拦截Http状态码都是基于pub.dev上graphql_flutter库来做处理。我这里主要是做了两个版本的研究拦截Http状态码:第一种是3.1.0一下版本;第二种是5.0.0支持空安全以上版本; 也就是说4.0.0-4.0.1断层了,没去管了哈~

一、适用于3.1.0一下版本拦截Http状态码

  因为我已经将拦截器作为插件上传到pub.dev上了,大家可以直接引用。

1.1 使用pub.dev上graphql_intercept_http_code_link库

在graphql_flutter: 3.1.0以下版本使用,在pubspec.yaml中加入

dependencies:
  flutter:
    sdk: flutter
  graphql_flutter: 3.1.0
  graphql_intercept_http_code_link: 1.0.0
1.2 在《Flutter中使用GraphQL进行数据请求》文章中的GraphQLUtil类中引用
  Future getGraphQLClient() async {
    Map headers = {};
    HttpLink httpLink = HttpLink(uri: baseUrl, headers: headers);
    //定义GraphQLInterceptHttpCodeLink
    GraphQLInterceptHttpCodeLink errorLink =
    GraphQLInterceptHttpCodeLink(httpResponseHandler: (fetchResult, streamResponse) {
      //这里已经捕获了状态码>300的http status code,做一些处理
      //TODO deal with http status code
      dealWithHttpStatusCode(streamResponse);
    });
    GraphQLClient client =
    GraphQLClient(cache: InMemoryCache(), link: errorLink.concat(httpLink));

    return client;
  }

//异常Http请求,根据状态码做相应处理
void dealWithHttpStatusCode(StreamedResponse streamResponse) async {
    Response response;
    if (streamResponse != null) {
      switch (streamResponse.statusCode) {
        case HttpStatus.notModified:
        case HttpStatus.badRequest:
        ...
        case HttpStatus.badGateway:
        case HttpStatus.serviceUnavailable:
          break;
        default:
          response.statusCode = streamResponse.statusCode;
          break;
      }
      response.statusMessage = streamResponse.reasonPhrase;
    }
  }

1.3 自定义拦截代码一览

  大家可以看一看怎么做的拦截http状态码,实际上,因为HttpLink在进行http请求之后,返回了stream。它也会把异常http状态码进行封装,把response塞在一个字典Map的response字段里,所以我们可以取出来然后拦截返回。

HttpLink部分源码如下:
//这是HttpLink一个StreamController里重新的onListen方法
Future onListen() async {
  StreamedResponse response;

  try {
    // httpOptionsAndBody.body as String
    final BaseRequest request = await _prepareRequest(parsedUri, operation, config);
    
    //进行网络请求
    response = await fetcher.send(request);
    
    //这里就是将reponse塞到一个Map的repsonse字段里,我们获取状态码就从这里入手
    operation.setContext({
      'response': response,
    });

    //这里实际上就是在包装返回Result,所以我们取不到状态码
    final FetchResult parsedResponse = await _parseResponse(response);

    controller.add(parsedResponse);
  } catch (failure) {
    ...
    controller.addError(translated);
  }

  await controller.close();
}
我们的拦截代码如下(大家有时间可以深入瞧一瞧很简单):
import 'dart:async';
import 'package:http/http.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:graphql/internal.dart';

typedef HttpResponseHandler = void Function(FetchResult, StreamedResponse);

class GraphQLInterceptHttpCodeLink extends Link {
  HttpResponseHandler httpResponseHandler;

  GraphQLInterceptHttpCodeLink({this.httpResponseHandler})
      : super(
          request: (Operation operation, [NextLink forward]) {
            StreamController controller;

            Future onListen() async {
              await controller
                  .addStream(forward(operation).map((FetchResult result) {
                if (result != null && result.errors != null) {
                  StreamedResponse response =
                      operation.getContext()["response"];
                  if ((response != null || response.statusCode >= 300) &&
                      httpResponseHandler != null) {
                    httpResponseHandler(result, response);
                  }
                }
                return result;
              }).handleError((error) {
                StreamedResponse response = operation.getContext()["response"];
                if ((response != null || response.statusCode >= 300) &&
                    httpResponseHandler != null) {
                  String message;
                  String errorMessage =
                      error.message != null ? error.message : '';
                  if (!isStringEmpty(errorMessage)) {
                    List msgList = errorMessage.split(':');
                    if (msgList.length >= 2) {
                      message = msgList[1].toString().trim();
                    }
                  }

                  StreamedResponse streamedResponse = StreamedResponse(
                      response.stream, response.statusCode,
                      reasonPhrase: isStringEmpty(message)
                          ? response.reasonPhrase
                          : message);

                  httpResponseHandler(null, streamedResponse);
                }
                throw error;
              }));

              await controller.close();
            }

            controller = StreamController(onListen: onListen);

            return controller.stream;
          },
        );

  static bool isStringEmpty(String value) {
    return value == null || value.length <= 0;
  }
}

二、支持5.0.0以上空安全使用版本

  因为Flutter2.0 在pubspec.yaml文件中sdk: ">=2.12.0 <3.0.0"。sdk必须支持空安全后,我们也需要升级graphql_flutter以支持空安全,而我在实际操作过程中,发现graphql_flutter升级后,原来的拦截状态码不适用了。这也是Flutter比价恶心的地方,很多库升级不向下兼容。直接面目全非的改。所以拦截器也得跟着改了。

2.1 使用pub.dev上graphql_intercept_http_code_link库

在graphql_flutter: 5.0.0以上版本使用,在pubspec.yaml中加入

dependencies:
  flutter:
    sdk: flutter
  graphql_flutter: 5.0.0
  graphql_intercept_http_code_link: 2.0.0
2.2 在《Flutter中使用GraphQL进行数据请求》文章中的GraphQLUtil类中引用

  这里的使用和1.2中的差不多,这里就不再赘述了。

2.3 自定义拦截代码一览

  在graphql_flutter 5.0.0中使用到的HttpLink类中,做了一些修改,它不再将repsonse直接放入Map中了,它将状态码大于300的请求直接返回抛出异常,这时,嘿嘿,我们只需要拦截捕获异常就可以了。

HttpLink部分源码如下:
@override
Stream request(
  Request request, [
  NextLink? forward,
]) async* {
  //进行网络请求
  final httpResponse = await _executeRequest(request);

  //封装response
  final response = await _parseHttpResponse(httpResponse);

  //状态码大于300的抛出异常
  if (httpResponse.statusCode >= 300 ||
      (response.data == null && response.errors == null)) {
    throw HttpLinkServerException(
      response: httpResponse,
      parsedResponse: response,
    );
  }

  yield Response(
    data: response.data,
    errors: response.errors,
    context: _updateResponseContext(response, httpResponse),
  );
}
我们拦截的代码如下:
import 'dart:async';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:http/http.dart' as Http;

typedef HttpResponseHandler = void Function(Response?, Http.Response);

class GraphQLInterceptHttpCodeLink extends Link {
  HttpResponseHandler httpResponseHandler;
  late StreamController controller;
  late Request gqlRequest;
  NextLink? forward;

  GraphQLInterceptHttpCodeLink({required this.httpResponseHandler});

  @override
  Stream request(Request request, [NextLink? forward]) {
    gqlRequest = request;
    this.forward = forward;
    controller = StreamController(onListen: onListen);

    return controller.stream;
  }

  void onListen() async {
    await controller.addStream(forward!(gqlRequest).handleError((error) {
      if(error != null && error is HttpLinkServerException){
        Http.Response httpResponse = error.response;
        if (httpResponse != null) {
            httpResponseHandler(null, httpResponse);
        }
      }
      throw error;
    }
    ));

    await controller.close();
  }
}

遇到问题,多多查看源码,百google而不得琪姐的时候,源码能带给你不一样的惊喜。

三、 结语

  没啥好结的~ 想说源码很重要,苏联时期摸着毛熊过河,21年摸着鹰酱过河,我们学一学摸着源码过活。O(∩_∩)O哈哈~

申明:禁用于商业用途,如若转载,请附带原文链接。https://www.jianshu.com/p/be5f83ebc0b8蟹蟹~

PS: 写文不易,觉得没有浪费你时间,请给个关注和点赞~

你可能感兴趣的:(Flutter GraphQL中如何拦截获取HttpCode)