今天继续补上早一阵聊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: 写文不易,觉得没有浪费你时间,请给个关注和点赞~ 😁