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는 저차원 클래스라고 생각됩니다.