History API

나혜수·2023년 2월 16일
0

자바스크립트 실전

목록 보기
13/19

Session

세션의 개념

세션이란 사용자가 웹 브라우저를 통해 서버에 접속한 시점부터 웹 브라우저를 종료하여 연결을 끝내는 시점까지 같은 사용자로부터 오는 일련의 요청을 하나의 상태로 보고 그 상태를 일정하게 유지하는 기술이다.
→ 클라이언트 별로 각각의 상태 정보를 "서버" 에서 저장하는 기술이다.

서버는 브라우저 별 개별 저장소인 Session 객체를 제공한다. 정보가 서버에 저장되기 때문에 쿠키보다 보안성이 우수하지만, 서버 부하가 발생할 수 있다.

stateless HTTP에게 기억력을!

The Hypertext Transfer Protocol (HTTP) is a stateless application-level protocol

HTTP는 상태를 저장하지 않는 특징을 가진다. 즉, 서버는 클라이언트의 정보를 저장하지 않고 HTTP로 하는 클라이언트의 모든 요청을 독립적으로 취급한다. 이런 식으로 처리하면 서버는 클라이언트를 위해 별다른 저장 공간을 할애하지 않아도 되기 때문에 많은 양의 요청을 처리할 수 있는 성능을 확보할 수 있다.

🧐HTTP 요청이 서로 독립적이라면 해당 요청을 누가 보낸 건지, 요청한 데이터를 보내도 되는지 서버는 어떻게 알 수 있을까?

세션은 HTTP가 지원하지 않는 클라이언트 정보를 잠시 저장할 수 있는 특별한 저장 공간이면서, 클라이언트가 서버에 실제로 연결되어 있진 않지만 마치 연결되어 있는 것처럼 만드는 논리적 연결을 의미한다. 즉, HTTP 프로토콜은 비접속형 프로토콜이기 때문에 매 접속마다 새로운 네트워크 연결이 이뤄지는데, 세션이 연결 유지를 가능하게 한다.


세션의 메커니즘

세션은 언제 어떻게 만들어질까? 처음 클라이언트의 요청을 받았을 때 클라이언트에 대한 정보가 없으면, 각 클라이언트에 대한 고유한 ID를 만들고 요청에 대한 응답 시 ID를 알려준다. 이후부터 클라이언트가 세션ID와 함께 요청을 하게 되면, 서버는 세션 ID로 클라이언트를 구분하여 클라이언트 요구에 맞는 서비스를 제공한다.

서버가 클라이언트 ID를 어떤 방법으로 추적할 것인지 정의한 것을 세션 트래킹 모드라고 한다. 트래킹 모드에는 ① 쿠키 사용 모드, ② URL Rewriting 모드, ③ SSL 모드가 있는데 대부분 서버에서 쿠키 사용 모드를 기본값으로 하고 있다.

쿠키 사용 모드에서 클라이언트는 세션 ID를 쿠키에 저장한다. 추후 요청을 할 때마다 쿠키에 담긴 ID를 같이 보내서 서버가 어떤 클라이언트의 요청인지 알 수 있게 한다.

🍪 클라이언트의 세션 ID가 저장된 쿠키를 세션 쿠키라고 부르기도 한다. 이때 브라우저가 종료되면 자동으로 없어지는 세션 쿠키와 세션을 저장하고 있는 쿠키는 다른 의미이니 혼돈하지 말자!



Hashbang

해시뱅은 말그대로 해시(#)와 뱅(!)이 합쳐진 단어로 SPA를 구현하기 위해서 사용되었다. 트위터의 URL에 들어가게 되면서 유명해졌는데 현재 트위터에서는 사용하고 있지 않다.

자바스크립트의 성능이 급격히 증가하면서 페이지 전체를 로딩하는 대신, 페이지를 딱 한 개만 두고 자바스크립트만으로 모든 메뉴를 다루는 것에 대한 요구가 생겼다.
이를 구현하기 위해서는 페이지가 갱신되지 않는 것이 중요한데, 예전 기술로는 페이지 갱신없이 URL을 변경할 수 있는 방법이 없다. 그렇기 때문에 페이지 갱신없이 URL이 변경되는 것처럼 보이게 하기 위해 해시뱅을 사용했다. 해시뱅에서 # 뒤에 붙는 부분을 fragment identifier 라고 부른다.

❗현재는 HTML5의 Histroy.pushState( )가 모든 브라우저에서 지원되므로 해시뱅은 사용하지 않아도 된다. → MDN History API


해시뱅이 동작하는 방법

한 페이지 안에서 점프하는 앵커
다른 페이지 말고 한 페이제 내에서도 링크를 만들 수 있다. 앵커라고 불리는 이 기능은 페이지가 긴 웹 문서에서 특정 요소를 클릭하면 특정 위치로 한 번에 이동하도록 도와준다.
앵커를 사용하려면 우선 이동하고 싶은 위치마다 id 속성을 이용해 앵커를 만들고 각각 다른 이름을 지정한다. 그리고 <a href="#앵커이름">으로 링크하면 된다.

<태그 id="앵커이름"> 텍스트 </태그>
<a href="#앵커이름"> 텍스트 or 이미지 </a>
  1. 사용자가 https://127.0.0.1/#!/device 요청

  2. 브라우저가 # 뒤의 !/device fragment identifier 를 로컬에 저장 후 https://127.0.0.1 서버를 호출

  3. 서버는 해당 페이지의 html을 대량의 자바스크립트 링크와 같이 사용자에게 리턴

  4. 브라우저가 자바스크립트를 실행시키고, 저장해뒀던 /device를 파싱해서 device에 대한 데이터를 서버에 요청

  5. 서버에서 받은 데이터를 배치



History API

사용자가 브라우저를 켜고 이곳 저곳 웹페이지를 돌아다닌 기록이 세션 히스토리이다. 세션 히스토리는 페이지를 이동할 때마다 쌓인다. 이를 통해 페이지 뒤로 가기, 다시 앞으로 가기 등의 이동이 가능해진다.

DOM의 Window객체는 history객체를 통해 브라우저의 세션 기록에 접근할 수 있는 메소드를 제공한다.

히스토리 이동

// 뒤로가기
histroy.back();
 
// 앞으로가기
histroy.forward();
 
// 특정 위치로 가기
history.go(-1);   // window.history.back()과 동일, 한 페이지 뒤로 이동
history.go(1);    // window.history.forward()와 동일, 한 페이지 앞으로 이동
history.go(-2);   // 두 페이지 이전으로 이동
history.go(0)     // 현재 페이지를 다시 불러오기

length 속성을 사용해 방문 기록 스택의 크기도 알아낼 수 있다. 현재 페이지를 기준으로 앞, 뒤 페이지가 없다면 length는 1이다.

let numberOfEntries = window.history.length

히스토리 추가 및 변경

HTML5는 pushState(), replaceState() 메소드를 제공하고 window.onpopstate 이벤트와 연동하여 동작한다. 이들을 통해 페이지 갱신없이 현재 url을 업데이트 할 수 있다.

+ location.href = url 의 경우 페이지가 갱신된다.
+ 웹 페이지를 이동할 때 window.onpopstate 이벤트가 발생한다.

현재 history 상태를 메소드의 매개 변수에 전달된 stateObj, title, URL로 대체한다. 이때 pushState()는 브라우저의 세션 기록 스택에 상태를 추가하고, replaceState()는 세션 기록 스택에 상태를 추가하지 않기 때문에 뒤로가기 버튼이 활성화되지 않는다. 예를 들어 게시물을 작성하고 완성 페이지로 이동한 다음 뒤로가기 버튼을 눌렀을 때, 다시 작성 페이지로 넘어가지 않게 하는 용도로 쓰인다.

history.pushState(stateObj, title, url)
history.replaceState(stateObj, title, url)
  
// stateObj :  history.state에 기록할 데이터, 보통은 state에 null을 넣는다.  
// title : 대부분의 브라우저는 현재 title 파라미터를 무시하고 있다. 빈 String을 전달 
/* url (Optional) : 새로운 URL은 현재 URL과 같은 출처를 가져야 하며, 
  그렇지 않을 경우 예외가 발생한다. 지정하지 않은 경우 문서의 현재 URL을 사용한다. */

🐣 예시

 <body>
    <section>
      <h2>push state</h2>
      <button id="push-state1">pushState1</button>
      <button id="push-state2">pushState2</button>
      <button id="push-state3">pushState3</button>
    </section>
    <section>
      <h2>replace state</h2>
      <button id="replace-state">replaceState</button>
    </section>
    <section>
      <h2>history state</h2>
      <span id="state"></span>
    </section>
  </body>
// 현재의 history state 값을 출력
const currentHistoryState = () => {
  document.getElementById('state').innerText = JSON.stringify(history.state);
};
currentHistoryState(); // null
 
// pushState()
document.getElementById('push-state1').addEventListener('click', () => {
  history.pushState({data: 'pushState1'}, '', '/push-state1');
  currentHistoryState();
});
document.getElementById('push-state2').addEventListener('click', () => {
  history.pushState({data: 'pushState2'}, '', '/push-state2');
  currentHistoryState();
});
document.getElementById('push-state3').addEventListener('click', () => {
  history.pushState({data: 'pushState3'}, '', '/push-state3');
  currentHistoryState();
});
 
// replaceState()
document.getElementById('replace-state').addEventListener('click', () => {
  history.replaceState({data: 'replaceState'}, '', '/replace-state');
  currentHistoryState();
});
 
// 브라우저의 뒤로가기 or 앞으로가기를 누르면 history state 값을 확인하여 출력
window.addEventListener('popstate', () => {
  currentHistoryState();
});

push state 버튼들을 누르면 화면을 새로 그리지 않고 URL이 파라미터로 보낸 URL 값으로 변경된다. 따라서 뒤로 가기 or 앞으로 가기를 누르면 이전의 URL로 화면을 새로 그리지 않고 다시 이동할 수 있다.

  • pushState
    pushState1 클릭하면 /push-state1 으로 URL 변경
    → pushState2 클릭하면 /push-state2 으로 URL 변경
    → 뒤로 가기 누르면 /push-state1 으로 URL 변경

  • replaceState
    pushState1 클릭하면 /push-state1 으로 URL 변경
    → pushState2 클릭하면 /push-state2 으로 URL 변경
    → replaceState 클릭하면 /replace-state 으로 URL 변경
    → 뒤로 가기 누르면 /push-state1 으로 URL 변경

History API 404 Error

SPA는 일반적으로 웹 브라우저에서 액세스 할 수 있는 하나의 색인 파일 (index.html)만 있고, 자바스크립트를 사용하여 애플리케이션의 탐색을 처리한다. 이때 history api로 url을 변경한 후 새로고침, 외부 링크를 타고 갔다가 다시 돌아오는 뒤로 가기 등을 하면 404에러가 발생한다. 브라우저가 "/경로"에 GET을 보내지만 실제 서버에는 "/경로"에 대한 정보가 없기 때문이다. 즉, 변경된 url의 실제 파일을 찾으려고 하기 때문에 404 에러가 발생한다. 이런 오류를 connect history api fallback 현상이라고 한다.

❓🧐 SPA에서 라우팅할 때 index.html 파일 하나만 두고 FeedPage.js, HomePage.js 이런 식의 페이지 뷰어 컴포넌트를 번갈아서 index.html에 렌더링을 하면서, 실제 페이지를 reload 하지는 않지만 주소창의 URL을 변경해주는 것인가?
참고로 라우팅은 네트워크에서 경로를 선택하는 프로세스이다.



location 객체

location은 URL 정보를 가져오는 객체이다. Document, Window 인터페이스가 이를 가지고 있고, 각각 Document.location, Window.location으로 접근할 수 있다.
URL 전체 또는 일부의 정보를 가져올 수 있는데 대표적인 속성은 다음과 같다.
Location - Web API | MDN

locationURL
location.hostURL의 호스트 부분 → 호스트명 : 포트 번호
location.hostnameURL의 도메인 부분
location.href온전한 URL
location.pathname'/' 문자 뒤 URL의 경로
location.protocolURL의 프로토콜 부분으로 마지막의 ':' 문자도 포함


🐣예제

// Create anchor element and use href property for the purpose of this example
var url = document.createElement('a');
url.href = 'https://developer.mozilla.org:8080/en-US/search?q=URL#search-results-close-container';
console.log(url.href);      // URL 전체 
console.log(url.protocol);  // https:
console.log(url.host);      // developer.mozilla.org:8080
console.log(url.hostname);  // developer.mozilla.org
console.log(url.port);      // 8080
console.log(url.pathname);  // /en-US/search
console.log(url.search);    // ?q=URL
console.log(url.hash);      // #search-results-close-container
console.log(url.origin);    // https://developer.mozilla.org:8080




History API로 SPA 구현하기

  1. location.pathname을 기준으로 어떤 페이지 컴포넌트를 렌더링할지 라우팅하는 route 함수를 정의해야 한다. url이 변경되는 경우 route 함수가 호출되야 한다.

  2. <a> 태그의 기본 기능을 꺼주고 pushstate를 호출해야 한다. 번거롭게 <a> 태그의 기본 기능을 꺼주는 작업을 하지 않고 다른 태그를 사용해도 되지만, 시멘틱을 생각하면 <a> 태그로 처리하는게 맞다.

    e.preventDefault()
    <a>, <submit> 태그를 클릭하면 href 링크를 통해 이동하거나, 창이 새로고침하여 실행된다. preventDefault는 이러한 동작을 막아준다.
    ① <a> 태그를 눌렀을 때도 href 링크로 이동하지 않게 할 경우
    ② <submit> 버튼을 눌렀어도 새로고침되지 않게 할 경우 주로 사용된다.

  3. 뒤로 가기 or 앞으로 가기는 클릭이 아니기 때문에 route 함수가 출력되지 않는다. 따라서 popstate 이벤트에 라우트 함수를 따로 걸어주어야 한다.
<!DOCTYPE html>
<html lang="ko">
<head>
    <title>history</title>
</head>
<body>
    <div id="container"></div>
    <a class="LinkItem" href="/product-list">product list</a>
    <a class="LinkItem" href="/article-list">article list</a>
    <script>
        // 2. route 함수 정의
        // location.pathname(url)에 따라 container에 들어갈 h1 태그 콘텐츠가 달라짐 
        function route() {
            const {pathname} = location // 객체 분해 문법 
            const $container = document.querySelector('#container')
                if(pathname === '/') {
                    $container.innerHTML = '<h1>Home</h1>'
                } else if(pathname ==='/product-list') {
                    $container.innerHTML = '<h1>상품 목록</h1>'
                } else if(pathname === '/article-list') {
                    $container.innerHTML = '<h1>게시글 목록</h1>'
                }
        }
        route() 
      
      
        // 1. url이 바뀌면서 route 함수 호출 
        /* window에 이벤트핸들링을 해준 것은 이벤트버블링
        이벤트 버블링은 DOM의 자식 노드에서 부모 노드로 이벤트가 전달되는 것 */
        window.addEventListener('click', e => {
            if(e.target.className ==='LinkItem') {
                e.preventDefault(); // a 태그를 눌러도 href 링크로 이동하지 않게함
                // a태그의 기본 기능을 꺼주고 pushstate 호출로 바꿔주기 위함 
                
                const {href} = e.target // 객체 분해 문법 
                const path = href.replace(window.location.origin, '') 
                // location 중 origin 제거

                history.pushState(null, null, path) // url 바꾸기 
                route() // 이벤트 발생 시 url 처리
            }
        })
      
      
        /* 뒤로가기 or 앞으로가기는 click이 아니기 때문에 route()가 출력되지 않는다. 
           따라서 popstate 이벤트에 route() 함수 걸어주기 */
        window.addEventListener('popstate', () => route()) 
    </script>
</body>
</html>


🐣 결과

🐣 History API 404 Error

profile
오늘도 신나개 🐶

0개의 댓글