Seize the day

POST : Backend study

Nest.js 서버에 fastify, https, gzip 적용하기

1. Fastify

fastify 적용은 딱히 어려움은 없었다. 일찍 적용해서 개발 단계에서 충분히 테스트하는 것이 낫겠다. 나중에 fastify로 바꿔서 문제가 생기면 찾기도 어렵고 그것 자체가 장애라서 곤란하겠지..

https://docs.nestjs.com/techniques/performance 를 참고하면 크게 무리가 없다. 
https://github.com/NGuard-Security/security_api/pull/7/commits/1f52ab480a02a26b69c1ee0cca3335d662b48e62 여기 수정을 참고하면 큰 문제 없다. 

특이한 점은 express를 사용할 때는 request.ip 가 undefined로 나온다는 것이다. (node.js가 아니라 bun으로 실행해서 그럴수도.. 확인은 못 함) 몇 시간 구글링해도 remote ip를 얻는 방법을 찾지 못했는데 fastify를 바꿨더니 문제가 없어졌다. 

 

2. 서버를 실행하고 종료시키는 스크립트를 만들었다. 

kill로 강제 종료하는 게 맞는지 모르겠다. 

dev_server.sh

echo "bun --watch ./src/main.ts"
bun --watch ./src/main.ts &
echo "server pid is $!"
echo $! > nest_server.pid

kill_server.sh

pid=$(<nest_server.pid)
echo "stop nest_server (pid is $pid)"
kill -9 $pid
rm nest_server.pid

 

3. https 적용 테스트

테스트 인증서 설치는 
https://growth-msleeffice.tistory.com/129
https://velog.io/@haru/how-to-local-testing-by-https 를 참고했다. mkcert 설치는 brew install mkcert 로 했다. 

fastify의 경우는 적용이 살짝 다른데..  https://docs.nestjs.com/faq/multiple-servers 를 참고했다.  
locoalhost와 특정 도메인의 테스트 인정서는 적용하는데 문제 없었다. 다만 localhost에서의  접속은 문제가 없었지만 안드로이드 앱으로 접속하는 것은 verification 실패 예외가 발생하여 실패하였다. 정상적인 인증서를 사용하면 이것도 문제가 없을 듯 하다.  결론은 개발은 인증서 없이 http로, 운영서버는 실제 인증서(LetsEncrypt)를 가지고 https로 구동시킬 계획이다. 

import fs from "fs";

const httpsOptions = {
  key: fs.readFileSync('./dev.mdiwebma.com+2-key.pem'),
  cert: fs.readFileSync('./dev.mdiwebma.com+2.pem'),
};

async function bootstrap() {
...
    new FastifyAdapter({https: httpsOptions}),
...

 

4. gzip 적용하기

https://docs.nestjs.com/techniques/compression 를 참고해서 구현.. 1줄만 추가하면 gzip이 지원된다니 놀라울지경이다... nginx로 gzip을 지원하려고 쌩쇼를 했었는데 그때도 서버 응답에서는 gzip으로 응답하기가 가능했는데, 앱에서 gzip 압축해서 요청할 경우에는 처리하지 못했었다. 그런데 nest.js에서    await app.register(compression);  이 1줄 추가로 요청과 응답에서 gzip이 잘 동작했다. 

Android app에서 테스트 코드..

            // gzip으로 압축하여 요청
            // TODO body의 크기가 일정 length보다 크면 gzip으로 압축하도록 수정
            val data: ByteArray = jsonObject.toString().toByteArray()
            val arr = ByteArrayOutputStream()
            val zipper: OutputStream = GZIPOutputStream(arr)
            zipper.write(data)
            zipper.close()

            val body = RequestBody.create(JSON_CONTENT_TYPE, arr.toByteArray())

            //val body = RequestBody.create(JSON_CONTENT_TYPE, jsonObject.toString())
            val request = Request.Builder()
                .url(getUrl(path))
                .header(JSON_WEB_TOKEN, jwt)
                .header("Content-Type", "application/json; charset=utf-8")
                .header("Content-Encoding", "gzip")
                .header("Accept-Encoding", "gzip")
                .post(body)
                .build()
            val response = httpClient.newCall(request).execute()
            
            
            
            // gzip 응답처리..            
            var bodyString: String?
            // gzip 디코딩
            if (response.header("Content-Encoding") == "gzip" ||
                response.header("content-encoding") == "gzip"
            ) {
                val responseBody = response.body() ?: throw IOException("body is null")
                BufferedInputStream(GZIPInputStream(responseBody.byteStream())).use { input ->
                    ByteArrayOutputStream().use { baos ->
                        val ba = ByteArray(1024)
                        while (true) {
                            val len = input.read(ba)
                            if (len == -1) break
                            baos.write(ba, 0, len)
                        }

                        bodyString = String(baos.toByteArray())
                    }
                }
            } else {
                bodyString = response.body()?.string()
            }

 

 

 

top

posted at

2024. 4. 27. 23:17


CONTENTS

Seize the day
BLOG main image
김대정의 앱 개발 노트와 사는 이야기
RSS 2.0Tattertools
공지
아카이브
최근 글 최근 댓글
카테고리 태그 구름사이트 링크