인프런 커뮤니티 질문&답변
이미지 처리 라우트 관련 문의
해결된 질문
작성
·
25
0
if (req.url === "/hello") {
res.writeHead(200, { "Content-Type": "text/html" });
const helloHTML = fs.readFileSync("./hello.html", "utf-8");
res.write(helloHTML);
res.end();
} else if (req.url == "/first.png") {
res.writeHead(200, { "Content-Type": "image/png" });
const image = fs.readFileSync("./first.png");
res.write(image);
res.end();
}위 코드와 같이 else if 구문을 추가하고 나서 /hello로 경로로 접근하고 /first.png로 접근하지 않았음에도 이전에는 깨졌던 이미지 파일이 정상적으로 노출되는 이유는 무엇인가요?hello.html 안의 <img src="./first.png"> 때문에 브라우저가 자동으로 /first.png를 추가 요청하는 것일까요?
찾아보니까 HTML subsource request라는 개념과 관련되어있는것 같아서 다음과 같이 생각을 정리해보았는데 맞는지 확인 부탁드립니다.
1) localhost:3000/hello 입력
2) GET /hello.html 요청
3) 서버에서 hello.html 응답
4) 브라우저가 HTML 다운로드
5) DOM 파싱
6) img 태그 발견
7) first.png 필요 확인 후 서버에 추가 요청
8) GET /first.png요청을 보냄
이 과정에서 Node.js 서버는 정적 파일을 자동 제공하지 않기 떄문에 8번 과정에서 추가로 발생한 /first.png 요청을 처리하기 위해 라우팅을 추가하였고 Else if 구문을 타서 정적 파일을 제공받았다.
답변 2
0
안녕하세요 김대엽님, 질문해주신 내용과 직접 정리하신 생각의 흐름이 아주 훌륭하며 브라우저의 렌더링 과정과 Node.js 서버의 특성을 정확하게 꿰뚫어 보셨습니다.
결론부터 확실하게 답변해 드리자면 예상하신 대로 HTML 문서 내부의 이미지 태그 때문에 브라우저가 자동으로 서버에 추가 요청을 보내는 것이 맞으며 이를 HTML 하위 리소스 요청이라고 부르는데, 이러한 요청 과정의 전체적인 흐름을 순서대로 살펴보겠습니다.
먼저 브라우저 주소창에 해당 경로를 입력하면 유저가 브라우저에 입력한 URL 경로가 해당 경로이므로 서버로 가는 실제 HTTP 요청은 확장자가 붙은 형태가 아니라 순수한 형태의 GET 요청이 되며, 서버 코드에서도 이를 기준으로 라우팅하고 있습니다.
그 후 서버에서 HTML 파일 내용을 읽어서 응답하면 브라우저가 HTML을 다운로드하고 위에서부터 아래로 문서를 읽어 내리는 DOM 파싱을 진행하게 됩니다.
이 과정에서 이미지 태그를 발견하게 되면 브라우저는 화면에 그림을 그리기 위해 해당 이미지가 추가로 필요하다는 것을 스스로 깨닫고 경로를 해석하게 되는데, 이때 현재 머물고 있는 주소창의 URL 경로를 기준으로 상대 경로가 루트 기준으로 해석되어 최종적으로 브라우저는 사용자가 개입하지 않아도 서버로 해당 이미지 파일에 대한 GET 요청을 자동으로 한 번 더 보내게 되는 것입니다.
이러한 원리로 인해 결론적으로 순수 Node.js의 기본 http 모듈은 똑똑하게 알아서 폴더 안의 파일을 찾아주는 기능이 없습니다. 마치 융통성 없는 안내원처럼 정확히 어떤 주소로 왔을 때 무엇을 주라는 지시가 없다면 그저 모른다고 답할 뿐이며, Apache나 Nginx 혹은 에디터의 라이브 서버처럼 폴더 내의 이미지나 CSS, JS 같은 정적 파일을 자동으로 제공하는 기능이 기본적으로 탑재되어 있지 않습니다.
따라서 마지막 과정에서 브라우저가 던진 추가 요청을 서버가 알아듣고 파일을 내려주기 위해서는 작성하신 코드처럼 분기문을 통한 라우팅 처리가 반드시 필요했던 것입니다. 하지만 쇼핑몰 웹사이트처럼 수십 개의 상품 이미지가 있는 경우 이미지 파일이 늘어날 때마다 코드를 열어서 분기문을 추가하는 것은 실무에서 유지보수 측면에서 사실상 불가능에 가깝기 때문에, 뒤에서 배울 static 내용 등을 활용하여 이 문제를 더 우아하게 해결하게 됩니다.
이를 바탕으로 현업에서 백엔드 서버를 구성할 때 쓰이는 미들웨어 라우팅 아키텍처를 살펴보면, 순수 http 모듈보다는 Express.js 같은 웹 프레임워크를 주로 사용하며 이 프레임워크는 내장 미들웨어를 통해 정적 파일 라우팅을 단 한 줄로 자동화해 줍니다. 쉽게 말해 특정 폴더를 통째로 외부에 개방해주는 역할을 하는 것인데, 코드로 예를 들면 다음과 같이 작성할 수 있습니다.
const express = require('express');
const app = express();
const path = require('path');
app.use('/static', express.static(path.join(__dirname, 'public'), {
index: 'index.html',
etag: true,
lastModified: true,
maxAge: '1d'
}));
app.get('/hello', (req, res) => {
res.sendFile(path.join(__dirname, 'hello.html'));
});
app.listen(3000);
이러한 코드가 내부적으로 동작하는 방식을 조금 더 이해하기 쉽게 상세히 살펴보면, 클라이언트로부터 이미지나 파일 요청이 들어왔을 때 미들웨어가 먼저 문지기처럼 URL을 가로채고 지정된 public 폴더 안에 해당 경로와 일치하는 파일이 존재하는지 파일 시스템 모듈을 통해 검사하게 됩니다. 이것은 마치 도서관 사서가 책 번호를 보고 서가에서 책을 찾는 것과 같아서, 파일이 존재한다면 내부적으로 데이터를 조금씩 흘려보내는 파일 읽기 스트림을 생성하여 디스크에서 파일을 읽어 응답 객체로 바로 연결해 주며 파일의 확장자를 분석하여 이미지 파일이면 브라우저가 올바르게 해석할 수 있도록 image/png와 같이 알맞은 콘텐츠 타입 헤더까지 자동으로 척척 설정해 줍니다. 반면 만약 일치하는 파일이 없다면 자기가 처리할 일이 아니라고 판단하고 다음 라우터로 제어권을 자연스럽게 넘기게 됩니다.
여기서 기본값 등 상세 디테일을 조금 더 덧붙이자면 이 미들웨어는 별도로 지정하지 않아도 해당 디렉토리 내의 index.html 파일을 기본값으로 먼저 찾아 응답하도록 설정되어 있으며, 브라우저가 똑같은 이미지를 매번 새로 다운로드하지 않고 임시 저장해둘 수 있도록 돕는 캐싱 기능인 ETag나 Last-Modified 같은 HTTP 헤더값들도 자동으로 생성해 주는 똑똑한 기본값을 가지고 있습니다. 또한 maxAge 옵션을 통해 브라우저가 이 파일을 얼마 동안 기억하고 있을지 캐시의 유효 기간을 밀리초 단위로 상세하게 제어할 수도 있습니다.
이처럼 설정을 마치면 브라우저의 추가 요청이 들어왔을 때 서버가 알아서 지정된 폴더에서 파일을 찾아 응답하므로 라우터를 일일이 만들 필요가 완전히 사라지게 됩니다. 실제 예시를 들어보자면 HTML 파일 하나에 화면을 예쁘게 꾸미기 위한 CSS 파일 5개와 동적인 기능을 위한 자바스크립트 파일 3개, 그리고 로고와 배너 이미지 10개가 포함되어 있다면 브라우저는 문서를 읽는 과정에서 총 18번의 하위 리소스 요청을 서버로 보내게 되는데, 이 미들웨어 단 한 줄 덕분에 개발자가 18개의 if 분기문을 고생스럽게 작성할 필요 없이 모든 정적 파일이 자동으로 클라이언트에게 빠르고 정확하게 전달되는 것입니다.
더 나아가 정적 파일을 제공하기 위한 CDN 아키텍처 등 트래픽이 많은 실제 서비스 환경에서는 Node.js 애플리케이션 서버가 직접 정적 파일을 서빙하게 두지 않습니다. Node.js는 한 번에 하나의 작업만 집중해서 처리하는 싱글 스레드 이벤트 루프 기반이므로 하드디스크에서 무거운 이미지나 동영상 파일을 읽고 전송하는 단순 노동에 소중한 자원을 소모하면 정작 중요한 결제 처리나 데이터베이스 조회 같은 핵심 비즈니스 로직을 처리하는 데 길이 막히는 병목 현상이 발생할 수 있기 때문입니다.
따라서 마치 메인 셰프가 요리에만 집중할 수 있도록 서빙 직원을 따로 고용하는 것처럼, 정적 파일 처리에 훨씬 최적화된 Nginx 같은 웹 서버를 앞단에 두어 이미지나 CSS 요청은 Nginx가 디스크에서 바로 읽어 처리하게 하거나 AWS S3와 CloudFront 같은 클라우드 스토리지 서비스 및 CDN을 연동하여 정적 파일을 분리해 올려두고 그 링크만 HTML에 삽입하는 방식을 주로 사용합니다. 이렇게 구성하면 전 세계 사용자들이 자신과 지리적으로 가장 가까운 캐시 서버에서 이미지를 다운로드하므로 웹사이트 로딩 속도가 비약적으로 상승하며, Node.js 서버는 순수하게 데이터 연산과 API 처리에만 온전히 집중할 수 있게 되어 전체적인 서버 성능과 안정성이 크게 향상됩니다.
참고해주세요! 감사합니다 :D
0
안녕하세요, 인프런 AI 인턴입니다. 김대엽님.
코드와 설명을 보니 개념을 잘 이해하고 계십니다. 말씀하신 것처럼, 브라우저는 HTML 문서를 받아서 DOM을 파싱하면서 <img src="./first.png"> 같은 요소를 발견하게 되면, 해당 이미지 파일을 서버에서 추가 요청하게 됩니다. 이 과정을 “HTML subresource request”라고 이해하신 것이 맞습니다.
이와 같은 과정을 조금 더 상세히 설명드리면 다음과 같습니다:
- 사용자가 브라우저에서
localhost:3000/hello를 입력합니다.
- 서버는
/hello경로에 대한 요청을 받아hello.html파일을 응답으로 보냅니다.
- 브라우저는
hello.html파일을 다운로드하고 DOM을 파싱합니다.
- 파싱 과정에서
<img src="./first.png">태그를 발견하고, 이미지 파일이 필요하다고 판단합니다.
- 이에 따라 브라우저는
GET /first.png요청을 서버로 보내게 됩니다.
- 작성하신 코드에서 else if 구문에 의해
/first.png요청이 적절히 처리되며, 서버는 이미지 파일을 응답으로 제공합니다.
Node.js의 기본 서버는 정적 파일을 자동으로 제공하지 않기 때문에, 반드시 이러한 요청을 수동으로 처리할 라우트를 정의해야 합니다. 이번에 추가한 else if 구문은 이러한 추가 요청을 처리하는 데 효과적입니다.
관련 질문과 더 많은 정보를 얻고 싶으시다면 아래 유사한 질문을 참조하시길 권장합니다:
- 정적 파일 직접 구현하기 강의 수강 후 궁금한 점 질문드립니다!
저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.




