如何集成Http拦截器以使请求重试?

时间:2020-05-03 18:54:53

标签: flutter

class ExpiredTokenRetryPolicy extends RetryPolicy {
  @override
  bool shouldAttemptRetryOnResponse(Response response) {
    if (response.statusCode == 401) {
      // Perform your token refresh here.
     // refreshToken();

      return true;
    }

    return false;
  }
}

如何调用此方法?

如何集成Http拦截器以防止重试请求?基本上我想在那里实现刷新令牌。

1 个答案:

答案 0 :(得分:4)

您可以在下面复制粘贴运行完整代码
我使用advice on editing的官方示例
为了模拟这种情况,我没有设置API密钥,这会产生401
我将maxRetryAttempts设置为10,因此您可以看到输出产生错误消息10
您可以像这样注入retryPolicy

HttpClientWithInterceptor.build(interceptors: [ WeatherApiInterceptor(),
 ], retryPolicy: ExpiredTokenRetryPolicy()),

代码段

class ExpiredTokenRetryPolicy extends RetryPolicy {
  @override
  int maxRetryAttempts = 10;

  @override
  bool shouldAttemptRetryOnResponse(http.Response response) {
    print(response.statusCode);
    if (response.statusCode == 401) {
      print("Perform your token refresh here in 401");
      return true;
    }

    return false;
  }
}

class _HomeScreenState extends State<HomeScreen> {
  WeatherRepository repository = WeatherRepository(
    HttpClientWithInterceptor.build(interceptors: [
      WeatherApiInterceptor(),
    ], retryPolicy: ExpiredTokenRetryPolicy()),
  );

输出

I/flutter (30233): {id: 707860, appid: YOUR-KEY-HERE, units: metric}
I/flutter (30233): 401
I/flutter (30233): Perform your token refresh here in 401
I/flutter (30233): {id: 707860, appid: YOUR-KEY-HERE, units: metric}
I/flutter (30233): 401
I/flutter (30233): Perform your token refresh here in 401
I/flutter (30233): {id: 707860, appid: YOUR-KEY-HERE, units: metric}
I/flutter (30233): 401
I/flutter (30233): Perform your token refresh here in 401
I/flutter (30233): {id: 707860, appid: YOUR-KEY-HERE, units: metric}
I/flutter (30233): 401
I/flutter (30233): Perform your token refresh here in 401
I/flutter (30233): {id: 707860, appid: YOUR-KEY-HERE, units: metric}
I/flutter (30233): 401
I/flutter (30233): Perform your token refresh here in 401
I/flutter (30233): {id: 707860, appid: YOUR-KEY-HERE, units: metric}
I/flutter (30233): 401
I/flutter (30233): Perform your token refresh here in 401
I/flutter (30233): {id: 707860, appid: YOUR-KEY-HERE, units: metric}
I/flutter (30233): 401
I/flutter (30233): Perform your token refresh here in 401
I/flutter (30233): {id: 707860, appid: YOUR-KEY-HERE, units: metric}
I/flutter (30233): 401
I/flutter (30233): Perform your token refresh here in 401
I/flutter (30233): {id: 707860, appid: YOUR-KEY-HERE, units: metric}
I/flutter (30233): 401
I/flutter (30233): Perform your token refresh here in 401
I/flutter (30233): {id: 707860, appid: YOUR-KEY-HERE, units: metric}
I/flutter (30233): 401
I/flutter (30233): Perform your token refresh here in 401
I/flutter (30233): {id: 707860, appid: YOUR-KEY-HERE, units: metric}

工作演示

https://s0pub0dev.icopy.site/packages/http_interceptor

完整代码

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http_interceptor/http_interceptor.dart';
import 'package:http/http.dart' as http;

const cities = [
  {
    "id": 707860,
    "name": "Hurzuf",
    "country": "UA",
    "coord": {"lon": 34.283333, "lat": 44.549999}
  },
  {
    "id": 519188,
    "name": "Novinki",
    "country": "RU",
    "coord": {"lon": 37.666668, "lat": 55.683334}
  },
  {
    "id": 1283378,
    "name": "Gorkhā",
    "country": "NP",
    "coord": {"lon": 84.633331, "lat": 28}
  },
  {
    "id": 1270260,
    "name": "State of Haryāna",
    "country": "IN",
    "coord": {"lon": 76, "lat": 29}
  },
];

const String OPEN_WEATHER_API_KEY = "YOUR-KEY-HERE";

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomeScreen(),
    );
  }
}

class ExpiredTokenRetryPolicy extends RetryPolicy {
  @override
  int maxRetryAttempts = 10;

  @override
  bool shouldAttemptRetryOnResponse(http.Response response) {
    print(response.statusCode);
    if (response.statusCode == 401) {
      print("Perform your token refresh here in 401");
      return true;
    }

    return false;
  }
}

class HomeScreen extends StatefulWidget {
  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  WeatherRepository repository = WeatherRepository(
    HttpClientWithInterceptor.build(interceptors: [
      WeatherApiInterceptor(),
    ], retryPolicy: ExpiredTokenRetryPolicy()),
  );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        title: const Text('Weather App'),
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.search),
            onPressed: () {
              showSearch(
                context: context,
                delegate: WeatherSearch(repository),
              );
            },
          )
        ],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Icon(
              Icons.wb_sunny,
              size: 64,
              color: Colors.grey,
            ),
            Container(
              height: 16,
            ),
            Text(
              "Search for a city",
              style: TextStyle(
                fontSize: 24.0,
                fontWeight: FontWeight.w300,
              ),
              textAlign: TextAlign.center,
            ),
          ],
        ),
      ),
    );
  }
}

class WeatherSearch extends SearchDelegate<String> {
  int selected = -1;
  WeatherRepository repo;

  WeatherSearch(this.repo);

  @override
  List<Widget> buildActions(BuildContext context) {
    return [
      IconButton(
        icon: Icon(Icons.clear),
        onPressed: () {
          selected = -1;
          query = "";
        },
      )
    ];
  }

  @override
  Widget buildLeading(BuildContext context) {
    return IconButton(
      icon: AnimatedIcon(
        icon: AnimatedIcons.menu_arrow,
        progress: transitionAnimation,
      ),
      onPressed: () {
        close(context, null);
      },
    );
  }

  @override
  Widget buildResults(BuildContext context) {
    final city = selected == -1 ? null : cities[selected];

    return city != null ? buildWeatherCard(city) : buildEmptyCard();
  }

  @override
  Widget buildSuggestions(BuildContext context) {
    final suggestionList = query.isEmpty
        ? cities
        : cities.where((p) => p["name"].toString().startsWith(query)).toList();
    return ListView.builder(
      itemCount: suggestionList.length,
      itemBuilder: (context, index) {
        return ListTile(
          onTap: () {
            selected = index;
            query = cities[selected]["name"];
            showResults(context);
          },
          title: Text(suggestionList[index]['name']),
          subtitle: Text(suggestionList[index]['country']),
        );
      },
    );
  }

  Widget buildWeatherCard(final city) {
    return FutureBuilder(
      future: repo.fetchCityWeather(city["id"]),
      builder: (context, snapshot) {
        if (snapshot.hasError) {
          return Center(
            child: Text(snapshot.error),
          );
        }

        if (!snapshot.hasData) {
          return Center(
            child: CircularProgressIndicator(),
          );
        }
        final weather = snapshot.data;
        final iconWeather = weather["weather"][0]["icon"];
        final main = weather["main"];
        final wind = weather["wind"];
        return Card(
          margin: EdgeInsets.all(16.0),
          child: Container(
            width: Size.infinite.width,
            padding: EdgeInsets.all(16.0),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: <Widget>[
                ListTile(
                  leading: Tooltip(
                    child: Image.network(
                        "https://openweathermap.org/img/w/$iconWeather.png"),
                    message: weather["weather"][0]["main"],
                  ),
                  title: Text(city["name"]),
                  subtitle: Text(city["country"]),
                ),
                ListTile(
                  title: Text("${main["temp"]} °C"),
                  subtitle: Text("Temperature"),
                ),
                ListTile(
                  title: Text("${main["temp_min"]} °C"),
                  subtitle: Text("Min Temperature"),
                ),
                ListTile(
                  title: Text("${main["temp_max"]} °C"),
                  subtitle: Text("Max Temperature"),
                ),
                ListTile(
                  title: Text("${main["humidity"]} %"),
                  subtitle: Text("Humidity"),
                ),
                ListTile(
                  title: Text("${main["pressure"]} hpa"),
                  subtitle: Text("Pressure"),
                ),
                ListTile(
                  title: Text("${wind["speed"]} m/s"),
                  subtitle: Text("Wind Speed"),
                ),
              ],
            ),
          ),
        );
      },
    );
  }

  Widget buildEmptyCard() {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Icon(
            Icons.wb_sunny,
            size: 64,
            color: Colors.grey,
          ),
          Container(
            height: 16,
          ),
          Text(
            "Search for a city",
            style: TextStyle(
              fontSize: 24.0,
              fontWeight: FontWeight.w300,
            ),
            textAlign: TextAlign.center,
          ),
        ],
      ),
    );
  }
}

const baseUrl = "https://api.openweathermap.org/data/2.5";

class WeatherRepository {
  HttpClientWithInterceptor client;

  WeatherRepository(this.client);

  // Alternatively you can forget about using the Client and just doing the HTTP request with
  // the HttpWithInterceptor.build() call.
  // Future<Map<String, dynamic>> fetchCityWeather(int id) async {
  //   var parsedWeather;
  //   try {
  //     var response = await HttpWithInterceptor.build(
  //             interceptors: [WeatherApiInterceptor()])
  //         .get("$baseUrl/weather", params: {'id': "$id"});
  //     if (response.statusCode == 200) {
  //       parsedWeather = json.decode(response.body);
  //     } else {
  //       throw Exception("Error while fetching. \n ${response.body}");
  //     }
  //   } catch (e) {
  //     print(e);
  //   }
  //   return parsedWeather;
  // }

  Future<Map<String, dynamic>> fetchCityWeather(int id) async {
    var parsedWeather;
    try {
      final response =
          await client.get("$baseUrl/weather", params: {'id': "$id"});
      if (response.statusCode == 200) {
        parsedWeather = json.decode(response.body);
      } else {
        return Future.error(
          "Error while fetching.",
          StackTrace.fromString("${response.body}"),
        );
      }
    } catch (e) {
      print(e);
    }
    return parsedWeather;
  }
}

class WeatherApiInterceptor implements InterceptorContract {
  @override
  Future<RequestData> interceptRequest({RequestData data}) async {
    try {
      data.params['appid'] = OPEN_WEATHER_API_KEY;
      data.params['units'] = 'metric';
      data.headers["Content-Type"] = "application/json";
    } catch (e) {
      print(e);
    }
    print(data.params);
    return data;
  }

  @override
  Future<ResponseData> interceptResponse({ResponseData data}) async => data;
}