URLRequest 만드는 방법 (feat. HTTPS )

Lily·2022년 1월 9일
0

서버와 통신을 하기 위해서는 URLSession에서 수행할 URLSessionTask 인스턴스를 만들어줘야 한다.

어떤 종류의 task를 만들어줄 지에 따라 URLSession에서 호출하는 함수는 정해져있다.

task의 종류 - URLSession의 task만드는 메서드

  1. URLSessionDataTask - URLSession의 dataTask(with:)
  2. URLSessionUploadTask - URLSession의 uploadTask(with:from:)
  3. URLSessionDownloadTask - URLSession의 downloadTask(with:)
  4. URLSessionStreamTask - URLSession의 streamTask(withHostName:port:) or streamTask(with:)

모든 task는 Key Value Observing(KVO)를 지원한다고 한다.

그런데 공통적으로 Task를 만들려면 url또는 URLRequest를 먼저 만들어줘야 한다.

그래서!
오늘 정리해 볼 내용은 HTTP통신을 위한 URLRequest💌를 한땀 한땀 만들어보는 방법에 대해 정리해보고자 한다

URLRequest

먼저 URLRequest에 간단하게 짚고 넘어가겠다.

A URL load request that is independent of protocol or URL scheme.

struct URLRequest

통신 프로토콜이나 URL scheme에 무관하게 request를 로드하는 URL이고,
구조체이다.

크게 핵심적인 두가지 property를 캡슐화하고 있다고 한다.

  • the URL to load
  • the policies used to load it

HTTP와 HTTPS requests는 부가적으로 두가지 property를 포함한다.

  • HTTP method (GET, POST, and so on)
  • HTTP headers

우리는 HTTPS Request를 만들것이기 때문에 HTTP methodHTTP headers 프로퍼티를 사용할 것 이다.


step 1️⃣ URL인스턴스 만들기 (request url)

URLRequest를 init하기 위해서는 request의 url이 필요하다. 이 url은 URL 타입의 인스턴스로 만든다.

// URLRequest의 이니셜라이져
init(url: URL, cachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy, timeoutInterval: TimeInterval = 60.0)

이 url로 말하자 할 것 같으면 API가이드에서 제시해주는 요청URL을 말한다.
참고를 위해 네이버의 API가이드를 참고해보았다.👇

string으로 URL인스턴스를 만드는 이니셜라이져가 제공된다.

init?(string: String)

이 방법 외에도 다른 initializer가 많지만, 쿼리파라미터가 포함된 URL 인스턴스를 만들 때 유용한 방법에 대해 소개해보겠다.

주로 GET메서드의 요청을 보낼 땐 URL에 쿼리 파라미터(물음표 부터)가 포함되어있다.

https://~~~/api/products?page_no=1&items_per_page=10

URLComponentsURLQueryItem을 이용하면 하드코딩을 줄이면서 URL을 만들 수 있다.

1️⃣ URLComponents 이니셜라이저로 baseURL을 먼저 생성한다.

2️⃣ URLQueryItem 이니셜라이저로 query 스트링를 만든다.

  • 이니셜라이저의 매개변수 name 에 query의 key, value 에는 query의 value를 넣어주면 \(name)=\(value)와 같은 형식으로 만들어준다.
  • queryItems 배열에 item을 추가해주면 query파트 시작에 "?"를 따로 작성해주지 않아도 된다. queryItem이 스스로 "?"를 붙이고 query 스트링을 이어붙인다. (이미 "?"가 있다면 붙이지 않는다. 똑똑하군🤖)

3️⃣ URLComponents의 프로퍼티 queryItems 배열에 넣는다.

코드로 보면 아래와 같다.

// 1️⃣ URLComponents 이니셜라이저로 baseURL을 먼저 생성한다. 
var urlComponents = URLComponents(string: "https://~~~/api/products")

// 2️⃣ URLQueryItem 이니셜라이저로 query 스트링을 만든다.
let pageNo = URLQueryItem(name: "page_no", value: "1")
let itemsPerPage = URLQueryItem(name: "items_per_page", value: "10")

// 3️⃣ baseURL의 queryItems 프로퍼티(배열)에 위에서 만든 파리미터를 set한다.
urlComponents?.queryItems = [pageNo, itemsPerPage]

let url = urlComponents.url 
// 완성된 URL객체 https://~~~/api/products?page_no=1&items_per_page=10

step 2️⃣ HTTPMethod 정해주기

URL인스턴스를 만들어서 URLRequest 인스턴스를 만들어줬다면

let urlRequest = URLRequest(url: url)

다음으로 할일은 URLRequest의 HTTPMethod를 정해주는 것이다.

URLRequest에는 httpMethod라는 인스턴스 프로퍼티가 있다.

var httpMethod: String? { get set }

요청하고자 하는 작업에 따라 httpMethod에 String을 지정해주면된다.

urlRequest.httpMethod = "GET"

default는 "GET"이다. 따라서 위 코드는 생략할 수 있다.

step 3️⃣ HTTPHeader 만들기

URLRequest는 header를 만들어주기 위한 addValue(:, field:)메서드를 제공한다.

mutating func addValue(_ value: String, forHTTPHeaderField field: String)

API가이드에 따라 HTTP Header에 들어가야할 내용을 채워준다.

Header에는 Content-Type에 대한 정보가 들어간다.

Content-Type이 application/json일 경우

//  Content-Type이 application/json일 경우
urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")

Content-Type이 multipart/form-data일 경우

multipart/form-data는 데이터의 파트 구분을 위해 쓰일 boundary를 Content-Type의 header에 명시해주어야한다. (;으로 구분 꼭 해주어야합니다) 그리고 여기서 명시한 boundary를 HTTP body에서 boundary로 사용한다.

// Content-Type이 multipart/form-data일 경우
urlRequest.addValue("multipart/form-data; boundary=----something" , forHTTPHeaderField: "Content-Type")

위 코드의 경우 "boundary로 ----something을 쓸것이다." 라고 약속해준 것과 같다. body의 내용과 겹치면 안되기 때문에 보통 UUID를 boundary로 많이 사용한다.


step 4️⃣ HTTPBody 만들기

POST 같은 메서드는 http메시지의 body가 필요하다. body에는 보내질 data(Data 타입)가 실리게 된다.

var httpBody: Data? { get set }

Content-Type이 application/json일 경우

body에 실을 객체를 JSON으로 encode 해서 URLRequest의 프로퍼티인 httpBody에 넣어주면된다.

guard let body = try? JSONEncoder().encode(body) else { return }
request.httpBody = body

Content-Type이 multipart/form-data일 경우

body를 만드는 방법이 좀 까다롭다.😠

하지만 multipart/form-data의 바디를 구성하는 규칙이 있으니 겁먹을 필요읎다!

모질라에서 예시로 든 Content-Type이 multipart/form-data일 경우 POST를 보낼 때 형식을 살펴보자.

POST /foo HTTP/1.1
Content-Length: 68137
Content-Type: multipart/form-data; boundary=----something
Content-Disposition: form-data; name="description"
----something

some text

----something
Content-Disposition: form-data; name="myFile"; filename="foo.txt"
Content-Type: text/plain

(content of the uploaded file foo.txt)

----something--   ✅ 마지막 바운더리에는 "--"를 끝에 추가. 중요🌟

multipart/form-data는 이름 그대로 여러파트로 구분된 form-data를 담고 있다.
각 파트는 또 header와 body로 구성되어있다.
그리고 줄바꿈 규칙이 굉장히 중요하다!

----something  1️⃣ boundary / 위 http header에서 명시한 boundary 사용하면 됨
Content-Disposition: form-data; name="myFile"; filename="foo.txt"  2️⃣  header /  이 파트의 이름을 알려줌
Content-Type: text/plain  2️⃣ header / 이 파트의 데이터 타입을 알려줌
3️⃣ 🌟 줄 바꿈 필수 🌟
(content of the uploaded file foo.txt)  4️⃣ body

위 블록이 하나의 파트이다.
1️⃣ boundary
2️⃣ header : Content-Diposition(멀티 파트 바디의 header로서 ) , Content-Type
3️⃣ 줄 바꿈
4️⃣ body
구성으로 이루어져있다.

이러한 파트를 여러개 만들어서 이어 붙여주고
마지막으로 멀티파트폼데이터는 여기서 "끝이다~" 라고 알려주는 boundary를 작성한다.

----something--   

이 때 boundary에는 --를 꼭 끝에 붙여주어야 한다.


그래서! 코드로 어떻게 작성하느냐!

하나의 멀티파트를 data로 만들어서 httpBody에 넣는 코드를 작성해보았다.

// Data 익스텐션에 string을 파라미터로 받아서 바로 append하기 위한 메서드 정의
extension Data {
    
    mutating func append(_ string: String, using encodingStyle: String.Encoding) {
        guard let data = string.data(using: encodingStyle) else {
            return
        }
        self.append(data)
    }
}

// Data 타입의 body 객체 생성 (이제 여기에 다 이어 붙일 것임)
var body = Data()
let boundary = "----something"
let lineBreak = "\r\n"

// 1️⃣ boundary 
body.append(boundary + lineBreak, using: .utf8)
// 2️⃣ header
body.append(
            "Content-Disposition: form-data; name=\"myFile\"; filename=\"foo.txt\"" + lineBreak,
            using: .utf8
        )
body.append("Content-Type: text/plain" + lineBreak, using: .utf8)
// 3️⃣ 줄 바꿈
body.append(lineBreak, using: .utf8)
// 4️⃣ body
let data = file foo.txt를 encode한 데이터
body.append(data)



// httpBodyd에 data 넣기 
request.httpBody = body

step 5️⃣ 완성된 URLRequest로 dataTask인스턴스 만들기

마지막 단계!
URLSession의 task만드는 메서드의 매개변수로 위에서 셋팅해둔 URLRequest인스턴스를 전달해주면 된다!

let dataTask = defaultSession.dataTask(with: url) { [weak self] data, response, error in
    defer {
      self?.dataTask = nil
    }
    
    if let error = error {
      self?.errorMessage += "DataTask error: " +
                              error.localizedDescription + "\\n"
    } else if
      let data = data,
      let response = response as? HTTPURLResponse,
      response.statusCode == 200 {
      self?.updateSearchResults(data)
      
      DispatchQueue.main.async {
        completion(self?.tracks, self?.errorMessage ?? "")
      }
    }
  }

URLRequest를 만들기 위한 여정이었다...

모두 모두 화이팅🔥

profile
i🍎S 개발을 합니다

0개의 댓글