2022. 1. 27. 03:11ㆍiOS/iOS
걸음마
앱을 만들다보면 로컬 작업만으로도 충분한 경우가 있지만, 웹 서버와의 통신이 필요한 경우가 대부분입니다. 웹서버 통신은 크게 http통신과 웹소켓통신으로 나뉘는데 저는 http통신을 이용했습니다.
- http 통신
http는 Hyper Text Transfer Protocol의 두문자어로, 인터넷에서 데이터를 주고받을 수 있는 프로토콜이며, URL기반으로 클라이언트(사용자)에서 서버로 요청(request)을 보내고, 서버로부터 응답(response)을 받는 형태의 통신입니다. 프로토콜….? 프로토콜은 쉽게 말해서 클라이언트와 서버가 통신할때,
클라이언트: 나는 요런요런 형식으로 데이터를 요청(request)할게 🙋♀️
서버: ㅇㅋㅇㅋ. 난 너가 요청한 형식 그대로 데이터를 받을게 🤦♀️
이런식으로 서로 데이터를 어떤 형식으로 주고 받으면 좋을지를 정한 “약속”입니다.
- http 메시지 구조
http(request / response)는 기본적으로 blank line을 제외한 세부분으로 구성되어있습니다. Start Line, Header, Body 이렇게 말이죠
바로위의 사진을 보시면 request와 response는 HTTP 메시지 구조가 서로 비슷합니다.(Response는 startline을 status line이라고 하기에 같지않고 비슷하다고 적었습니다.) 간단하게 살펴보면,
start line에는 실행되어야 할 요청 또는 요청 수행에 대한 성공 또는 실패가 기록되는데 보통 항상 한 줄로 끝납니다.
HTTP headers에는 요청에 대한 설명, 혹은 메시지 본문에 대한 설명이 들어갑니다.
blank line에는 요청에 대한 모든 메타 정보가 전송되었음을 알리는 빈줄이 삽입됩니다.
body에는 요청과 관련된 내용이 옵션으로 들어가거나(HTML 폼 콘텐츠 등) 응답과 관련된 문서가 들어갑니다. 본문의 존재 유무 및 크기는 첫 줄과 HTTP헤더에 명시됩니다.
간단하게 HTTP메시지 구조가 뭔지에 대해 알아봤다면 이제 좀더 깊게,
Request와 Response별로 나눠서 살펴보겠습니다.
Request
- Start Line
HTTP 메서드, 2) Request target, 3) HTTP version이 있습니다. 요청의 의도를 담고 있는 GET, POST, PUT, DELETE 등이 있는데, 보통 GET과 POST를 가장 많이 사용합니다.
GET(가져오다): 존재하는 자원에 대한 요청 (오직! 데이터를 받기만 합니다) - 입력한 정보가 url에 노출되고 정보를 보여줘도 상관없는 기능을 수행할때 이용
POST(게시하다): 새로운 자원을 생성
PUT(집어넣다): 존재하는 자원에 대한 변경
DELETE(지우다): 존재하는 자원에 대한 삭제
2. http headers
request 메시지 body의 총 길이와 같은, request에 대한 추가 정보를 담고 있습니다.
key : value 값으로 되어있고 headers는 다시 general headers, request headers, entity headers 이렇게 세 부분으로 나뉩니다.
자주 사용되는 header 정보에는 Host(요청이 전송되는 target의 host url), User-Argent(요청을 보내는 클라이언트에 대한 정보), Accept(해당 요청이 받을 수 있는 response타입), Connection(해당 요청이 끝난후 클라이언트와 서버가 계속해서 네트워크 커넥션을 유지할건지 아니면 끊을것인지에 대해 지시하는 부분), Content-Type(해당 요청이 보내는 메시지 body의 타입), Content-Length(메세지 body길이)가 있습니다.
3. body
해당 request의 실제 메시지/내용
body가 없는 request들도 많은데 GET request들은 body가 없는 경우가 많습니다.
Response
- Status Line
HTTP response의 시작줄은 상태줄이라고 불리며 보통 프로토콜 버전, 상태코드(요청의 성공여부), 상태텍스트(짧고 간결하게 상태 코드에 대한 설명을 남김)와 같은 정보를 남깁니다
2. http headers
request의 headers와 동일한데 다만 response에서만 사용되는 header값들이 있습니다. 예를들어 User-Agent대신 Server header가 사용됩니다.
Location(301, 302 상태 코드일때만 볼 수 있는 Header로, 서버의 응답이 다른 곳에 있다고 알려주면서 해당 위치(URl)를 지정합니다), Server(웹 서버의 종류), Age(max-age 시간 내에서 얼마나 흘렀는지 초 단위로 알려주는 값), Referrer-policy(서버 referrer정책을 알려주는값), Proxy-Authenticate(요청한 서버가 프록시 서버인 경우 유저 인증을 위한 값
3. body
http request메시지의 body와 동일하며 마찬가지로 전송하는 데이터가 없으면 비어있습니다.
이정도면 http에 대해서 조금은 톺아봤다고 할 수 있을것같습니다(감히..!) 위의 내용도 숙지하지 못한 상태로, api 통신에 성공했던것이 오히려 놀라울 따름입니다..ㅎㅎ 그만큼 정말정말정말 중요한 내용이면서 기초적인 내용이니 자주 반복 숙지하면 좋겠습니다.
도입(feat. postman)
api 명세서를 보면 크게 request와 response로 나뉘는데……….…….아. 이제야 왜 post man을 썼는지 알것 같습니다.
request의 header, body규약들에 맞게 postman에서 1) request url(baseUrl + 백앤드 개발자님께서 만들어주신 주소.예를들면 아래의 /review/station/get) 2) http메서드 설정(제가 하려는 작업은 POST네요), 3) Content-Type 4) Authorization에 토큰 입력 5) body를 입력하고 send버튼을 누르면 서버에서 어떤 응답을 주는지 확인하기 위해서 postman을 이용하는 것이었습니다…:)
먼저 postman을 통해 서버 통신이 제대로 되는지 확인 후, 실제 코드로 서버통신을 위한 작업을 시작합니다.
근데 여기서 드는 의문.. 왜 “제가 하려던 작업”은 리뷰이기 때문에 조회(GET)의 의미를 갖고 있는데 http메서드가 POST인거…?소오름
그래서 검색해봤더니,
“GET 방식의 요청은 브라우저에서 Caching 할 수 있다. 때문에 POST 방식으로 요청해야 할 것을 보내는 데이터의 크기가 작고 보안적인 문제가 없다는 이유로 GET 방식을 요청한다면 기존에 caching 되었던 데이터가 응답될 가능성이 존재한다. 때문에 목적에 맞는 기술을 사용해야 한다.“
라는 글을 발견했습니다. 아. 기존에 caching되었던 데이터가 응답될 가능성이 존재할때는 get대신 post방식을 이용하기도 하나 봅니다…! “get은 조회, post는 자원생성” 이렇게만 이해하고 넘어갔던 제 자신을 반성합니다…..;)
실전(feat. urlSession)
그럼 이제 위에서 학습했던 서버 통신을 실제 코드로 구현해볼 시간입니다.
postman을 이용해서 통신에 성공하면 json형식의 response body값이 생성됩니다.
여기서 원하는 정보를 뽑아내기 위해 구조체를 생성해야 합니다.
- codable
제 구조체의 일부입니다. 각각의 구조체 마다 Codable이라는 프로토콜을 채택하고 있습니다.
Codable은 JSON형태의 데이터를 쉽게 처리하기 위한 프로토콜입니다. 사용법은 간단합니다. 위의 json키와 아래의 codable을 따르는 구조체의 프로퍼티를 동일하게 맞춰주면 되는데, 만약 프로퍼티를 다르게 정의할 경우 아래와 같이 CodingKeys를 통해 Codable을 사용하면 됩니다.
import Foundation
// MARK: - APIResponse
struct APIResponse: Codable {
let status: Int?
let message: String?
let data: DataClass?
let requestTime, completeTime: Int?
}
// MARK: - DataClass
struct DataClass: Codable {
let stationReviewRate: Int?
let reviewList: [ReviewList]?
}
// MARK: - ReviewList
struct ReviewList: Codable {
let stationID, message: String?
let rate: Int?
let reviewListSelf: Bool?
let updatedAt: String?
let replyList, feedback: JSONNull?
enum CodingKeys: String, CodingKey {
case stationID = "stationId"
case message, rate
case reviewListSelf = "self"
case updatedAt
case replyList, feedback
}
}
- URLSession
URLSession은 iOS앱에서 HTTP기반의 데이터를 다운로드 하거나 업로드하는 API를 제공하는 클래스입니다. URLSession API를 사용하기 위해서 애플리케이션은 세션을 생성합니다. 그리고 해당 세션은 관련된 데이터 전송작업 그룹을 조정합니다. URLSession Task는 작업에 따라서 DataTask, UploadTask, DownloadTask로 나뉘는데 저는 DataTask를 이용해서 데이터를 받아왔습니다.
- URLRequest
URLRequest는 로드할 URL과 로드하는데 사용되는 정책 이 두가지를 property로 캡슐화합니다. 또한 HTTP 및 HTTPS 요청의 경우 URLRequest에는 HTTP메서드(GET, POST등)와 HTTP헤더, httpBody가 포함됩니다.
Request url을 기반으로, 서버에 요청할 http메서드, 헤더값(Content-Type, Authorlization), 바디값(jsonData)을 request변수에 담아주고 있습니다.
- JSONSerialization
JSON객체를 Array 또는 Dictionary로, Array 또는 Dictionary를 JSON으로 바꿔주는 객체입니다. 이때 딕셔너리의 key값은 String이어야하고, object는 String, Number, Array, Dictionary 또는 Null중에 하나여야 합니다.
var request = URLRequest(url: url)
request.addValue(apiKey, forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
let json: [String: Any] = ["stationId": "TE서울테헤란"]
let jsonData = try? JSONSerialization.data(withJSONObject: json, options: [])
request.httpBody = jsonData
- URLSession.shared.dataTask()
이는 비동기적(async)으로 동작하는 함수입니다. 마지막 단계로 앞서 만들어둔 URLRequest 인스턴스를 전달해주면 되는데, 서버로부터 응답 데이터를 받아서 data객체를 가져오는 작업을 수행합니다.
- JSONDecoder
아까 위에서 APIResponse 모델을 만들때 Codable 프로토콜을 채택해줬었습니다. JSONDecoder는 이 Codable을 채택한 타입의 인스턴스들을 디코딩 하도록 도와줍니다. 덕분에 json형식이었던 data에서 원하는 자원에 접근할 수 있게 되는 것입니다.(jsonResult.reviewList에 접근할 수 있게되는것이 그 이유)
let task = URLSession.shared.dataTask(with: request) { [weak self] data, res, error in
guard let data = data, error == nil else { return }
print(String(data: data, encoding: .utf8))
do {
let jsonResult = try JSONDecoder().decode(DataClass.self, from: data)
DispatchQueue.main.async {
self?.reviewList = jsonResult.reviewList
self?.tableView.reloadData()
//print(String(data: data, encoding: .utf8))
}
} catch {
print(error)
}
}
task.resume()
+ datatask(비동기로 처리된다는 관점에서)와 main thread, io thread에 대해서 내일 이어서 학습하겠습니다.
confluence작성에 도움을 주었던 블로그입니다:)
https://deepwelloper.tistory.com/98
'iOS > iOS' 카테고리의 다른 글
bannaviiOS) webView에서 사진, 카메라 사용하기 (0) | 2022.11.01 |
---|---|
bannaviiOS) Layout Issues: Position and size are ambiguous for WKWebView - maskcheck. (0) | 2022.10.31 |
iOS) multipart/form-data 이해하기 (0) | 2022.01.25 |
iOS) splash screen, time out (feat.서버 마비) (0) | 2022.01.24 |
iOS) 사진앨범의 이미지들을 앨범에 가져올때 사용되는 함수 및 프레임워크 (0) | 2022.01.24 |