[Flutter] Dart:io 패키지를 사용한 Http 통신 구현 및 주의점

2021. 2. 4. 18:34Programming/Flutter

반응형

이 글은 dart:io library를 참조하여 작성되었습니다. 만약 문제가 발생하는 경우, 다트의 버전을 확인해주세요.

dart:io 패키지를 사용하면 Http 통신을 구현할 수 있습니다. 아마 좀 더 쉽게 HTTP 통신을 할 수 있는 패키지를 사용할수도 있겠지만... 요번에는 dart:io패키지를 사용하여 HTTP 통신을 구현하는 방법과 주의해야할 사항을 정리합니다.

Dart:io library를 사용한 Http 통신 구현

dart:io library 페이지를 살펴보면 다음과 같은 내용을 발견할 수 있습니다.

HttpServer and HttpClient
The classes HttpServer and HttpClient provide HTTP server and HTTP client functionality.

대충 HttpServer 클래스와 HttpClient 클래스를 사용해서 서버-클라이언트를 구현할 수 있다는 내용입니다. 이번에는 서버를 구현할 건 아니니까, HttpClient 클래스의 내용만 살펴볼겁니다. HttpClient Class 페이지를 살펴보도록 합시다.

HttpClient client = new HttpClient();
client.getUrl(Uri.parse("http://www.example.com/"))
  .then((HttpClientRequest request) {
    // Optionally set up headers...
    // Optionally write to the request object...
    // Then call close.
    ...
    return request.close();
  })
  .then((HttpClientResponse response) {
    // Process the response.
    ...
  });

위 코드는 HttpClient Class 페이지에서 발견할 수 있는 코드로, http://www.example.com/에 GET으로 HTTP 요청을 보내고 응답에 대한 처리를 하는 코드입니다. 특이하게도 getUrl() 메소드의 호출결과에 의해 비동기적으로 HttpClientRequest 객체가 생성되는데, 직접 HttpClientRequest의 생성자를 호출하려고하면 abstract라서 생성할 수 없다는 에러를 볼 수 있습니다. GET으로 HTTP 요청을 보낼때는 get()혹은 getUrl()을 이용하면되고, 그 외에는 put(), putUrl(), post(), postUrl(), delete(), deleteUrl(), head(), headUrl()등을 사용하면 됩니다. 요청 방식을 String으로 지정해주면 되는 open(), openUrl()도 있습니다. 단일 HTTP API를 요청하는게 아니라 추상화를 한다면, open()이나 openUrl()을 사용하는게 코드 양이 줄어들겠죠.

여기서 다른 언어와 조금 다르게 특이한 점이라면 HttpClient.getUrl()에 의해 HttpClientRequest 객체가 비동기적으로 생성되는데, 실제 HTTP 요청이 발생하는 건 HttpClientRequest.close()가 호출되는 시점이라는 점입니다. 즉, 첫 번째 then()에서 HttpClientRequest 객체가 생성되는데, HttpClientRequest.close()를 호출하지 않으면 HTTP 요청이 발생하지 않습니다. 결과적으로 HTTP 요청 후 응답을 받아서 처리하려면 then을 두 번 써야한다는 것이죠.

HttpClient client = new HttpClient();

var request = await client.getUrl(Uri.parse("http://www.example.com/"))
// Optionally set up headers...
// Optionally write to the request object...
// Then call close.

var response = await request.close();
// Process the response.

예제 코드를 async/await을 사용해서 정리한 코드입니다. 전체적으로 가독성이 올라간 느낌이네요.

Dart:io library를 사용한 Http 통신 구현시 주의할 점

HttpException: HTTP headers are not mutable

HTTP 요청을 할 때 요청 본문의 Content-Type을 지정하기 위해서, 헤더의 내용을 수정해야할 때가 있습니다.

Unhandled Exception: HttpException: HTTP headers are not mutable
이 때 위처럼 HttpException: HTTP headers are not mutable 예외가 발생하는 경우가 있는데요. 어떤 경우인지 살펴보도록 합시다.

HttpClientRequest Class 페이지를 살펴보면 아래와 같이 헤더의 내용을 수정하는 예제를 발견할 수 있습니다.

HttpClientRequest request = ...
request.headers.contentType
    = new ContentType("application", "json", charset: "utf-8");
request.write(...);

일반적으로 위와 같이 HttpClientRequest객체headers 속성에 접근하여 직접 값을 수정해주면, 헤더값이 적용하게됩니다. 간단하죠. headers를 수정할 수 없다는 예외가 발생하는 경우를 찾아보려면, HttpClientRequest Class 페이지에서는 찾아볼 수 없고 HttpClientRequest.headers 페이지를 찾아봐야합니다.

HttpHeaders headers
Returns the client request headers.

The client request headers can be modified until the client request body is written to or closed. After that they become immutable.

headers라는 녀석은 재밌게도 수정 가능한 속성값이지만 HttpClientRequest.write()를 통해 요청 본문이 작성되거나, HttpClientRequest.close()를 통해 HTTP 요청이 발생한 경우에는 불변(immutable)값이 되어버립니다. 즉, 불변값이 된 이후에 헤더값을 추가하거나 수정하려고 시도하는 경우에는 Unhandled Exception: HttpException: HTTP headers are not mutable 예외가 발생하게 되는 것이죠. 일반적인 경우는 아니지만, HTTP 요청을 추상화하는 과정에서 한번쯤 겪어볼만한 내용같네요.

HTTP 요청 본문이 전달되지 않는 경우

다시 한번 HttpClientRequest Class 페이지의 예제를 살펴봐주세요.

HttpClientRequest request = ...
request.headers.contentType
    = new ContentType("application", "json", charset: "utf-8");
request.write(...);

예제의 마지막 줄에서는 HttpClientRequest.write()를 호출해서 HTTP 요청 본문을 추가하고 있는데요. 이후 HttpClientRequest.close()를 요청하면 HttpClientRequest.write()에 전달한 파라메터값이 본문에 첨부되어야 할 것 같지만... 제대로 전달되지 않습니다. 문제는 HttpClientRequest.contentLength이 자동으로 할당되지 않기 때문인데요. 보통 다른 언어에서는 자동으로 해줄법한 contentLength값이 0으로 남아있기때문에 요청 본문이 추가되지 않는 사태가 발생합니다.

HttpClientRequest request = ...
String requestBody = ...
request.headers.contentType
    = new ContentType("application", "json", charset: "utf-8");
request.contentLength = requestBody.length;
request.write(requestBody);
val response = await request.close();

짜잔, 이제 요청이 제대로 동작하는 걸 볼 수 있습니다. 크게 어려울 건 없지만 Flutter/Dart의 자료량이 많지 않아서 헤멜 수 있는 HTTP 요청과 주의점에 대해 살펴봤습니다. 그럼 안녕! ' ㅂ')/

반응형