일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
- java
- 자료구조
- 제로베이스
- maven
- 그래프탐색
- 프로젝트진행
- 탄력근무
- 시급합니다
- Node.js
- webServlet
- JavaScript
- OpenAPI프로젝트
- 탐색알고리즘
- Spring
- 백엔드공부
- 내돈내산
- 원격근무
- 교육철학과 교육사
- 백엔드
- 개발자
- OAuth
- BFS
- YBM전화영어
- 자바스크립트
- 최단경로문제
- Queue
- npm
- 백엔드스쿨
- array
- 전화영어
- Today
- Total
인생자취
TIL | 토이프로젝트 2 - 이미지 리사이징 서버 본문
1. 프로젝트 설계
본 프로젝트는 키워드로 검색하여 나온 이미지를 원하는 사이즈로 리사이징해서 돌려주는 서버를 구현하는 것이다.
1.1 프로젝트 개요
본 프로젝트 구현에 앞서, /*개발을 수월하게 하기 위해*/ 이미지를 자유롭게 불러오는 웹 서비스가 필요하다. 따라서 본 프로젝트는 unsplash.com의 api를 통해 이미지 소스를 불러올 수 있도록 세팅하였다. 그리고 이미지 리사이징을 구현하기 위해 node의 sharp 패키지를 사용하였다. 본 패키지를 선택한 이유는 /*강의 영상에서 선택한 탓이 가장 크지만, */ 코어 부분이 c++로 짜여있어 이미지 리사이징, 포맷 컨버팅과 같은 동작을 훨씬 빠르게 하기 때문이다.
1.2 Frontend UI 디자인
서버만 구현하므로 frontend ui 디자인은 없음.
1.3 Backend
2. 구현 flow
- main.js
- unsplash-js : unsplash api, 이미지를 가져올 때 사용.
- node-fetch : unsplash api로 이미지를 가져올 때 pipeline을 사용하여 요청/응답을 받아올 수 있음.
- sharp : 이미지 리사이징
- imagesize : 이미지 사이즈 값을 반환
- fs : 캐시 이미지를 리턴하기 위함. api가 demo 버전이어서 5,000 requests/hour 이다.
- stream: pipe를 기다리기 위해서 pipeline을 사용.
// @ts-check
const http = require('http')
const fs = require('fs')
const path = require('path')
const fetch = require('node-fetch') //node-fetch@2 버전으로 재설치 후 {default:fetch} > fetch로 변경.
const { createApi } = require('unsplash-js')
const { pipeline } = require('stream')
const { promisify } = require('util')
const sharp = require('sharp')
const imageSize = require('image-size')
require('dotenv').config()
const unsplash = createApi({ // 이미지를 가져오기위한 api 세팅, key값과 fetch를 세팅한다.
accessKey: process.env.UNSPLASH_API_ACCESS_KEY,
// @ts-ignore
fetch: fetch.default,
})
// @param {string} query
async function searchImage(query) { //불러오는 이미지의 query를 의미한다. 예시) sea-salt
const result = await unsplash.search.getPhotos({ query })
if (!result.response) {} //응답이 없는 경우 예외처리
const image = result.response.results[0] //많은 이미지 중에 첫번째 이미지를 선택함
if (!image) {} //예외처리
return { // 이미지(정보)를 가져옴
description: image.description || image.alt_description,
url: image.urls.regular,
}
}
/**
* 이미지를 Unsplash에서 검색하거나, 이미 있다면 캐시된 이미지를 반환.
*/
async function getCachedImageOrSearchedImage(query) { //파일 내에 존재하는가.
const imageFilePath = path.resolve(__dirname, `../images/${query}`) //이미지 파일 절대 경로
if (fs.existsSync(imageFilePath)) { //존재하면 반환하는 메세지
return {}
}
const result = await searchImage(query) //이미지 검색
const resp = await fetch.default(result.url) //이미지 url
resp.body.pipe(fs.createWriteStream(imageFilePath)) // 이미지 파일 경로를 stream으로 pipe에 연결
// 요청한 서버에 1차로 응답해주는 것(?)
await promisify(pipeline)(resp.body, fs.createWriteStream(imageFilePath)) // body를 pipeline에 넣어서 응답대기
const size = imageSize.default(imageFilePath) // size는 default 값으로
return { //이미지(정보) 반환
message: `Returning new image: ${query}, width: ${size.width}, height: ${size.height}`,
stream: fs.createReadStream(imageFilePath),
}
}
/**
* @param {string} url
* @returns
*/
function convertURLToImageInfo(url) {
// URL을 어떻게 파싱할 것인가?
const urlObj = new URL(url, `http://localhost:${PORT}`)
/**
* @param {string} name
* @param {number} defaultValue
* @returns
*/
function getSearchParam(name, defaultValue) { // 파라미터를 받아오는 형태 지정
const str = urlObj.searchParams.get(name)
return str ? parseInt(str, 10) : defaultValue // 숫자로 변환, 없으면 defalutvalue
}
const width = getSearchParam('width', 400)
const height = getSearchParam('height', 400)
return {
query: urlObj.pathname.slice(1),
width,
height,
}
}
const server = http.createServer((req, res) => {
async function main() {
if (!req.url) { } //서버 연결이 안된 경우 예외 처리
const { query, width, height } = convertURLToImageInfo(req.url)
try {
const { message, stream } = await getCachedImageOrSearchedImage(query)
console.log(message)
await promisify(pipeline)( // promisify에 pipeline을 넣어서 응답을 받음
stream, // 이미지 파일 정보
sharp() // 이미지 리사이징 옵션들
.resize(width, height, {
fit: 'cover', // cover : 정해진 영역을 모두 채우는 것, conatain : 공백 생김
background: '#ffffff',
})
.png(),
res
)
} catch {
res.statusCode = 400
res.end()
}
}
main()
})
const PORT = 5000
server.listen(PORT, () => {
console.log(`The server is listening at port: ${PORT}`)
})
- images folder (캐시 이미지 저장용)
- .env (환경변수)
- package.json
{
"engines": {
"node": "14.16.1"
},
"scripts": {
"server": "nodemon src/main.js"
},
"devDependencies": {
"csv-parse": "^4.4.6",
"eslint": "^8.21.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-node": "^11.1.0",
"nodemon": "^2.0.19",
"prettier": "^2.7.1",
"typescript": "^4.7.4"
},
"dependencies": {
"dotenv": "^16.0.1",
"image-size": "^1.0.2",
"node-fetch": "^2.6.7",
"sharp": "^0.30.7",
"unsplash-js": "^7.0.15"
}
}
3. 구현 결과
- 이미지 원본
- 이미지 리사이징 결과 1
- 이미지 리사이징 결과 2
4. 이슈
- node-fetch 패키지 오류 ; npm 패키지 버전에 따른 오류로 commonJS 와 ES modules가 혼용(?)되어 생긴 이슈가 있었다.
패키지를 설치한 후 불러와서 const로 선언하는 것부터 문제가 있었다.
> node-fetch@2로 재설치 후 {default:fetch} 를 fetch로 변경한 후 해결됨.
'개발 > Dev | 웹개발' 카테고리의 다른 글
TIL | 토이프로젝트 4 - npm package maker (0) | 2022.07.27 |
---|---|
TIL | 토이프로젝트 3 - Github 관리 CLI 만들기 (0) | 2022.07.27 |
TIL | 토이프로젝트 1 - 채팅서비스 (0) | 2022.07.27 |
TIL | 자동화 테스트 모듈 파헤치기 (0) | 2022.07.26 |
TIL | REST와 라우팅에 대한 정리 (0) | 2022.07.26 |