美文网首页
Flutter GraphQL中如何拦截获取HttpCode

Flutter GraphQL中如何拦截获取HttpCode

作者: 天上飘的是浮云 | 来源:发表于2021-06-24 11:48 被阅读0次

      今天继续补上早一阵聊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<GraphQLClient> getGraphQLClient() async {
        Map<String, String> 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<void> 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(<String, StreamedResponse>{
          '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<FetchResult> controller;
    
                Future<void> 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<FetchResult>(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<Response> 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<Response> controller;
      late Request gqlRequest;
      NextLink? forward;
    
      GraphQLInterceptHttpCodeLink({required this.httpResponseHandler});
    
      @override
      Stream<Response> request(Request request, [NextLink? forward]) {
        gqlRequest = request;
        this.forward = forward;
        controller = StreamController<Response>(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

          本文链接:https://www.haomeiwen.com/subject/lcyxeltx.html