URLSession์ Swift์์ ์๋ฒ์ ์ํตํ๊ธฐ ์ํด ์ ๊ณต๋๋ ํด๋์ค.
URLSession์ ์ฌ์ฉํ๋ฉด ๋ฐ์ดํฐ ๋ค์ด๋ก๋, ์ ๋ก๋, API ์์ฒญ ๋ฑ์ ์ฝ๊ฒ ์ํํ ์ ์๋ค.
URLSession์ ๋คํธ์ํฌ ์์ฒญ์ ์ฒ๋ฆฌํ๊ณ ์๋ต์ ๋ฐ์์ค๋ ๊ณผ์ ์ ๊ด๋ฆฌํ๋ค.
์๋ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ Alamorfire์ moya๋ ์๋ค.
โ URLSession์ ์ฃผ์ ๊ตฌ์ฑ ์์
๐ 1. URLSession
: ๋คํธ์ํฌ ์์ฒญ์ ์ํํ๋ ์ฃผ์ ๊ฐ์ฒด
๐ 2. URLSessionConfiguration
: ์ธ์ ์ ๊ตฌ์ฑํ๋ ํ๊ฒฝ ์ค์ ์ ์๋ฏธํ๋ ์์ฑ
- .default
๊ธฐ๋ณธ ํต์ ์ผ๋ก ๋์คํฌ๋ฅผ ์ด์ฉํ ์ ๋ณด ์ ์ฅ์ ํ๋ configuration
๊ทธ๋ฅ ๋ธ๋ผ์ฐ์ ๋์ธ๋
let defaultSession = URLSession(configuration: .defualt)
- .ephemeral
default ๋ ๋น์ทํ์ง๋ง, ์ฟ ํค๋ ์ธ์ฆ์, ์บ์ ๋ฑ์ ์ ์ฅํ์ง ์์ ๋ ์ฌ์ฉ
ex) ๋ธ๋ผ์ฐ์ ์ํฌ๋ฆฟ ๋ชจ๋, Safari์ ๊ฐ์ธ ์ ๋ณด๋ณดํธ ๋ชจ๋
let ephemeralSession = URLSession(configuration: .ephemeral)
- .background
๋คํธ์ํฌ๋ฅผ ํตํด ํ์ผ ๋ค์ด๋ก๋ ๋ฐ์๋
ex) ์ฑ์ด ๋ฐฑ๊ทธ๋ผ์ด๋์ ์์ ๋, ๋ค์ด๋ก๋/์ ๋ก๋ํ ๋ ์ฌ์ฉ
let backgroundSession = URLSession(configuration: .background)
๐ 3. URLSessionTask
: ๋ฐ์ดํฐ ์ ์ก ์์
- URLSessionDataTask
์๋ฒ์์ ๋ฉ๋ชจ๋ฆฌ๋ก ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ฑฐ๋ ๋ฐ์ดํฐ๋ฅผ ์๋ฒ๋ก ๋ณด๋ผ ๋ ์ฌ์ฉ.
์๋ฒ์ ์งค๊ณ , ์์ฃผ ์์ฒญ๋๋ ์์ ์ ๋ํด ์ฌ์ฉ๋๋ค.
๋ฐฑ๊ทธ๋ผ์ด๋์์๋ ์ง์โ
- URLSessionDownloadTask
๋ฐ์ดํฐ๋ฅผ ํ์ผ์ ํํ๋ก ์ฝ๊ณ ๋์ฉ๋ ํ์ผ์ ๋ค์ด๋ก๋ํ ๋ ์ฌ์ฉ.
๋ค์ด๋ฐ์ ํ์ผ์ ์์ ๋๋ ํ ๋ฆฌ์ ์ ์ฅํด๋๊ณ ๋ค์ด๋ก๋์ ์ฑ๊ณตํ๋ค๋ฉด ๋๋ ํ ๋ฆฌ์ ์ฃผ์๋ฅผ completion handler์ ๋ฐํํ๋ค.
์ด๋ ์์ ํ์ผ์ completionHandler๊ฐ ์ข ๋ฃ๋๊ธฐ ์ ๋ค๋ฅธ ๋๋ ํ ๋ฆฌ์ ์ฎ๊ฒจ์ค์ผ ๋ฐ์ดํฐ๊ฐ ์ญ์ ๊ฐ ์๋๋ค.
๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ง์
- URLSessionUploadTask
๋ฐ์ดํฐ๋ฅผ ์๋ฒ๋ก ์ ๋ก๋ํ ๋ ์ฌ์ฉ.
๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ง์
- URLSessionWebSocketTask
TCP๋ TLS๋ฅผ ํตํด ๋ฉ์์ง๋ฅผ ์ฃผ์ ๋ฐ์ ๋ ์ฌ์ฉ
โ URLSession์ ์ฌ์ฉ ๋ฐฉ๋ฒ
1. URLSessionConfiguration ์ค์
๊ธฐ๋ณธ ์ค์ , ์์ ์ค์ , ๋ฐฑ๊ทธ๋ผ์ด๋ ์ค์ ๋ฑ ๋ค์ํ ๊ตฌ์ฑ์ ์ฌ์ฉํ ์ ์๋ค.
let defaultSession = URLSession(configuration: .default)
URLSessionConfigration ๊ฐ์ฒด๊ฐ ์ธ์ ์ ํ๋ฒ ์ค์ ๋ ์ดํ ์์ ์ ๋ฌด์๊ฐ ๋๊ธฐ ๋๋ฌธ์,
์๋ก์ด URLSession ์ธ์คํด์ค๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค.
๋ํ, URLSession์ ์ฑ ์ ์ญ์์ ์ฌ์ฉ๋๊ธฐ ๋๋ฌธ์ shared๋ผ๋ ์ด๋ฆ์ผ๋ก ์ฑ๊ธํค ์ธ์ ๋ ๊ฐ๋ฅํ๋ค.
์ด ์ธ์ ์ ์ค์ ๊ฐ์ฒด๋ฅผ ๊ฐ์ง๊ณ ์์ง ์์, configuration์ ๋ฐ๊พธ๊ธฐ ์ํด์๋ ์ถ๊ฐ ์ค์ ์ด ํ์ํ๋ค.
์ฑ๊ธํค ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ ์ธ์ ์ ๋ฐ๋ก ์์ฑํ ํ์๊ฐ ์๋ค.
2. URLSession ์ธ์คํด์ค ์์ฑ
์ค์ ์ ์ฌ์ฉํ์ฌ URLSession ์ธ์คํด์ค๋ฅผ ์์ฑํ๋ค.
3. URLRequest ๊ฐ์ฒด ์์ฑ
์์ฒญํ URL๊ณผ ๊ธฐํ ์์ฒญ ์ ๋ณด๋ฅผ ํฌํจํ๋ URLRequest ๊ฐ์ฒด๋ฅผ ์์ฑ
- URL
์์ฒญ์ ๋ณด๋ผ ๋์์ URL๋ก, URL ๊ฐ์ฒด๋ฅผ ํตํด ์ค์ ๋๋ค.
var request = URLRequest(url: URL(string: "https://example.com")!)
- HTTP Method
์์ฒญ์ ์ ํ์ ๋ํ๋ด๋ ๋ฉ์๋๋ก๋ GET, POST, PUT, DELETE ๋ฑ์ด ์๋ค.
request.httpMethod = "GET"
- HTTP Headers
์์ฒญ์ ๋ํ ์ถ๊ฐ ์ ๋ณด๋ฅผ ์ ๊ณตํ๋ ํค-๊ฐ ์์ด๋ค. ์๋ฅผ ๋ค์ด, Content-Type, Authorization, User-Agent ๋ฑ์ด ์๋ค.
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Bearer token", forHTTPHeaderField: "Authorization")
- HTTP Body
์ฃผ๋ก POST๋ PUT ์์ฒญ์์ ์ฌ์ฉ๋๋ฉฐ, ์๋ฒ๋ก ์ ์กํ ๋ฐ์ดํฐ๋ฅผ ๋ด๊ณ ์๋ค. Data ๊ฐ์ฒด๋ก ์ค์ ๋๋ค.
let jsonData = try? JSONSerialization.data(withJSONObject: ["key": "value"], options: [])
request.httpBody = jsonData
- Cache Policy
์บ์๋ ์๋ต ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ ์ง ์ฌ๋ถ๋ฅผ ๊ฒฐ์ ํ๋ค.
๊ธฐ๋ณธ๊ฐ์ .useProtocolCachePolicy์ด๋ฉฐ,
๋ค๋ฅธ ์ต์ ์ผ๋ก๋ .reloadIgnoringLocalCacheData, .returnCacheDataElseLoad, .returnCacheDataDontLoad ๋ฑ์ด ์๋ค.
request.cachePolicy = .reloadIgnoringLocalCacheData
- Timeout Interval
์์ฒญ์ด ์๊ฐ ์ด๊ณผ๋ก ์คํจํ๊ธฐ ์ ๊น์ง ๋๊ธฐํ๋ ์๊ฐ์ ์ง์ ํ๋ค. ๊ธฐ๋ณธ๊ฐ์ 60์ด.
request.timeoutInterval = 30.0 // 30์ด๋ก ์ค์
- Network Service Type
์์ฒญ์ด ์ฌ์ฉํ๋ ๋คํธ์ํฌ ์๋น์ค ์ ํ์ ์ง์ ํ๋ค.
๊ธฐ๋ณธ๊ฐ์ .default์ด๋ฉฐ, ๋ค๋ฅธ ์ต์ ์ผ๋ก๋ .background, .video, .voice, .responsiveData ๋ฑ์ด ์๋ค.
request.networkServiceType = .video
4. URLSessionDataTask ์์ฑ ๋ฐ ์คํ
๋ฐ์ดํฐ ์์ฒญ์ ๋น๋๊ธฐ๋ก ์ํํ๊ธฐ ์ํด URLSessionDataTask๋ฅผ ์์ฑํ๊ณ ์คํํ๋ค.
URL ์์ฑ๊ณผ ํ์ํ ์ ๋ณด๋ฅผ ์ด์ ์์ฒญํ๋ ๋ถ๋ถ์ด๋ค.
URLSession์์ URL์ ์์ฒญ ํ dataTask์์ ์๋ต์ด ์ค๋ฉด ์ฝ๋ ์ํ
์๋ต์ completion(closure)์ delegate ๋ ๊ฐ์ง ๋ฐฉ๋ฒ์ด ์๋ค.
์ธ์ ๋ด์์ ์งํ๋๋ Task๋ค์ ๊ณตํต๋ Delegate ๊ฐ์ฒด๋ฅผ ๊ณต์ ํ๊ณ ์๋ค.
์ด Delegate๋ฅผ ๊ตฌํํ๋ฉด ํต์ ๊ณผ์ ์์ ๋ฐ์ํ๋ ๊ถํ ์ธ์ฆ ์คํจ, ์๋ฒ๋ก๋ถํฐ์ ๋ฐ์ดํฐ ๋์ฐฉ, ๋ฐ์ดํฐ ์บ์ฑ ๋ฑ
๋ค์ํ ์ด๋ฒคํธ์ ๋ํ ์ ๋ณด๋ฅผ ๋ฐ์ ์ ์๋ค.
Delegate๊ฐ ํ์ํ์ง ์๋ค๋ฉด, Session์ ์์ฑํ ๋ ์ธ์๋ก nil์ ์ ๋ฌํ๋ค.
// URLSession ์์ฑ, delegate ์ธ์๋ก nil์ ์ ๋ฌ
let session = URLSession(configuration: configuration, delegate: nil, delegateQueue: nil)
์ธ์ ๊ฐ์ฒด๋ Delegate๋ฅผ ์ฑ์ด ์ข ๋ฃ๋๊ฑฐ๋ ์ธ์ ์ด ๋๋ ๋๊น์ง ๊ฐํ๊ฒ ์ฐธ์กฐํ๋ค.
๋ฉ๋ชจ๋ฆฌ ๋ฆญ์ ๋ฐฉ์งํ๊ธฐ ์ํด ๋์ด์ ์ฌ์ฉํ์ง ์๋ ์ธ์ ์ invalidateAndCancel() ๋ฉ์๋๋ฅผ ํธ์ถํด ๋ฌดํจํํ๋ค.
// ์ธ์
๋ฌดํจํ ๋ฐ ์ทจ์
session.invalidateAndCancel()
โ URLSession์ ์ฌ์ฉํ ์์
๊ฐ๋จํ GET ์์ฒญ์ ๋ณด๋ด๊ณ JSON ์๋ต์ ์ฒ๋ฆฌํ๋ ์์
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
fetchData()
}
// ์๋ฒ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๋ ๋ฉ์๋ ์ ์ธ
private func fetchData() {
let defaultUrlSession = URLSession(configuration: .default)
guard let url: URL = URL(string: "https://reqres.in/api/users/1") else {
print("URL is not correct")
return //ํจ์์ ์คํ์ ์ฆ์ ์ข
๋ฃํ๊ณ ํจ์๊ฐ Void๋ฅผ ๋ฐํ
}
// URLRequest ์ธ์คํด์ค ์ค์
var request: URLRequest = URLRequest(url: url)
// ์์ฒญ์ ์ฌ์ฉํ HTTP๋ฉ์๋ ์ค์ - GET ๋ฉ์๋ ์ฌ์ฉ
request.httpMethod = "GET"
// HTTP ํค๋ ์ค์ - json ๋ฐ์ดํฐ ํ์์์ ๋ํ๋
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
// URLSession ์์ฑ (๊ธฐ๋ณธ default ์ธ์
)
let session: URLSession = URLSession(configuration: .default)
// dataTask - URLSession์ ์ฌ์ฉํด ์์ฒญ ์ํ
session.dataTask(with: request) { (data, response, error) in
// http ํต์ response ์๋ status code ๊ฐ ํจ๊ป์ค๋๋ฐ, 200๋ฒ๋๊ฐ ์ฑ๊ณต์ ์๋ฏธ.
let successRange: Range = (200..<300)
// ํต์ ์ฑ๊ณต
guard let data, error == nil else { return }
if let response: HTTPURLResponse = response as? HTTPURLResponse{
print("status code: \(response.statusCode)")
// ์์ฒญ ์ฑ๊ณต (StatusCode๊ฐ 200๋ฒ๋)
if successRange.contains(response.statusCode){
// decode
guard let userInfo: ResponseData = try? JSONDecoder().decode(ResponseData.self, from: data) else { return }
print(userInfo)
} else { // ์์ฒญ ์คํจ (Status code๊ฐ 200๋ ์๋)
print("์์ฒญ ์คํจ")
}
}
}.resume() //์์
์์
}
}
// ๋ฐ์ดํฐ ๊ตฌ์กฐ์ฒด ์ ์
struct UserData: Codable {
let id: Int
let email: String
let firstName: String
let lastName: String
let avatar: URL
// JSON ํค์ ๊ตฌ์กฐ์ฒด ํ๋กํผํฐ ๊ฐ์ ๋งคํ์ ์ํด CodingKeys ์ด๊ฑฐํ ์ ์
enum CodingKeys: String, CodingKey {
case id
case email
case firstName = "first_name"
case lastName = "last_name"
case avatar
}
}
// Support ๊ตฌ์กฐ์ฒด ์ ์
struct SupportData: Codable {
let url: URL
let text: String
}
// ์ต์์ ๊ตฌ์กฐ์ฒด ์ ์
struct ResponseData: Codable {
let data: UserData
let support: SupportData
}
๐ Codable ํ๋กํ ์ฝ์ ์ค์ํ๋ ๊ตฌ์กฐ์ฒด์์ CodingKeys๋ฅผ ์ฌ์ฉํ๋ ์ด์ ?
Codable ํ๋กํ ์ฝ์ ์ค์ํ๋ ๊ตฌ์กฐ์ฒด์์ CodingKeys๋ฅผ ์ฌ์ฉํ๋ ์ฃผ๋ ์ด์ ์
JSON ๋ฐ์ดํฐ์ ํค์ Swift ๊ตฌ์กฐ์ฒด์ ํ๋กํผํฐ๋ช ์ด ๋ค๋ฅผ ๋, ์ด๋ฅผ ๋งคํํด์ค ์ ์๋ ์ ์ฐ์ฑ์ ์ ๊ณตํ๋ค.
์ฃผ๋ก ๋ค์๊ณผ ๊ฐ์ ๊ฒฝ์ฐ์ ํ์ฉ๋๋ค.
- ํ๋กํผํฐ ๋ช
๋ช
๊ท์น์ด ๋ค๋ฅธ ๊ฒฝ์ฐ
: JSON ๋ฐ์ดํฐ์ ํค๊ฐ snake_case ํ์์ด๊ฑฐ๋ ๋ค๋ฅธ ๋ช ๋ช ๊ท์น์ ๋ฐ๋ฅด๋ ๊ฒฝ์ฐ,
Swift์ camelCase ๋ช ๋ช ๊ท์น์ ๋ง์ถ๊ธฐ ์ํด CodingKeys๋ฅผ ์ฌ์ฉ. - ์ผ๋ถ ๋ฐ์ดํฐ๋ง ๋์ฝ๋ฉ/์ธ์ฝ๋ฉ
: JSON์๋ ํ์ํ ์ผ๋ถ ๋ฐ์ดํฐ๋ง ์์ ๊ฒฝ์ฐ, CodingKeys๋ฅผ ์ฌ์ฉํ์ฌ ํ์ํ ๋ฐ์ดํฐ๋ง ์ฒ๋ฆฌ. - ๊ธฐ๋ณธ๊ฐ ์ฒ๋ฆฌ
: JSON์ ํน์ ํค๊ฐ ์์ ๋ ๊ธฐ๋ณธ๊ฐ์ ์ฌ์ฉํ๋๋ก CodingKeys ์ค์ .
๐ URLSession์ .default๊ณผ .shared์ ์ฐจ์ด์ ?
- URLSession.shared
: ์ ํ๋ฆฌ์ผ์ด์
๋ด์์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณต๋๋ URLSession ์ธ์คํด์ค.
์ฑ ์ ์ฒด์์ ๊ณต์ ๋๋ฉฐ, ๊ธฐ๋ณธ ๊ตฌ์ฑ์ ์ฌ์ฉํ์ฌ ๋คํธ์ํฌ ์์ฒญ์ ์ฒ๋ฆฌํ๋ค.
๋๋ถ๋ถ์ ๊ฒฝ์ฐ์์ ์ฌ์ฉํ ์ ์๊ณ , ์ถ๊ฐ์ ์ธ ์ธ๋ถ ์ค์ ์ด ํ์ํ์ง ์์ ๋ ์ฌ์ฉ.
- URLSession(configuration: .default)
: ๊ฐ๋ฐ์๊ฐ ์ง์ ๊ตฌ์ฑ์ ์ค์ ํ์ฌ ์์ฑํ๋ URLSession ์ธ์คํด์ค.
๊ธฐ๋ณธ ๊ตฌ์ฑ๊ณผ๋ ๋ค๋ฅธ ํ์์์, ์บ์ ์ ์ฑ ๋ฑ์ ์ต์ ์ ์ถ๊ฐ๋ก ์ค์ ํ ์ ์๋ค.
ํน์ ์๊ตฌ์ฌํญ์ ๋ง์ถฐ ํน์ ํ์์์์ด๋ ์บ์ ์ ์ฑ ์ค์ ๊ณผ ๊ฐ์ ์ธ๋ถ์ ์ธ ์ปค์คํฐ๋ง์ด์ง์ด ํ์ํ ๋ ์ฌ์ฉ.
๐ ๋คํธ์ํฌ ์์ฒญ์ ๋ณด๋ผ ๋ ์ฃผ์ HTTP ํค๋๋ค์ ์ข ๋ฅ์ ์ญํ
๋คํธ์ํฌ ์์ฒญ ์ ํน์ ํ HTTP ํค๋๋ฅผ ์ค์ ํ๋ฉด ์๋ฒ์์ ํต์ ์ ๋์ฑ ์ ๊ตํ๊ฒ ์ ์ดํ ์ ์๋ค.
- Content-Type
: ์์ฒญ ๋ฐ๋์ ๋ฏธ๋์ด ํ์ ์ ์ ์. ์ฃผ๋ก JSON ๋ฐ์ดํฐ๋ฅผ ์ ์กํ ๋ ์ฌ์ฉ.
์: Content-Type: application/json - Accept
: ํด๋ผ์ด์ธํธ๊ฐ ์ํ๋ ์๋ต์ ๋ฏธ๋์ด ํ์ ์ ์๋ฒ์ ์๋ฆผ. ์๋ฒ๋ ์ด๋ฅผ ์ฐธ๊ณ ํ์ฌ ์ ์ ํ ํฌ๋งท์ผ๋ก ์๋ต.
์: Accept: application/json - Authorization: ์ธ์ฆ ์ ๋ณด๋ฅผ ์๋ฒ์ ์ ๋ฌํ์ฌ ๋ณด์์ ์ธ ์ ๊ทผ์ ์ ์ด. ์ฃผ๋ก ํ ํฐ ๊ธฐ๋ฐ ์ธ์ฆ์์ ์ฌ์ฉ.
์: Authorization: Bearer {token} - User-Agent
: ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ณด๋ฅผ ์๋ฒ์ ์ ๊ณตํ์ฌ, ์๋ฒ๊ฐ ์์ฒญ์ ๋ฐ์ ํด๋ผ์ด์ธํธ ์ข ๋ฅ๋ฅผ ์๋ณ.
์: User-Agent: MyApp/1.0 (iOS 14.5; iPhone 12) - Cache-Control: ์บ์ฑ ๋์์ ์ค์ . ํด๋ผ์ด์ธํธ์ ์๋ฒ ๊ฐ ์บ์ฑ ์ ์ฑ
์ ์กฐ์ .
์: Cache-Control: no-cache - Cookie: ํด๋ผ์ด์ธํธ๊ฐ ์๋ฒ์ ๋ณด๋ธ ์ฟ ํค๋ฅผ ํฌํจํ์ฌ ์ธ์
๊ด๋ฆฌ ๋ฑ.
์: Cookie: sessionid=123456
๐ HTTP์์ฒญ์ ํค๋๋ฅผ ์ค์ ํ ๋ ์ฌ์ฉ๋๋ URLRequest ๊ฐ์ฒด์์ ์ฌ์ฉ๋๋ ๋ฉ์๋
- addValue(_:forHTTPHeaderField:)
: ์ง์ ๋ HTTP ํค๋ ํ๋์ ๊ฐ์ ์ถ๊ฐํ๋ค. ๋์ผํ ํค๋ ํ๋๊ฐ ์ด๋ฏธ ์กด์ฌํ๋ ๊ฒฝ์ฐ, ๊ธฐ์กด ๊ฐ์ ์ถ๊ฐ - setValue(_:forHTTPHeaderField:)
: ์ง์ ๋ HTTP ํค๋ ํ๋์ ์ ๊ฐ์ ์ค์ ํ๋ค. ๋์ผํ ํค๋ ํ๋๊ฐ ์ด๋ฏธ ์กด์ฌํ๋ ๊ฒฝ์ฐ, ์ด์ ๊ฐ์ ๋์ฒด๋จ. - allHTTPHeaderFields
: ํ์ฌ ์ค์ ๋ ๋ชจ๋ HTTP ํค๋ ํ๋๋ฅผ ๊ฐ์ ธ์ค๊ฑฐ๋ ์ค์ . - httpMethod
: HTTP ์์ฒญ ๋ฉ์๋(์: GET, POST ๋ฑ)๋ฅผ ์ค์ . - httpBody
: HTTP ์์ฒญ์ ๋ณธ๋ฌธ ๋ฐ์ดํฐ๋ฅผ ์ค์ . - timeoutInterval
: ๋คํธ์ํฌ ์์ฒญ์ ํ์์์ ์๊ฐ์ ์ค์ .
์ด๋ค ๋ฉ์๋๋ค์ ์ ์ ํ ํ์ฉํ์ฌ URLRequest ๊ฐ์ฒด๋ฅผ ๊ตฌ์ฑํ๋ฉด, ์ํ๋ ํํ์ HTTP ์์ฒญ์ ๋ณด๋ด๊ณ
ํ์ํ ํค๋๋ ๋ฐ์ดํฐ๋ฅผ ํฌํจํ ์ ์๋ค.
๐ Reference
https://jeonyeohun.tistory.com/357
https://jazz-the-it.tistory.com/19