초기 웹 페이지는 단순한 서버-클라이언트의 구조를 따랐다. 처음부터 TCP / IP 위에서 구현되도록 설계되었다. 지금의 TCP는 무겁고 느리다고 까이지만, 이 당시에는 연결 지향적 특성 때문에 안정적이고 신뢰성있는 통신을 제공하여 평이 좋았다.
초기 HTTP 구조는 매우 간단했다. 요청 메서드도 GET 한가지였고, 헤더나 상태 코드도 없었고 응답은 무조건 HTML 파일 그 자체였다.
하지만 웹이 인기를 끌다 보니 기존 HTTP 사양만으로는 사용자들의 요구사항을 충족시킬 수 없었고 명시적으로 약속된 사양이 없어 여러 브라우저들 간의 혼란이 있어서 HTTP 기본 구조를 그대로 유지하면서 HTTP를 표준화하기 위해 HTTP WG 조직이 탄생하고 이 때 HTTP/1.0이 탄생하게되었다.
📌 HTTP/1.0
HTTP Header에 버전정보 명시되어있다.
요청 메서드 GET, POST, HEAD 3가지
상태 코드 추가 되어 클라이언트 측에서 요청 결과에 따라 동작하도록 설계 가능
Content-type의 도움으로 HTML 이외의 파일도 전송할 수 있게되었다.
1 2 3 4 5 6 7 8 9 10 11 12
<!-- 요청 --> GET /mypage.html HTTP/1.0 User-Agent: ...
<!-- 응답 --> 200 OK ... Content-Type: text/html <HTML> A page with on image <img src="/myimage.jpg" /> </HTML>
HTTP/1.0의 간단한 요청과 응답 예시이다.
📌 HTTP/1.1
❓ HTTP/1.0의 문제
HTTP/1.0에서는 요청에 따른 응답이 수신되면 TCP 연결 바로 종료
웹 페이지가 복잡해짐에 따라 이러한 TCP 핸드쉐이크 과정을 새로 거쳐야 하여 속도가 느려지는 문제가 있었다.
1개의 요청을 보내면 1개의 응답을 받고 1번의 연결이 종료된다. 만약 10번의 요청을 보내면 TCP 연결을 10번 해야하므로 서버에 부담이 된다.
❗️ 문제 해결책
연결 상태 유지(Persistent connection) : 기본적으로 한번 수립한 연결을 재사용 할 수 있도록 설정
지정한 시간동안 연결을 끊지 않는 방식
연결 유지 시간 길어지면 서버 부하 🔺
Pipelining(파이프라이닝) : 클라이언트가 여러 요청 연달아 보내야할 때, 먼저 보낸 요청의 응답을 기다리는 것이 아니라 발생한 요청은 일단 전송하는 방식
클라이언트에서 여러 요청을 순차적으로 보내면 서버는 받은 순서에 따라 응답을 제공한다.
📌 HTTP/2
❓ HTTP/1.1의 문제
매 요청마다 헤더를 중복해서 전송해야만 하는데 이것이 굉장한 낭비였다.
서버가 항상 요청받은 순서대로 응답해야하므로 HOLB(Head-of-Line Blocking) 방식이었다.
하나의 연결 내에서 응답 다중화(multiplexing)을 할 수 없어 요청이 순차적으로 처리되어야 했는데, 서버가 응답 작성 중간에 문제가 생기면 후속 요청들이 전송되지 못하고 지연되는 문제가 있었다.
❗️ 문제 해결책
SPDY 프로토콜 기반으로 동작 ⇒ 웹 페이지 로드 시간 단축
이진 프로토콜 : 단순 텍스트를 전송하는 것보다 컴퓨터 입장에서 훨씬 더 효율적으로 데이터 전송 가능
응답 다중화 지원 : 하나의 TCP 연결에서 여러 요청을 동시에 처리할 수 있다. 바이너리 프레이밍으로 TCP 연결을 스트림, 메시지, 프레임으로 세분화하였다. 이는 요청 별 순서를 반드시 지켜야 했던 HTTP/1.1과 대조적이다.
스트림 : 요청과 응답이 양방향으로 오가는 연결 단위
메시지 : 하나의 요청과 응답을 구성하는 단위
프레임 : 메시지를 구성하는 최소 단위
위 사진에서 하나의 TCP 연결에서 3개의 스트림이 존재한다.
5번 스트림은 클라이언트 측에서 서버로 데이터 전송하고, 1,3번 스트림은 서버 측에서 클라이언트로 데이터를 전송중이다.
1번 프레임 사이에 3번 스트림의 프레임이 끼워져있다.
이로써 HTTP/1.1에서 발생한 HOLB 문제 해결할 수 있었다.
헤더 필드 압축 지원 : 달라진 부분만 다시 전송하는 코딩 기법 사용
📌 HTTP/3
❗️ TCP로 인한 문제점
앞서 헤더 필드 압축을 통해 HTTP/1.1에서 발생한 HOLB 문제를 해결했다고 하였지만 이와 별개로 TCP로 인해 발생하는 HOLB 문제는 해결할 수 없다는 한계가 있었다.
신뢰성 지향하기에 데이터 손실 발생 시 데이터를 재전송하는데 순서대로 처리해야하기 때문에 HOLB 문제를 해결할 수 없다.
혼잡제어를 수행하기 때문에 전송 속도를 낮은 상태에서 높이는 방식으로 속도 제어를 하는데 이는 네트워크 상황이 좋을 때 불필요한 지연을 발생시킨다.
문제를 해결하기 위해 TCP/IP기반이 아닌 UDP기반인 QUIC 프로토콜 위에서 동작하는 HTTP/3가 나왔다.
⇒ UDP는 신뢰성을 보장하지 않는데 QUIC 신뢰성 기능을 직접 구현하여 신뢰성 기능이 제공되는 UDP 기반의 프로토콜이다.
👍 HTTP/3 장점
0-RTT 기능 : 연결 정보를 캐싱하여 재사용하여 다음 연결때는 바로 연결이 가능
연결 다중화 지원 : 각 스트림이 독립적으로 동작한다.
HTTP/2 방식의 경우 TCP 특성상 데이터 손실이 발생할 경우 데이터 복구를 우선 처리하여 HOLB가 발생한다.
HTTP/3는 데이터 손실이 발생해도 연결 내 스트림이 독립적으로 동작하기 때문에 다른 스트림에 영향을 주지 않는다.
연결 별 고유 UUID 지원
TCP 기반의 통신은 wifi 환경에서 셀룰러 환경으로 이동하는경우 IP주소가 변경되어 연결 재수립 과정을 거쳐야하지만, QUIC는 UUID(Connection ID) 연결 ID 기반으로 식별하여 연결을 유지할 수 있다.
📌 HTTP Message
HTTP 메시지는 서버와 클라이언트 간에 데이터가 교환되는 방식이다. 메시지 타입에는 요청(Request), 응답(Response)이 있다.
요청과 응답의 구조는 유사하다.
실행 되어야 할 요청 또는 요청의 성공, 실패가 기록된 한줄
요청에 대한 설명, 메시지 본문에 대한 설명이 포함된 HTTP Header 세트(optional)
요청에 대한 모든 메타 정보가 전송되었음을 알리는 빈 줄(blank line)
요청과 관련된 내용이 옵션으로 들어가거나, 응답과 관련된 문서가 들어간다. 본문의 존재 유무는 첫 줄과 HTTP Header에 명시된다.
✏️ HTTP Request
요청은 클라이언트가 서버로 전달해서 서버의 액션이 일어나게끔 하는 메시지이다.
1. Start Line
HTTP 메서드
GET,POST 등의 서버가 수행해야 할 동작을 나타낸다.
요청 타겟 : URL 또는 (프로토콜,포트, 도메인)
요청 타겟 포맷은 HTTP 메서드에 따라 달라진다.
Origin 형식 : 끝에 ‘?’와 쿼리 문자열이 붙는 절대경로이다. 가장 일반적인 형식으로 GET, POST, HEAD, OPTIONS 메서드와 함께 사용한다.
1 2 3 4
POST / HTTP 1.1 GET /background.png HTTP/1.0 HEAD /test.html?query=alibaba HTTP/1.1 OPTIONS /anypage.html HTTP/1.0
absolute 형식 : 완전한 URL 형식으로, 프록시에 연결하는 경우 대부분 GET과 사용한다.
1
GET http://developer.mozilla.org/en-US/docs/Web/HTTP/Messages HTTP/1.1
마지막으로 HTTP 버전이 들어간다.
2. Header
대소문자 구분없는 문자열 다음에 콜론(:)이 붙고 그 뒤에 오는 값은 Header에 따라 달라진다.
HTTP Method
HTTP Version
Host
Content-Type
3. Body(본문)
모든 요청에 본문이 들어가지 않는다. GET, HEAD, DELETE, OPTIONS 처럼 리소스를 가져오는 요청은 본문이 필요 없다.
보통 POST 요청으로 서버에 데이터를 업데이트 하기 위해 본문이 필요하다.
본문은 두가지 종류로 나뉜다.
단일-리소스 본문(single-resource bodies) : Header 2개(Content-Type, Content-Length)로 정의된 단일 파일
다중-리소스 본문(muliple-resource bodies) : 파트마다 다른 정보를 지닌다.
✏️ HTTP Response
응답은 요청에 대한 서버의 답변이다.
1. Start Line
프로토콜 버전 (보통 HTTP/1.1)
상태 코드 : 요청의 성공 여부를 나타낸다. 200, 404 또는 302
상태 텍스트 : 상태 코드에 대한 설명글
1
HTTP/1.1 404 Not Found.
2. Header
요청 Header와 동일한 구조이다.
HTTP Version
Status
Content-Type
Set-Cookie
3. Body
모든 응답에 Body가 들어가지는 않는다. 201, 204 상태 코드를 가진 응답은 Body가 없다.
📌 HTTPS
기존 프로토콜에서 데이터가 쉽게 도난당하는 것을 방지하기 위해 SSL(Secure Socket Layer) 또는 TLS(Transport Layer Security) 프로토콜 을 사용하여 브라우저와 서버를 암호화하여 연결해준다.
또한 데이터가 전송중에 손상되거나 수정되는 것을 방지한다.
구글같은 포털에서 SEO 가산점을 준다.
✏️ SSL(Secure Socket Layer) 프로토콜
HTTPS 프로토콜이 텍스트를 암호화 할 수 있도록 도와주는 프로토콜이다. 암호화 원리는 공개키 암호화 방식이다.
SSL 프로토콜 통제권을 IETF로 넘기면서 TLS로 바뀐 상태인데 현재는 두가지를 혼용해서 사용하고 있다.
공개키 암호화 방식
공개키 암호화 방식은 공개키와 개인키를 사용하는 방식이다.
공개키는 모두에게 공개해서 사용하는 키이다.
개인키는 서버에서만 가지고 있는 키이다.
예시
A 서버가 HTTPS 적용하기 위해 공개키와 개인키를 만든다.
CA 기업을 선택하고 해당 CA 기업에게 공개키 관리를 부탁하며 계약한다.
CA 기업은 기업명, A 서버 공개키, 공개키 암호화 방법을 담은 인증서를 만들고 해당 인증서를 CA 기업의 개인키로 암호화해서 A 서버에게 제공한다.
A 서버는 CA 기업의 개인키로 암호화된 인증서를 가지고서 클라이언트로부터 HTTPS이 아닌 요청이 오면 암호화된 인증서를 건네준다.
클라이언트는 예를 들어 main.html 을 서버에 요청했다고 하자. HTTPS 요청이 아니기 때문에 CA기업의 개인키로 암호화된 인증서를 받게된다.
CA 기업의 공개키는 모두에게 공개되므로 클라이언트는 인증서를 공개키로 복호화하여 A 서버 공개키를 얻었다.
클라이언트가 A 서버에 요청을 보낼 때 텍스트를 공개키로 암호화하며 HTTPS 요청을 보낸다.
서버는 개인키로 암호화된 텍스트를 복호화하여 데이터를 해석하고 응답을 개인키로 암호화하여 클라이언트에게 보낸다.
과거의 리액트는 상태를 가지는 컴포넌트는 클래스형으로, 상태를 가지지 않는 컴포넌트는 함수형으로 작성하였다. 왜 그랬을까?
Container 컴포넌트는 상태를 가지는데, 함수형 컴포넌트로 생성하게 되면 함수 호출하여 컴포넌트를 생성할 때 마다 상태가 초기화된 값을 가지기 때문에 Container 컴포넌트는 클래스형으로만 작성하였다.
this를 사용하기 때문에 클래스형 컴포넌트가 생성방식이 더 복잡하다.
클래스형 컴포넌트에는 render() 메서드가 꼭 있어야 한다.
클래스형 컴포넌트에서 이벤트 등록 시 this 바인딩을 꼭 해줘야만 한다.
📌 stateless 컴포넌트
stateless 컴포넌트는 말 그대로 상태를 가지지 않는 컴포넌트이다. 이러한 컴포넌트를 stateless 컴포넌트 또는 presentational(프레젠테이셔널) 컴포넌트라고 한다.
목적
시각적 표현에 중점을 둔 컴포넌트
시각적 표현을 목적으로 하니 복잡한 비즈니스 로직 필요없다
상태를 가질 필요가 없고 외부에서 전달 받은 데이터를 화면에 그려준다.
✏️ stateless 컴포넌트는 함수형으로 작성한다?
표현을 목적으로 하는 컴포넌트는 복잡한 비즈니스 로직이 필요없고 이를 클래스로 작성할 경우 Babel 컴파일러가 클래스를 컴파일 해줄 때 함수형 컴포넌트일 때 보다 훨씬 더 많은 코드로 컴파일 해주기 때문에 stateless 컴포넌트를 클래스형으로 작성하는 것은 낭비이다.
📌 stateful 컴포넌트
상태를 가지는 컴포넌트를 stateful 컴포넌트 또는 Container(컨테이너) 컴포넌트라고 한다.
목적
비즈니스 로직을 가지고 있다.
state(상태)를 가진다.
Presentational 컴포넌트에게 Props를 전달하여 UI 렌더링 하도록 한다.
화면을 그리는 최소한의 스타일 정보를 가진다.
❓ stateful 컴포넌트는 함수형으로 작성하지 않는다?
하지만, React Hook의 등장으로 함수형 컴포넌트로도 Container 컴포넌트의 역할이 가능해졌다.
🏓 소감
클래스형 컴포넌트와 함수형 컴포넌트에 차이에 대해 알게되었다.
프로젝트를 시작하게 될 때, 컴포넌트를 생성할텐데 이 때, 이 컴포넌트가 상태를 가지는 컴포넌트인지 상태를 갖지 않는 컴포넌트인지 구분을 짓고 컴포넌트를 생성해야겠다.
오늘날 리액트에서 React Hook이 등장하게 되면서 클래스형으로만 작성되던 stateful Component가 어떻게 함수형 컴포넌트로 바뀌게 되었는지를 배우게 되면 리액트에 대한 이해가 한층 더 깊어질 것 같다.
App이라는 클래스는 야구게임 App이다. 야구 게임의 룰을 변경하지 않는 이상 인스턴스를 생성할 때마다 초기화할 값이 없다. 그러므로 초기화 하지 않는 프로퍼티 값들은 클래스 필드에 정의해두었다.
❗️ 요구사항 제대로 읽기
요구사항 제대로 읽지 않아서 라이브러리 사용하여 랜덤값 구하면 되는데, 랜덤값 구하는 로직 직접 짜서 다시 처음부터 시작하게되었다.
또 제대로 안읽어서 특정 메서드 사용하여 구하라했는데 라이브러리에서 제공하는 다른 메서드로 구현하여 다시 작성했다…
❗️jest를 통한 테스트 케이스는 어떻게 확인할 수 있는지 알게되었다.
주어진 라이브러리를 활용하여 1번은 테스트 하는 것을 알 수 있었다. 그런데 2번째 값을 얻기 위해서 내가 해주어야 하는 것이 무엇인지 모르겠다.
내가 직접 예상되는 값을 생각하여 테스트에 입력값만 적어주고 소스코드에서는 해당 입력값이 넘어왔다가 가정하고 그에 대한 처리를 해주면 되는 것이라는 것을 알게되었다.
❗️랜덤값 받은 것을 프로퍼티로 저장해두자
함수형 프로그래밍을 하면서 함수를 호출하고 반환된 값을 다른 함수에게 전달하는 방식으로 랜덤값과 입력값을 비교하고 결과를 반환하는 방식으로 하였는데, 이 방법보다는 App 이라는 클래스가 랜덤값과 입력값을 가져 한곳에서 이 데이터를 가져다가 사용하는 것이 적절하다고 판단하여 위와 같이 프로퍼티로 저장해두었다.
❗️형변환 확인하기
테스트 케이스를 작성할 때, answers 배열의 요소를 문자열로 넣어준 것을 제대로 확인하지 않아 왜 자꾸 문제가 발생하는지 제대로 파악하기 어려웠다. 아마도 Jest를 사용한 디버깅이 익숙하지 않은 탓이었다. 때문에 여기서 의외로 많은 시간을 보냈다. 단위 테스트를 자주 하면서 Jest 디버깅에도 익숙해지도록 노력해야겠다.
🏓 소감
저번주에 이어 2주차에 접어들게 되었다. 학원이 끝나고 혹은 주말에 틈틈히 문제를 풀었는데도 익숙하지 않는 Jest 테스트 방법 때문에 해결방법도 모른체로 삽질(?) 을 많이했던 한주였다.
그래도 주변에 물어도보고 검색도 하면서 구현까지는 어찌저찌 완성하였다.
하지만 리팩터링에 시간을 많이 쏟지 못한 것이 아쉬웠다. 우테코 문제 푸는 것도 중요하지만 당장 리액트 수업도 복습해야하고, CS 공부도 해야하고 알고리즘 문제도 풀어야하고 해야할 일이 너무 많다.
// ToggleButton.test.jsx describe("ToggleButton 컴포넌트", () => { test("활성 상태 여부에 따라 활성(ON)/비활성(OFF) 텍스트가 표시됩니다.", () => { let onText = "ON"; let offText = "OFF";