Node.js로 웹 서버 분리, 비디오 Streaming하기

thyme·2022년 6월 15일
1
post-thumbnail

회사에서 웹 서버랑 운영서버를 분리할 일이 생겼다
사실 프로젝트를 할 때는 AWS를 썼기에 따로 분리할 일이 없었다
처음 써보는 Node.js라서 잠깐 멈칫했지만 해야지..!

무엇을 해야하나요?

  1. 사용자가 처음 웹을 켰을 때 다 똑같은 포트번호를 쓰지않고 동적으로 포트번호를 지정
  2. 사용자가 프로필 이미지를 업로드하면 다른 서버로 파일을 전송
  3. Node.js 를 이용해서 비디오를 스트리밍
  4. 다른 서버에서 이미지를 불러오기

제일 먼저 쿼리부터
데이터베이스에는 포트번호 여러개를 넣어놨다
ex ) 30012 , 40093

그럼 랜덤으로 하나만 불러오는 쿼리로 일단 포트번호를 받는다

SELECT public_ip, port FROM 테이블 명 ORDER BY RAND() LIMIT 1

사용자가 접속하면 포트 번호를 contentServerPort라는 이름으로 세션에 넣어줬다 그러면 프론트단에서 script로

var port = '<%=(String)session.getAttribute("contentServerPort")%>';

이런식으로 받아줄 수 있으니까 이 포트번호를 노드 서버로 먼저 보내줘야 포트를 열어줄 수 있는데 Socket.io를 이용했다
노드에서 소켓포트로 3090포트를 먼저 열어놓은 상태로 포트번호를 주고받았다

const socket = io.connect("http://localhost:3090", {path: "/socket.io", transports: ['websocket']});

    socket.on('connect', ()=>{

        console.log('연결 성공');

        socket.emit('port', port);

        socket.disconnect();

    });

사용자가 처음 접속했을 때 단 한번만 주고받으면 되기에 on대신 once를 썼다 포트 번호를 받으면 그 포트 번호로 실제 사용할 서버를 열어준다


io.once('connection', (socket, res, req) => {
	socket.once('port', (port) => {
		console.log("client request port : " + port);
		
		server.listen(port, function() {
				console.log("running");
				console.log("사용중인 포트" + server.address().port);

			});

			socket.disconnect();
			
		
	});

});

이제 연결은 끝 !
이제 http://localhost:포트번호/context 식으로 요청 할 수 있다
프론트 단에서는 FormData를 생성하고 이미지 파일을 append해주고 ajax로 요청해준다 왜 ajax 요청이 두개냐면 첫번째 요청은 연결한 Node 서버로
두번째 요청은 데이터베이스에 넣기 위해 본 서버로 가는 요청이다
popupShow는 미리 팝업 함수를 만들어놓았기에 모든 요청이 다 끝나면 팝업이 띄워지게끔 했다


			  
	        	$.ajax(
						{
							
								url : 'http://localhost:'+port+'/uploadUserProfile',
								type : 'POST',
								enctype: 'multipart/form-data',
								processData : false,
						       	contentType : false,
						       	async:false,
						
								 data: 
									
									form,
							     
							      
									complete : function(data){
										
						     			$.ajax(
                     							{
                        											
                					url : '/app/uploadUserProfile',
                        			type : 'POST',
                        			enctype: 'multipart/form-data',
                        			processData : false,
                        			contentType : false,
                        											
                        			 data: 
                        					form,
                        												     
                							success: function(data){										    	  	
                        												      }
                        												});	     			
							    	 	  popupShow("프로필이 변경되었습니다.",1);
							})
	            	
	

그러면 이제 다시 돌아와서 f라는 변수에 파일을 저장하고 mv로 파일을 이동한다
(사랑해요 구글... 한참 헤맸어요..)
req.body.usersId는 미리 form에 세션에서 받은 사용자 아이디를 넣어놨다 그리고 200코드 전송으로 끝났다는거 알려주기

app.post('/uploadUserProfile', async (req, res)=>{
	try{
		if(!req.files){
			console.log("Not File");
		}else{
			
			console.log(req.body.usersId);
			let f = req.files.file;
			console.log(f);
			console.log(f.mimetype);
			var type = f.mimetype;
			var str = type.split('/');
			var mimeType = str[1];
			
			console.log(mimeType);
			f.mv('app/profile/' + 'id_' + req.body.usersId + '_profilePicture_.' + mimeType);
			res.status(200).send('suc');
		}
	}catch(err){
		console.log(err);
	}
	});
	

그리고 불러올 때는 데이터베이스에서 파일 이름을 가져오고 ajax로 파일 이름을 담아서 요청하고 노드 서버는 파일을 찾아서 전송해준다

app.get('/userProfile', function(req, res){
		console.log(server.address().port);
		console.log(req.query.profilePicture);
		var profile_picture = req.query.profilePicture;
		fs.readFile('./app/profile/' + profile_picture, function(error, data) {
			res.writeHead(200);
			res.end(data);
		});
	});

그럼 만약에 이미지 태그가

<img src = "" id="profile_img">

이런식으로 있으면

document.getElementById("progile_img").src="http://localhost:포트번호/userProfile?profile_picture=파일이름";

이런 식으로 넣으면 제대로 나온다
여기까지 이미지 전송하고 출력하는건 끝

스트리밍 시작..^^
왜 또 socket을 썼냐? 비디오는 파일을 불러오는게 아니라 이미 서버에 올라가있는 url로 재생을 하게 되어있고 데이터베이스에서 관리가 되는게 아니라서 프론트 단에서 그 때 그 때 수정을 해준다 그래서 일단 url을 vidSrc라는 이름으로 받아오고 got이라는 친구를 써서 stream 해줬다 사실 어려운 코드는 없으니까 그냥 쓱 봐도 이해될거다


io.on('connection', (socket, res, req) => {

	console.log("connect");	
	
	socket.on('vidSrc', (vidSrc) => {
		console.log('Message received: ' + vidSrc);
		var count = 0;
		app.get('/specialVid', (req, res) => {
			var vid_url_from_client = got.stream(vidSrc);
			const range = req.headers.range;
			console.log(range);
			vid_url_from_client.on("downloadProgress", ({ transferred, total, percent }) => {
				const percentage = Math.round(percent * 100);
				console.log(`progress: ${transferred}/${total} (${percentage}%)`);
			}).on("error", (error) => {
				console.error(`Download failed: ${error.message}`);
			}).on("finish", () => {
				console.log("finished");
			});
			vid_url_from_client.pipe(res);

		});
	});

});

		server_socket.listen(3090, function(){
				console.log("server socket running");
			
			})

		

마지막으로 전체 코드
웹 서버에 넣어준 content_server.js 풀 소스코드

var app = require('express')();
var app_socket = require('express')();

var server = require('http').createServer(app);
var server_socket = require('http').createServer(app_socket);

const socketIO = require("socket.io");
const io = socketIO(server_socket, { path: "/socket.io" });

var fs = require('fs');
const http = require('http');
const got = require("got");

const cors = require('cors');
const multer = require('multer');
const axios = require('axios');
const FormData = require('form-data');

const path = require("path");
const express = require('express');
const router = express.Router();
const fileUpload = require('express-fileupload');


const date = new Date();


let year = date.getFullYear();
let month = ("0" + (date.getMonth() + 1)).slice(-2);
let day = ("0" + date.getDate()).slice(-2);

var today = year+ "_" + month + "_" + day;
var today2 = year+ "_" + month + "_" + day;


var port_ = 39150;


app.use(fileUpload({
	createParentPath : true
}));


app.post('/uploadUserProfile', async (req, res)=>{
	try{
		if(!req.files){
			console.log("Not File");
		}else{
			
			console.log(req.body.usersId);
			let f = req.files.file;
			console.log(f);
			console.log(f.mimetype);
			var type = f.mimetype;
			var str = type.split('/');
			var mimeType = str[1];
			
			console.log(mimeType);
			f.mv('app/profile/' + 'id_' + req.body.usersId + '_profilePicture_.' + mimeType);
			res.status(200).send('suc');
		}
	}catch(err){
		console.log(err);
	}
	});
	
	
	
	app.get('/userProfile', function(req, res){
		console.log(server.address().port);
		console.log(req.query.profilePicture);
		var profile_picture = req.query.profilePicture;
		fs.readFile('./app/profile/' + profile_picture, function(error, data) {
			res.writeHead(200);
			res.end(data);
		});
	});
	
	




app.post('/uploadUserSR', async (req, res) => {
	try {
		if (!req.files) {
			console.log("Not File");
		} else {

			console.log(req.body.usersId);
			let f = req.files.file;
			console.log(f);
			console.log(f.mimetype);
			var type = f.mimetype;
			var str = type.split('/');
			var mimeType = str[1];

			console.log(mimeType);
			f.mv('app/sr/' + 'id_' + req.body.usersId + '_sr_' + today + "." + mimeType);
			res.status(200).send('suc');
		}
	} catch (err) {
		console.log(err);
	}
});

io.once('connection', (socket, res, req) => {
	socket.once('port', (port) => {
		console.log("client request port : " + port);
		
		server.listen(port, function() {
				console.log("running");
				console.log("사용중인 포트" + server.address().port);

			});

			socket.disconnect();
			
		
	});

});

io.on('connection', (socket, res, req) => {

	console.log("connect");	
	
	socket.on('vidSrc', (vidSrc) => {
		console.log('Message received: ' + vidSrc);
		var count = 0;
		app.get('/specialVid', (req, res) => {
			var vid_url_from_client = got.stream(vidSrc);
			const range = req.headers.range;
			console.log(range);
			vid_url_from_client.on("downloadProgress", ({ transferred, total, percent }) => {
				const percentage = Math.round(percent * 100);
				console.log(`progress: ${transferred}/${total} (${percentage}%)`);
			}).on("error", (error) => {
				console.error(`Download failed: ${error.message}`);
			}).on("finish", () => {
				console.log("finished");
			});
			vid_url_from_client.pipe(res);

		});
	});

});

		server_socket.listen(3090, function(){
				console.log("server socket running");
			
			})

		

profile
음악하다가 개발자로 취업한 썰 푼다

0개의 댓글