본문 바로가기
개발 언어/Flutter

Flutter 네트워크 요청 Http와 Dio

by 주호파파 2025. 5. 26.
728x90
반응형

1. 선택 기준

1) 성능

  • 두 라이브러리 모두 Dart의 HttpClient를 기반으로 하며, 실제 요청 속도는 거의 차이가 없습니다.

2) 패키지 크기

  • http 패키지는 약 60KB 크기의 .dex 파일을 포함합니다.
  • Dio는 기능이 풍부한 만큼, 3.2MB의 핵심 엔진 + 1.25MB의 프레임워크 코드를 포함하여 전체 앱 크기를 많이 증가시킵니다.

3) 대용량 파일 처리

  • http는 대용량 파일 다운로드 시 메모리 오버플로우 문제가 발생하기 쉽습니다.
  • Dio는 스트리밍 다운로드, 파일 분할 업로드, 중단 후 이어받기 등을 지원하여 메모리 문제를 회피할 수 있습니다.

4) Header 설정의 유연성

  • http는 전역 설정 재사용 메커니즘이 없기 때문에, 매 요청마다 Header를 직접 설정해야 합니다.
    → 이를 간소화하려면 자체 http 도구 클래스를 감싸서 사용하는 것이 좋습니다.
  • Dio는 BaseOptions를 통해 공통 Header를 설정할 수 있고, 모든 요청에서 재사용됩니다.
    또한 개별 요청에서는 Options를 사용해 전역 설정을 덮어쓸 수 있어 유연성도 높습니다.

5) 파라미터 전달 복잡도

  • 두 라이브러리 모두 큰 차이는 없습니다.

6) 로그 출력

  • http는 로그를 수동으로 출력해야 합니다.
  • Dio는 LogInterceptor 또는 PrettyDioLogger를 통해
    자동으로 포맷된 로그를 출력할 수 있으며,
    요청/응답 정보, 소요 시간 등을 출력하고 프록시 설정도 지원하여 패킷 캡처 및 디버깅에 유리합니다.

7) 2차 포장

  • 두 라이브러리 모두 싱글톤 패턴 등으로 baseUrl, Header, 파라미터 등을 공통 처리하는 방식으로
    2차 포장이 가능합니다.

2. 기본 사용 비교

1) GET 요청

final extraHeaders = {'test': '111'};  // 사용자 정의 Header
final queryParams = {'page': '1', 'per_page': '10'};  // 요청 파라미터
const urlRepo = "https://api.github.com/users/flutter/repos";
final urlGet = Uri.parse(urlRepo).replace(queryParameters: queryParams);

// [http]
print('Response: $urlGet');
final response = await http.get(urlGet, headers: extraHeaders);
if (response.statusCode == 200) {
  print('Response: ${response.body}');
  final List<dynamic> dataList = jsonDecode(response.body);  // 데이터 변환
  print('Response: id=${dataList[1]["id"]}');
} else {
  print('Response-error');
}

// [Dio]
final responseDio = await Dio().get(
  urlRepo,
  queryParameters: queryParams,
  options: Options(
    headers: extraHeaders,
    responseType: ResponseType.json,
  ),
);
if (responseDio.statusCode == 200) {
  print('DioResponse: ${responseDio.data}');
  final List<dynamic> dataList = responseDio.data;  // 자동 파싱
  print('DioResponse: id=${dataList[1]["id"]}');
} else {
  print('DioResponse-error');
}

Dio의 responseType 설정:

  • ResponseType.json: 기본값, 응답을 자동으로 Map 또는 List로 파싱
  • ResponseType.plain: 문자열(String) 형태로 원문 반환
  • ResponseType.stream: 스트림(Stream) 형태로 반환
  • ResponseType.bytes: 바이트 배열(Uint8List)로 반환

2) POST 요청

final extraHeaders = {
  'Authorization': 'Bearer ghp_xxxxxx',
  'Accept': 'application/vnd.github.v3+json',
};
const urlRepo = "https://api.github.com/repos/{user}/{repo}/issues";
final url = Uri.parse(urlRepo);
final body = {
  'title': 'testIssues',
  'body': '상세 재현 단계...',
  'labels': ['test', 'issues'],
};

// [http]
final response = await http.post(
  url,
  headers: extraHeaders,
  body: jsonEncode(body),
);
print('response: ${response.statusCode}');
if (response.statusCode == 200 || response.statusCode == 201) {
  print('response: ${response.body}');
} else {
  print('Response-error');
}

// [Dio]
final responseDio = await Dio().post(
  urlRepo,
  data: body,
  options: Options(
    headers: extraHeaders,
    responseType: ResponseType.plain,  // 원문 반환
  ),
);
print('response: ${responseDio.statusCode}');
if (responseDio.statusCode == 200 || responseDio.statusCode == 201) {
  print('DioResponse: ${responseDio.data}');
} else {
  print('DioResponse-error');
}

3) 파일 업로드

final extraHeaders = {
  'Authorization': 'Bearer ghp_xxxxxx',
  'Accept': 'application/vnd.github.v3+json',
};
const urlRepo = "https://api.github.com/repos/{user}/{repo}/contents/files/testUpload1.txt";
String? fileSha = null;

final fileBytes = await rootBundle.load("assets/testUpload.txt");
final base64Content = base64Encode(fileBytes.buffer.asUint8List());
final body = {
  'message': 'Upload file',
  'content': base64Content,
  if (fileSha != null) 'sha': fileSha,
};

// [http]
final response = await http.put(
  Uri.parse(urlRepo),
  headers: extraHeaders,
  body: jsonEncode(body),
);
print('response: ${response.statusCode}');
if (response.statusCode == 200 || response.statusCode == 201) {
  print('response: ${response.body}');
} else {
  print('Response-error');
}

// [Dio]
final responseDio = await Dio().put(
  urlRepo,
  data: body,
  options: Options(
    headers: extraHeaders,
    responseType: ResponseType.json,
  ),
);
print('DioResponse: ${responseDio.statusCode}');
if (responseDio.statusCode == 200 || responseDio.statusCode == 201) {
  print('DioResponse: ${responseDio.data}');
} else {
  print('DioResponse-error');
}

4) 파일 다운로드 + 진행률 표시

var urlRepo = "https://file.dircleaner.com/apk/dircleaner_2.1.3.apk";
final saveDir = await getExternalStorageDirectory();
final saveFile = File('${saveDir?.path}/test.apk');
print('다운로드 시작: $urlRepo');

// [http]
final client = http.Client();
final request = http.Request('GET', Uri.parse(urlRepo));
final response = await client.send(request);
final totalBytes = response.contentLength ?? 0;
int receivedBytes = 0;
final sink = saveFile.openWrite();

response.stream.listen(
  (chunk) {
    receivedBytes += chunk.length;
    print('진행률: $receivedBytes/$totalBytes -> ${saveFile.path}');
    sink.add(chunk);
  },
  onDone: () {
    sink.close();
    client.close();
    print('다운로드 완료: ${saveFile.path}');
  },
  onError: (e) {
    client.close();
    print('다운로드 실패: $e');
  },
);

// [Dio]
try {
  await Dio().download(
    urlRepo,
    saveFile.path,
    onReceiveProgress: (received, total) {
      print('Dio 다운로드 진행률: $received/$total -> ${saveFile.path}');
    },
    options: Options(
      headers: {'User-Agent': 'Flutter-App'},  // 일부 서버는 UA 필요
      receiveTimeout: const Duration(minutes: 5),  // 대용량 다운로드는 타임아웃 연장 필요
    ),
  );
  print('Dio 다운로드 완료: $saveFile');
} on DioException catch (e) {
  print('Dio 다운로드 실패: $e');
}
728x90
반응형