# 서버와의 데이터 통신

서버와의 데이터 통신은 웹 애플리케이션에서 매우 중요한 역할을 한다. Dart에서는 HTTP 패키지를 활용하여 쉽게 서버와 통신할 수 있다. 이 챕터에서는 Dart의 HTTP 패키지를 사용하여 서버에 요청을 보내고, 데이터를 주고받는 방법을 설명한다. 다양한 HTTP 요청 방식과 서버 응답 처리 방법에 대해 다룬다.

#### HTTP 요청 방식

서버와의 통신을 위해 HTTP 요청 방식을 이해하는 것이 중요하다. HTTP 요청은 클라이언트가 서버에 데이터를 요청하거나 전달하는 방식이다. Dart에서 HTTP 요청을 수행할 때 사용할 수 있는 주요 방식은 다음과 같다:

* GET: 서버로부터 데이터를 요청하는 방식이다.
* POST: 서버에 데이터를 제출하는 방식이다.
* PUT: 서버의 기존 데이터를 업데이트하는 방식이다.
* DELETE: 서버에서 데이터를 삭제하는 방식이다.

**GET 요청**

`GET` 요청은 서버로부터 데이터를 요청하는 가장 기본적인 방식이다. 클라이언트가 서버에 요청을 보내면 서버는 해당 요청에 대한 데이터를 응답으로 돌려준다. Dart에서는 `http.get` 메소드를 사용하여 GET 요청을 보낼 수 있다.

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

void fetchData() async {
  var response = await http.get(Uri.parse('https://example.com/data'));
  if (response.statusCode == 200) {
    print('Response data: ${response.body}');
  } else {
    print('Failed to load data');
  }
}
```

위 코드에서는 `http.get` 메소드를 사용하여 `https://example.com/data` 주소로 GET 요청을 보내고, 서버가 성공적으로 응답한 경우 그 데이터를 출력한다.

**POST 요청**

`POST` 요청은 클라이언트가 서버로 데이터를 전송할 때 사용된다. 이를 통해 새로운 데이터를 서버에 저장하거나 처리할 수 있다. Dart에서 POST 요청을 보내려면 `http.post` 메소드를 사용한다.

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

void sendData() async {
  var response = await http.post(
    Uri.parse('https://example.com/data'),
    body: {'key1': 'value1', 'key2': 'value2'},
  );
  if (response.statusCode == 200) {
    print('Data sent successfully');
  } else {
    print('Failed to send data');
  }
}
```

이 예제에서는 POST 요청을 통해 서버로 데이터를 전송하고, 성공적으로 전송된 경우 "Data sent successfully"라는 메시지를 출력한다.

#### 비동기 처리

HTTP 요청은 네트워크 통신이기 때문에 응답 시간이 길어질 수 있다. 이를 처리하기 위해 Dart에서는 비동기 프로그래밍을 활용하여 HTTP 요청이 완료될 때까지 기다리는 동안 다른 작업을 수행할 수 있도록 한다. Dart에서 비동기 처리를 위해 `async`, `await` 키워드를 사용한다.

`async`는 비동기 함수임을 나타내며, `await`는 해당 작업이 완료될 때까지 대기한다는 의미이다.

```dart
Future<void> fetchDataAsync() async {
  var response = await http.get(Uri.parse('https://example.com/data'));
  print(response.body);
}
```

위 코드에서 `fetchDataAsync` 함수는 `async` 키워드를 사용하여 비동기적으로 실행되며, `await` 키워드를 사용하여 HTTP 요청이 완료될 때까지 기다린다.

#### 서버 응답 처리

서버가 클라이언트로 응답을 보내면, 해당 응답에는 상태 코드와 응답 본문이 포함된다. 상태 코드는 서버가 요청을 성공적으로 처리했는지 여부를 나타내며, 대표적인 상태 코드는 다음과 같다:

* 200: 성공 (OK)
* 404: 요청한 리소스를 찾을 수 없음 (Not Found)
* 500: 서버 오류 (Internal Server Error)

서버 응답 본문은 요청한 데이터를 포함하거나, 에러 메시지를 포함할 수 있다. Dart에서는 `http.Response` 객체를 사용하여 응답의 상태 코드와 본문을 처리할 수 있다.

```dart
void handleResponse(http.Response response) {
  if (response.statusCode == 200) {
    print('Success: ${response.body}');
  } else {
    print('Error: ${response.statusCode}');
  }
}
```

이 코드는 서버의 응답 상태 코드를 확인하고, 성공적인 응답과 에러를 구분하여 처리하는 방법을 보여준다.

#### JSON 데이터 처리

서버와 클라이언트 간의 데이터 통신에서는 주로 JSON 형식을 사용하여 데이터를 주고받는다. Dart에서 JSON 데이터를 처리하기 위해 `dart:convert` 패키지를 사용할 수 있다. JSON 데이터를 Dart 객체로 변환하거나, Dart 객체를 JSON 형식으로 변환할 때 사용된다.

```dart
import 'dart:convert';

void parseJson(String jsonString) {
  var data = jsonDecode(jsonString);
  print('Parsed data: $data');
}

String jsonString = jsonEncode({'key': 'value'});
```

이 예제에서는 `jsonDecode`를 사용하여 JSON 문자열을 Dart 객체로 변환하고, `jsonEncode`를 사용하여 Dart 객체를 JSON 문자열로 변환하는 과정을 보여준다.

#### PUT 요청

`PUT` 요청은 서버의 기존 데이터를 수정하거나 업데이트할 때 사용된다. 서버에서 기존 리소스를 업데이트하거나 새 리소스를 생성할 때 유용하게 사용된다. Dart에서는 `http.put` 메소드를 사용하여 PUT 요청을 보낼 수 있다.

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

void updateData() async {
  var response = await http.put(
    Uri.parse('https://example.com/data/1'),
    body: {'name': 'updated_name', 'value': 'updated_value'},
  );
  if (response.statusCode == 200) {
    print('Data updated successfully');
  } else {
    print('Failed to update data');
  }
}
```

위 코드에서는 `PUT` 요청을 통해 특정 리소스(`/data/1`)의 데이터를 업데이트하고, 서버 응답 상태 코드가 `200`일 경우 업데이트 성공 메시지를 출력한다.

#### DELETE 요청

`DELETE` 요청은 서버에서 리소스를 삭제할 때 사용된다. 클라이언트가 서버에 특정 데이터를 삭제하도록 요청하는 방식이다. Dart에서는 `http.delete` 메소드를 사용하여 DELETE 요청을 보낼 수 있다.

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

void deleteData() async {
  var response = await http.delete(Uri.parse('https://example.com/data/1'));
  if (response.statusCode == 200) {
    print('Data deleted successfully');
  } else {
    print('Failed to delete data');
  }
}
```

이 예제에서는 특정 리소스(`/data/1`)를 서버에서 삭제하고, 성공적인 응답을 처리하는 방법을 보여준다.

#### 헤더 설정

서버와의 통신에서 `헤더(Header)`는 클라이언트가 서버에 요청할 때 함께 전송하는 추가적인 정보를 포함한다. 예를 들어, `Content-Type`이나 인증 정보 등을 헤더에 포함하여 서버에 요청을 보낼 수 있다.

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

void sendDataWithHeaders() async {
  var response = await http.post(
    Uri.parse('https://example.com/data'),
    headers: {'Content-Type': 'application/json'},
    body: '{"key": "value"}',
  );
  if (response.statusCode == 200) {
    print('Request sent with headers');
  } else {
    print('Failed to send request with headers');
  }
}
```

이 코드에서는 `Content-Type`을 `application/json`으로 지정하여 JSON 데이터를 서버에 전송하고, 서버 응답을 처리하는 방법을 설명한다.

#### 인증 처리

서버와 클라이언트 간의 보안 통신에서는 인증(Authorization)이 중요한 요소이다. Dart에서 서버와의 통신 시 인증 정보를 헤더에 포함하여 보낼 수 있다. 가장 일반적인 방식 중 하나는 `Bearer` 토큰을 사용하는 방식이다.

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

void sendAuthenticatedRequest() async {
  var response = await http.get(
    Uri.parse('https://example.com/protected'),
    headers: {'Authorization': 'Bearer your_token_here'},
  );
  if (response.statusCode == 200) {
    print('Authenticated request successful');
  } else {
    print('Failed to authenticate request');
  }
}
```

이 예제에서는 `Authorization` 헤더에 `Bearer` 토큰을 포함하여 서버의 보호된 리소스에 접근하는 방법을 보여준다.

#### Stream을 통한 데이터 처리

Dart의 `Stream`은 서버로부터 지속적으로 데이터를 받아야 할 때 유용하다. 예를 들어 실시간으로 데이터를 주고받는 WebSocket 통신이나 서버에서 데이터를 스트리밍 방식으로 받아야 할 때 사용된다.

Dart의 `Stream`은 비동기 데이터 이벤트를 처리하는 데 유용한 도구이며, HTTP 패키지에서도 `Stream` 기반으로 서버와의 데이터를 주고받을 수 있다.

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

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

  response.stream.transform(utf8.decoder).listen((value) {
    print('Received chunk: $value');
  });
}
```

이 코드는 `Request` 객체를 사용하여 서버에 스트리밍 요청을 보내고, 서버가 반환하는 데이터를 스트리밍 방식으로 받아서 처리하는 방법을 설명한다.

#### 서버로 데이터 보내기: Form 데이터 전송

Dart에서는 서버에 데이터를 보낼 때 다양한 방식으로 데이터를 전송할 수 있다. 가장 기본적인 방법 중 하나는 `Form` 데이터를 사용하는 방식이다. `Form` 데이터는 일반적으로 키-값 쌍으로 이루어진 데이터를 서버에 전송하는 방식으로, 특히 HTML 폼에서 입력된 데이터를 전송할 때 많이 사용된다.

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

void sendFormData() async {
  var response = await http.post(
    Uri.parse('https://example.com/form'),
    body: {
      'username': 'exampleUser',
      'password': 'examplePassword',
    },
  );
  if (response.statusCode == 200) {
    print('Form data sent successfully');
  } else {
    print('Failed to send form data');
  }
}
```

위 코드에서는 `Form` 형식으로 데이터를 서버에 전송하고, 성공적으로 전송되었을 경우 `statusCode`가 `200`인지를 확인하는 방법을 보여준다.

#### 서버로 파일 전송

서버와의 통신에서 파일을 전송할 때는 `MultipartRequest`를 사용하여 파일을 업로드할 수 있다. Dart의 HTTP 패키지에서는 `http.MultipartRequest` 클래스를 사용하여 여러 파트로 이루어진 요청을 서버에 보낼 수 있다.

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

void sendFile() async {
  var request = http.MultipartRequest(
    'POST',
    Uri.parse('https://example.com/upload'),
  );
  request.files.add(await http.MultipartFile.fromPath('file', 'path/to/file'));

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

이 코드는 `MultipartRequest`를 통해 서버로 파일을 전송하는 방법을 설명한다. `http.MultipartFile.fromPath` 메소드를 사용하여 파일 경로를 지정하고, 해당 파일을 서버로 업로드한다.

#### 서버로 JSON 데이터 전송

Dart에서 서버와의 통신 시 가장 많이 사용하는 데이터 형식 중 하나는 JSON이다. JSON은 경량 데이터 교환 형식으로, Dart에서 서버로 JSON 데이터를 전송하려면 HTTP 요청의 `Content-Type`을 `application/json`으로 지정하고, 데이터를 JSON 형식으로 인코딩하여 전송할 수 있다.

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

void sendJsonData() async {
  var response = await http.post(
    Uri.parse('https://example.com/json'),
    headers: {'Content-Type': 'application/json'},
    body: jsonEncode({'name': 'example', 'age': 30}),
  );
  if (response.statusCode == 200) {
    print('JSON data sent successfully');
  } else {
    print('Failed to send JSON data');
  }
}
```

이 예제에서는 `jsonEncode` 함수를 사용하여 Dart 객체를 JSON 형식으로 변환하고, 이를 서버에 전송하는 방법을 보여준다. 서버에서 응답을 받을 때도 마찬가지로 JSON 데이터를 처리할 수 있다.

#### 서버 응답에 따른 데이터 처리

서버로부터 받은 응답은 다양한 데이터 형식일 수 있다. Dart에서는 서버 응답 데이터를 처리할 때 주로 JSON 형식을 사용하며, 이를 Dart 객체로 변환한 후 원하는 방식으로 데이터를 사용할 수 있다.

```dart
import 'dart:convert';

void handleJsonResponse(String responseBody) {
  var data = jsonDecode(responseBody);
  print('Name: ${data['name']}');
  print('Age: ${data['age']}');
}
```

서버에서 받은 JSON 응답을 처리할 때는 `jsonDecode` 함수를 사용하여 문자열을 Dart 객체로 변환하고, 객체의 각 필드를 접근하여 원하는 데이터를 사용할 수 있다.

#### 네트워크 에러 처리

서버와의 데이터 통신에서 발생할 수 있는 다양한 에러를 처리하는 것이 중요하다. Dart에서는 HTTP 요청을 보낼 때 발생할 수 있는 네트워크 오류나 서버 오류를 처리하기 위해 `try-catch` 구문을 사용할 수 있다.

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

void fetchDataWithErrorHandling() async {
  try {
    var response = await http.get(Uri.parse('https://example.com/data'));
    if (response.statusCode == 200) {
      var data = jsonDecode(response.body);
      print('Data received: $data');
    } else {
      print('Server error: ${response.statusCode}');
    }
  } catch (e) {
    print('Failed to fetch data: $e');
  }
}
```

이 코드는 네트워크 오류 및 서버 오류를 처리하는 방법을 보여준다. `try-catch` 구문을 사용하여 서버가 응답하지 않거나 잘못된 응답을 보낸 경우에 대한 오류 처리를 할 수 있다.
