Dart에서 HTTP 요청을 보내는 작업은 http 패키지를 사용하여 이루어진다. 이 패키지는 pub.dev에서 제공되며, 매우 간단한 방식으로 HTTP 클라이언트 기능을 사용할 수 있다. 주로 REST API와 같은 서버 통신에 많이 사용된다.

1. http 패키지 설치

먼저 http 패키지를 설치해야 한다. 이는 Dart의 패키지 관리자 pub을 통해 간단히 설치할 수 있다. 프로젝트의 pubspec.yaml 파일에 다음 내용을 추가한다.

dependencies:
  http: ^0.13.0

그 후, pub get 명령을 실행하여 패키지를 설치한다.

2. HTTP GET 요청 보내기

HTTP GET 요청은 서버로부터 데이터를 가져오는 데 사용된다. 예를 들어, 특정 API에서 데이터를 받아오는 경우, http.get 메소드를 사용한다. Dart에서 HTTP GET 요청을 보내는 기본 구조는 아래와 같다.

import 'package:http/http.dart' as http;

void fetchData() async {
  final response = await http.get(Uri.parse('https://example.com/data'));

  if (response.statusCode == 200) {
    // 성공적으로 데이터를 받아왔을 때
    print(response.body);
  } else {
    // 오류가 발생했을 때
    print('Failed to load data');
  }
}

코드 설명

  1. http.get 메소드를 통해 GET 요청을 보낸다. 이 메소드는 비동기 함수이므로 await 키워드를 사용하여 요청이 완료될 때까지 기다린다.
  2. Uri.parse는 문자열 URL을 Uri 객체로 변환한다.
  3. 요청이 성공하면 상태 코드가 200일 때 데이터를 출력하며, 실패한 경우에는 오류 메시지를 출력한다.

3. HTTP POST 요청 보내기

HTTP POST 요청은 주로 서버에 데이터를 전송할 때 사용된다. 이는 사용자 로그인, 데이터 업로드 등의 작업에 주로 활용된다. POST 요청을 보내는 기본 구조는 다음과 같다.

import 'package:http/http.dart' as http;

void sendData() async {
  final response = await http.post(
    Uri.parse('https://example.com/data'),
    headers: <String, String>{
      'Content-Type': 'application/json; charset=UTF-8',
    },
    body: '{"name": "John", "age": 30}',
  );

  if (response.statusCode == 200) {
    // 성공적으로 데이터를 전송했을 때
    print('Data sent successfully');
  } else {
    // 오류가 발생했을 때
    print('Failed to send data');
  }
}

코드 설명

  1. http.post 메소드를 사용하여 POST 요청을 보낸다. GET 요청과 달리, POST 요청은 데이터(body)를 서버로 전송한다.
  2. headers를 통해 요청의 타입을 지정하며, 주로 application/json 형식으로 데이터를 전송한다.
  3. 요청의 응답 상태 코드를 확인하여 요청이 성공했는지 실패했는지를 판별한다.

4. Query Parameters 사용

GET 요청을 보낼 때, URL에 직접 파라미터를 추가하여 서버로 데이터를 보낼 수 있다. 이를 Query Parameters라고 한다. Dart에서 Query Parameters를 추가하는 방법은 Uri 객체를 사용하는 것이다.

import 'package:http/http.dart' as http;

void fetchDataWithParams() async {
  final uri = Uri.https('example.com', '/data', {'name': 'John', 'age': '30'});
  final response = await http.get(uri);

  if (response.statusCode == 200) {
    print(response.body);
  } else {
    print('Failed to load data');
  }
}

코드 설명

  1. Uri.https 메소드를 통해 HTTPS URI를 생성하며, Query Parameters를 Map 형태로 전달할 수 있다.
  2. Uri 객체를 사용하여 GET 요청을 보내고, 서버로부터 데이터를 받아온다.

5. 비동기 처리

Dart에서는 비동기 처리로 인해 코드가 멈추지 않고 동작할 수 있게 설계되어 있다. 이로 인해 HTTP 요청은 비동기적으로 실행되며, 요청이 완료될 때까지 다른 작업을 수행할 수 있다.

비동기 HTTP 요청은 Dart의 Future 클래스를 사용하여 처리된다. await 키워드를 사용하면 비동기 함수를 호출하고 그 함수가 완료될 때까지 대기할 수 있다. Dart에서 비동기 HTTP 요청을 효과적으로 처리하기 위한 방법은 다음과 같다.

Future<void> fetchData() async {
  try {
    final response = await http.get(Uri.parse('https://example.com/data'));

    if (response.statusCode == 200) {
      print('Data fetched: ${response.body}');
    } else {
      print('Failed to load data');
    }
  } catch (e) {
    print('Error occurred: $e');
  }
}

코드 설명

  1. 비동기 처리를 위한 asyncawait를 사용한다.
  2. try-catch 구문을 통해 오류가 발생할 경우 적절히 처리할 수 있다.

6. HTTP 헤더 추가하기

HTTP 요청을 보낼 때, 헤더에 추가 정보를 포함할 수 있다. 이 정보는 서버가 요청을 처리하는 데 필요한 메타데이터를 제공하는 데 사용된다. 예를 들어, Content-Type 헤더를 추가하여 요청 본문이 어떤 형식인지 서버에 알리거나, 인증 토큰을 포함할 수 있다.

import 'package:http/http.dart' as http;

void sendRequestWithHeaders() async {
  final response = await http.get(
    Uri.parse('https://example.com/data'),
    headers: <String, String>{
      'Authorization': 'Bearer your_api_token_here',
      'Custom-Header': 'CustomHeaderValue',
    },
  );

  if (response.statusCode == 200) {
    print('Request successful: ${response.body}');
  } else {
    print('Request failed: ${response.statusCode}');
  }
}

코드 설명

  1. headers 파라미터는 Map 형태로 작성되며, key가 헤더의 이름, value가 헤더의 값이다.
  2. 여기서는 Authorization 헤더에 Bearer 토큰을 포함하여 인증을 수행한다.
  3. Custom-Header라는 이름의 커스텀 헤더를 추가할 수도 있다.

7. JSON 데이터 처리

서버와 통신할 때 가장 흔히 사용되는 데이터 형식은 JSON이다. Dart에서는 dart:convert 패키지의 jsonEncodejsonDecode 함수를 사용하여 JSON 데이터를 처리한다.

JSON 데이터를 사용한 POST 요청

POST 요청을 보내면서 JSON 데이터를 서버로 전송할 때는 Content-Type 헤더를 application/json으로 설정하고, 본문을 JSON 형식으로 인코딩해야 한다.

import 'dart:convert';
import 'package:http/http.dart' as http;

void sendPostWithJson() async {
  final response = await http.post(
    Uri.parse('https://example.com/data'),
    headers: <String, String>{
      'Content-Type': 'application/json; charset=UTF-8',
    },
    body: jsonEncode(<String, String>{
      'name': 'John',
      'age': '30',
    }),
  );

  if (response.statusCode == 200) {
    print('Data sent successfully: ${response.body}');
  } else {
    print('Failed to send data: ${response.statusCode}');
  }
}

JSON 데이터 디코딩

서버에서 JSON 응답을 받을 때는 jsonDecode 함수를 사용하여 Dart 객체로 변환할 수 있다.

import 'dart:convert';
import 'package:http/http.dart' as http;

void fetchAndDecodeJson() async {
  final response = await http.get(Uri.parse('https://example.com/data'));

  if (response.statusCode == 200) {
    final Map<String, dynamic> data = jsonDecode(response.body);
    print('Name: ${data['name']}');
    print('Age: ${data['age']}');
  } else {
    print('Failed to load data');
  }
}

코드 설명

  1. jsonEncode 함수는 Map을 JSON 문자열로 변환하여 POST 요청의 본문으로 전송한다.
  2. jsonDecode 함수는 JSON 응답을 Map이나 List와 같은 Dart 객체로 변환한다.
  3. jsonEncodejsonDecode를 통해 Dart 객체와 JSON 간의 상호 변환을 쉽게 처리할 수 있다.

8. Multipart 요청

Multipart 요청은 파일을 업로드할 때 자주 사용된다. Dart에서는 http.MultipartRequest 클래스를 사용하여 이러한 요청을 처리할 수 있다. 이 방법을 사용하면 이미지나 문서와 같은 파일을 서버로 업로드할 수 있다.

import 'dart:io';
import 'package:http/http.dart' as http;

void uploadFile() async {
  var request = http.MultipartRequest(
    'POST',
    Uri.parse('https://example.com/upload'),
  );

  request.files.add(
    await http.MultipartFile.fromPath(
      'file',
      '/path/to/file.jpg',
    ),
  );

  var response = await request.send();

  if (response.statusCode == 200) {
    print('File uploaded successfully');
  } else {
    print('Failed to upload file');
  }
}

코드 설명

  1. http.MultipartRequest는 파일을 포함한 POST 요청을 보낼 수 있게 해준다.
  2. http.MultipartFile.fromPath 메소드를 사용하여 파일을 요청에 추가한다.
  3. request.send 메소드를 통해 요청을 전송한다. 응답은 StreamedResponse로 제공되며, 성공 시 파일 업로드가 완료된다.

9. HTTP 요청의 상태 코드 처리

HTTP 요청을 보낼 때, 서버는 다양한 상태 코드로 응답한다. 이 상태 코드를 확인함으로써 요청이 성공했는지, 아니면 실패했는지 판별할 수 있다. 상태 코드는 3자리 숫자로 표현되며, 다음과 같은 카테고리로 나뉜다.

Dart에서 HTTP 요청을 처리할 때, response.statusCode를 통해 상태 코드를 확인할 수 있다.

import 'package:http/http.dart' as http;

void checkStatusCode() async {
  final response = await http.get(Uri.parse('https://example.com/data'));

  if (response.statusCode == 200) {
    print('Request successful');
  } else if (response.statusCode == 404) {
    print('Resource not found');
  } else if (response.statusCode == 500) {
    print('Server error');
  } else {
    print('Unexpected status code: ${response.statusCode}');
  }
}

코드 설명

  1. response.statusCode로 서버의 응답 상태 코드를 확인할 수 있다.
  2. 상태 코드에 따라 적절한 처리를 할 수 있도록 분기문을 사용한다. 예를 들어, 200은 요청 성공, 404는 리소스를 찾을 수 없음을 의미한다.

10. HTTP 요청에 Timeout 적용하기

네트워크 상황에 따라 HTTP 요청이 지연되거나 응답이 없을 수 있다. 이런 경우를 대비해, 요청에 타임아웃을 설정할 수 있다. Dart에서는 timeout 메소드를 사용하여 요청이 일정 시간 내에 완료되지 않으면 타임아웃을 발생시킨다.

import 'package:http/http.dart' as http;

void fetchDataWithTimeout() async {
  try {
    final response = await http.get(Uri.parse('https://example.com/data'))
      .timeout(const Duration(seconds: 5));

    if (response.statusCode == 200) {
      print('Data fetched successfully');
    } else {
      print('Failed to load data: ${response.statusCode}');
    }
  } catch (e) {
    print('Request timed out: $e');
  }
}

코드 설명

  1. timeout 메소드를 사용하여 요청이 5초 내에 완료되지 않으면 타임아웃을 발생시킨다.
  2. try-catch 구문을 통해 타임아웃이나 기타 네트워크 오류가 발생할 때 적절한 예외 처리를 수행한다.

11. Stream을 사용한 대규모 데이터 처리

HTTP 요청을 통해 대규모 데이터를 다운로드할 경우, 모든 데이터를 한 번에 메모리로 로드하는 대신, Stream을 사용하여 데이터를 점진적으로 처리할 수 있다. Dart의 StreamedResponse 객체는 이러한 데이터 흐름을 처리하는 데 사용된다.

import 'dart:convert';
import 'package:http/http.dart' as http;

void fetchLargeData() async {
  var request = http.Request('GET', Uri.parse('https://example.com/large-data'));
  var response = await request.send();

  if (response.statusCode == 200) {
    await for (var bytes in response.stream.transform(utf8.decoder)) {
      print('Received chunk: $bytes');
    }
  } else {
    print('Failed to fetch data');
  }
}

코드 설명

  1. http.Request 객체를 사용하여 요청을 생성하고, request.send()를 호출하여 데이터를 스트리밍한다.
  2. response.stream.transform(utf8.decoder)를 통해 응답 스트림을 UTF-8로 디코딩한다.
  3. await for 구문을 사용하여 데이터가 부분적으로 수신될 때마다 처리할 수 있다.

12. Dart에서 HTTPS 인증서 검사 우회

Dart에서 HTTPS 요청을 보낼 때, 기본적으로 인증서 검사가 수행된다. 하지만 테스트 환경 등에서 인증서 검사를 우회하고자 할 때는 Dart의 HttpClient를 커스터마이징하여 처리할 수 있다. 이 방법은 보안상 위험할 수 있으므로 실제 운영 환경에서는 권장되지 않는다.

import 'dart:io';
import 'package:http/http.dart' as http;

class CustomHttpOverrides extends HttpOverrides {
  @override
  HttpClient createHttpClient(SecurityContext? context) {
    return super.createHttpClient(context)
      ..badCertificateCallback = (X509Certificate cert, String host, int port) => true;
  }
}

void main() {
  HttpOverrides.global = CustomHttpOverrides();
  fetchData();
}

void fetchData() async {
  final response = await http.get(Uri.parse('https://example.com'));

  if (response.statusCode == 200) {
    print('Request successful');
  } else {
    print('Failed to load data');
  }
}

코드 설명

  1. HttpOverrides를 상속받은 클래스를 정의하고, createHttpClient 메소드를 오버라이드하여 badCertificateCallback을 통해 모든 인증서를 신뢰하도록 설정한다.
  2. HttpOverrides.global에 이 설정을 적용하여, 이후의 모든 HTTP 요청에 대해 이 설정이 적용된다.