서문

2023.08.15 인프콘이 열렸다.

1년에 한번 밖에 없는데다가, 추첨이라서 가기도 힘든 기회인데 운좋게 당천되서 참가하게됐다.

최대한 즐기고 배우기 위해 여러글을 찾아봤었고, 나만의 간단후기와 함께 팁을 써보려고 한다.

처음 컨퍼런스 갔거나, 인프콘에 대해 궁금하면 많은 도움이 될 것이다.

 

1. 사전준비

이미 티켓팅은 마쳤다는 전제로 글을 작성할 예정이다

인프콘은 매년 8월에 진행하고, 인프런에서 신청 가능하다. 하지만 추첨제이니, 신청하고 기도하는게 최선..

하지만 다시보기는 할 수 있으니 너무 걱정하지 마시길

 

사전 준비는 별로 할게 없지만, 두개는 꼭하면 좋을 듯하다.

 

1. 시간표 짜기

시간표는 보통 인프런 사이트에서 세션(프로그램) 소개하는 곳에서 함께 설정할 수 있다.

막상 가서 어떤 프로그램을 들을지 정하려면 힘드니, 어떤 세션을 들을지 미리 확인해보는게 좋다.

한개의 Time Cell에 여러 세션이 진행되니, 자신이 가장 듣고 싶은 세션을 미리 지정해두고 시간표를 만들어두자.

흔한 백엔드 개발자의 시간표

 

2. 부스 확인하기

크게 시간은 안들여도 되고, 간단하게 어떤 기업들이 참여하는지만 살펴보자. (함께하는 기업들에 있다.)

어짜피 가서 다 볼거지만, 미리 알아둬서 나쁠 건 없다고 생각한다.

보통 시간표나 부스는 인프콘 홈페이지에서 확인 가능하다.

 

 

2. 인프콘 당일

 

입장할때 받는 굿즈

당일에도 도착하기 전에는 준비할것은 따로 없다.

세션을 들으면서 필기할 노트나 노트북과 가방만 있으면 된다. (굿즈 담을 가방은 현장에서 준다.)

해당 장소 (2023은 코엑스) 가서, 문자로 받은 QR코드를 보여주고, 입장 굿즈를 받아서 들어가면된다.

보통 시간에 맞춰서 가면 줄도 별로 없고 바로 입장가능한 것 같다.

 

이제 할일은 3가지 정도가 있다. (선택)

 

1. 부스 탐방

컨퍼런스를 후원해주는 기업들이 부스를 열고 굿즈나 이벤트를 하며 참여자들과 소통하는 곳이다.

대부분의 유명한 부스들은 줄서는 시간이 대부분(5분~ 20분정도)이다.

부스들의 목적은 많은 개발자들을 자신들의 인재풀에 등록하고 네트워킹하기 위함이다.

덤으로 회사 소개도 하고 네트워킹 및 자사 서비스 소개도 가능하다.

부스에가면 굿즈도 주고, 안가면 여러모로 손해본다고 생각해서 꼭 한번 다 돌아보는 것을 추천한다.

 

2. 세션 듣기

미리 정리해온 시간표를 보며 알맞는 강연실에 가면 된다.

인기 세션은 사람들이 많아서 앉을 자리가 없거나, 못들어가게 할 수 있으니 5분전에 미리 도착하는게 좋은 것 같다.

그리고 세션을 들으며 필기할 준비도 하면 정말 금상첨화이다.

세션의 퀄리티나 내용은 호불호나 케바케가 많아서 따로 말할건 없지만, 웬만해서는 관심있으면 다 들어보는게 좋은 것 같다.

그리고 세션이 끝나면 따로 발표자에게 질문을 할 수 있는 공간이나 시간이 있으니 너무 걱정하지는 말자.

 

3. 네트워킹

15시 40분 부터는 따로 네트워킹 시간이 있다.

내향적이거나, 별로 할것도 없을 것 같아서 많이 안가는 분들이 많겠지만, 은근 숨겨진 보석인 것 같다.

다양한 개발자들과 기업 채용담당자나 실무자들과 대화도 해볼 수 있는 기회이다.

내향형이라도 가면 열심히 매칭시켜주고 대화를 할 수 있도록 도와주니 걱정하지 않아도 될듯

 

3. 2023 인프콘 후기

여러모로 정말 많이 배우고, 느낀것이 많았던 시간이다. 여러 개발지식 + 네트워크를 얻을 수 있는 좋은 기회였다.

다들 비싼돈 주고 컨퍼런스를 가는지 이해가 됐다. 다음에도 좋은 기회가 있으면 꼭 참여해야겠다.

 

마지막으로 인프런에서 정리한 인프콘2023 정리글을 소개하며 마무리하겠다.

https://www.inflearn.com/pages/infcon-2023-report

 

숫자로 돌아보는 인프콘 2023 - 인프런 | 스토리

뜨거웠던 IT인의 축제, 인프콘 2023! 함께해 주신 많은 분의 열정 덕분에 두 번째 인프콘도 성황리에 마무리할 수 있었습니다. 인프콘 2023에는 어떤 직무의 참가자가 가장 많았을지, 가장 많은 참

www.inflearn.com

https://www.inflearn.com/pages/infcon-2023-sketch

 

벌써 두 번째 개최, 인프콘 2023 현장 스케치 - 인프런 | 스토리

인프런의 오프라인 IT 콘퍼런스, 인프콘 2023이 8월 15일 코엑스 그랜드볼룸과 아셈볼룸에서 개최되었어요. 무려 8,700 여명이 참가 신청을 해주신 가운데, 추첨을 통해 선발된 참가자 및 세션 발표

www.inflearn.com

 

늦은 감이 있지만, 이번 2023 토스 NEXT 개발 챌린지 후기를 남긴다. 

Sever 직군 기준이니 참고바란다.

 

아마 토스 NEXT 관련 준비하는데, 도움이 될수도..?

 

1. 코딩 테스트

 

코딩테스트는 엄청 어려운 정도는 아니였다.

알고리즘 문제 4~6개 정도, 서술형 문제 4~7개 정도가 나왔었다. (정확한 갯수는 기억이 잘...)

 

알고리즘 문제

알고리즘 문제는 엄청 어려운 수준은 아니고 평소에 알고리즘을 풀어봤다면 느낌이 오는 수준이었다.

프로그래머스 Lv2 정도 난이도 인데, 문제 유형은 다양하게 나왔다. (NP, BFS, Backtracking 등)

 

사실상 인터넷을 접속할 수 있게 해주어서, 정말 안풀리는 사람들은 검색찬스를 썼을지도 모르겠다.

요즘 ChatGPT에게 물어보면 다 알려준다고 들었어서, 복붙해볼까 생각은 해봤지만, 역시나 문제는 그림도 있고, 글은 복사가 안되게 설정되있었다.

 

서술형 문제

사실상 고비였던 서술형 문제. 사실 이 부분 답변을 제대로 못해서 떨어졌을거라 생각했을 정도.

기본 CS보다는 진짜 최적화, 기능 향상, 문제상황 파악 및 문제해결 느낌으로 나왔다.

관련 지식을 접해보지 못했다면 손도 못댈수준 정도였다. 그래도 어찌어찌 3~4문제 정도 답변을 써서 제출했다.

이부분은 맥락이나 사전지식이 없으면 검색자체를 못하기 때문에, 여기서 변별력을 내려고 하는게 보였다.

 

그리고 사실상 서술형을 잘 못봐서 포기하고 있었는데, 코테 합격소식이 들려왔다.

소식 이후 1.5주 뒤에 1차면접이 잡혔다. (나는 늦게 면접을 본 편) 대부분 1~2주 안에 잡히는 것 같았다.

 

2. 포트폴리오  제출

그다음 할일은 포트폴리오 제출이다. 이부분은 미리 준비해두면 좋을 듯하다. 기간이 5~7일 정도 기간을 줬던 것 같다.

포트폴리오로 합불을 정하는게 아니라, 면접때 볼 소스를 미리 확인하려고 하는 것 같다.

그렇다고 대충내도 되는게 아니라, 면접을 생각해서 정말 자신있는 항목들만 쓰는것을 강추! (잘 모르는 것들을 내면 나중에 후회한다.)

 

이력서 참고한 사이트들

1. https://famelee.oopy.io/

 

FameLeeㅣ말에 힘이 있는 창업가

새로운 패러다임을 만들고 싶은 창업가입니다. 말에 힘이 있는 사람이 되기 위해 하루하루, 성장을 갈망합니다.

famelee.oopy.io

2. https://techblog.woowahan.com/2531/

 

이직초보 어느 개발자의 이력서 만들기 | 우아한형제들 기술블로그

{{item.name}} 안녕하세요 저는 올해 2월부터 우아한형제들의 배라개발팀에서 일하고 있는 구인본입니다. 작년 연말에 잠시 휴식을 가진 후 1월부터 이직을 준비하면서 경험했던 것 중에 이력서를

techblog.woowahan.com

3. https://wonny.space/writing/work/engineer-resume

 

개발자 이력서 작성하기 (feat. 이력서 공개) | Wonny Log

이력서 작성 시 참고하면 좋을 정보와 체크리스트 공유 | 2022년 4월 8일 업데이트 안녕하세요, 워니입니다. 이력서 작성 방법에 대한 글을 올린 후, 수많은 강의 제안과 이력서 …

wonny.space

4. 내 포트폴리오 : https://www.notion.so/lyva/30bcb036bcc443dc81eadeabe829d2bc?pvs=4

3. 1차 면접

면접 준비

1차면접 발표가 나자마나 일단 카카오톡 오픈톡방을 찾아들어갔다.

보통 대기업, 유니콘에서 하는 공채는 오픈톡방이 만들어져있고, 거기서 정보공유도 하기때문이다.

그곳에서 미리 면접본 사람들의 Tip이나 정보를 조금은 주워들을 수 있다. 합격하면 공유도 가능!

 

그다음으로는 server직군에 맞는 CS관련 공부를 했다. (오랜만에 하니까 꽤나 어려웠다.)

CS는 유명한 질문들을 모아둔 github을 보며 공부했고, 개인적으로 대규모 트래픽 처리와 관련된 책을 읽었다.

 

참고한 깃헙 주소들 : 

https://rokrokss.com/post/2019/04/06/%EB%A9%B4%EC%A0%91-%EC%A4%80%EB%B9%84-%EC%A0%84%EC%82%B0-%EC%88%98%EC%97%85-%EC%B4%9D%EC%A0%95%EB%A6%AC.html#ds-and-algo

 

ROKROKSS

 

rokrokss.com

https://gyoogle.dev/blog/

 

👨🏻‍💻 Tech Interview

최종 수정 : 12/17/2022, 7:23:59 AM

gyoogle.dev

 

면접 후기

그리고 대망의 면접날

면접은 1시간 30분 정도 Google Meet로 진행됐다.

 

면바면이니 참고만 하도록 하자.

진행은 말하기 어려우니.. 간단하게 말하면 트래픽 처리 및 서버 문제나 오류가 있을때 어떻게 대처할지 물어봤다.

그리고 포트폴리오에서 세세하게 물어보면서 3~4번 꼬리질문을 받는데, 정말 생각하기 힘든 질문도 받았다.

그럴때, 정말 토스 개발자들은 이정도까지 생각하고 고려하면서 개발하구나..를 떠올렸다.

 

예상못했던 질문들이 많아서 그런지.. 생각보다 답변을 제대로 못했다.

그래도 면접관님들은 나를 편하게 해주려고 하고, 해서 끝까지 집중해서 했었다.

마지막 10분은 준비해왔던 역질문을 하고 회의실을 나왔다.

 

면접이 끝나고 복기하면서 되돌아보는데, 씁쓸했다.

아무리 면바면이라고 하지만.. 나라도 나를 안뽑을 것 같았다. 

그리고 다음날까지 전화가 오지 않으며 결국 탈락 메일을 받았다. (합격한 사람들은 보통 1~2일내에 전화로 합격통보를 받는다고 한다.)

 

4. 후기

그래도 토스에서는 어떻게 일하고, 어떤 가치나 기술들을 중요하게 여기는지 알 수 있는 좋은 기회였고, 면접을 준비하면서 새로운 것들도 배우고, 잘 몰랐던 것들도 다시 공부할 수 있어서 좋았다. 앞으로 또 어떤 기회가 있을지는 모르겠지만, 자주 면접보는 것도 좋은듯.

출처 : https://blog.hexabrain.net/202

생각보다 자주쓰이는데 까먹어서, 계속 찾아보게 되는 정규표현식을 정리해보았다.

1. 메타문자 (원래 의미가 아닌 새로운 의미)

1-1. [ ] 문자 클래스

  • 문자 클래스(character class)인 [ ].  "[ ] 사이의 문자들과 매치"라는 의미를 갖는다.
[abc] # # abc 중 하나와 매치
  • [ ] 안에 - 를 사용하면 두 문자 사이의 범위를 뜻한다.
[a-zA-Z] : 알파벳 모두
[0-9] : 숫자
  • [자주 사용하는 문자 클래스]
    • \\d - 숫자와 매치, [0-9]와 동일한 표현식이다.
    • \\D - 숫자가 아닌 것과 매치, [^0-9]와 동일한 표현식이다.
    • \\w - 문자+숫자(alphanumeric)와 매치, [a-zA-Z0-9_]와 동일한 표현식이다.
    • \\W - 문자+숫자(alphanumeric)가 아닌 문자와 매치, [^a-zA-Z0-9_]와 동일한 표현식이다.
    • \\s - whitespace 문자와 매치, [ \\t\\n\\r\\f\\v]와 동일한 표현식이다. 맨 앞의 빈 칸은 공백문자(space)를 의미한다.
    • \\S - whitespace 문자가 아닌 것과 매치, [^ \\t\\n\\r\\f\\v]와 동일한 표현식이다.
  • [ ] 안의 ^는 Not을 뜻한다.
[^0-9] # 숫자를 제외한 문자만 매치
[^abc] # a, b, c를 제외한 모든 문자와 매치

1-2. 모든 문자 .

  • 정규 표현식의 Dot(.) 메타 문자는 줄바꿈 문자인 \\n을 제외한 모든 문자와 매치됨을 의미
a.b # a + 모든 문자 + b를 뜻한다.
  • 원래 . 으로 사용하고 싶으면 [ ] 에 넣으면 된다.
a[.]b #a.b만 가능

1-3. 반복문자 *

  • 0~무한대 반복은 * 을 이용하면 된다.
lo*l # looooool도 매치됨
  • 1~무한대 반복은 +를 이용하면 된다.
lo+l # loool매치 but, ll는 매치안됨
  • 0~1 반복은 ?을 이용하면 된다.
lo?l #ll, lol매치 but loool은 매치 안됨
  • 반복 횟수 지정은 { } 를 이용하면 된다. - {m,n}은 m번에서 n번 반복까지 허용
lo{3,5}l # loool, looool, loooool만 매치

1-4. ETC

  • |은 or과 같은 의미이다. → 둘 중하나와 매치
a|b|c #a, b, c 중아무거나 다 매치 abc도 가능
  • 문자열의 처음과 매치하기 ^
^a # a로 시작하는 문자열만 매치함

@ [^]와는 다른 의미임

  • 문자열의 제일 마지막과 매치 $
a$ #마지막 글자가 a여야 매치

 

# 조건표현식

  • 표현식1(?=표현식2): 표현식1 뒤의 문자열이 표현식2와 매치되면 표현식1 매치.

'hello(?=world)' # hello 뒤에 world가 있으면 hello를 매치

  • 표현식1(?!표현식2): 표현식1 뒤의 문자열이 표현식2와 매치되지 않으면 표현식1 매치.

'hello(?!world)' # hello 뒤에 world가 없으면 hello를 매치

  • (?<=표현식1)표현식2: 표현식2 앞의 문자열이 표현식1과 매치되면 표현식2 매치.

'(?<=hello)world' # world 앞에 hello가 있으면 world를 매치

  • (?<!표현식1)표현식2: 표현식2 앞의 문자열이 표현식1과 매치되지 않으면 표현식2 매치.

'(?<!hello)world' # world 앞에 hello가 없으면 world를 매치


1-5. 정규표현식 모듈

Python 에서는 re 모듈을 통해 정규표현식을 사용한다.

import re

compile 정규표현식 컴파일

re.compile() 명령을 통해 정규표현식을 컴파일하여 변수에 저장한 후 사용할 수 있다.

변수이름 = re.compile('정규표현식')

정규표현식을 컴파일하여 변수에 할당한 후 타입을 확인해보면 _sre.SRE_Pattern 이라는 이름의 클래스 객체인 것을 볼 수 있다.

p = re.compile('[abc]')
print(type(p))

1-6. 정규표현식 매치 검색 함수 4가지

  1. Match : 시작부터 일치하는 패턴 찾기
p.match('aaaaa')
<_sre.SRE_Match object; span=(0, 5), match='aaaaa'>

p.match('bbbbbbbbb')
<_sre.SRE_Match object; span=(0, 9), match='bbbbbbbbb'>

p.match('1aaaa')
None

p.match('aaa1aaa')
<_sre.SRE_Match object; span=(0, 3), match='aaa'>
  1. Search : 전체 문자열에서 첫번째 매치 찾기
p.search('aaaaa')
<_sre.SRE_Match object; span=(0, 5), match='aaaaa'>

p.search('11aaaa')
<_sre.SRE_Match object; span=(2, 6), match='aaaa'>

p.search('aaa11aaa')
<_sre.SRE_Match object; span=(0, 3), match='aaa'>

p.search('1aaa11aaa1')
<_sre.SRE_Match object; span=(1, 4), match='aaa'>
  1. Findall : 모든 매치를 찾아 리스트로 변환
p.findall('aaa')
['aaa']

p.findall('11aaa')
['aaa']

p.findall('1a1a1a1a1a')
['a', 'a', 'a', 'a', 'a']

p.findall('1aa1aaa1a1aa1aaa')
['aa', 'aaa', 'a', 'aa', 'aaa']
  1. Finditer : 모든 매치를 찾아 반복가능 객체로 반환
p.finditer('a1bb1ccc')
<callable_iterator object at 0x7f850c4285f8>

f_iter = p.finditer('a1bb1ccc')
for i in f_iter:
    print(i)

1-7. 매치 객체의 메서드

패턴 객체의 메서드를 통해 리턴된 매치 객체는 아래와 같은 정보를 담고 있다.

<_sre.SRE_Match object; span=(매치 시작지점 인덱스, 매치 끝지점 인덱스), match='매치된 문자열'>

매치 객체는 내부 정보에 접근할 수 있는 네 가지 메서드를 제공한다.

group() 매치된 문자열 출력
start() 매치 시작지점 인덱스 출력
end() 매치 끝지점 인덱스 출력
span() (start(), end())를 튜플로 출력
p = re.compile('[a-z]+')
result = p.search('1aaa11aaa1')
print(result)

위의 코드를 실행하면 아래의 매치 오브젝트를 얻는다.

<_sre.SRE_Match object; span=(1, 4), match='aaa'>

매치 객체의 메서드를 실행한 결과는 아래와 같다.

result.group()
aaa

result.start()
1

result.end()
4

result.span()
(1, 4)

1-8. 그룹화

정규표현식을 () 안에 넣으면 그 부분만 그룹화된다. groups 메서드를 통해 그룹들을 튜플 형태로 리턴 할 수 있다.

p = re.search('(hello)(world)', 'helloworld') # 정규표현식 hello와 world의 매치 결과를 각각 그룹화하였다
grouping = p.groups()
print(grouping)

('hello', 'world') # 각 그룹의 매치 결과가 튜플로 묶여서 리턴됨

group 메서드를 통해 각 그룹을 호출할 수 있다.

p.group() # 인자를 넣지 않으면 전체 매치 결과 리턴
helloworld

p.group(0) # group()와 같다
helloworld

p.group(1) # 1번 그룹 매치 결과 리턴
hello

p.group(2) # 2번 그룹 매치 결과 리턴
world

1-9. 컴파일 옵션

정규표현식을 컴파일 할 때 옵션을 지정해줄 수 있다.

변수이름 = re.compile('정규표현식', re.옵션)


DOTALL, S

.은 줄바꿈 문자 \\n 를 제외한 모든 것과 매치된다. 컴파일 할 때 re.DOTALL 또는 re.S 옵션을 넣어주면 \\n 까지 매치되도록 할 수 있다.

p = re.compile('.') # 옵션 없음
result = p.findall('1a\\nbc')
print(result)
['1', 'a', 'b', 'c'] # \\n이 매치되지 않음

p = re.compile('.', re.DOTALL) # re.DOTALL 옵션 추가
result = p.findall('1a\\nbc')
print(result)
['1', 'a', '\\n', 'b', 'c'] # \\n까지 매치


IGNORECASE, I

re.IGNORECASE 또는 re.I 옵션을 넣어주면 대소문자를 구별하지 않고 매치된다.

p = re.compile('[a-z]') # 소문자만 매치
result = p.findall('aAbB')
print(result)
['a', 'b']

p = re.compile('[a-z]', re.IGNORECASE) # re.IGNORECASE 옵션 추가
result = p.findall('aAbB')
print(result)
['a', 'A', 'b', 'B'] # 소문자와 대문자 모두 매치


MULTILINE, M

re.MULTILINE 또는 re.M 옵션을 넣어주면 여러 줄의 문자열에 ^ 와 $ 를 적용할 수 있다.

text = '''student-1-name: James
student-2-name: John
student-3-name: Jordan
teacher-1-name: Mike
student-5-name: John'''
p = re.compile('^student.*') # 뒤따라 오는 문자 종류와 개수에 상관없이 student로 시작하는 문자열 매치 
result = p.findall(text)
print(result)
['student-1-name: James'] # 첫 줄만 매치되었다.

p = re.compile('^student.*', re.MULTILINE) # re.MULTILINE 옵션 추가
result = p.findall(text)
print(result)
['student-1-name: James', 'student-2-name: John', 'student-3-name: Jordan', 'student-5-name: John']
# student로 시작하는 모든 줄이 매치되었다.

p = re.compile('.*John$') # John으로 끝나는 문자열 매치
result = p.findall(text)
print(result)
['student-5-name: John'] # 가장 마지막 줄만 매치되었다

p = re.compile('.*John$', re.MULTILINE) # re.MULTILINE 옵션 추가
result = p.findall(text)
print(result)
['student-2-name: John', 'student-5-name: John'] # John으로 끝나는 모든 줄이 매치됨


VERBOSE, X

re.VERBOSE 또는 re.X 옵션을 주면 좀 더 가독성 좋게 정규표현식을 작성할 수 있게 된다. 아래의 두 표현식은 동일하게 작동한다.

p = re.compile(r'&[#](0[0-7]+|[0-9]+|x[0-9a-fA-F]+);')
p = re.compile(r"""
 &[#]                # Start of a numeric entity reference
 (
     0[0-7]+         # Octal form
   | [0-9]+          # Decimal form
   | x[0-9a-fA-F]+   # Hexadecimal form
 )
 ;                   # Trailing semicolon
""", re.VERBOSE) # re.VERBOSE 옵션 추가

re.VERBOSE 옵션을 추가하면 정규표현식에 컴파일시 자동으로 제거되는 공백과 코멘트를 추가할 수 있게된다.(단, [] 안에 입력된 공백문자 제외)

정규식에서 /를 쓰려면

실제 코드에서는 ////를 써주면 된다

 

 


참고 :

[Python 문법] 정규표현식 (Regular Expressions)

점프 투 파이썬

1. 개요

1-1. 이 글을 정리한 이유 ⭐️

  • 현재도 코드리뷰를 하고 있지만, 매뉴얼이나 규칙이 있었으면 하는 마음으로 만들었습니다.
  • 분명 시간도 추가적으로 들고 귀찮을 수도 있겠지만, 좋은 코드를 만들었으면 합니다.
  • 남들 따라하는 것이 아닌, 우리만의 코드리뷰 문화를 이야기해보고 결정하여, 정착시켰으면 합니다.

1-2. 코드리뷰를 하면 뭐가 좋을까?

  1. 자신은 알고 있는 부분을 타인에게 설명하면서 다시 생각을 정립할 수 있습니다. (자신 실력 상승)
  2. 타인에게 설명하기 위해 작성한 코드를 한번 더 보게 되어 스스로 확인하게 됩니다. (셀프 피드백)
  3. 코드를 짤 때 최대한 이해하기 쉽게 짜게 된다. (가독성 좋고, 좋은 코드 작성 가능성 상승)
  4. 자신이 개발한 부분만 아는 것이 아니라, 동료들의 부분을 좀 더 알게 되어 전반적인 서비스 로직의 이해도가 올라간다. (서비스 이해도 향상)
  5. Code Convention(패턴)을 확실하게 통일할 수 있다. (통일성)
  6. 자신이 놓친 실수를 피드백하고 도와줄 수 있다. ( print문 실수, 오타 등 실수캐치 )
  7. 동료의 코드를 보면서 새로운 스타일이나 깨달음을 얻을 수 있습니다. (발전)

즉, 코드리뷰는 단순히 버그를 사전에 발견하거나 문제점을 찾는 목적을 넘어서
전체적인 조직의 역량을 강화하는 중요한 역할을 한다.


2. 코드 리뷰 매뉴얼

2-1. 기본 규칙

  1. 서로의 잘못이나 오류를 지적하려는 의도가 아니라 피드백을 해주는 것이다. ( 공격적인 말투는 절대 X )
  2. 코드엔 정답이 없기때문에, 무조건 누가 맞는게 아니라, 함께 발전시키는 것이다.
  3. 질문을 하거나 대답을 할 때는 상대방이 상황을 전혀 모르다고 가정하고 충분한 문맥을 전달해야 한다.
  4. 당장 운영에 문제가 있는 hotfix건과 주석 제거와 같이 로직에 큰 변화가 없는 건은 스킵한다.
  5. 작은 작업 및 기능 단위로 PR(Pull Request)를 올리고, Merge 전에 코드리뷰를 진행한다.

2-2. 코드리뷰 규칙 ⭐️⭐️

PR남기는 사람

  • PR 본문은 PR 템플릿(Pull Request Template)에 따라 일관된 형태로 남긴다.
  • PR에는 최소 2명의 리뷰 담당자를 지정한다.

리뷰어

  • 리뷰 담당자는 가벼운 마음으로 아래 역할을 수행한다.
    1. 오타 찾기 ( print문, 오타, convention오류 등 )
    2. 모르는 것 질문하기 ( 변수명, 비즈니스 로직 등 )
      • 질문받은 사람은 이를 설명하면서 다시 복기할 수 있고 질문한 사람은 답변을 보고 로직에 관한 지식을 얻는 게 목적
    3. 새로운 대안 제시하기 ( 이건 A 목적인 건가요? A 목적이 맞는다면 이렇게 해보면 어떨까요?)
    4. 되도록 읽는 사람이 한 번에 정보를 확인할 수 있도록 한다. ( 가독성 생각하기 )
    5. 급한 우선순위 작업이 아니라면, PR을 틈틈히 Review하자 ( 당일 내로 처리하는 것 추천 )
    6. 리뷰할 것이 없으면, 칭찬남기기. → 모든 리뷰는 댓글에 하면된다.
    7. 만약 너무 바쁜 상황이라, Reivew를 하지 못할 상황이라면 다른 사람을 리뷰어로 지정해달라고 말하자.

2-3 Pull Request Template

제목

PR의 제목에는 가장 뒤에 [D-??] (??에 숫자 넣기)

언제까지 리뷰를 요청하는지 적기. (우선순위 파악 목적)

만약 오늘까지 리뷰를 해줬으면 하면, D-0이렇게 추가

Ex) D-1 이벤트 페이지 구현완료

본문

### 작업 분류
- [ ] 버그 수정
- [ ] 신규 기능
- [ ] 기존 기능 수정

### 기능 기획 
	<!--
		왜 이 기능을 추가했는지 간단하게 적기
		ex) 고양이가 울지 않으니 고양이 같지 않아서, 고양이 야옹소리 기능 추가
	-->

### 작업 개요 ( 대분류 ) 
	<!--
		ex) 고양이가 야옹 소리를 내도록 수정
	-->

### 작업 상세 내용 ( 상세 분류 )
	<!--
	  ex)
	  1. 네 발 짐승 클래스에 `크앙` 함수 추가
	  2. 고양이 클래스에서 `크앙` 함수에 `미야아옹.wav` 재생시킴
	-->

### 테스트 내역
	<!--
	  ex)
	  1. 페이징이 잘 처리되는지 확인
	  2. 특정한 값을 넣었을때 제대로 처리하는지 확인
	-->

아래 링크를 참고하면 PR Template 생성 가능 (Repository 관리자만 가능 - Bitbucket 관련)

Save time with default pull request descriptions - Bitbucket

리뷰어 선정

이건 각자 팀에 맞게 알아서 설정


2-4. 리뷰 예시

리뷰 내용 가장 앞에 Pn을 입력하면 좋을 것 같습니다. ( 코멘트를 강조하는 수준 표현 )

  • P1: 꼭 반영해주세요 (Request changes)

리뷰어는 PR의 내용이 서비스에 중대한 오류를 발생할 수 있는 가능성을 잠재하고 있는 등 중대한 코드 수정이 반드시 필요하다고 판단되는 경우, P1 태그를 통해 리뷰 요청자에게 수정을 요청합니다. 리뷰 요청자는 p1 태그에 대해 리뷰어의 요청을 반영하거나, 반영할 수 없는 합리적인 의견을 통해 리뷰어를 설득할 수 있어야 합니다.

  • P2: 웬만하면 반영해 주세요 (Comment)

작성자는 P2에 대해 수용하거나 만약 수용할 수 없는 상황이라면 반영할 수 없는 이유를 들어 설명하거나 다음에 반영할 계획을 명시적으로(JIRA 티켓 등으로) 표현할 것을 권장합니다. Request changes 가 아닌 Comment 와 함께 사용됩니다.

  • P3: 그냥 사소한 의견입니다 (Approve)

작성자는 P3에 대해 아무런 의견을 달지 않고 무시해도 괜찮습니다.


3. 마무리

더 좋은 코드와 서비스를 만들기 위해서는 좋은 개발문화가 있어야 합니다.

현실에 타협하지 않고 더 좋은 서비스를 만들기 위해 코드리뷰 문화가 더 잘 정착하면 좋다.

억지로 하는 것은 문화가 아닙니다. 우리가 왜 하는지 알고, 함께 자발적으로 만들어나가는 것이 문화라고 생각한다.

코드리뷰 문화도 정답이 있는 것이 아니라 팀의 특성에 따라 잘맞는 방법이 다르다.

제가 쓴 것이 정답이 아닌, 함께 이야기해보고 자신의 팀에 가장 잘 맞는 코드리뷰 문화를 만들면 좋겠습니다.

 


참고자료 : 

코드리뷰가 쏘아올린 작은공 | 우아한형제들 기술블로그

코드 리뷰 | TechWell

코드 리뷰의 목적은 성장이어야 한다

주니어 개발자들의 (얕은) 코드리뷰 도입기

코드 리뷰 in 뱅크샐러드 개발 문화 | 뱅크샐러드

이번에는 주로 작은 서비스에서 자주 쓰이는 EventBus에 대해 알아볼 것이다. 조금 규모가 커지면, 보통 Vuex를 사용하는 경향이 있다. 그래도 알아두면 나쁠것은 없으니 알아보자.

 

  1. 부모/자식 컴포넌트 사이에서 호출하기 위한 설정 -> 부모컴포넌트와 자식컴포넌트 사이에는 특별한 호출방식이 있다.
  2. 모든 컴포넌트에 적용할 데이터/메소드 설정 -> 전역변수로 생각하면 편하다.
  3. 전혀 관계없는 컴포넌트 사이에서 호출하기 위한 설정 -> 아무관계도 아니지만, 전역으로 할 필요는 없는 경우

3. 전혀 관계없는 컴포넌트 간의 호출 - EventBus

일반적으로 메소드, 변수를 정의할 때 한 오브젝트나 컴포넌트 단위로 묶어서 사용되기 때문에 이벤트를 사용할 필요가 없이 현재 위치에 포함된 메소드/변수를 호출하여 사용할 수 있습니다. 하지만 각각 분리되어 있는 개체에 전송하거나 알려줘야한다면 어떻게 해야할까요?

 

이럴때 공통으로 데이터들을 주고 받을 수 있는 공간을 만들고, 이를 통해서 서로 규격에 맞춰 데이터들을 주고 받으면 될 것 입니다. 이벤트를 등록하고 받을 준비가 끝났다면 언제 어디서든지 데이터들을 주고 받고, 각 이벤트요청 상황에 따라 원하는 메소드들을 수행할 수 있게 만든 것이 EventBus입니다. 쉽게 말해 컴포넌트간 메소드들을 서로 호출하게 해주는 것입니다. 아무 관계가 없는 컴포넌트 사이에서도 가능합니다.

 

vue.js에서 이벤트를 쉽게 다루기 위해 EventBus라는 개념을 이용할 수 있으며, 누구나 쉽게 사용할 수 있습니다.

// 이벤트버스 생성
var EventBus = new Vue()
// 이벤트 발행
EventBus.$emit('message', 'hello world');
// 이벤트 구독
EventBus.$on('message', function(text) {
    console.log(text);
});

자, 이렇게 하여 vue.js에서 이벤트를 쉽게 활용할 수 있습니다. 위와 같이 구현해놓는다면 컴포넌트, Vueapp이 전혀 다르더라도 서로 쉽게 호출할 수 있습니다.

 

 

이렇게 Vue에서 데이터/메소드를 호출/교환하는 방법에 대해 알아봤습니다. 저도 공부를 하며 정리한 것이라 틀린내용이 있을 수 있습니다. 또한 제가 모르는 것들이 더 많을 수도 있습니다. 그럴땐 꼭 피드백 부탁드립니다. 감사합니다.

 

 

 

참고 블로그 : 

https://vuejs-kr.github.io/jekyll/update/2017/02/13/vuejs-eventbus/

https://song8420.tistory.com/379

저번에 이어서 이번에는 전역적으로 사용할 수 있는 방법에 대해 알아보자.

 

  1. 부모/자식 컴포넌트 사이에서 호출하기 위한 설정 -> 부모컴포넌트와 자식컴포넌트 사이에는 특별한 호출방식이 있다.
  2. 모든 컴포넌트에 적용할 데이터/메소드 설정 -> 전역변수로 생각하면 편하다.
  3. 전혀 관계없는 컴포넌트 사이에서 호출하기 위한 설정 -> 아무관계도 아니지만, 전역으로 할 필요는 없는 경우

 

2. 모든 컴포넌트에 적용하기 - Vuex Store

2-1. Vuex란?

Vuex는 Vue.js의 상태관리 라이브러리로 애플리케이션의 모든 컴포넌트에 대한 중앙 집중식 저장소 역할을 하며 의도적인 방법으로 상태를 변경 및 관리할 수 있다. React의 Redux와 비슷하다고 보면 된다.

 

컴포넌트는 일반적으로 부모-자식의 관계를 가지고 props와 event를 통해 서로의 데이터를 주고받는다고 하였다.

하지만 Vuex는 말 그대로 중앙 집중식 저장소 이기 때문에 props와 event에 얽매이지 않아도 된다.

컴포넌트의 구조가 복잡한 경우에는 props와 event를 통한 데이터 전달보다는 Vuex를 통해 별도의 저장소에서 데이터를 관리하는 것이 올바르다. 쉽게말해서 전역변수로 지정하면, 어디던지 다 쓸 수 있다는 말.

 

2-2. Vuex 구조

Vuex는 state, mutations, action, getters 4가지 형태로 관리가 되며, 이때 이 관리 포인트는 store 패턴을 사용하고 통상 store라고 불린다. 이 4가지는 서로간의 간접적으로 영향이 있으며 단방향 데이터 흐름으로 볼수 있다.

 

여기서는 간단하게 4가지의 개념만 소개하겠다. (사용방법은 다른 블로그 참조)

 

1. State

State는 Vue 컴포넌트에서 data로 볼 수 있다. 

원본 소스의 역할을 하며, View와 직접적으로 연결되어있는 Model이다. 

이 state는 직접적인 변경은 불가능하고 mutation을 통해서만 변경이 가능하다.

mutation을 통해 state가 변경이 일어나면 반응적으로 View가 업데이트된다.

 

핵심 : 쉽게 말해 변수라고 생각하면된다. 이 값을 바꾸려면, mutation의 함수를 통해서만 바꿀 수 있다.

 

2. Mutations

Mutation은 state를 변경하는 유일한 방법이고 이벤트와 유사하다. 

mutation은 함수로 구현되며 첫 번째 인자는 state를 받을 수 있으며, 두 번째 인자는 payload를 받을 수 있다. (payload는 변경하고 싶은 값을 뜻함)  (동기처리 기준)

여기서 payload는 여러 필드를 포함할 수 있는 객체형태도 가능하다.

이 mutation은 일반적으로(Helper를 쓰지 않는 경우)는 직접 호출을 할 수 없으며, commit을 통해서만 호출할 수 있다.

 

결론 : 변수를 바꾸는 함수. 쉽게 setter라고 이해하면 편하다.

호출 방식 : commit (state, payload)

 

3. Action

Action은 mutation과 비슷하지만 mutation과는 달리 비동기 작업이 가능하다. 

또한 mutation에 대한 commit이 가능하여 action에서도 mutation을 통해 state를 변경할 수 있다.

action에서는 첫 번째 인자를 context 인자로 받을 수 있으며 이 context에는 state, commit, dispatch, rootstate와 같은 속성들을 포함한다. 두 번째 인자는 mutation과 동일하게 payload로 받을 수 있다.

 

commit을 통해 mutation을 호출했다면 Action은 dispatch를 통해서 호출한다.

context의 속성을 보면 dispatch가 있는 것으로 보아 action에서는 서로 다른 action을 호출할 수 있다는 것을 볼 수 있다.

 

결론 : Mutation을 호출하는 함수. 비동기 작업에 사용되므로, 주로 Action을 호출해서 Mutation을 실행시킴

호출 방식 : dispatch('함수명', '전달인자')

 

4. Getters

Getters는 쉽게 Vue 컴포넌트에서 Computed로 볼 수 있다. 

말로 풀자면 계산된 속성인데 getter의 결과는 종속성에 따라 캐시 되고 일부 종속성이 변경된 경우에만 다시 재계산된다.

즉, 특정 state에 대해 어떠한 연산을 하고 그 결과를 View에 바인딩할 수 있으며, state의 변경 여부에 따라 getter는 재계산이 되고 View 역시 업데이트를 일으킨다.

이때 state는 원본 데이터로서 변경이 일어나지 않는다.

 

결론 : state를 수정하고 나서 값을 업데이트 할 때 확인용으로 호출 (잘안쓰는 것 같음..)

호출 방식 : this.$store.getters['경로/함수명']

 

 

데이터 흐름

 

이렇게 전반적인 vuex store에 대한 개념을 알아봤다. 마지막으로는 잘 안쓰이지만, 알아두면 좋을 EventBus에 대해 설명하겠다.

 

 

참고 블로그 :

https://kdydesign.github.io/2019/05/09/vuex-tutorial/

http://ccambo.github.io/Dev/Vue/5.what-is-the-store-in-vuex/

Vue는 컴포넌트 단위로 메소드/ 변수 등을 구성한다. 이번에는 컴포넌트 사이에서 어떻게 메소드 / 데이터를 호출하는지 알아보자.

너무 자세하게 설명하는 것이 아닌, 이럴땐 이렇게 하며 좋다라는 개념과 지식을 전달하기 위함이다.

구체적인 내용은 구글링 해보시길. ( 이정표 같은 글을 추구 )

 

경우의 수는 3가지가 있다.

  1. 부모/자식 컴포넌트 사이에서 호출하기 위한 설정 -> 부모컴포넌트와 자식컴포넌트 사이에는 특별한 호출방식이 있다.
  2. 모든 컴포넌트에 적용할 데이터/메소드 설정 -> 전역변수로 생각하면 편하다.
  3. 전혀 관계없는 컴포넌트 사이에서 호출하기 위한 설정 -> 아무관계도 아니지만, 전역으로 할 필요는 없는 경우

 

각각의 상황에서 어떤 기능을 써야되는지 알아보자.

 


1. 부모/자식 컴포넌트 사이에서 호출 - Props & Emit

대부분의 컴포넌트는 부모 / 자식의 관계를 가지고 있다. 여기서 말하는 부모 / 자식 관계는 "부모가 자신의 컴포넌트 안에 자식 컴포넌트를 불러온다"의 개념이다. 

 

Ex) Home.vue와 InputField.vue라는 컴포넌트가 있다. Home.vue 컴포넌트 안에서 <inputField> </inputField> 이렇게 호출하면 Home이 부모 / inputField가 자식 컴포넌트가 된다.

 

부모 / 자식 컴포넌트간의 데이터 교환은 3가지 방법이 있다.

  1. 부모에서 자식에게 보내주기
  2. 자식에서 부모의 값을 바꾸기
  3. 자식, 부모 둘다 함께 쓸 수 있게 설정

 

1-1. 부모에서 자식에게 보내주기 - Props

아래와 같이 부모 컴포넌트에서 자식 컴포넌트를 호출할때, ":속성이름= 속성값"이렇게 데이터를 보내줄 수 있다.

<template>
  <inputField :title = "YOUYOU"/>
</template>

<script>
import inputField from '@/components/layout/inputField';
</script>

 

그럼 자식 컴포넌트에서는

<template>
  <div>
    <form>
      <label>ID</label>
      <input type="text" :value = "title" style = "padding: 20px; border: 1px pink solid">
      <button>Submit</button>
    </form>
  </div>
</template>
 
 
<script>
export default {
  props : {
    title : {
      type : String,
      required : true
    }
  }
}
</script>

위 코드처럼 props를 정의하고, 그 안에 받아올 변수 명과 type을 설정할 수 있다.

 

결론 : ":title" 처럼 제일 앞에 :가 붙어있으면, 자식컴포넌트에게 props로 보내주는 것이다.

 

1-2. 자식에서 부모의 값을 바꾸기 - Emit

자식 컴포넌트에서 변경된 값을 부모 컴포넌트에 적용할 때는 "$Emit"을 사용한다.

자식에서는 아래와 같이 작성한다. this.$emit('@에서 작성한 emit 명칭', 현재 컴포넌트에서 전송할 Event나 Data 명)

      <label>ID</label>
      <input type="text" :value = "title" @input = "$emit('titleFromChild', $event)" style = "padding: 20px; border: 1px pink solid">
      <button>Submit</button>

 

 

부모에서는 아래와 같이 작성한다. @emit으로받아올event명="현재 컴포넌트에서 사용할 Event 명"

<inputField :title = "title" @titleFromChild="title = $event.target.value"/>

이렇게 하면, 자식에서 정의한 값을 부모에게 넘겨줄 수 있다.

 

결론 : $Emit을 사용하면 자식의 데이터를 부모가 사용할 수 있다. 

 

1-3. 자식, 부모 둘다 쓸 수 있게 설정 - v-model

지금까지 작성한 것은 부모에서 자식으로 props를 내려주고, 자식에서 $emit으로 event값을 부모로 올려준 다음, 부모에서 props로 내려주는 data 값을 event 값으로 변경해주는 방식이였다. 이것을 조금 더 단순화하는 방식이 v-model이다.

 

v-model을 html 태그에 적용하면 양방향 바인딩이 되어 props를 자식 컴포넌트에서 수정하게 되지만, 컴포넌트 자체에 v-model을 걸게되면 부모-자식 컴포넌트 간 바인딩이 이루어져서 자식에서 직접 props를 바꾸는 것이 아니라 부모 컴포넌트에서 props에 해당하는 data 값을 바꾸는 방식이 된다.

 

부모에서는 아래와같이 v-model에 값을 넣어주면 된다.

  <div>
    <inputField v-model="title" /> 
  </div>

 

그럼 자식에서는 아래와 같이 value로 받아올 수 있다. 그리고 value값을 자식에서 변경하면, 부모의 값도 함께 변경된다.

<input type="text" :value = "value">

 

결론 : v-model 속성을 사용하면, 부모-자식 컴포넌트에 양방향 통신이 가능하다.

 

 

이렇게 부모 자식간의 데이터 교환을 정리해봤다. 가장 많이 쓰이는 만큼 개념을 잘 정리하고, 활용하는 것을 추천한다.

다음에는 2번. Vuex Store에 대해 알아보겠다.

 

참고 블로그 : 

https://whitepro.tistory.com/255

https://velog.io/@gillog/Vue.js-props-emit-%EB%B6%80%EB%AA%A8-%EC%9E%90%EC%8B%9D-Component-Data-%EC%A3%BC%EA%B3%A0-%EB%B0%9B%EA%B8%B0

사건의 발단

 

개발을 하던 도중, api를 보내는데 request에 cookie를 안보내주거나, set-cookie를 해도 쿠키에 저장이 안되거나 등등의 문제점이 생겼다. 그래서 계속 찾아보다가 cookie, session에 대한 정리를 해봐야겠다고 생각하며 글을 쓰기 시작했다.

 

 

사용이유

 

HTTP의 특성 중 하나는 connectionless, stateless가 있다. (사용자가 누군지 저장을 안함)

그래서 서버는 요청을 보내는 클라이언트가 누구인지 매번 확인 해야 한다.

이 특성을 보완하기 위해, 세션과 쿠키를 사용한다.

 

# connectionless : 클라이언트가 요청을 한 후 응답을 받으면 연결을 끊어버림

# stateless : 통신이 끝나면 상태 정보를 유지 않는 특성

 

# 쿠키와 세션 보는 방법 :

브라우저에서 F12를 눌러 개발자 도구에 들어가고, "애플리케이션(Application)"을 누르고, 좌측의 쿠키를 누르면 볼 수 있다.

 

쿠키 (Cookie)

정의

  • 클라이언트(브라우저) 로컬에 저장되는 Key-Value 형식의 데이터 파일
  • 유효시간을 정할 수 있으며, 브라우저를 종료해도 인증이 유지된다.
  • 클라이언트에 300개의 쿠키저장 가능 / 하나의 도메인당 20개의 값만 가질 수 있음 / 쿠키 하나는 4KB까지 저장

 

저장 방법

  • Response Header에서 Set-Cookie의 속성으로 클라이언트에서 쿠키를 만들게 함
  • 서버측에서 클라이언트로 '저장해!'라고 명시해줌

예시

사용방법

  • 클라이언트에서 따로 지정해주지 않아도, 특별한 설정 없이 Request시에 가지고 있는 Cookie를 Request Header에 넣어서 자동으로 전송

 

쿠키의 구성 요소

  • 이름 : 각각의 쿠키를 구별하는 데 사용되는 이름
  • 값 : 쿠키의 이름과 관련된 값
  • 유효시간 : 쿠키의 유지시간
  • 도메인 : 쿠키를 전송할 도메인
  • 경로 : 쿠키를 전송할 요청 경로

 

쿠키의 동작 방식

  1. 클라이언트가 페이지를 요청
  2. 서버에서 쿠키를 생성 후 HTTP 헤더에 쿠키를 포함 시켜 응답
  3. 요청을 할 경우 HTTP 헤더에 쿠키를 함께 보냄
  4. 쿠키의 내용을 본 서버는, 이전에 보낸 클라이언트를 확인하며 응답을 보냄
  5. 서버에서 쿠키를 읽어 이전 상태 정보를 변경 할 필요가 있을 때 쿠키를 업데이트 하여 변경된 쿠키를 HTTP 헤더에 포함시켜 응답

 

쿠키 사용 예

  • 방문 사이트에서 로그인 시, "아이디와 비밀번호를 저장하시겠습니까?"
  • 쇼핑몰의 장바구니 기능
  • 자동로그인, 팝업에서 "오늘 더 이상 이 창을 보지 않음" 체크, 쇼핑몰의 장바구니

 

 

세션(Session)

정의

  • 쿠키에 기반하고 있음!!! (중요)
  • 사용자 정보 파일을 서버 측에서 관리함 (쿠키는 브라우저에 저장)
  • 서버에서 세션 ID를 부여하고, 브라우저가 종료할 때까지 인증을 유지한다.
  • 접속 시간에 제한을 둘 수도 있음

예시

동작 방식

  1. 클라이언트가 서버에 접속 시 서버로부터 세션 ID를 발급 받음
  2. 클라이언트는 세션 ID에 대해 쿠키를 사용해서 저장하고 가지고 있음
  3. 클라리언트는 서버에 요청할 때, 이 쿠키의 세션 ID를 같이 서버에 전달해서 요청
  4. 서버는 세션 ID를 전달 받아서 별다른 작업없이 세션 ID로 세션에 있는 클라언트 정보를 가져와서 사용
  5. 클라이언트 정보를 가지고 서버 요청을 처리하여 클라이언트에게 응답

 

특징

  • 사용자에 대한 정보를 서버에 두기 때문에 쿠키보다 보안에 좋지만, 사용자가 많아질수록 서버 메모리를 많이 차지하게 됩니다.
  • 즉 동접자 수가 많은 웹 사이트인 경우 서버에 과부하를 주게 되므로 성능 저하의 요인이 됩니다.
  • 각 클라이언트에게 고유 ID를 부여
  • 세션 ID로 클라이언트를 구분해서 클라이언트의 요구에 맞는 서비스를 제공

 

세션 사용 예

  • 로그인 같이 보안상 중요한 작업을 수행할 때 사용
쿠키와 세션의 차이
  • 쿠키와 세션은 비슷한 역할을 하며, 동작원리도 비슷합니다. 그 이유는 세션도 결국 쿠키를 사용하기 때문입니다.
  • 가장 큰 차이점은 저장되는 위치입니다. (세션 - 서버 / 쿠키 - 브라우저)
  • 세션은 보안적으로 더 좋고, 쿠키는 속도면에서 좋다. (세션은 서버 처리가 필요해서)
  • 세션은 브라우저를 종료하면 사라지지만, 쿠키는 지정한 시간까지 계속 살아있음 (세션은 브라우저의 다른 탭에서도 공유)
  • 쿠키는 값들을 모두 request header에서 모두 볼 수 있어 보안이 안좋지만, session은 session_id만 보내주고, 처리도 서버에서 해서 보안에 좋다.

 

세션이 더 좋은데, 왜 쿠키를 쓸까?

세션은 서버의 자원을 사용하기 때문에 무분별하게 만들다보면 서버의 메모리가 감당할 수 없어질 수가 있고

속도가 느려질 수 있기 때문에 쿠키가 유리한 경우가 있습니다. 그래서 적절히 나눠서 사용하면 좋습니다.

사건의 발단

사내 관리자 페이지에서, 현재 운영중인 모든 API를 테스트 할 수 있는 기능을 추가하고 있었다.

Swagger를 사용하지 않고, 사내 API를 테스트 해볼 수 있는 페이지였다.

사내에서 관리하는 API 서버가 여러개이고, 로그인 인증까지 해야되서 복잡한 상황이었다.

프론트에서 api를 호출하는 방식을 사용하던중, 이때까지는 전혀 문제가 없었던 CORS에러가 나오기 시작했다.

 

웹개발자라면 한번은 봤을 CORS 에러

 

CORS 에러

먼저 CORS에러가 무엇인지 알아보자

출처 :&nbsp;https://developer.mozilla.org/ko/docs/Web/HTTP/CORS

"교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제입니다.

웹 애플리케이션은 리소스가 자신의 출처(도메인, 프로토콜, 포트)와 다를 때 교차 출처 HTTP 요청을 실행합니다.

 

보안 상의 이유로, 브라우저는 스크립트에서 시작한 교차 출처 HTTP 요청을 제한합니다.

예를 들어, XMLHttpRequest와 Fetch API는 동일 출처 정책을 따릅니다. 즉, 이 API를 사용하는 웹 애플리케이션은 자신의 출처와 동일한 리소스만 불러올 수 있으며, 다른 출처의 리소스를 불러오려면 그 출처에서 올바른 CORS 헤더를 포함한 응답을 반환해야 합니다. " 

(출처: MDN)

 

교차 출처 리소스 공유 (CORS) - HTTP | MDN

교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라

developer.mozilla.org

 

위 내용을 정리해보자면

- CORS는 서로 다른 출처(Origin) 간에 리소스를 전달하는 방식을 제어하는 체제이며,

- CORS 요청이 가능하려면 서버에서 특정 헤더인 Access-Control-Allow-Origin과 함께 응답할 필요가 있다.

 

그러므로 CORS 정책을 위반하여 서로 다른 출처를 가진 상태에서 무언가를 요청하게 되면 브라우저가 보안 상의 이유로 차단을 해버린다!

 

EX) 클라이언트 포트가 3000번이고 서버의 포트가 8000번, 클라이언트(3000)에서 서버(8000)로 리소스를 요청했을 때 CORS 에러 메시지가 클라이언트 콘솔에 빨갛게 뜨고 데이터를 주지 않게 된다.

 

왜냐? 3000번 포트에서 3000번이 아닌, 8000번 포트로 API를 호출했으니, 보안상의 문제가 있는 줄 알고 차단한 것이다.

 

결론 : CORS 문제를 해결하려면 동일한 출처에서 리소스를 요청하면 된다.

 

 

해결과정

 

1. CORS 에러가 뜬 상황

참고 : maca-admin port: 31010 / maca-admin-api port: 31000

 

여기서 여기저기서 구글링을 하니 해야되는 것은 3가지로 압축됐다.

  1. 프론트엔드에서 Access-Control-Allow-Origin을 허용받게 처리하기
  2. 백엔드에서 Response 헤더의 Access-Control-Allow-Origin를 True로 만들어주기
  3. 프론트에서 로그인정보를 cookie에 넣어 보낼 수 있도록 처리

 

2. Cors 처리 시작 (feat. flask-cors)

에러메시지를 보면 request’s credential을 하려면, Acccss-Control-Alllow-Origin header가 *이면 안된다고 한다.

그리고 XMLHttpRequest가 withCredentials 특성에 의해 컨트롤 된다고 한다.

 

아직 잘 모르겠지만 해야될 것은 두가지인 것 같다.

 

Access-Control-Allow-Origin header의 값을 *(전체) 말고 내 프론트 주소 (31010포트)로 바꿔주자 (위에서 말한 1번, 2번)

withCredential이 어떤 놈인지 찾아보고, 해야될게 있나 찾아보자. (위에서 말한 3번)

 

3. Access-Control-Allow-Origin 설정 & withCredential 처리

나는 백엔드가 Flask라서 찾아보니, flask_cors라는 라이브러리를 통해 처리가 가능하다고 한다. (공식문서)

pip install flask-cors

그리고 어떻게 설정하는지 찾아보니 Flask 프로젝트에서 app=Flask(__name__)을 해주는 곳 뒤에 코드를 추가하라고 한다.

 

그래서 api repository 최상위에 있는 __init.py__ 에 코드를 추가해준다.

17번, 25번 줄을 추가해주었다.

25번 줄을 추가할때 /bo/는 api 제일 앞 형식에 맞게, ‘origins’에는 허용할 웹페이지 주소를 넣는다.

(나는 프론트 포트가 31010이라서 저렇게 넣었다.)

 

만약 여러 포트를 허용하고 싶다면,

'origins':["http:naver.com", "http:hello.com"]

이렇게 리스트 안에 넣어주면 된다.

 

그리고 우연히 공식문서를 보던중 아까 말한 withCredential에 대한 해결책도 찾았다.

https://flask-cors.readthedocs.io/en/latest/api.html?highlight=supports_credentials#using-cors-with-cookies

CORS(app)을 해줄때, supports_credentials=True를 함께주면, 쿠키를 함께 넘겨줄 수 있다는 뜻이다.

 

만약 cookie를 넘겨주지 않아도 되면, 설정하지 않아도 된다. (나는 cookie로 로그인 정보를 넘겨줘서 설정)

다른 언어에서는 'withCredential'이라고 하는데, 여기서는 'supports_credentials'라고 한다.

 

자... 이제 설정도 끝났으니 완벽하게 되겠지?!

 

4. 두번째 시련 시작

어림도 없지. 아래와 같은 에러가 또 떴다.

에러메시지를 읽어보니, response의 Access-Control-Allow-Origin 헤더가 값이 한개여야 되는데, 두개가 들어왔다는 것.

 

그래서 다른 API 호출 한것을 보니

Credentials는 True로 제대로 됐지만, access-control-allow-origin에 값이 두개가 들어가 있다.

 

flask-Cors 공식문서에 따르면 supports_credentials를 설정하면 access-control-allow-origin이 *이 되면 안된다고 한다.

그래서 access-control-allow-origin : *을 지워보려고 한다.

 

하지만 아무리 프로젝트에서 관련 코드를 찾아봐도 관련 정보가 없었다. (CORS관련 설정이 없음)

그렇게 삽질을 하다가 찾아낸 곳은 바로 Nginx 였다.

docker의 Nginx 설정을 찾다보니

사진과 같이 모든 header에 *이 붙이는 코드가 있었다. (이놈이 문제!!)

그래서 이걸 #을 붙여 주석처리해주자! (참고 : 만약 권한이 없다고 뜨면, root로 나와서 해보자)

 

5. 해치웠나?

설정 값을 바꾼 후 Nginx를 reload해주고, uwsgi도 다시 실행시켜 준다.

드디어 실행이 된다.

 

가장 처음에 정리한 해야될 3가지를 살펴보면, 

  1. 프론트엔드에서 Access-Control-Allow-Origin을 허용받게 처리하기 (O)
  2. 백엔드에서 Response 헤더의 Access-Control-Allow-Origin를 True로 만들어주기 (O)
  3. 프론트에서 로그인정보를 cookie에 넣어 보낼 수 있도록 처리 (O)

이렇게 CORS 처리의 여정이  끝났다.

 

결론 : 

CORS에러는 프론트와 백 두곳 모두 설정 값을 바꿔줘야 한다.

그리고 정말 안되면, NGINX의 설정을 보자!!

+ Recent posts