5월 09, 2022

[Java] 환경변수 설정 및 java의 프로그램 실행 순서

 javac.exe : 컴파일러

java.exe : JVM구동 명령

자바 컴파일러를 다른 디렉토리에서도 접근할 수 있게 환경변수(path)에 bin 경로를 등록해주려 합니다.

https://nobacking.tistory.com/76 을 그대로 따라해보았으나

source로 적용해주려 하면 


해당 오류가 뜨고 echo $JAVA_HOME으로 확인해보아도 아무것도 뜨지 않습니다.


echo $PATH로 확인했을때 


Javapath는 뜨긴하는데요..


https://zetawiki.com/wiki/%EB%A6%AC%EB%88%85%EC%8A%A4_$JAVA_HOME_%ED%99%98%EA%B2%BD%EB%B3%80%EC%88%98_%EC%84%A4%EC%A0%95

해당 링크를 참고하여



로 환경변수를 등록해주고 


echo $JAVA_HOME으로 확인해줍니다.


5월 09, 2022

WSL2에 Java를 설치하고 Eclipse를 통해 프로젝트 생성까지

WSL2에 Eclispe 개발환경을 구축하는 과정을 기록합니다.

이 글은 과도한 CTRL+C,V 및 잘못된 정보를 포함하고 있을 가능성이 매우 높음을 알립니다 ㅎ..


1. JAVA설치 

sudo apt update

sudo apt install openjdk-8-jdk

로 자바를 wsl에 설치해줍니다

java -version

javac --version

으로 버전을 확인해줍니다

 

2. Eclipse IDE와 WSL연동

우선, WSL의 GUI프로그램이 화면에 그래픽 창을 띄울 수 있게 하기 위해 vcxsrv를 설치합니다.

https://sourceforge.net/projects/vcxsrv/

에서 다운로드를 진행해줍니다.


다운로드 시 체크해야 할 사항은

https://hbase.tistory.com/303 를 참고하였습니다.


다음으로 WSL에게 디스플레이 정보를 알려줘야 합니다.

위의 링크를 따라했으나, 

export DISPLAY=($cat /etc/resolv.conf | grep nameserver | awk '{ print $2 }'):0

zsh: parse error near `|'

에러가 발생합니다.

https://psychoria.tistory.com/739 다른 링크를 참조하여 디스플레이 설정을 진행했습니다.


export DISPLAY="`grep nameserver /etc/resolv.conf | sed 's/nameserver //'`:0”

export LIBGL_ALWAYS_INDIRECT=1

해당 명령어를 실행하고, 이를 ~/.bashrc의 최하단에 vim을 통해 입력해두고 저장해줍니다.


이후 https://www.eclipse.org/downloads/packages/ 에서 이클립스 리눅스 버전을 다운로드 합니다.


mnt 디렉토리 하에 있는 해당 파일을 cp명령어를 통해 home으로 옮기고, 

tar -xvf eclipse-jee-2022-03-R-linux-gtk-x86_64.tar.gz

로 압축을 해제합니다.


그리고 

cd eclipse

./eclipse

로 실행을 해주려는데...!

org.eclipse.m2e.logback.configuration: Activated before the state location was initialized. Retry after the state location is initialized.

SWT OS.java Error: Failed to load swt-pi3, loading swt-pi4 as fallback.

Eclipse:

An error has occurred. See the log file

에러가 발생합니다.


https://stackoverflow.com/questions/10165693/eclipse-cannot-load-swt-libraries

해당 스택오버플로우 글을 통해 에러를 해결했습니다.



저는 /usr/lib/jni 디렉토리 자체가 없어서 문제가 발생한 듯합니다.

위의 명령어에서 gtk-3 이 아니라 gtk-4로 바꿔서 명령어를 입력해줍니다.

ln -s /usr/lib/jni/libswt-* ~/.swt/lib/linux/x86_64/

이후 해당 명령어를 실행해줍니다.


이제 ./eclipse로 실행해봅니다.


잘 실행되는 것 같죠....?

pjtTest라는 프로젝트를 만들어보았습니다.


src파일은 실제로 내가 작성한 코드들이 저장된 디렉토리이고,
bin파일은 바이너리, 즉 컴파일 한 이후 컴퓨터가 이해할 수 있는 형식으로 변환된 파일이 담긴 디렉토리 입니다.

패키지 익스플로러에서 
src파일을 우클릭한 이후, New를 클릭하여 새로운 자바 클래스를 만들어줍니다.
자바 프로그램을 구성하는 하나하나의 파일들을 클래스라고 부릅니다.
클래스를 생성했습니다.

main을 입력한 이후, 컨트롤+스페이스로 메인 메소드를 호출합니다

이 메인 메소드는 프로그램을 실행할때, 컴퓨터가 제일 처음 찾아서 실행하는 메소드입니다.
즉, 프로그램의 출발점을 지정해주는 메소드입니다.



귀엽게 Hello World 를 찍어봅니다.
System.out.println(); 은 앞서와 마찬가지로 sysout만 입력하고 컨트롤 + 스페이스 입력하면 저절로 완성됩니다.

Ctrl+S로 저장(저장할때 컴파일까지 됩니다. 
저장 이후 bin파일에 들어가보면 pjtTest안에 MainClass.class파일이 형성된 것을 볼 수 있습니다.

프로젝트를 마우스 우클릭하고, Run as java application을 통해 Hello World를 실행가능합니다.

이렇게 Hello World까지 찍어보았습니다! 







4월 21, 2022

에러 기록) WSL 늦게 켜지는 문제/ Code .

 가상 머신 또는 컨테이너에서 응답을 받지 못했기 때문에 작업 시간이 초과되었습니다.


[코드 4294967295 (0xffffffff)로 프로세스 종료됨]


Powershell에서 WSL을 실행하면 한~참 있다가 이 에러가 뜨고, 그 후에 WSL이 켜집니다.


https://gyunseo.com/devlog/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%9D%B4%EB%A6%84%EC%9D%84-%EC%B0%BE%EC%9D%84-%EC%88%98-%EC%97%86%EC%8A%B5%EB%8B%88%EB%8B%A4-%EC%BD%94%EB%93%9C-4294967295-0xffffffff%EB%A1%9C-%ED%94%84%EB%A1%9C/

해당 링크를 따라하니 이제 바로바로 켜지기는 하는데 이제는 code . 으로 vscode 여는 명령이 작동하지 않습니다....


https://www.reddit.com/r/vscode/comments/ilwkhi/cant_launch_vs_code_from_wsl2/

껐다키니깐 되네요 허헛... 


도커와 WSL 관련해서 자잘한 문제가 꾸준히 생기는데, 추후에 정리해서 한번에 포스팅하겠습니다.

4월 20, 2022

TIL)HTTP - Cache와 Cookie

 

Cache

HTTP캐시는 서버의 지연을 줄이기 위하여 주어진 웹 페이지 요소를 임시로 저장하는 기술입니다. 캐시 시스템은 이를 통과하는 문서들의 사본을 저장하며, 특히 이후에 필요할 것 같은 요소들을 저장합니다.


Cookie란

브라우저에 저장돼 있는 "{name} = {value}" pair입니다. 

브라우저는 Response의 Set-Cookie 헤더를 통해 Server에게 전달받은 값을 쿠키를 세팅할 수 있습니다.

이후 브라우저는 Requset의 Cookie 헤더를 통해 이 쿠키를 서버에게 보낼 수 있습니다




언뜻 보기에는 쿠키와 캐시 모두 정보를 저장하는 방법으로 보입니다. 


그러나 둘은 용도 및 성격의 차이점이 있습니다.

쿠키는 사용자의 PC를 인증하고, 커스터마이징 된 웹사이트를 구현하기 위해 존재합니다.

쿠키는 인증 정보 및 사용자의 활동과 성향을 트랙하기 위한 목적으로 고안됐습니다.

사용자가 웹사이트를 방문할 때, login정보와 개인정보 등을 쿠키에 담아서 전송합니다. 

웹사이트를 사용하면서 매번 로그인을 할 필요가 없는 이유는, 브라우저에 있는 쿠키를 이용하여 인증 절차를 대신하기 때문입니다. (매 request마다 쿠키가 서버로 보내집니다)

또한 개인정보의 경우, 유저의 선호사항 등이 담깁니다.

쿠키는 브라우저가 닫히면서 같이 삭제되거나, 일정 시간이 지난 이후 자동으로 expired(만료)됩니다

쿠키는 누구나 삭제 혹은 block할 수 있기에, 쿠키에 의존하는 웹 어플리케이션은 쿠키의 삭제 혹은 블록 이슈에 대해 취약합니다.


반대로 캐시는, 필요한 웹 페이지 리소스를  저장하는 방식입니다. 사실 메모리에 저장돼 있는 캐시 파일은 일종의 쿠키처럼 동작합니다. 

그러나, 쿠키와 다르게 캐시는 웹 페이지가 렌더링을 할 때, 이를 좀 더 빠르게 할 수 있게 도와주는 역할을 합니다. 

쿠키와 다르게 캐시는 유저가 수동으로 삭제해주기 전까지, 메모리에 남아있습니다. 




쿠키와 캐시의 차이점을 기록한 표입니다. 포인트들을 빨간색으로 체크해두었습니다.


참고

https://developer.mozilla.org/ko/docs/Web/HTTP/Caching

https://medium.com/@maheshlsingh8412/cookie-session-story-of-a-stateless-http-3cd09cc01541

https://www.quora.com/What-is-the-difference-between-cookies-and-cache

4월 19, 2022

TIL) HTTP 헤더 : Content-Type

 

HTTP 콘텐트 타입은 리소스의 미디어 타입을 나타내기 위해 존재합니다.

Media Type이란, 파일의 형식을 나타내기 위해 파일과 함께 보내지는 string입니다.

데이터를 받는 서버(request일때), 클라이언트(response일때)는 Content-Type을 보고 데이터의 형식을 파악하고, 이를 어떻게 처리할지 결정하게 됩니다.

예를 들어, 브라우저는 서버로부터의 Response를 보고, 여기의 Content-Type을 통해 컴퓨터에 보여줘야 하는 컨텐트의 타입을 알 수 있습니다. 


이때 브라우저는 MIME sniffing 이라는 작업을 수행하는데요.  

MIME은 "Multipurpose Internet Mail Extensions"의 약자입니다. 원래는 이메일에 있는 non-ASCII Text와 non-text binary를 정의하기 위해 고안됐다고 합니다.

현재, Mime standard에 있는 content-type들은 HTTP 프로토콜에서 request나 response의 컨텐트 타입을 파악하기 위해 쓰입니다.

브라우저는 바로 이 MIME 타입을 Content-Type을 보면서 파악합니다. (Content-Type은 MIME타입의 일부입니다)

개발자들은 Response 내용에 걸맞지 않는 Content-Type을 부여할 수 있습니다, 



사진은 자바스크립트 리소스에 text/plain 의 컨텐트 타입이 잘못 부여된 경우입니다.

브라우저는 이러한 경우에도 웹사이트가 의도대로 작동할 수 있도록 Content-Type이 잘못 표시된 리소스를 분석하여 렌더링 할 수 있습니다.

여기서 MIME Sniffing 이 사용됩니다. Content_Type에 의존하기보다, response의 content에 대해 직접 분석하여서 , 브라우저는 효과적으로 mime type을 Sniffing을 통해 알 수 있습니다. MIME Sniffing의 알고리즘은 브라우저마다 상이합니다.

 

X-Content-Type-Options

보안 이슈를 위하여 , 브라우저가 MIME Sniffing을 하지 않도록 서버의 HTTP Response header에 X-Content-Type-Option을 줄 수 있습니다. 

이 헤더의 유일한 값은 "nosniff"입니다. 

 

문법

type/subtype

type은 카테고리이며, subtype은 개별 혹은 멀티파트 타입입니다.


application/octet-stream

Content-Type에는 수많은 종류가 있습니다. 그 중 주목할만한 application/octet-stream에 대해 얘기해보겠습니다

8 비트(1바이트) 단위의 바이너리 데이터를 의미합니다(...?) 엄밀히 따지면 모든 컴퓨터 상 파일은 binary data이긴 합니다만...

이렇게 두루뭉술하게 표현한 이유는 , 특별히 표현할만한 프로그램이 존재하지 않는 경우에 octet-steam을 사용하기 때문입니다.

보통 브라우저는 해당 타입에 대해 유저에게 실행할지 묻거나, 실행하지 않는다고 합니다.

"Content-Dispostion" 헤더를 "attachment로 줌으로써 해당 데이터를 수신 받은 브라우저가 파일을 저장, 또는 다른 이름으로 저장여부를 설정하게 할 수 있습니다.

뭐라 정의하기 애매한 경우에 해당 Content-Type을 주는 듯 합니다.



참고

https://www.coalfire.com/the-coalfire-blog/mime-sniffing-in-browsers-and-the-security 

https://www.geeksforgeeks.org/http-headers-content-type/

https://pygmalion0220.tistory.com/entry/HTTP-Content-Type

http://www.webmadang.net/community/community.do?action=read&boardid=5001&page=1&seq=3

https://dololak.tistory.com/130

4월 18, 2022

TIL) 구글 코딩 인터뷰 풀이

 https://www.youtube.com/watch?v=XKu_SEDAykw&t=430 

해당 영상을 보고 풀이 과정을 기록한 코드입니다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# 문제
# collection of numbers
# find matching pair equal to sum(given)
# [1,2,3,9]  sum 8
# [1,2,4,4]  sum 8
 
# 매칭 pair 없는 경우도 있음
# memory에 있고, ordered된 상태(ascending)
# repeating element? : 같은 인덱스에 있는 값 재활용 불가능
# 다른 인덱스에 같은 값은 나올 수 있음
# int, 마이너스도 가능
 
# nested for loop 사용 가능 i Loop과 i+1 Loop
# 그러나 O(n^2) 이기에 time consuming함 
 
# 1이라고 치면, 7을 찾으면 되는데.. sorted 돼있기에 binary search하면 된다
# log algorithm 
# 아직 좀 느림
 
ex = [1,2,3,4,4]
 
def binary_search(array, left, right, ele):
  if right >= left:
    mid = (left+right)//2
    if array[mid] == ele:
      return True
    elif array[mid] > ele:
      return binary_search(array,left,mid-1, ele)
    else:
      return binary_search(array,mid+1, right, ele)
  else:
    return False
    
def slightly_better_pair_sum(array,sum):
  for i in range(len(array)):
    comp=sum-array[i]
    if binary_search(array,i+1,len(array)-1,comp):
      return "yes"
  return "No"
 
# 단방향인 binary search보다 
# 가장 큰 sum은 마지막 두 값일 것임
# 가장 작은 sum은 첫 두 값
# 그러니깐, 첫번째 값보다 작은 것은 없고, 마지막 값보다 큰 것은 없음
# range를 가운데로 줄여가며 linear하게 접근
 
# binary search를 하게된다면, 각 element마다 binary search를 반복해야 하는 반면, 이렇게하면 한번의 흐름으로 끝남
 
# python으로 해보자
def findSumPair(array,sum):
  left = 0
  right = len(array)-1
  while right>left:
    if array[left] + array[right] == sum:
      return "yes"
    elif array[left] + array[right] < sum:
      left += 1
    else:
      right -= 1
  return "No"
 
 
# print(findSumPair([1,2,4,4],8))
# O(n) 의 시간 복잡도 
 
 
# sorted 돼있지 않다면? - error check
# sort 한 이후 해당 코드 반복
 
def SortPairSum(array,sum):
  array.sort()
  left = 0
  right = len(array) -1
  while right>left:
    if array[left] + array[right] == sum:
      return "yes"
    elif array[left] + array[right] < sum:
      left += 1
    else:
      right -= 1
  return "No"
 
# print(findSumPair([4,5,2,3,7],8))
 
# 개선점
# O(nlog n)의 시간복잡도
# O(n) 으로 돌아가기 위하여 
  # array를 가면서 만나는 요소를 딕셔너리에 넣음
  # 현재 요소의 complement(요소와 더해서 sum되는 것)없다는 것은
  # array를 loop하면서 현재 요소의 complement가 딕셔너리에 있는지 확인하고
  # 있으면 yes를 리턴
  # 없으면 no를 리턴하고 현재 element를 딕셔너리에 더함
 
def smartestPairSum(array,sum):
  dictionary = dict()
  for item in array:
    comp = sum - item
    if not comp in dictionary:
      dictionary[item] = True
    else:
      return "YES"
  return "No"
 
print(smartestPairSum([4,5,2,3,7],8))
# 더해서 sum이 되는
# complement1 , complment2이 있을때,
# 먼저 나온 complement1을 기록해두었다가, 이후에 complemnet2 가 나오면 딕셔너리에서 이를 찾아서 합이 되는 수가 있음을 return 하고 끝냄.
# array개수만큼 operation이 늘어나기에 O(n)임
cs

참조 : https://github.com/VicodinAbuser/ZTM-DS-and-Algo-Python/blob/master/venv/Scripts/How%20to%20solve%20coding%20problems/Google%20Interview%20Question.py 


4월 17, 2022

TIL) HTTP메서드 속성

 

1. 안전

: 계속해서 메서드를 호출해도 리소스를 변경하지 않습니다.

GET은 안전 속성을 가지고 있으나, POST,PUT,PATCH 등은 호출했을 때 리소스의 변경이 일어나기 때문에 안전하지 않습니다. 



2. 멱등 Idempotent  => f(f(x) = f(x)

: 호출의 건수와 상관 없이 호출 결과가 항상 동일합니다.

GET은 서버에서 리소스를 불러오는 메서드이기에 멱등합니다.

DELETE 요청을 여러번 하여도, 삭제된 결과 자체는 똑같기에 DELETE는 멱등합니다.

PUT의 경우, 리소스를 완전히 "덮어쓰는" 메서드이기 때문에 여러번 요청하여도 같은 값으로 덮어쓰기에 멱등합니다. 리소스를 매 요청시마다 갱신하기에 멱등성이 생기는 것입니다.

그러나 POST는 멱등하지 않습니다. 예를 들어, POST를 통해 이미지를 업로드 할때, 여러번 POST하게 되면 중복 업로드가 될 수 있습니다.

더 심각하게는, POST를 통해서 결제를 처리하는 상황을 생각해 볼 수 있습니다. 

- 클라이언트는 POST를 통하여 결제 요청을 보냅니다.

- 서버는 결제 요청을 받고, 결제 로직을 실행합니다. 

이때 결제를 3번 요청하게 된다면, 3번의 중복 결제가 이뤄질 것입니다. 클라이언트 입장에서는 한번의 결제를 원하겠지만, 클라이언트의 의도와 다르게 여러번 결제가 중복해서 일어났습니다. 이처럼 POST는 멱등하지 않습니다


PATCH의 경우도 리소스의 일부를 변경하는 작업이기에, 멱등하지 않습니다.


멱등한 메서드들을 이용하여 자동 복구 메커니즘을 구현할 수 있습니다.

DELETE를 요청했는데, 서버에서 응답이 없다면 클라이언트는 다시금 DELETE를 요청할 수 있습니다. 이는 DELETE가 몇 번 호출하든 같은 결과를 내는 메서드라는 점을 활용하여, 클라이언트에서 임의적으로 재차 요청을 하는 것입니다.

이를 통하여 멱등성을 구분하는 이유를 알 수 있습니다.

서버 입장에서, 요청 실패 이후 재전송된 요청을 처리해도 괜찮은가? 에 대한 가이드를 제공해 주는 것이 멱등성입니다.



3. 캐시 가능

캐시 가능이라 함은 , 요청한 응답의 리소스를 향후 재사용을 위해 저장할 수 있는 속성을 의미합니다.

웹 캐시는 중간에 요청을 가로채, 서버로부터 리소스를 직접 다운로드 하는 대신, 리소스의 복사본을 반환합니다.

 응답 결과 리소스를 캐시해서 사용해도 괜찮을까요? 

이론적으로 GET,POST,PATCH 메소드는 캐시가 가능하다고 하지만, 실제로는 GET정도만 캐시를 사용할 수 있습니다. 

웹 캐시는 리소스를 보여주는데 필요한 시간을 줄여서, 웹 사이트를 더 빠르게 만들 수 있는 방법입니다.

캐시에는 두가지 종류가 있습니다.

1. 사설(Private)

2. 공유(Shared)

Shared cache는 한 명 이상의 사용자가 재사용 할 수 있도록 응답을 저장한 캐시인 반면, 

Private cache는 한 명의 사용자만  사용하는 캐시입니다.

대표적인 사설캐시로는 "브라우저 캐시"가 있습니다. 사용자가 http요청 이후 다운로드한 문서들을 저장하고 있다가, 앞뒤로 움직이기, 저장 등을 수행할 때 추가적인 서버 요청 없이 캐시를 활용합니다.

프록시캐시는 대표적인 공유 캐시입니다. 회사 혹은 ISP(Internet Service Provider)는 여러 사용자들을 위해 로컬 네트워크의 일부로 웹 프록시를 설치할 수 있습니다. 이를 통해 공통적으로 조회가 많이 되는 리소스들은 재사용되어 성능을 향상할 수 있습니다.



4월 16, 2022

TIL) Stateless

 

무상태 프로토콜 

"컴퓨팅에서 무상태 프로토콜(stateless protocol)은 어떠한 이전 요청과도 무관한 각각의 요청을 독립적인 트랜잭션으로 취급하는 통신 프로토콜로, 통신이 독립적인 쌍의 요청과 응답을 이룰 수 있게 하는 방식이다. 무상태 프로토콜은 서버가 복수의 요청 시간대에 각각의 통신 파트너에 대한 세션 정보나 상태 보관을 요구하지 않는다. 반면, 서버의 내부 상태 유지를 요구하는 프로토콜은 상태 프로토콜(stateful protocol)로 부른다."

(https://ko.wikipedia.org/wiki/%EB%AC%B4%EC%83%81%ED%83%9C_%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C)

4월 15, 2022

TIL) 웹 브라우저 요청 흐름

https://commons.wikimedia.org/wiki/File:Tcp-handshake.svg 에 접속합니다

HTML 파일을 받아옵니다.

해당 링크 안에 이미지로 

https://upload.wikimedia.org/wikipedia/commons/9/98/Tcp-handshake.svg  가 들어있습니다.

브라우저는 HTML파일을 읽다가 이 이미지 주소로 서버에 요청을 보냅니다.

이것이 간단한 HTTP 트랜잭션입니다.


이미지를 받아오는 과정을 순서화하면,

1. DNS 조회 후 여기에서 서버의 IP 추출. 

2. PORT 정보 조회

3. HTTP 요청 메시지 생성

    


4. 웹 브라우저는 TCP 3 Way-handshake 방식으로 서버와 커넥션을 생성한다.

5. 웹 브라우저는 서버에 HTTP 요청을 보낸다.

6. 서버는 HTTP 요청을 해석한다. 

7. 서버는 요청을 받은 메서드를 찾는다.

8. 찾게되면 이를 200상태코드와 응답 메시지에 동봉하여 클라이언트에게 전송한다.

9. 서버와 클라이언트는 TCP4 Way-handshake 방식으로 커넥션을 종료한다.



(4way handshake 방식, 출처 https://gyoogle.dev/blog/computer-science/network/TCP%203%20way%20handshake%20&%204%20way%20handshake.html)

TCP4 Way-handshake 과정
a. 클라이언트는 FIN 보내고 FIN WAIT
b. 서버는 FIN을 확인했다는 ACK를 클라이언트에게 보냄
c. 데이터를 모두 보낸 이후 종료됐다는 FIN 플래그를 클라이언트에게 회신
d. 클라이언트는 혹시 받지 못한 데이터가 있을 수 있으므로 TIME_WAIT으로 기다린다
e. 서버는 ACK를 받은 이후 소켓을 닫는다
f. TIME_WAIT 이후 클라이언트도 닫는다.
4월 14, 2022

TIL) 인터넷 프로토콜 4 계층

 

정보가 전송될때에는 위에서 아래로, 애플리케이션 -> ~ -> 네트워크 인터페이스의 방향으로 전송됩니다.

애플리케이션 계층에서 정보를 만들어 전달하고, 전송 계층에서 정보의 통신 노드를 연결하고, 인터넷 계층에서 통신 노드간 패킷을 전송 및 라우팅 합니다. 네트워크 인터페이스 계층에서는 이를 실제로 전기 신호로 변환하여 전달합니다.

  • IP
클라이언트와 서버에 IP 주소가 부여됩니다.
IP는 지정한 주소에 데이터를 전달합니다. 이때의 통신 단위는 패킷(Packet)입니다.
IP패킷에는 출발지, 목적지, 전송데이터의 정보가 담깁니다.
    
IP **프로토콜은 패킷을 받을 대상이 없어도 전달된다는 비연결성, 패킷의 순서를 보장할 수 없고 중간에 패킷이 사라지는 경우를 대비할 수 없다는 비신뢰성의 두가지 단점이 있습니다.

또한 같은 IP를 사용하는 서버에서 여러 애플리케이션들과 통신하고 있다면, 이를 구분해야 하는 문제가 생깁니다.


이러한 문제를 해결하기 위해서 TCP 프로토콜을 사용합니다.

위의 애플리케이션 계층에서 데이터가 전달되면, 이를 포함한 tcp 정보를 생성한 이후, 이들을 담은 IP패킷을 만들어 LAN등의 네트워크 인터페이스를 통해서 정보를 전송합니다.

TCP 프로토콜은 IP의 단점을 보완하는 IP위에서 동작하는 프로토콜입니다.
  • 연결 지향적이며 (TCP 3 Way-handshake)
  • 데이터 전달을 보증하며(데이터가 중간에 누락된 경우 알 수 있습니다)
  • 흐름을 제어하고
  • 혼잡을 제어합니다

IP가 패킷을 이용한 통신의 방식이라면, TCP/UDP는 전송을 제어하는 "프로토콜"입니다.

IP가 목적지를 찾아가는 것, 그 자체에 중점을 둔다면, 

TCP정보에는 출발지와 목적지의 Port 정보, 전송 제어, 순서, 검증에 대한 정보들이 있습니다.

잠깐, PORT가 뭘까요? 목적지 서버(IP)의 다양한 프로세스(애플리케이션)을 구분하기 위한 번호입니다.

위의 3 Way-handshake는 TCP에서 연결을 확인하는 방식입니다.
1. Synnchronize : 클라이언트에서 서버로 SYN(접속요청) 메시지를 보냅니다
2. Acknowledge: 서버에서 SYN에 대한 응답으로 ACK을 보냅니다. 이때 서버에서도 SYN을 보내야 하기에 이를 ACK과 동봉합니다
3. 클라이언트도 서버의 SYN에 대한 답장으로 ACK을 보냅니다.

이때 3번의 ACK과 함께 데이터도 전송하곤 합니다.

3 way-handshake는 실제적인 연결을 보장하는 것이 아닌, "개념적인" 연결입니다. 서로 syn, syn+ack, ack 의 과정이 이뤄졌기에  연결이 됐다는 논리적 보장이 있을 뿐입니다. 또한, handshake과정에서 거치는 수많은 노드들에 대해서도 알 방법이 없습니다.

흐름 제어의 경우. TCP 헤더의 Window size를 이용해 주고/받을 수 있는 데이터의 양을 정합니다. 이는 수신자 측에서 자신의 상황을 보아가며 수시로 변경합니다.

혼잡 제어는 무엇일까요? 데이터가 지나가는 네트워크 망의 혼잡도를 제어하기 위한 방식입니다. 데이터 송신자는 초기 데이터 송신량을 낮게 잡은 이후, 수신 여부를 확인하며 이를 천천히 늘려나갑니다. 이를 통해 현재 네트워크에서 가장 적합한 데이터 송출량을 확인할 수 있습니다. 이를 slow start 라고 합니다. 혼잡 제어에는 이 외에도 다양한 방식이 있다고 합니다.

어느 순간 congestion point를 만나면, window 크기를 1로 줄인 이후 slow start를 다시 적용하는 것을 볼 수 있습니다



TCP와 같은 전송계층에는 UDP도 있습니다. 

전송제어 , 순서, 검증에 대한 정보가 담긴 TCP와 다르게, UDP에는 담겨있는 정보와 기능이 거의 없습니다.
다만 단순하고 빠릅니다. TCP와 다르게 프로토콜에 손을 댈 수도 있습니다.

IP와 거의 같다고도 볼 수 있지만, Port 정보와 체크섬(메시지가 제대로 왔는지 검증해주는 데이터) 가 담깁니다.


** 프로토콜 : 컴퓨터 사이(내부 포함)의 데이터 교환 방식을 정의하는 규칙 체계. 기기간 통신은 교환되는 데이터에 대한 상호 합의를 요하는데, 이런 형식을 정의하는 규칙의 집합이 프로토콜.

해당 글을 참조하였습니다 : https://aws-hyoh.tistory.com/entry/TCPIP-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0

4월 13, 2022

TIL) Cache : 검증 헤더와 조건부 요청

 

브라우저는 서버의 응답결과를 캐시에 저장합니다

이후 재차 요청을 보내기 전에, 브라우저는 캐시의 유효기간이 초과되지 않았다면 네트워크를 거치지 않고 캐시를 통해 데이터를 조회합니다.


이때, 만약 캐시의 시간이 초과됐다면 브라우저는 똑같은 응답을 위한 요청을 재차 보내야만 합니다. 이때, 서버에서 데이터를 바꾼 것이 없다면 똑같은 데이터를 굳이 다시 다운로드 받아야 하나? 하는 의문이 생깁니다.


이때 검증 헤더와 조건부 요청을 이용합니다. 

캐시 유효 시간이 초과된 이후, 서버에 다시 요청을 보낼 때, 다음의 두가지 경우의 수가 있습니다.

1. 서버에서 기존 데이터를 변경한 경우

2. 서버에서 기존 데이터를 변경하지 않은 경우

2번의 경우, 서버가 데이터를 변경하지 않았다면 기존 데이터를 재사용 할 수 있겠죠? 다만 이때 서버의 데이터와 클라이언트의 데이터가 같다는 것을 검증할 필요성이 있습니다.

이를 위해서 검증헤더를 사용합니다. 검증 헤더는 캐시 데이터와 서버의 데이터가 같은 지를 검증하는 역할을 합니다.

검증헤더에는 Last-Modified와 ETag가 있습니다. 

Last-modified의 경우, 조건부 요청 헤더에 if-modified-since 를 사용합니다. 

캐시의 데이터 최종 수정일과, 서버의 데이터 최종 수정일이 다를 경우, 200OK, 같을 경우 304 Not Modified(변한게 없다)의 응답이 옵니다. 304 Not Modified의 경우, 데이터가 바뀌지 않았으므로 HTTP BODY가 오지 않습니다.


ETag 는 if-Not-Match의 조건부 요청 헤더를 사용합니다. 

ETag는 캐시용 데이터에 고유한 이름을 달아놓은 것입니다.

ETag는 서버 사이드에서 관리하는 것이기에, 캐시 제어 로직은 서버에서 온전히 가지고 있다고 볼 수 있습니다.

 서버는 데이터가 변동됐을 시, 해시를 통해서 ETag를 재 생성합니다(해시 함수에 넣는 값이 동일하면 해싱의 결과도 동일하기에, 이를 통해 데이터의 변동을 표현할 수 있습니다). 사용자는 ETag를 통해서 데이터가 변경됐는지 아닌지를 알 수 있습니다.


개발자도구를 켜서, 네트워크 탭에서 트랜잭션을 확인할 수 있습니다 


자세히 보면 204는 검은색이고, 200은 회색입니다.

회색인 경우는 기존 캐시에서 불러왔음을 뜻합니다.


해당 특정 트랜젝션을 클릭해보면, 캐시에서 불러왔음을 알 수 있는 문구가 있네요.

이렇게 서버로 부터 데이터가 바뀌지 않았다는 응답인 304를 확인해 볼수도 있습니다.


4월 12, 2022

TIL) django.db.utils.IntegrityError: (1048, "Column 'kakao_id' cannot be null")

 

코드 


시리얼라이저


모델


해당 값을 바디에 담아서 POST요청을 보냄



serializer.save() -> serializers.py의 get_or_create에서 문제가 생김

kakao_id를 .get으로 키가 없는 경우 빈 값 들어가도록 처리해주었고, kakao_id 모델 컬럼은 blank=True이므로 kakao_id가 없이 요청을 보내도 문제 없는 것 아닌가?

get_or_create는 다른 기능에서 사용하는 코드라 로직을 변경하기 어려움 

해당부분을 주석처리하고 요청 보내면 원하는대로 user object가 만들어지긴 함

kakao_id가 없으면 get_or_create()에서 get을 하기위해 찾아 줄 조건인 kakao_id 자체가 없어지기에 해당 코드는 kakao_id 값이 없이 요청이 들어오면 동작하지 않는 것이 맞음


생각해보니, 핸드폰 로그인에서도 1회 회원가입 이후 회원 정보를 get해와야 하기에 serializer를 나눠서 get_or_create()를 써주자 


해당 serializer를 추가하여 이를 이용했을 시 원하는 대로 동작한다.


3월 27, 2022

도커 배포하며 겪은 사소한 에러들

 

에러


이미지를 만들려고 할때마다 이 에러가 뜹니다

해결책으로 이 링크 https://dev.to/shriekdj/python-failed-building-wheel-for-backportszoneinfo-on-linux-2mo8 

의 tzdata 를 설치했더니( 두번째 해결책)

이젠 다른 에러가 뜹니다 


문제 해결

저는 pip freeze > requirements.txt로 pip freeze 항목을 그대로 다 requirements에 옮겨줬는데,

그러다 보니 제가 손수 깐 것이 아닌 장고 등을 설치하면서 깔린 라이브러리 등이 있다고 생각합니다.

그래서 backports.zoneinfo라는 제가 깔지 않은것이 pip freeze에 들어가지 않았나 생각이 들었습니다.

그 부분 requirments.txt에서 지워주고 다시 이미지 만드니 이미지가 만들어집니다.

requirements.txt는 pip로 모듈을 설치할때마다 성실하게 업데이트를 해줘야 한다는 것을 느꼈습니다.


문제는 requirments에 gunicorn 관련 내역이 없어서 이미지로 컨테이너를 실행하려니컨테이너가 실행 안됩니다,

가상환경 키고(구니콘이 있는), pip freeze로 gunicorn부분을 확인한 후 이를 requirements에 복사붙여넣기 해줍니다. 이를 바탕으로 이미지를 다시 만들어줍니다.

이미지를 다시 만들었습니다.

근데 도커콘테이너 실행하려니.


이 에러가 뜹니다.

docker ps -a 로 꺼진 콘테이너 확인해보니

api란 이름의 콘테이너가 꺼진채로 있습니다

이 콘테이너를 지워줍니다.



내 컴퓨터 - ec2 - docker 이렇게 연결되는 것이 아니라 ,

내 컴퓨터 → docker

아마존이 가진 ec2 → docker 이런식으로 별개의 컴퓨터 두개에서 도커로 서버를 돌린다고 생각하면 될듯 싶습니다.


여태껏 도커로 서버를 키려할 때 해당 명령어만을 사용했었는데

이 명령어로 키면 wecode/projcet 이미지로 api란 이름을 가진 새로운 컨테이너를 만들고 그거로 서버를 여는것과 같습니다

그래서 매번 이름이 안겹치게 새로운 이름을 가진 애들을 열어줬어야 했습니다. 

docker run -p 8000:8000 wecode/project:0.1.0

그러고 싶지 않으면 이렇게 —name api 부분을 없애주면 됩니다 , 이러면 새로운 이름의 컨테이너 안 열고 기존 컨테이너를 이용하여 그냥 바로 서버열기 가능합니다.

3월 27, 2022

SOLID 원칙

 객체지향 프로그래밍을 하며 지켜야 할 5가지의 원칙을 SOLID 원칙이라 합니다.

A. SRP(Single Responsibility Principle) - 단일 책임의 원칙 

클래스는 오직 하나의 기능만 가지며, 클래스의 모든 작업은 그 단일 책임에 종속돼야 합니다.

이에 맞추어  https://github.com/wecode-bootcamp-korea/30-2nd-WantU-backend/tree/main/applications 해당 링크의 applications view를 살펴보겠습니다.

우선 리팩토링 이전의 StatusView 코드입니다.


해당 view는 지원자의  지원 상태와, 그것들의 개수를 데이터베이스에서 가져오는 역할을 하고 있습니다.

34번째 줄에서 application.status.status는db에서 직접적으로 상태값을 가져오는 역할을 합니다. 

StatusView는 지원상태를 return 해줄 뿐 아니라, DB에서 직접 상태값들을 가져와 return할 값에 직접적으로 투입하고 있습니다. 

하나의 클래스가 여러 기능을 하고 있다고 보여지기에, enum클래스를 따로 만들어주어 상태값을 enum클래스를 통해 불러올 수 있도록 하겠습니다. 




enum 클래스를 분리해준 이후 , 이 클래스의 value값과 상태값의 id를 맞추어서 개수를 셀 때 Status 클래스를 사용해줍니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class StatusView(View):
    @login_decorator
    def get(self, request):
        applicant = Applicant.objects.filter(user=request.user).prefetch_related('application_set')
 
        if not applicant.exists():
            return JsonResponse({'result':"NO valid application"}, status = 404)
 
        applications_in_applicant = applicant[0].application_set.all().select_related('status')          
        status_list               = [application.status.id for application in applications_in_applicant]
 
        result = { 
            "applied"         : status_list.count(Status.APPLIED.value), 
            "document_passed" : status_list.count(Status.DOCUMENT_PASSED.value),
            "accepted"        : status_list.count(Status.ACCEPTED.value),
            "rejection"       : status_list.count(Status.REJECTION.value)
            }
 
        result["total"= len(status_list)
 
        return JsonResponse({'result':result}, status = 200
cs


B. OCP(Open Close Principle) - 개방폐쇄의 원칙

클래스는 확장에 대해서는 개방적이어야 하고, 변경에 대해서는 폐쇄적이어야 합니다.

클래스의 코드 변경은 이 클래스를 사용하는 모든 시스템에 영향을 미치게 됩니다. 따라서 클래스의 기능을 추가하려면, 기존 기능의 변경이 아닌 새로운 함수의 추가가 이상적입니다.


C. LSP(The Liskov Substitution Principle) - 리스코브 치환의 원칙

부모클래스 T의 서브타입인 자식 클래스 S의 객체는 어떠한 문제도 없이 부모 클래스의 객체를 대체할 수 있습니다.

문제없는 대체를 위해서, 자식 클래스는 부모클래스의 기능을 오버라이드 하지 않아야합니다. 자식 클래스는 부모의 기존 기능을 건드리지 않은 채, 확장만 수행하여야 LSP를 만족하게 됩니다

D. ISP(Interface Segregation Principle) - 인터페이스 분리의 원칙

바꿔 말하면, 인터페이스의 단일 책임 원칙입니다. 구체적인 책임을 지는 여러 인터페이스를 분리하여, 사용하지 않는 인터페이스가 존재하지 않게 해야 됩니다.


E. DIP(Dependency Inversion Principle) - 의존성 역전의 원칙

고차원의 모듈과 클래스는 저차원의 모듈과 클래스에 의존해서는 안됩니다. 고차원, 저차원 모두 구체에 의존적이지 않고, 추상에 의존해야 합니다. 구체는 추상에 의존적입니다.

고차원 모듈 또는 클래스는 저차원 모듈 또는 클래스를 도구로 하여 동작하는 클래스입니다. 추상은 두 클래스를 연결하는 인터페이스이고, 구체는 도구가 동작하는 방법입니다. 

자동차가 타이어를 사용할때, 한국타이어, 금호타이어등은 "구체"이고, 단순 "타이어"는 "추상"이라 할 수 있습니다. 자동차라는 고차원 클래스는 타이어라는 추상적 "인터페이스"에 의존하고 있습니다. 이때 타이어들은 모두 인터페이스 입니다.




출처 https://server-engineer.tistory.com/228

인터페이스를 통해 고수준 클래스가 저수준 클래스에 의존성을 가지지 않게 할 수 있습니다.

앞서 Enum클래스로 상태값을 분리해준 과정에서, StatusView는 고차원 클래스, Enum을 상속받은 Status는 저차원 클래스라고 생각됩니다.

3월 26, 2022

[리팩토링] SQL문과 쿼리 개수를 확인하여 prefetch와 select_related의 성능 확인하기

 



해당 erd의 applicants에서 application_status까지 타고 들어가려 합니다.




Get 요청 보냈을 시 


저는 SQL문을 알지 못합니다. 쿼리문을 보고 직관적으로 해석해보았습니다. 틀린 해석일 가능성이 큽니다.

1. id가 8 인 유저를 user테이블로부터 불러옵니다

2. applicants 중에서 user_id가 8인 것을 찾습니다. -> 7번 applicant

3. applications에서 applicant_id가 7 인것을 찾습니다

4. inner join을 이용하여 application_status 중 applications.status_id 와 같은 id를 가진 행을 엮은 뒤, applicant_id가 7 인 것을 찾습니다 



이 부분에서 prefetch_related를 빼주었더니 


오히려 쿼리의 숫자가 하나 줄어들었습니다




이 부분이 없어졌군요

그니까 위의 3번 부분이 없어진 것입니다.


반면에 select_related를 주석처리 해주었더니

쿼리가 21개로 급증했습니다

inner join으로 미리 데이터를 엮어오지 않아서 매번 for문이 돌때마다 데이터를 db에서 가져오는 것을 볼 수 있습니다.


왜 prefetch를 썼더니 쿼리 개수가 하나 더 늘어났을까요??

 https://stackoverflow.com/questions/31237042/whats-the-difference-between-select-related-and-prefetch-related-in-django-orm

이 링크의 답변을 보시면, select_related는 SQL join을 하여 데이터를 SQL서버의 테이블 일부분으로 받는 반면, prefetch_related는 또 다른 쿼리를 발생시켜서 기존 object의 불필요한 컬럼을 줄여준다(inner join을 하여 테이블의 컬럼을 늘리지 않는다는 뜻으로 받아들였습니다) 고 합니다. 

다시 한번 코드를 보면,

applicant에서 status로 가는 과정에서 역참조로 application_set을 사용하는 것은 맞습니다. 다만 prefetch를 사용하지 않아도, 그 밑의 코드에서 application_set.all() 로 역참조한 application의 쿼리셋을 가져오고, 거기에 select_related로 status를 가져오고 있습니다 

저는 prefetch를 사용하면 미리 application을 db에서 join으로 긁어와서 추후에 application_set을 사용할때 db를 다시한번 hit할 일이 없다고 생각했습니다. 그러나 prefetch_related는 "파이썬" 상에서 join을 실시하며(db에서 미리 join을 일으키지 않으며), join될 각 테이블에 대한 쿼리를 발생시킵니다.

따라서 prefecth_related를 사용했을 시 이 부분이 추가되지 않았나 생각이 듭니다


 


한번 더 실행해보니 


쿼리 숫자가 5개로 또 한번 줄어들었습니다.
그런데 화면상에 뜨는 쿼리문은 그대로이네요.. 이 부분은 알게되면 추후 포스팅하겠습니다


밑의 CompanyView를 봅시다.

이 코드의 경우 

applicant에서 application으로 가서 job_position으로 한번 더 이동 한 뒤 , 거기서 각각 company의 name / job_category의 name / 해당 테이블의 created_at을 뽑아와야 하는 구조입니다


쿼리가 무려 31개나 날아갑니다.

자세히보면 job_category, company, application_status를 가져오는 쿼리가 중복됩니다.

job_position만 select_related로 join할 것이 아니라 job_category와 company는 select_related로,  status는 prefetch로 가져옵니다. 

쿼리의 개수가 획기적으로 줄어들었습니다.




application 쿼리셋에서 시작해서, 어떻게 두다리 너머의 company와 category 까지 가져올 수 있을지 고민했는데, 그냥 job_position에서 __ 로 넘어가면 되는거였습니다.

다시한번 prefetch와 select의 차이를 알기 위하여 

prefetch 를 select_related로 바꿔주었습니다.

쿼리가 하나 줄어들었습니다.

마지막의 이 부분이 없어졌네요 



앞서 첨부한 스택오버플로우 답변글에 이런 답변이 있습니다. select_related와 prefetch_related의 쿼리 개수가 딱 하나 차이나는것이 이 답변을 증명하네요.

"prefetch_related run the query on the first model , collects all the IDs it needs to prefetch and then runs a query with an IN clause in the WHERE with all the IDs that it needs. "

이 답변에 해당하는 쿼리가 이것인듯 하네요