클라이언트-서버 통신

WooSeong·2021년 5월 8일
0

학습 노트

목록 보기
18/22

영속성 있는 데이터의 저장

자바스크립트와 HTML, CSS를 통해 아름답게 디자인 되었으며 상호작용할 수 있는 웹페이지를 만들 수 있다. 하지만, 문서란 원래 정보를 저장하고 찾아보고 활용할 수 있어야 한다. 자바스크립트에 특정한 객체를 지정해 두고 정보를 저장할 수 있지만, 해당 웹페이지를 다시 불러오는 순간에 저장된 정보는 사라지게 된다.

언제든지 정보를 저장하고 찾아보고 활용할 수 있도록 하기 위해선 웹페이지를 새로고침 하는 것과 무관하게 특정 데이터를 보관할 수 있는 장소를 마련해 두어야 한다.

현대의 웹 어플리케이션은 매순간 엄청난 양의 데이터를 처리해야 한다. 설령 자바스크립트 객체에 이러한 정보를 담아두고 다시 불러와도 남아 있게 구현할 수 있다 하더라도 많은 양의 데이터를 처리하는데 있어선 분명한 한계가 존재한다.

데이터의 보관과 웹페이지(웹어플리케이션)의 분리

그렇다면 눈에 보이는 부분을 구현하는 것과 데이터를 보관하고 정리하고 처리하는 부분을 분리한다면 각자의 역할에 충실하여 좀 더 효율적으로 작동할 수 있도록 할 수 있지 않을까?

이런 관점을 프로그래밍 구조로 나타낸 것이 클라이언트와 서버의 관계다.

클라이언트를 구현하는데 필요한 코드에 데이터를 저장하는 것이 아니라 데이터를 저장하는 다른 공간을 만들어 두고 모든 데이터를 그 공간에 저장한다.

리소스가 존재하는 곳과 리소스를 사용하는 앱을 분리시킨 것을 2-Tier 아키텍쳐, 클라이언트 - 서버 아키텍쳐라 한다.

서버의 기능을 좀 더 나누어 서버를 리소스를 전달해주는 앱으로 두고 리소스 저장 공간(데이터베이스)를 따로 둔 형태를 3-Tier 아키텍쳐라 한다.

추상화의 관점

사용자는 서버에 저장된 모든 데이터를 필요로 하지 않는다. 문서로써 비슷한 역할을 하는 사전을 생각해보면(종이로 되어 있는 '옥편') apple의 의미를 알기 위해선 천페이지가 넘어가는 사전을 한장한장 넘겨 가면서 사전식 정렬에 따라 찾아내어야 한다. 사전은 휴대하기에도 불편하며 한번 정보를 찾을 때 마다 모든 데이터를 확인해야 하는 불편함이 있다.

웹 어플리케이션에서 종이사전을 찾는것과 같은 과정이 일어난다면 얼마나 불편할까? 인터넷 쇼핑을 할때 티 한장을 찾기 위해 서버의 모든 정보를 한번에 받고 사용자가 그 중 원하는 것을 찾아내어야 한다. 단지 티셔츠 한장을 사기 위해 엄청난 양의 데이터를 받아 직접 데이터중에서 원하는 것을 찾는 것은 정말 짜증나는 과정일 것이다.

그렇기 때문에 클라이언트 단에서는 사용자가 상호작용 할 수 있는 툴을 구동하는데 필요한 정보만을 받아 구현하기만 하고 검색을 하는 등의 데이터와 상호작용 하는 행동을 하게 되면 해당 데이터는 서버단에서 검색하고 찾아내어 특정 정보만 클라이언트단에 보내도록 구조화 되어 있는 것이다.

그리고 이렇게 주고 받는 과정이 있기 때문에 클라이언트와 서버는 '통신'을 해야 한다.

HTTP와 API

세상에는 수없이 많은 클라이언트와 서버가 있고 이들이 모두 각자의 규격에 따라 통신한다면 다른 클라이언트나 서버에 접속할때 마다 해당 규격을 숙지해야 한다. 어떤 환경에서든 호환되는 통신을 구축하기 위해선 일종의 약속이 필요하다.

HTTP(HyperText Transfer Protocol)은 W3 상에서 정보를 주고 받을수 있는 프로토콜(규약)이다.

요청과 응답(Request and Response)

클라이언트는 서버에게 특정 데이터나 행동을 '요청' 한다. 서버는 클라이언트의 요청을 받고 그에 따른 약속된 '응답'을 클라이언트에게 준다. 이러한 요청과 응답은 특정 언어로 통신하겠다는 약속하에 이루어 진다. HTTP가 바로 그 약속이다.

프로토콜은 HTTP만 존재하지 않는다. 메일 통신에 사용되는 SMTP, 파일 전송에 사용되는 FTP등도 통신 규약이다.

요청측 : 클라이언트

클라이언트는 사용자를 대신하여 동작하는 모든 도구를 의미한다. 브라우저, 어플리케이션, 웹어플리케이션, POS등 여러 형태가 있다. 가장 흔하게 생각할 수 있는 것은 브라우저 이기 때문에 클라이언트를 브라우저로 단순화 시켜서 생각해 보도록 하자.

브라우저는 '항상' 요청을 보내는 개체입니다. 그것은 결코 서버가 될 수 없습니다.

요청 메세지

HTTP를 통해 클라이언트가 서버에게 요청을 보낼때 미리 정해진 규칙(구조)에 따라 메세지를 보낸다. 요청 메세지는 다음으로 구성된다.

  • 메서드 : 클라이언트가 서버에게 요청하는 행동을 나타낸다.

    • GET : 특정 리소스의 표시를 요청, 오직 데이터를 받기만 하며 브라우저가 처음 가동될때 처음보내는 (CORS 상황이 아니라면) 메서드이다. 멱등성을 충족한다.(CRUD ⇒ Read)

    • POST : 특정 리소스를 서버에 제출할때 사용하는 메서드이다. 멱등성이 없으며 서버에 변화를 일으킨다.(CRUD ⇒ Create)

      멱등성? 멱등성이란 동일한 요청을 한 번 보내는 것과 여러 번 연속으로 보내는 것이 같은 효과를 지니는 것을 의미한다.

    • PUT : 서버의 특정 리소스를 요청 Payload로 교체한다. 멱등성을 충족한다. (CRUD ⇒ Update)

    • DELETE : 서버의 특정 리소스를 삭제한다. 멱등성을 충족한다.(CRUD ⇒ Delete)

    • OPTIONS : 특정 리소스의 통신을 설정하는데 사용하며 CORS와 관련된다.


    자주 사용되는 메서드는 위와 같으며 그밖에 메서드로는

- PATCH : 서버의 특정 리소스 중에 일정 부분만 수정하는데 사용된다.
- HEAD : GET과 동일하지만 응답 본문을 포함하지 않는다. (응답의 헤더만 읽는다.)
- CONNECT, TRACE
  • 경로 : 서버에 라우팅이 설정되어 있다면 특정 리소스로 접근하기 위한 라우팅 경로를 표시한다.
  • 프로토콜 버전 : 현재 통신에 사용되는 프로토콜과 그 버전을 나타낸다.
  • 헤더 : 메서드, 경로, 프로토콜은 가장 윗 줄에 한줄로 표시되며 이후 내용은 헤더이다. 헤더는 필수 정보 외에 부가적인 정보를 전달해 준다.
  • 본문 : 헤더 밑에 위치하는 전송되는 정보의 본문이다. 서버를 통해 얻으려는 최종적인 데이터는 본문에 표시된다. json을 비롯한 여러 형식으로 전송된다.

출처 MDN : HTTP 개요

응답측 : 서버

클라이언트에 의한 요청에 대한 문서를 제공하는 논리적 단일 기계, 물리적인 단일 기계가 아닌 것에 주의해야 한다.

응답 메세지

클라이언트의 요청을 받은 서버는 요청 메세지를 해석하고 이에 맞는 응답을 HTTP 에 맞추어 클라이언트에게 보낸다. 응답 메세지는 다음으로 구성된다.

  • 프로토콜 버전 : 응답에 사용되는 프로토콜 버전을 명시한다.
  • 상태 코드 : 클라이언트 요청에 대한 서버의 응답을 함축적으로 나타내어 주는 3개 숫자다.
    • 100 : 임시적인 응답이며 지금까지의 상태가 괜찮다는 것을 의미한다.
    • 200 : 요청에 대해 성공하였음을 의미한다.
      • 200 OK : 요청이 성공적으로 되었음을 의미한다. 성공은 HTTP 메서드에 따라 의미가 달라진다.
      • 201 Created : 요청이 성공적 이었으며 새로운 리소스가 서버에 생성 되었음을 의미한다.
    • 300 : 요청에 대해서 하나 이상의 응답이 가능하다. 클라이언트 에서 이 중 하나를 반드시 선택해야 한다.
    • 400 : 서버가 요청을 이해할 수 없다는 것을 의미한다. 클라이언트단의 잘못된 요청
      • 403 Forbidden : 클라이언트가 컨텐츠에 접근할 권리가 없다는 의미
      • 404 Not Found : 요청받은 리소스를 찾을 수 없다.
    • 500 : 서버가 처리 방법을 모르는 상황이 발생, 서버단의 잘못 혹은 서버 연결이 안되었을 경우를 의미한다.
      • 502 : Bad Gateway : 게이트웨이가 잘못되어 잘못된 응답을 수신했다는 의미
      • 504 : Gateway Timeout : 서버 연결시간이 초과 되었다는 의미
  • 상태 메세지 : 상태 코드와 함께 오는 메세지로 OK, Created, Not Found 등이 있다.
  • 헤더 : CORS 상황일때는 CORS preflight에 대한 응답으로 Access-Control-Allow를 헤더에 실어 보낸다. 본문의 형식도 헤더에 적혀 있다.(Content-Type)
  • 본문

출처 MDN : HTTP 개요

Content-Type

응답 헤더의 구성 요소중 Content-Type은 응답 본문이 어떤 형식인지 알려준다. 일종의 '확장자' 개념인데 파일을 저장하거나 열어볼때 확인할 수 있는 확장자는 파일이 어떤 형태를 가지고 있는지 정보를 대략적으로 알려준다.(메모장은 .txt, 압축파일은 .zip, 자바스크립트파일은 .js와 같이 말이다.) Content-Type은 MIME 타입으로 표시된다.

MIME 타입

MIME 타입은 클라이언트에게 전송된 문서의 형태를 알려주기 위한 매커니즘이다. 각 문서와 함께 올바른 MIME 타입을 전송하도록 서버가 정확히 설정하는 것이 중요하다.

일반적인 MIME 타입의 구조는 다음과 같다 : type/subtype

대표적인 MIME 타입은 이런게 있다.

  • text/plain
  • image/jpeg
  • audio/ogg
  • video/mp4
  • application/json

API (Application Programming Interface)

클라이언트와 서버와의 통신에 있어서 서버가 보내는 응답은 서버를 설계한 백엔드 개발자의 특정 목적에 따라 특정 데이터를 취사 선택하여 특정 형태로 보내게 된다. 이때 해당 정보를 받아 클라이언트 에이전트에 구현해야 하는 프런트엔드 개발자는 응답에 대하여 사전 정보가 없으면, 혹은 요청을 할때 해당 자료로 접근하기 위한 경로라던가 메서드를 알지 못한다면 원하는 데이터에 접근할 수 없게 된다.

이때 API는 프런트엔드 개발자에게 일종의 '설명서' 역할을 한다.

중요한건 API가 HTTP 통신을 위해서만 존재하는 개념이 아니다. API의 일반적인 개념은 다음과 같다.

API(Application Programming Interface 애플리케이션 프로그래밍 인터페이스, 응용 프로그램 프로그래밍 인터페이스)는 응용 프로그램에서 사용할 수 있도록, 운영 체제나 프로그래밍 언어가 제공하는 기능을 제어할 수 있게 만든 인터페이스를 뜻한다. 주로 파일 제어, 창 제어, 화상 처리, 문자 제어 등을 위한 인터페이스를 제공한다.

RESTful API (Representational State Transfer)

API중 웹 통신에 초점을 맞추어 웹의 활용성을 늘리기 위해 고안된 API가 바로 RESTful API다. Roy T.Fielding의 Web을 망가트리지 않고 HTTP를 개선하기 위한 API 설계 철학을 의미하기도 한다. 해당 설계 철학에 기준을 두고 기준을 충족하기 위한 제약의 모음은 다음과 같다.

6가지 제약

  1. Client - Server : 요청이 HTTP를 통해 이루어지는 클라이언트-서버 아키텍쳐
  2. Stateless : 각 요청과 응답이 이전의 요청과 응답과 연결되어 있지 않고 관계가 없는 상태
  3. Cacheable : 캐싱가능해야 한다.
  4. Layered System : 서버는 다중 계층으로 구성될 수 있으며, 프록시나 게이트웨이 같은 네트워크 기반의 중간 매체를 사용할 수 있고 로드 밸런싱을 두어 구조상의 유연성을 둘 수도 있다.
  5. Code on Demend : 사용자의 요구에 따라 그 요구에 따른 코드가 호출되어야 한다.(Optional)
  6. Uniform interface : URI로 지정한 리소스에 대한 동작을 통일되고 한정적인 인터페이스로 수행해야 한다.

6가지의 제약중 4번까지의 제약은 HTTP를 통해 만족할 수 있다. 5번 제약은 자바스크립트로 구현 가능하다. RESTful API를 구현하기 위해 개발자가 주의해야 할 제약은 6번째이다.

Uniform interface

이를 구현하기 위한 조건은 다음과 같다.

  • Identification of resources : API의 URI는 자원을 표현하는데 중점을 두어야 한다. 행위를 표현하는 것은 옳지 않다.(자원의 식별) URI에서 리소스를 나타낼때는 명사를 사용해야 한다.
  • Manipulation of resources through representation : 표현을 통한 자원의 수정 ⇒ 자원에 대한 행위는 HTTP 메서드로 표현해야 한다.
  • Self-descriptive messages : RESTful API 메세지만 보고도 요청과 응답이 무엇을 하려하는가를 이해할 수 있어야 한다.
  • Hypermedia As The Engine Of Application State(HATEOAS) : 클라이언트가 특정 리소스에 접근하기를 원한다면, 리턴되는 지시자에 의해 구별될 수 있어야 한다. ⇒ wikipedia의 글을 읽던 도중 관련된 URI로 연결되는 하이퍼텍스트 링크가 예시

Best Practice

RESTful API를 구현하기 위해 참고해야 하는 가장 효율적인 주의사항은 다음과 같다.

  1. 리소스를 나타내는데 명사를 사용해야 한다.
  2. 일관성을 지켜야 한다.
    • 슬래시 구분자(/)는 계층 관계를 나타낼때 사용한다.
    • URI 마지막 문자로 슬래시(/)를 포함하지 않는다.
    • 하이픈(-)은 URI 가독성을 높이는데 사용한다.
    • 언더바(_)는 URI에 사용하지 않는다.
    • URI 경로에는 소문자가 적합하다.
    • 파일 확장자는 URI에 포함시키지 않는다.
  3. CRUD는 URI에 사용하지 말아야 한다.
  4. filter가 필요하면 query component를 사용해야 한다.

CORS (Cross-Origin Resource Sharing)

초기의 클라이언트는 단일 서버에게서 모든 정보를 받았다. 페이지 구성에 대한 정보(HTML)와 디자인(CSS) 그리고 데이터는 모두 단일 서버에 저장되어 있었고 클라이언트는 서버의 응답을 표시해주기만 했으므로 클라이언트에 의한 서버로의 위협은 상대적으로 작았다.

서버에서 내려준 정보만을 가지고 읽고 쓰기 때문에 해당 출처는 안전하였다.

고도화된 최근의 웹

최근의 웹은 단일 서버에서 모든 정보를 받지 않는다. HTML과 CSS를 구현하는 웹서버와 데이터의 처리를 담당하는 Database와 서버단이 분리되어 있고 필요에 따라 여러 출처에서 데이터를 가져온다. 이와 같이 서버가 신용할 수 없는 출처에 대해 접근해야 하기에 서버의 안정성이 취약해 졌다.

보안상의 문제를 해소하기 위해 브라우저에선 서버에게 무작정 요청을 보내지 않고 현재 클라이언트와 서버가 다른 출처를 따른다면 미리 서버에게 '내가 지금 이걸 보낼건데 괜찮아?' 라고 물어보도록 하게 된다.

동일 출처 정책(same-origin policy)

동일한 출처(Origin)에서 온 요청인지 확인하는 기준은 동일 출처 정책을 따른다. SOP에 따르면 동일 출처는 다음을 의미한다.

프로토콜과 포트, 호스트(도메인) 3가지가 모두 동일하면 동일 출처로 본다.

SOP에 따라 동일 출처가 아니라면 CORS 상황이 발생한다.

CORS preflight

SOP에 반하여 동일하지 않은 출처로 판단이 될경우 '브라우저'는 서버에게 본 요청을 보내기 전에 미리 사전 요청을 보낸다. '내가 지금 이걸 보내려 하는데 괜찮아?'라는 메세지를 서버에게 미리 보내고 서버는 해당 '요청'에 대해 Access-Control-Allow-Origin과 같은 Access-Control-Allow 헤더로 '응답'한다.

Access-Control-Allow 헤더

CORS preflight에 대한 서버의 응답은 헤더에 다음 4가지의 서버 규칙을 클라이언트로 전송하게 된다.

  • Access-Control-Allow-Origin : 서버가 허용하는 도메인을 알려준다.
    • * : 모든 도메인에 대해 허용한다.
    • 특정 도메인 주소 : 해당하는 도메인만 허용한다.
  • Access-Control-Allow-Methods : 서버가 허용하는 HTTP 메서드를 알려준다.
  • Access-Control-Allow-Headers : 서버가 허용하는 헤더를 알려준다.
  • Access-Control-Max-Age : 동일 preflight 요청에 대한 수락 유지 시간을 알려준다. 초단위로 되어있다.

CORS preflight 의 예외

CORS preflight가 필요하지 않은 단순 요청(simple requests)들이 있다. 요청이 단순 요청에 해당하려면(CORS preflight를 트리거 하지 않으려면) 다음과 같은 조건을 '모두' 만족해야 한다.

  • 다음 중 하나의 메서드 : GET, HEAD, POST
  • 유저 에이전트(브라우저 등)가 자동으로 설정한 헤더 : Accept, Accept-Language, Content-Language, DPR, Downlink, Save-Data, Viewport-Width, Width, Content-Type
    • Content-Type은 다음만 허용된다.
      • application/x-www-form-urlencoded
      • multipart/form-data
      • text/plain
  • 요청에 사용된 XMLHttpRequestUpload 객체에는 이벤트 리스너가 등록되어 있지 않음
  • 요청에 ReadableStream 객체가 사용되지 않음.

preflight 응답에 대한 브라우저의 행동

preflight 응답을 받은 브라우저는 해당 응답의 헤더에 Access-Control-Allow 항목들을 해석하여 보내려고 하는 원래의 요청을 서버가 허용하는지 판단한다. 만일 서버 허용 여부를 벗어난다면 해당 요청을 브라우저에서 거부한다.

profile
성장하는 개발자를 꿈꿉니다

0개의 댓글