2024-11-30,   Hwa-Yong, KANG

이번 포스팅에서는 HTTP2 / H2 적용을 위한 SSL 에 대해서 조사하고, 적용 방법에 대해서 작성해 보도록 하겠습니다.

HTTP/2 Protocol

1. HTTP/2 Protocol 이란

  • HTTP/2 는 HTTP/1 의 확장으로 기존의 HTTP/1 과의 호환성을 유지하며 성능에 초첨을 맞춘 프로토콜

  • Multiplexed Streams
    • HTTP/2는 하나의 TCP 연결을 통해 여러 데이터 요청을 병렬로 전송할 수 있다.
    • HTTP/2는 Multiplexed Streams를 이용하여 Connection 한 개로 동시에 여러 개의 메시지를 주고 받을 수 있으며 응답은 순서에 상관없이 Stream으로 주고 받는다. 따라서 RTT 시간이 줄어들어 별도의 최적화 과정이나 도메인 샤딩없이 웹 사이트 로드 속도가 빨라진다. HTTP/1.1의 Connection Keep-Alive, Pipelining의 개선된 것을 알 수 있다.
  • Header Compression
    • HTTP/2는 중복 헤더 프레임을 압축해서 전송한다.
    • 클라이언트와 서버에서 모두 이전 요청에 사용된 헤더 목록을 유지관리한다.
  • Binary protocol
    • 텍스트 프로토콜에서 바이너리 프로토콜로 변화
    • 기존 HTTP/1에서 사용된 frame의 복잡성을 편리하게 해주고, 텍스트와 공백들이 섞여 혼동이 발생하던 명령들보다 명령어를 단순하게 구현할 수 있다.
    • HTTP/2 구현을 사용하는 브라우저는 네트워크를 통해 전송하기 전에 동일한 텍스트 명령을 바이너리로 변환한다.

사설 SSL 인증서 생성

1. 사설 SSL 인증서란

  • SSL 인증서는 기본적으로 공용 인터넷 환경을 기준으로 하기 때문에 즉, 사설/폐쇄 환경를 고려하지 않음
  • 따라서 사설 SSL 인증서는 공인 인증 기관이 아닌 특정 조직에서 발급
  • 이런 인증서는 인터넷으로 서비스되는 웹서버가 아니라 기업 내 폐쇄망에서 직원들이 접속하는 웹서버에서 주로 사용

2. 사설 SSL 인증서 생성 : openssl 사용

  • 루트 인증서(CA) 생성
    // rootCA.key 파일 생성
    openssl genrsa -out rootCA.key 2048
    
    // rootCA.key 파일 내용 확인
    cat rootCA.key
    
  • 루트 인증서 CSR(인증 서명 요청) 파일 생성
    // rootCA.csr 파일 생성
    openssl req -new -key rootCA.key -out rootCA.csr
    
    // rootCA.csr 파일 내용 확인
    cat rootCA.csr
    
  • 루트 인증서(CA)를 만들고 자체 서명
    // rootCA.crt 파일 생성
    openssl x509 -req -sha256 -days 999999 -in rootCA.csr -signkey rootCA.key -out rootCA.crt
    
    // rootCA.crt 파일 내용 확인
    cat rootCA.crt
    
  • 서버 인증서(CA) 생성
    // server.key 파일 생성
    openssl genrsa -out server.key 2048
    
    // server.key 파일 내용 확인
    cat server.key
    
  • 서버 인증서 CSR(인증 서명 요청) 파일 생성
    // server.csr 파일 생성
    openssl req -new -key server.key -out server.csr
    
    // server.csr 파일 내용 확인
    cat server.csr
    
  • 서버 인증서(CA)를 만들고 자체 서명
    // server.crt 파일 생성
    openssl x509 -req -sha256 -days 999999 -in server.csr -signkey server.key -out server.crt
    
    // server.crt 파일 내용 확인
    cat server.crt
    
  • 웹 서버에 SSL 인증서 적용
    // CA 인증서를 포함하는 서버 인증서 생성
    cat server.crt rootCA.crt > server.pem
    

Next.js 에서 HTTP2/H2 적용 (Node Server 사용)

1. 적용과정

  • server.js 파일 생성 (구글의 spdy 사용)
    const { createServer: http } = require('http');
    const { parse } = require('url');
    const next = require('next');
    const path = require('node:path');
    const express = require('express')
    const compression = require('compression')
    const spdy = require('spdy');
    const fs = require('node:fs');
    
    const dev = process.env.NODE_ENV !== 'production';
    const app = next({ dev });
    const handle = app.getRequestHandler();
    
    const ports = {
      http: 3001,
      https: 3443,
    };
    
    const httpsOptions = {
      key: fs.readFileSync(path.join(__dirname, '/server-key.pem')),
      cert: fs.readFileSync(path.join(__dirname, '/server.pem'))
    };
    
    const shouldCompress = (req, res) => {
      // don't compress responses asking explicitly not
      if (req.headers['x-no-compression']) {
        return false
      }
    
      // use compression filter function
      return compression.filter(req, res)
    }
    
    app.prepare().then(() => {
      http((req, res) => {
        const parsedUrl = parse(req.url, true);
        handle(req, res, parsedUrl);
      }).listen(ports.http, (err) => {
        if (err) throw err;
        console.log(`> HTTP: Ready on http://localhost:${ports.http}`);
      });
    
      // create the express app
      const expressApp = express();
    
      // set up compression in express
      expressApp.use(compression({ filter: shouldCompress }))
    
      // declaring routes for our pages
      expressApp.get('/', (req, res) => {
        return app.render(req, res, '/', req.query)
      })
    
      // fallback all request to next request handler
      expressApp.all('*', (req, res) => {
        return handle(req, res)
      })
    
      spdy.createServer(httpsOptions, expressApp).listen(ports.https, error => {
        if (error) {
          console.error(error)
          return process.exit(1)
        } else {
          console.log(`> HTTPS: Ready on https://localhost:${ports.https}`)
        }
      })
    });
    
  • package.json 파일 수정
    "scripts": {
      "dev": "next dev",
      "build": "next build",
      "start": "next start",
      "lint": "next lint",
      "secure": "node server.js",  // 실행 명령어 추가
    },
    
  • 실행 - 터미널에서 실행
    npm run secure
    

Nginx 를 이용한 HTTP2/H2 적용

1. 적용과정

  • docker-file 생성 (dockerfile-nginx)
    FROM nginx:1.23.3-alpine
    COPY ./nginx/nginx.conf /etc/nginx/nginx.conf
    
    EXPOSE 443
    EXPOSE 50003
    EXPOSE 50008
    
  • docker compose 파일 생성 (docker-compose.yml)
    version: '3'
    services:
      nextjs:
        build:
          context: .
          dockerfile: ./docker/dockerfile-front
        image: emma_front_ssl:latest
        container_name: emma-front-ssl
        ports:
          - "50003:3000"
        command: "npm run start"
        environment:
          TZ: "Asia/Seoul" # 한국 시간대 설정
        networks:
          - front_net
    
      nginx:
        build:
          context: .
          dockerfile: ./docker/dockerfile-nginx
        image: emma_nginx:latest
        container_name: emma-nginx
        ports:
          - "50008:80"
          - "443:443"
        volumes:
          - /etc/pki/ca-trust/source/anchors/server.pem:/etc/nginx/ssl/server.crt
          - /etc/pki/ca-trust/source/anchors/server.key:/etc/nginx/ssl/server.key
        environment:
          TZ: "Asia/Seoul"
        depends_on:
          - nextjs
        networks:
          - front_net
    
    networks:
      front_net:
    
  • docker 실행
    docker compose -f docker-compose.yml up -d --build
    

참고 URL

업데이트: