이번 프로젝트를 진행하면서 multer-s3를 사용해 보게 되었는데, 팀장님이 작성한 코드만 보며 조각조각땃땃따를 하다 보니 도대체 얘는 어떻게 진행되고 있는 것인가... 도저히 이해를 못 하겠던 것이었다...
계속되는 머리털뜯기... 그걸 보는 팀장님... (진짜 답답하셨을 듯 이해를 1도 못하고 있어서...)
그도 그럴게 Node.js로 api 한번 만들어보질 않았는데 바로 Nest.js typeorm 이렇게 넘어가서 그런 걸 지도
미들웨어에 대한 개념도 부족해서 (저번에 팀장님이 설명해 주셨는데 또 잊었음!! 이게 다 블로그를 안 써서 그런 것 같음!!!)
이번에 Express로 미들웨어 함수를 만들어 보라고 하시길래 이번엔 절대 잊지 않겠다... 는 마음으로 만드는 과정을 샥샥 적어볼까 한다.
공부 잘하는 사람 커비처럼 집어삼켜서 그 사람 지식 내 것으로 만들고 싶다
Express 앱에서 사용하기 위한 미들웨어 작성
미들웨어란 무엇이냐?
요청 오브젝트(req), 응답 오브젝트(res), 그리고 애플리케이션의 요청-응답 주기 중 그다음의 미들웨어 함수에 대한 액세스 권한을 갖는 함수이다. 그 다음의 미들웨어 함수는 일반적으로 next라는 이름의 변수로 표시된다.
미들웨어 함수는 다음과 같은 task를 수행할 수 있다.
- 모든 코드를 실행
- 요청 및 응답 오브젝트에 대한 변경을 실행
- 요청-응답 주기를 종료
- 스택 내의 그 다음 미들웨어를 호출
현재의 미들웨어 함수가 요청-응답 주기를 종료하지 않는 경우에는 next()를 호출하여 그 다음 미들웨어 함수에 제어를 전달해야 한다. 그렇지 않으면 해당 요청은 정지된 채로 방치된다. 이게 무슨 말인가 하면...
미들웨어 함수를 실행했는데 응답이 없다? 그런데 next도 없다..? 그럼 timeout이 날 맞이해 줄 거라는 의미다^^7
위 사진의 요소들을 살펴보자면
get: 미들웨어 함수가 적용되는 HTTP 메서드 (post, patch, delete 등이 있다)
'/': 미들웨어 함수가 적용되는 경로(라우트)로 '/sign-up' 등으로 용도에 맞게 설정할 수 있다.
function: 미들웨어 함수
req: 미들웨어 함수에 대한 HTTP 요청 인수
res: 미들웨어 함수에 대한 HTTP 응답 인수
next: 미들웨어 함수에 대한 콜백 인수 (일반적으로 next라 불림)
그리고 내가 지금 해야 될 건 무엇이냐
string data가 아닌 multipart/form-data를 받아야 한다는 것이다
multer를 사용해서
...
Multer란 어떤 모듈이냐?
multipart/form-data를 다루기 위한 node.js의 미들웨어로 파일 업로드를 위해 사용되는 모듈이다.
multer는 body객체와 한 개의 file 혹은 여러 개의 files 객체를 request객체에 추가한다.
body객체는 폼 텍스트 필드의 값을 포함하고, 한 개 혹은 여러개의 파일 객체는 폼을 통해 업로드된 파일들을 포함하고 있다.
multer package 안에는 여러 종류의 미들웨어가 있다.
- storage: 저장할 공간에 대한 정보. 디스크나 메모리 저장 가능
- diskStorage: 하드디스크에 업로드 파일을 저장
- destination: 저장할 경로
- filename: 저장할 파일명 (파일명+날짜+확장자 형식)
- limits: 파일 개수나 파일 사이즈 제한
각각의 파일은 아래의 정보를 포함하고 있다고 한다.
Key | Description | Note |
fieldname | 폼에 정의된 필드 명 | |
originalname | 사용자가 업로드한 파일 명 | |
encoding | 파일의 엔코딩 타입 | |
mimetype | 파일의 Mime 타입 | |
size | 파일의 byte 사이즈 | |
destination | 파일이 저장된 폴더 | DiskStorage |
filename | destination에 저장된 파일 명 | DiskStorage |
path | 업로드된 파일의 전체 경로 | DiskStorage |
buffer | 전체 파일의 Buffer | MemoryStorage |
const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const app = express();
const port = 3000;
try {
fs.readdirSync('uploads'); // 폴더 확인
} catch (err) {
fs.mkdirSync('uploads'); // 폴더 생성
}
const upload = multer({
storage: multer.diskStorage({
// 저장하는 공간: 하드디스크
destination: function (req, res, cb) {
// 저장 위치
cb(null, 'uploads/'); // uploads라는 폴더 안에 저장
},
filename: function (req, res, cb) {
const ext = path.extname(res.originalname); // 파일의 확장자
const uniqueSuffix = `${Date.now()}-${Math.round(Math.random() * 1e9)}`;
cb(null, `${res.fieldname}-${uniqueSuffix}${ext}`);
},
}),
limits: { fileSize: 5 * 1024 * 1024 }, // 5메가로 용량 제한
});
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'multer-study.html'));
});
app.post('/upload', upload.single('image'), (req, res) => {
const file = req.file;
if (!file) {
return res.status(400).send('No file uploaded.');
}
res.send('File uploaded!');
});
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
storage 속성에는 destination(어디에)과 filename(저장할 이름)을 지정하였다.
destination과 filename의 매개변수는 req, res, cb로 동일한데 다음과 같은 값을 가지고 있다.
req: 요청에 대한 정보
res: 응답, 업로드한 파일에 대한 정보
cb: 콜백함수로 첫 번째 인수에는 에러가 있다면 에러를, 두 번째 인수에는 실제 경로나 파일 이름을 넣어주면 된다고 한다.
req나 res의 데이터를 가공해 cb로 넘기는 것.
위 설정은 uploads라는 폴더에 '파일명-현재시간-랜덤숫자.확장자' 파일명으로 업로드를 하는 것이다.
그리고 업로드를 실행했을 때 res에 찍히는 값들은 아래와 같다.
// console.log(res)의 값
{
fieldname: 'image',
originalname: 'asdf.png',
encoding: '7bit',
mimetype: 'image/png'
}
// console.log(ext)의 값
.png
// console.log(uniqueSuffix)의 값
1704975872967-311838807
// 그런고로 파일 이름은
image-1704975872967-311838807.png
res를 찍어봤으니 req도 한 번 찍어보자
^^....
<ref *2> IncomingMessage {
_readableState: ReadableState {
objectMode: false,
highWaterMark: 16384,
buffer: BufferList { head: null, tail: null, length: 0 },
length: 0,
pipes: [],
flowing: false,
ended: true,
endEmitted: true,
reading: false,
constructed: true,
sync: false,
needReadable: false,
emittedReadable: false,
readableListening: false,
resumeScheduled: false,
errorEmitted: false,
emitClose: true,
autoDestroy: true,
destroyed: true,
errored: null,
closed: true,
closeEmitted: true,
defaultEncoding: 'utf8',
awaitDrainWriters: null,
multiAwaitDrain: false,
readingMore: false,
dataEmitted: true,
decoder: null,
encoding: null,
[Symbol(kPaused)]: true
},
_events: [Object: null prototype] {},
_eventsCount: 0,
_maxListeners: undefined,
socket: <ref *1> Socket {
connecting: false,
_hadError: false,
_parent: null,
_host: null,
_closeAfterHandlingError: false,
_readableState: ReadableState {
objectMode: false,
highWaterMark: 16384,
buffer: BufferList { head: null, tail: null, length: 0 },
length: 0,
pipes: [],
flowing: true,
ended: false,
endEmitted: false,
reading: true,
constructed: true,
sync: false,
needReadable: true,
emittedReadable: false,
readableListening: false,
resumeScheduled: false,
errorEmitted: false,
emitClose: false,
autoDestroy: true,
destroyed: false,
errored: null,
closed: false,
closeEmitted: false,
defaultEncoding: 'utf8',
awaitDrainWriters: null,
multiAwaitDrain: false,
readingMore: false,
dataEmitted: false,
decoder: null,
encoding: null,
[Symbol(kPaused)]: false
},
_events: [Object: null prototype] {
end: [Array],
timeout: [Function: socketOnTimeout],
data: [Function: bound socketOnData],
error: [Function: socketOnError],
close: [Array],
drain: [Function: bound socketOnDrain],
resume: [Function: onSocketResume],
pause: [Function: onSocketPause]
},
_eventsCount: 8,
_maxListeners: undefined,
_writableState: WritableState {
objectMode: false,
highWaterMark: 16384,
finalCalled: false,
needDrain: false,
ending: false,
ended: false,
finished: false,
destroyed: false,
decodeStrings: false,
defaultEncoding: 'utf8',
length: 0,
writing: false,
corked: 0,
sync: true,
bufferProcessing: false,
onwrite: [Function: bound onwrite],
writecb: null,
writelen: 0,
afterWriteTickInfo: null,
buffered: [],
bufferedIndex: 0,
allBuffers: true,
allNoop: true,
pendingcb: 0,
constructed: true,
prefinished: false,
errorEmitted: false,
emitClose: false,
autoDestroy: true,
errored: null,
closed: false,
closeEmitted: false,
[Symbol(kOnFinished)]: []
},
allowHalfOpen: true,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: Server {
maxHeaderSize: undefined,
insecureHTTPParser: undefined,
requestTimeout: 300000,
headersTimeout: 60000,
keepAliveTimeout: 5000,
connectionsCheckingInterval: 30000,
joinDuplicateHeaders: undefined,
rejectNonStandardBodyWrites: false,
_events: [Object: null prototype],
_eventsCount: 2,
_maxListeners: undefined,
_connections: 2,
_handle: [TCP],
_usingWorkers: false,
_workers: [],
_unref: false,
allowHalfOpen: true,
pauseOnConnect: false,
noDelay: true,
keepAlive: false,
keepAliveInitialDelay: 0,
highWaterMark: 16384,
httpAllowHalfOpen: false,
timeout: 0,
maxHeadersCount: null,
maxRequestsPerSocket: 0,
_connectionKey: '6::::3000',
[Symbol(IncomingMessage)]: [Function: IncomingMessage],
[Symbol(ServerResponse)]: [Function: ServerResponse],
[Symbol(kCapture)]: false,
[Symbol(async_id_symbol)]: 5,
[Symbol(http.server.connections)]: ConnectionsList {},
[Symbol(http.server.connectionsCheckingInterval)]: Timeout {
_idleTimeout: 30000,
_idlePrev: [TimersList],
_idleNext: [TimersList],
_idleStart: 68,
_onTimeout: [Function: bound checkConnections],
_timerArgs: undefined,
_repeat: 30000,
_destroyed: false,
[Symbol(refed)]: false,
[Symbol(kHasPrimitive)]: false,
[Symbol(asyncId)]: 4,
[Symbol(triggerId)]: 1
},
[Symbol(kUniqueHeaders)]: null
},
_server: Server {
maxHeaderSize: undefined,
insecureHTTPParser: undefined,
requestTimeout: 300000,
headersTimeout: 60000,
keepAliveTimeout: 5000,
connectionsCheckingInterval: 30000,
joinDuplicateHeaders: undefined,
rejectNonStandardBodyWrites: false,
_events: [Object: null prototype],
_eventsCount: 2,
_maxListeners: undefined,
_connections: 2,
_handle: [TCP],
_usingWorkers: false,
_workers: [],
_unref: false,
allowHalfOpen: true,
pauseOnConnect: false,
noDelay: true,
keepAlive: false,
keepAliveInitialDelay: 0,
highWaterMark: 16384,
httpAllowHalfOpen: false,
timeout: 0,
maxHeadersCount: null,
maxRequestsPerSocket: 0,
_connectionKey: '6::::3000',
[Symbol(IncomingMessage)]: [Function: IncomingMessage],
[Symbol(ServerResponse)]: [Function: ServerResponse],
[Symbol(kCapture)]: false,
[Symbol(async_id_symbol)]: 5,
[Symbol(http.server.connections)]: ConnectionsList {},
[Symbol(http.server.connectionsCheckingInterval)]: Timeout {
_idleTimeout: 30000,
_idlePrev: [TimersList],
_idleNext: [TimersList],
_idleStart: 68,
_onTimeout: [Function: bound checkConnections],
_timerArgs: undefined,
_repeat: 30000,
_destroyed: false,
[Symbol(refed)]: false,
[Symbol(kHasPrimitive)]: false,
[Symbol(asyncId)]: 4,
[Symbol(triggerId)]: 1
},
[Symbol(kUniqueHeaders)]: null
},
parser: HTTPParser {
'0': null,
'1': [Function: parserOnHeaders],
'2': [Function: parserOnHeadersComplete],
'3': [Function: parserOnBody],
'4': [Function: parserOnMessageComplete],
'5': [Function: bound onParserExecute],
'6': [Function: bound onParserTimeout],
_headers: [],
_url: '',
socket: [Circular *1],
incoming: [Circular *2],
outgoing: null,
maxHeaderPairs: 2000,
_consumed: true,
onIncoming: [Function: bound parserOnIncoming],
[Symbol(resource_symbol)]: [HTTPServerAsyncResource]
},
on: [Function: socketListenerWrap],
addListener: [Function: socketListenerWrap],
prependListener: [Function: socketListenerWrap],
setEncoding: [Function: socketSetEncoding],
_paused: false,
_httpMessage: ServerResponse {
_events: [Object: null prototype],
_eventsCount: 1,
_maxListeners: undefined,
outputData: [],
outputSize: 0,
writable: true,
destroyed: false,
_last: false,
chunkedEncoding: false,
shouldKeepAlive: true,
maxRequestsOnConnectionReached: false,
_defaultKeepAlive: true,
useChunkedEncodingByDefault: true,
sendDate: true,
_removedConnection: false,
_removedContLen: false,
_removedTE: false,
strictContentLength: false,
_contentLength: null,
_hasBody: true,
_trailer: '',
finished: false,
_headerSent: false,
_closed: false,
socket: [Circular *1],
_header: null,
_keepAliveTimeout: 5000,
_onPendingData: [Function: bound updateOutgoingData],
req: [Circular *2],
_sent100: false,
_expect_continue: false,
_maxRequestsPerSocket: 0,
locals: [Object: null prototype] {},
[Symbol(kCapture)]: false,
[Symbol(kBytesWritten)]: 0,
[Symbol(kNeedDrain)]: false,
[Symbol(corked)]: 0,
[Symbol(kOutHeaders)]: [Object: null prototype],
[Symbol(errored)]: null,
[Symbol(kHighWaterMark)]: 16384,
[Symbol(kRejectNonStandardBodyWrites)]: false,
[Symbol(kUniqueHeaders)]: null
},
[Symbol(async_id_symbol)]: 15,
[Symbol(kHandle)]: TCP {
reading: true,
onconnection: null,
_consumed: true,
[Symbol(owner_symbol)]: [Circular *1]
},
[Symbol(lastWriteQueueSize)]: 0,
[Symbol(timeout)]: null,
[Symbol(kBuffer)]: null,
[Symbol(kBufferCb)]: null,
[Symbol(kBufferGen)]: null,
[Symbol(kCapture)]: false,
[Symbol(kSetNoDelay)]: true,
[Symbol(kSetKeepAlive)]: false,
[Symbol(kSetKeepAliveInitialDelay)]: 0,
[Symbol(kBytesRead)]: 0,
[Symbol(kBytesWritten)]: 0
},
httpVersionMajor: 1,
httpVersionMinor: 1,
httpVersion: '1.1',
complete: true,
rawHeaders: [
'Host',
'localhost:3000',
'Connection',
'keep-alive',
'Content-Length',
'38400',
'Cache-Control',
'max-age=0',
'sec-ch-ua',
'"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
'sec-ch-ua-mobile',
'?0',
'sec-ch-ua-platform',
'"macOS"',
'Upgrade-Insecure-Requests',
'1',
'Origin',
'http://localhost:3000',
'Content-Type',
'multipart/form-data; boundary=----WebKitFormBoundarySREJJdJ3tdvDuAFa',
'User-Agent',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept',
'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'Sec-Fetch-Site',
'same-origin',
'Sec-Fetch-Mode',
'navigate',
'Sec-Fetch-User',
'?1',
'Sec-Fetch-Dest',
'document',
'Referer',
'http://localhost:3000/',
'Accept-Encoding',
'gzip, deflate, br',
'Accept-Language',
'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7,ja;q=0.6,zh-CN;q=0.5,zh;q=0.4'
],
rawTrailers: [],
joinDuplicateHeaders: undefined,
aborted: false,
upgrade: false,
url: '/upload',
method: 'POST',
statusCode: null,
statusMessage: null,
client: <ref *1> Socket {
connecting: false,
_hadError: false,
_parent: null,
_host: null,
_closeAfterHandlingError: false,
_readableState: ReadableState {
objectMode: false,
highWaterMark: 16384,
buffer: BufferList { head: null, tail: null, length: 0 },
length: 0,
pipes: [],
flowing: true,
ended: false,
endEmitted: false,
reading: true,
constructed: true,
sync: false,
needReadable: true,
emittedReadable: false,
readableListening: false,
resumeScheduled: false,
errorEmitted: false,
emitClose: false,
autoDestroy: true,
destroyed: false,
errored: null,
closed: false,
closeEmitted: false,
defaultEncoding: 'utf8',
awaitDrainWriters: null,
multiAwaitDrain: false,
readingMore: false,
dataEmitted: false,
decoder: null,
encoding: null,
[Symbol(kPaused)]: false
},
_events: [Object: null prototype] {
end: [Array],
timeout: [Function: socketOnTimeout],
data: [Function: bound socketOnData],
error: [Function: socketOnError],
close: [Array],
drain: [Function: bound socketOnDrain],
resume: [Function: onSocketResume],
pause: [Function: onSocketPause]
},
_eventsCount: 8,
_maxListeners: undefined,
_writableState: WritableState {
objectMode: false,
highWaterMark: 16384,
finalCalled: false,
needDrain: false,
ending: false,
ended: false,
finished: false,
destroyed: false,
decodeStrings: false,
defaultEncoding: 'utf8',
length: 0,
writing: false,
corked: 0,
sync: true,
bufferProcessing: false,
onwrite: [Function: bound onwrite],
writecb: null,
writelen: 0,
afterWriteTickInfo: null,
buffered: [],
bufferedIndex: 0,
allBuffers: true,
allNoop: true,
pendingcb: 0,
constructed: true,
prefinished: false,
errorEmitted: false,
emitClose: false,
autoDestroy: true,
errored: null,
closed: false,
closeEmitted: false,
[Symbol(kOnFinished)]: []
},
allowHalfOpen: true,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: Server {
maxHeaderSize: undefined,
insecureHTTPParser: undefined,
requestTimeout: 300000,
headersTimeout: 60000,
keepAliveTimeout: 5000,
connectionsCheckingInterval: 30000,
joinDuplicateHeaders: undefined,
rejectNonStandardBodyWrites: false,
_events: [Object: null prototype],
_eventsCount: 2,
_maxListeners: undefined,
_connections: 2,
_handle: [TCP],
_usingWorkers: false,
_workers: [],
_unref: false,
allowHalfOpen: true,
pauseOnConnect: false,
noDelay: true,
keepAlive: false,
keepAliveInitialDelay: 0,
highWaterMark: 16384,
httpAllowHalfOpen: false,
timeout: 0,
maxHeadersCount: null,
maxRequestsPerSocket: 0,
_connectionKey: '6::::3000',
[Symbol(IncomingMessage)]: [Function: IncomingMessage],
[Symbol(ServerResponse)]: [Function: ServerResponse],
[Symbol(kCapture)]: false,
[Symbol(async_id_symbol)]: 5,
[Symbol(http.server.connections)]: ConnectionsList {},
[Symbol(http.server.connectionsCheckingInterval)]: Timeout {
_idleTimeout: 30000,
_idlePrev: [TimersList],
_idleNext: [TimersList],
_idleStart: 68,
_onTimeout: [Function: bound checkConnections],
_timerArgs: undefined,
_repeat: 30000,
_destroyed: false,
[Symbol(refed)]: false,
[Symbol(kHasPrimitive)]: false,
[Symbol(asyncId)]: 4,
[Symbol(triggerId)]: 1
},
[Symbol(kUniqueHeaders)]: null
},
_server: Server {
maxHeaderSize: undefined,
insecureHTTPParser: undefined,
requestTimeout: 300000,
headersTimeout: 60000,
keepAliveTimeout: 5000,
connectionsCheckingInterval: 30000,
joinDuplicateHeaders: undefined,
rejectNonStandardBodyWrites: false,
_events: [Object: null prototype],
_eventsCount: 2,
_maxListeners: undefined,
_connections: 2,
_handle: [TCP],
_usingWorkers: false,
_workers: [],
_unref: false,
allowHalfOpen: true,
pauseOnConnect: false,
noDelay: true,
keepAlive: false,
keepAliveInitialDelay: 0,
highWaterMark: 16384,
httpAllowHalfOpen: false,
timeout: 0,
maxHeadersCount: null,
maxRequestsPerSocket: 0,
_connectionKey: '6::::3000',
[Symbol(IncomingMessage)]: [Function: IncomingMessage],
[Symbol(ServerResponse)]: [Function: ServerResponse],
[Symbol(kCapture)]: false,
[Symbol(async_id_symbol)]: 5,
[Symbol(http.server.connections)]: ConnectionsList {},
[Symbol(http.server.connectionsCheckingInterval)]: Timeout {
_idleTimeout: 30000,
_idlePrev: [TimersList],
_idleNext: [TimersList],
_idleStart: 68,
_onTimeout: [Function: bound checkConnections],
_timerArgs: undefined,
_repeat: 30000,
_destroyed: false,
[Symbol(refed)]: false,
[Symbol(kHasPrimitive)]: false,
[Symbol(asyncId)]: 4,
[Symbol(triggerId)]: 1
},
[Symbol(kUniqueHeaders)]: null
},
parser: HTTPParser {
'0': null,
'1': [Function: parserOnHeaders],
'2': [Function: parserOnHeadersComplete],
'3': [Function: parserOnBody],
'4': [Function: parserOnMessageComplete],
'5': [Function: bound onParserExecute],
'6': [Function: bound onParserTimeout],
_headers: [],
_url: '',
socket: [Circular *1],
incoming: [Circular *2],
outgoing: null,
maxHeaderPairs: 2000,
_consumed: true,
onIncoming: [Function: bound parserOnIncoming],
[Symbol(resource_symbol)]: [HTTPServerAsyncResource]
},
on: [Function: socketListenerWrap],
addListener: [Function: socketListenerWrap],
prependListener: [Function: socketListenerWrap],
setEncoding: [Function: socketSetEncoding],
_paused: false,
_httpMessage: ServerResponse {
_events: [Object: null prototype],
_eventsCount: 1,
_maxListeners: undefined,
outputData: [],
outputSize: 0,
writable: true,
destroyed: false,
_last: false,
chunkedEncoding: false,
shouldKeepAlive: true,
maxRequestsOnConnectionReached: false,
_defaultKeepAlive: true,
useChunkedEncodingByDefault: true,
sendDate: true,
_removedConnection: false,
_removedContLen: false,
_removedTE: false,
strictContentLength: false,
_contentLength: null,
_hasBody: true,
_trailer: '',
finished: false,
_headerSent: false,
_closed: false,
socket: [Circular *1],
_header: null,
_keepAliveTimeout: 5000,
_onPendingData: [Function: bound updateOutgoingData],
req: [Circular *2],
_sent100: false,
_expect_continue: false,
_maxRequestsPerSocket: 0,
locals: [Object: null prototype] {},
[Symbol(kCapture)]: false,
[Symbol(kBytesWritten)]: 0,
[Symbol(kNeedDrain)]: false,
[Symbol(corked)]: 0,
[Symbol(kOutHeaders)]: [Object: null prototype],
[Symbol(errored)]: null,
[Symbol(kHighWaterMark)]: 16384,
[Symbol(kRejectNonStandardBodyWrites)]: false,
[Symbol(kUniqueHeaders)]: null
},
[Symbol(async_id_symbol)]: 15,
[Symbol(kHandle)]: TCP {
reading: true,
onconnection: null,
_consumed: true,
[Symbol(owner_symbol)]: [Circular *1]
},
[Symbol(lastWriteQueueSize)]: 0,
[Symbol(timeout)]: null,
[Symbol(kBuffer)]: null,
[Symbol(kBufferCb)]: null,
[Symbol(kBufferGen)]: null,
[Symbol(kCapture)]: false,
[Symbol(kSetNoDelay)]: true,
[Symbol(kSetKeepAlive)]: false,
[Symbol(kSetKeepAliveInitialDelay)]: 0,
[Symbol(kBytesRead)]: 0,
[Symbol(kBytesWritten)]: 0
},
_consuming: true,
_dumped: false,
next: [Function: next],
baseUrl: '',
originalUrl: '/upload',
_parsedUrl: Url {
protocol: null,
slashes: null,
auth: null,
host: null,
port: null,
hostname: null,
hash: null,
search: null,
query: null,
pathname: '/upload',
path: '/upload',
href: '/upload',
_raw: '/upload'
},
params: {},
query: {},
res: <ref *3> ServerResponse {
_events: [Object: null prototype] { finish: [Function: bound resOnFinish] },
_eventsCount: 1,
_maxListeners: undefined,
outputData: [],
outputSize: 0,
writable: true,
destroyed: false,
_last: false,
chunkedEncoding: false,
shouldKeepAlive: true,
maxRequestsOnConnectionReached: false,
_defaultKeepAlive: true,
useChunkedEncodingByDefault: true,
sendDate: true,
_removedConnection: false,
_removedContLen: false,
_removedTE: false,
strictContentLength: false,
_contentLength: null,
_hasBody: true,
_trailer: '',
finished: false,
_headerSent: false,
_closed: false,
socket: <ref *1> Socket {
connecting: false,
_hadError: false,
_parent: null,
_host: null,
_closeAfterHandlingError: false,
_readableState: [ReadableState],
_events: [Object: null prototype],
_eventsCount: 8,
_maxListeners: undefined,
_writableState: [WritableState],
allowHalfOpen: true,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: [Server],
_server: [Server],
parser: [HTTPParser],
on: [Function: socketListenerWrap],
addListener: [Function: socketListenerWrap],
prependListener: [Function: socketListenerWrap],
setEncoding: [Function: socketSetEncoding],
_paused: false,
_httpMessage: [Circular *3],
[Symbol(async_id_symbol)]: 15,
[Symbol(kHandle)]: [TCP],
[Symbol(lastWriteQueueSize)]: 0,
[Symbol(timeout)]: null,
[Symbol(kBuffer)]: null,
[Symbol(kBufferCb)]: null,
[Symbol(kBufferGen)]: null,
[Symbol(kCapture)]: false,
[Symbol(kSetNoDelay)]: true,
[Symbol(kSetKeepAlive)]: false,
[Symbol(kSetKeepAliveInitialDelay)]: 0,
[Symbol(kBytesRead)]: 0,
[Symbol(kBytesWritten)]: 0
},
_header: null,
_keepAliveTimeout: 5000,
_onPendingData: [Function: bound updateOutgoingData],
req: [Circular *2],
_sent100: false,
_expect_continue: false,
_maxRequestsPerSocket: 0,
locals: [Object: null prototype] {},
[Symbol(kCapture)]: false,
[Symbol(kBytesWritten)]: 0,
[Symbol(kNeedDrain)]: false,
[Symbol(corked)]: 0,
[Symbol(kOutHeaders)]: [Object: null prototype] { 'x-powered-by': [Array] },
[Symbol(errored)]: null,
[Symbol(kHighWaterMark)]: 16384,
[Symbol(kRejectNonStandardBodyWrites)]: false,
[Symbol(kUniqueHeaders)]: null
},
route: Route {
path: '/upload',
stack: [ [Layer], [Layer] ],
methods: { post: true }
},
body: [Object: null prototype] { title: '' },
file: {
fieldname: 'image',
originalname: 'asdf.png',
encoding: '7bit',
mimetype: 'image/png',
destination: 'uploads/',
filename: 'image-1704976867905-748609076.png',
path: 'uploads/image-1704976867905-748609076.png',
size: 38126
},
[Symbol(kCapture)]: false,
[Symbol(kHeaders)]: {
host: 'localhost:3000',
connection: 'keep-alive',
'content-length': '38400',
'cache-control': 'max-age=0',
'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"macOS"',
'upgrade-insecure-requests': '1',
origin: 'http://localhost:3000',
'content-type': 'multipart/form-data; boundary=----WebKitFormBoundarySREJJdJ3tdvDuAFa',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'sec-fetch-site': 'same-origin',
'sec-fetch-mode': 'navigate',
'sec-fetch-user': '?1',
'sec-fetch-dest': 'document',
referer: 'http://localhost:3000/',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7,ja;q=0.6,zh-CN;q=0.5,zh;q=0.4'
},
[Symbol(kHeadersCount)]: 38,
[Symbol(kTrailers)]: null,
[Symbol(kTrailersCount)]: 0
}
req의 끝부분쯤에 file 객체가 보이는데 여기서 업로드한 이미지의 자세한 정보를 볼 수 있다~~~
{
fieldname: 'image',
originalname: 'asdf.png',
encoding: '7bit',
mimetype: 'image/png',
destination: 'uploads/',
filename: 'image-1704976826388-3119730.png',
path: 'uploads/image-1704976826388-3119730.png',
size: 38126
}
참고
'일하면서 공부해욧' 카테고리의 다른 글
git pull (0) | 2024.01.11 |
---|---|
데이터베이스 외부접속하기 (Docker, MySQL, 포트포워딩) (0) | 2023.07.16 |
Delete `␍` eslint (prettier/prettier) (0) | 2023.07.01 |
custom class validator (0) | 2023.06.25 |
node.js와 express (0) | 2023.06.18 |