12월 16일부터 18일까지 42서울 내에서 온라인 해커톤이 열렸다.
주제는 42api, 라즈베리 파이를 이용하여 클러스터(교육장)에서 사용할 수 있는 앱을 만드는 것이었다.
무엇을 만들지?
"혹시 minishell 과제하셨나요?"
"아니요"
"아.. 알겠습니다. 감사합니다."
"혹시..." x 10000
-카뎃이라면 한 번쯤 겪어 보는 일
42 서울에서는 동료학습이 이루어진다. 과제를 할 때 주어진 것은 과제 요구사항뿐이고 해결하기 위해서는 구글, 동료 등 직접 방법을 찾아야 한다. 실제로 과제 도중 어떤 것이 해결되지 않거나, 개념 이해가 어려운 경우가 종종 있는데, 그럴 때 사람들은 같은 과제를 진행 중인 사람들과 함께 과제를 해나가기도 하고, 이미 그 과제를 해결한 사람에게 제대로 이해한 것이 맞는지, 어떻게 하는 것이 좋을지 물어보기도 한다. 그런데 초반 과제를 제외하고는 사람들의 진도가 제각각이라서 누가 어떤 과제를 진행했는지 쉽게 알 수가 없어서 위처럼 "혹시... 이 과제하셨나요?" 하는 것이 일상이다.
그래서 이런 문제를 해결해보고 싶어서 해커톤 아이디어로,
현재 해당 과제를 진행 중이거나, 완료한 사람들의 인트라와 좌석 정보를 표시해 주는 앱을 선택했다.
어떻게 만들지?
아이디어를 정했으니 다음은 구체적으로 어떤 기능을 넣고, 어떤 기술을 이용해야 할 지에 대해서 고민했다.
우리는 iOS에 대해서 경험이 (거의) 없었고, api를 사용하는 것에 대한 지식도 없는 상태였다.
서버를 하는 사람도 없었기 때문에, 라즈베리파이나 센서는 사용하지 않기로 했다.
그래서 아이패드 앱을 완성하는 것을 목표로 초반 기획을 잡았다.
앱 기능은 간단하다.
1. 메인 화면에서 과제를 선택한다.
2. 검색을 누른다.
3. 42 api를 이용해 현재 과제를 진행 중인/완료한 사람들의 정보를 받아온다.
4. 정보를 테이블 형태로 인트라 아이디와, 상태, 좌석을 띄워준다.
5. 우측 상단의 버튼을 클릭하면 현재 진행 중인 사람, 과제를 완료한 사람, 보너스 파트까지 완료한 사람으로 분류해서 목록을 띄워준다.
이 기능을 구현하기 위해 생각했던 초반 생각은
나와 njeong 님이 swift를 이용해서 기본 화면을 구현하고, hyulee가 42 api를 사용하는 법을 알아와서 각 코드를 합치는 것이었다.
근데 웬걸.. 역시 해커톤은 상상한 대로 돌아가지 않는 것 같다. 42 api 가 이렇게나.. 복병일 줄.. 몰랐지.. (+ 개발 실력)
애증의 42 api
42 api 사이트 : api.intra.42.fr/apidoc
실제로 개발과정에서 가장 많은 시간과, 품이 들어간 부분이다. 첫 번째로 42 api에 대한 사전 지식이 없었고, 두 번째로 iOS로 요청을 보내고 받을 줄 몰랐다. 실제로 3일간의 개발기간을 모두 42 api에 쏟았다고 해도 과언이 아닐 정도.
"The API is RESTful, uses JSON over HTTPS and lets you authenticate users with OAuth 2.0."
api 사이트 메인에 적힌 것처럼 42 API는 RESTful API고, JSON 형식으로 값을 리턴하고, OAuth 2.0. 방식으로 유저 인증을 한다.
처음에 저 문장을 읽으면서도 그래서 어떻게 해야 하는데? 싶었다. 42 API를 이용하려면 일단 내가 유효한 토큰을 가지고 있다는 것을 서버에 알려야 하는데 OAuth라는 것을 처음 들어서 어떤 방식으로 해야 하는지 헤매는 시간이 길었다. 그래서 진짜 이 사람 저 사람 붙잡고 그래서 어떻게 해야 되는 건데..? 하고 다녔다. 이 자리를 빌려 도와준 Jaejeon님께 다시 한번 무한한 감사를 드리고 싶다.
근데 지금 블로깅 하면서 다시 api 사이트 읽고 있는데, 진짜 다 알려준다. 여러분 기억하세요.. RTFM...
아무튼 다음에 시도해 볼 분들을 위해 간단하게 정리하면
OAuth 2.0. 은 유저 인증을 위한 표준 프로토콜이다. 외부 앱 (third-party)이 HTTP 서비스를 이용하여, 자원의 소유자와의 상호작용을 하게끔 한다. 이 방식을 사용해서 유저를 인증하고, 권한을 부여한다.
작동하는 방식은 간단(?)하다.
1. Client에서 Resource Owner(자원을 관리하는 사용자) 에게 요청을 보낸다.
2. Resource Owner는 Client에게 권한을 부여(authorization grant)한다.
3. Client는 받은 권한을 이용해서 Authorization Server(인증 서버)에 token을 요청한다.
4. Authorization Server에서는 권한이 유효한지 판단하고, 유효하다면 access token을 Client에게 보내준다.
5. Client는 받은 access 토큰을 이용하여 Resource server(자원이 올라가 있는 서버, ex. 42 api server)에 요청을 보낸다.
6. Resource server에서는 토큰이 유효한지 판단하고, 유효하다면 자원을 Client에게 보낸다.
요약하면 1 ~ 4의 과정을 거쳐 유효한 사용자임을 확인받고, 토큰이 만료될 때까지 5, 6을 반복해서 서버와 통신하는 방법이라고 할 수 있다.
42api 이용하기
42 api도 이 인증과정을 이용하기 때문에 api를 이용하는 순서는 위와 같다.
자세히 정리해보면.
1. 42 intra > setting > API에서 앱 정보를 등록(Register a new App)하고 Client ID와 Secret key를 발급받는다.
2. 등록을 위해서는 하단의 화면에서 필요한 정보를 채워 넣는다. (아무거나 넣어도 상관없다) redirect url의 경우 로그인을 할 때 이용이 되는데, 그 외에는 이용이 되지 않으니 google.com처럼 유효한 웹 페이지를 넣어놓으면 된다. 로그인이 필요한 경우 iOS에서는 ios custom url scheme를 이용하여 앱 자체만의 주소를 지정하고, 그 주소를 넣어준다.
3. 발급받은 id와 key를 이용하여 "https://api.intra.42.fr/oauth/token"에 POST 요청을 보낸다. 이때 grant_type을 지정해 준다.
grant_type의 경우 42 api를 이용하여 유저를 구분하는 것이 아닌, 정보만 요청할 시에는 client_credential 타입을 이용하면 된다. 로그인을 구현하고자 한다면 authorization code를 이용한다.
4. 요청을 보내고 response를 받아서 token을 받아와 준다. client_credential의 경우 json으로 access_token 값이 따로 받아지고, authorization code의 경우 반환되는 Url의 뒤에 code? {acces_token}이런 식으로 받아와 진다.
5. 각 타입에 맞게 토큰을 파싱 해주면, 토큰 생성이 완료된다.
3 - 5번에 해당하는 코드는 아래와 같다.
func getToken() {
let parameters = [
"grant_type": "client_credentials",
"client_id": self.appId,
"client_secret": self.appSecret]
let paramData = try! JSONSerialization.data(withJSONObject: parameters, options: [])
var request = URLRequest(url: self.apiTokenUrl!)
request.httpMethod = "POST"
request.httpBody = paramData
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue(String(paramData.count), forHTTPHeaderField: "Content-Length")
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data, error == nil else {
print("error=\(String(describing: error))")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(String(describing: response))")
}
let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: AnyObject]
self.token = json!["access_token"] as? String
}
task.resume()
print(self.token)
}
6. 받아온 토큰을 이용해서 42 api에 필요한 정보들을 요청한다. 요청을 보낼 때에는 HTTP Header에 token을 넣어주어야 한다.
7. 하고자 하는 것에 맞춰서 GET, POST 요청을 보낸다. (어차피 학생은 GET 요청밖에 보내지 못한다.)
8. 얻고자 하는 데이터에 맞춰서 url을 작성한다. (예시 : https://api.intra.42.fr/users/sunhpark - sunhpark이라는 id를 가진 user의 정보)
9. response를 받아서 이용하고자 하는 정보에 맞게 구조체를 만들어서 파싱 해 준다. (iOS의 경우)
* tip
- POSTMAN이라는 것을 이용하면 구현하기 전에 요청을 보냈을 때 어떤 값이 들어오는지를 보다 편하게 알 수 있다.
- quicktype.io라는 서비스가 있는데, json 형식을 swif에서 사용할 수 있는 구조체 형식으로 자동으로 변환해준다.
6 - 9의 코드는 아래와 같다.
(여기에서 sleep (1)을 썼는데.... 그러면 안된다... 사실 받아올 때까지 기다려줘야 하는데 비동기 처리하는 방법을 몰라서 그렇게 처리했다.)
func getUserName(project_id: Int) {
var info: [Project] = []
if self.token == nil {
print("error")
}
else {
var pageIndex = 1
repeat {
print("Project start")
print(pageIndex)
var url = "https://api.intra.42.fr/v2/projects/\(project_id)/projects_users?filter[campus]=29&page[number]=\(pageIndex)"
var request = URLRequest(url: URL(string: url)!)
request.httpMethod = "GET"
request.allHTTPHeaderFields = [
"Authorization": "Bearer \(self.token!)"
]
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data, error == nil else {
print("error")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(String(describing: response))")
}
info = try! JSONDecoder().decode([Project].self, from: data)
self.projectInfo.append(contentsOf: info)
}
task.resume()
pageIndex += 1
sleep(1)
} while (info.count != 0)
print("Project finish")
// 점수가 높은 순으로 프로젝트 유저 정렬
self.projectInfo.sort { (Project1, Project2) -> Bool in
return Project1.finalMark ?? 0 > Project2.finalMark ?? 0
}
}
}
앱 구현하기
앱 구현은 생각보다 깔끔하게 완성이 됐다.
과제 선택은 Picker로, 결과 화면은 table view를 이용해서 구현했고,
버튼을 눌렀을 때 서버에 요청을 보내고, 결과 화면으로 정보가 넘어가게끔 했다.
완성된 앱은 다음과 같다.
후기
사실 이거 하는 도중에 기말고사 봤다.. 전날이.. 우 테코 마지막 과제 제출일이었다... 그리고 해커톤 끝나고.. 시험 또 봤다....
그래도 참여한 것에는 후회가 없다. 최근 참여한 해커톤 등에서 완성을 못하는 경우가 좀 있었어서 이번에는 앱 구현만 하자! 너무 어렵게 가려고 하지 말자!라는 생각으로 참여했는데, 목표를 달성한 것 같아서 기분이 좋다. 코로나로 인해서 많이 지쳐있었고, 재밌을 일도 없어서 의욕이 없던 참이었는데, 좋은 동기 부여가 된 것 같다. 오랜만에 살아서? 스스로? 뭔가 하는 느낌
같이 한 동료들도 너무 좋았다. 각자 해결 방법을 열심히 찾아보고 시도하고, 무언가 됐을 때 함께 기뻐한 hyulee, njeong 님께도 감사를 표한다. iOS공부를 안 한지도 너무 오래됐고, 다른 지식도 없어서 힘들었지만 그래도 결국 해낸 나 자신도 칭찬.
물론 아쉬운 부분이 더 많은 것도 사실이다. 이걸 실제로 사용하기에는 더 손봐야 할 부분이 많다. (비동기라던가 비동기라던가 비동기라던.. 가..) 지금 다 sleep으로 잠재워 놨는데, 사실 response time이 제각각이라 그렇게 하면 도중에 받아오지 못하는 정보가 생기기도 하고, 기본적으로 불필요한 delay가 생겨버리는 것이라서 유저가 사용하기에도 불편하다. 생각보다 받아와야 하는 정보가 많고, 요청을 보내야 하는 횟수도 많아서 이런 부분을 수정하고 싶다. 첫 목표는 sleep을 없애고도 정상 동작하게 하는 것.. 두 번째는 정보를 받아오는 동안 유저에게 로딩 중임을 보여주는 것.. 종강했으니 42 과제부터 좀 하면서 천천히 손 봐야겠다.
그래도 많은 것을 배웠고, 즐길 수 있었던 해커톤이었다. 마지막에 다른 사람들 것도 구경하는데 다들 너무 잘하셔서 놀랐다.
다음 해커톤이 열리면 더 재밌는 아이디어로, 더 잘해봐야지.
Git Hub 링크
'42SEOUL' 카테고리의 다른 글
[42Seoul] IOT 전문가 박진현님을 만나다 (0) | 2020.07.21 |
---|