안녕하세요. 명월입니다.
이 글은 Python의 웹 소켓을 이용해서 웹 페이지에서 파일 업로드하는 방법에 대한 글입니다.
예전에 제가 작성한 글 중에 ajax를 이용해서 파일 업로드하는 방법에 대한 글이 있습니다.
링크 - [Jquery] ajax를 이용해 파일 업로드하는 방법
그 내용의 원래 목적은 업로드 할 파일이 대용량일 경우, form submit을 이용해서 서버에 업로드를 할 때 여러가지 제한 사항에 걸려서 ajax로 파일을 나누어 전송하는 방법에 대해 설명하고 있습니다.
업로드를 해야할 파일이 1기가나 2기가 정도의 큰 파일를 업로드를 하려고 하면 server 측의 request buffer max size도 재설정을 해야합니다.그 때 버퍼 사이즈가 너무 크게 설정되면 전체적인 웹 성능에 영향을 주기 때문에 파일을 쪼개서 전송할 필요가 있습니다. form request로는 파일을 나눌 수가 없으니 ajax로 전송을 하는 것 입니다.
프로그램의 흐름은 업로드할 데이터를 base64코드로 변환하고 string타입의 데이터로 반복적으로 ajax 호출을 통해 파일을 업로드하는 것입니다.
글 내용 중에 이런 방식은 ajax보다는 websocket이 더 나을 것 같다라고 직상한 것이 있는데 어떤 분이 그럼 websocket으로 구현된 파일 업로드하는 방법에 대한 질문이 있어서 구현했습니다.
websocket은 python으로 구현을 했습니다. 혹시 다른 언어도 필요하다면 댓글 남겨주세요.
링크 - [Python] Websocket을 사용하는 방법
import asyncio;
# 웹 소켓 모듈
import websockets;
# base64를 binary로 변환하는 모듈
import base64;
# 업로드 할 때 데이터 정보에 관한 클래스
class Node():
# 생성자
def __init__(self):
# 파일 이름
self.__filename = '';
# 파일 사이즈
self.__filesize = 0;
# base64로 된 파일 데이터
self.__data = '';
# 파일 이름 프로퍼티
@property
def filename(self):
return self.__filename;
@filename.setter
def filename(self, filename):
self.__filename = filename;
# 파일 사이즈 프로퍼티
@property
def filesize(self):
return self.__filesize;
@filesize.setter
def filesize(self, filesize):
# websocket에서는 string type으로 데이터가 오기 때문에 int형으로 변환
self.__filesize = int(filesize);
# 파일 데이터 프로퍼티
@property
def data(self):
return self.__data;
@data.setter
def data(self, data):
self.__data = data;
# 파일 데이터를 연속적으로 추가하는 함수
def add_data(self, data):
self.__data += data;
# 파일 전송이 끝났는지 확인하는 함수
def is_complate(self):
# 다운 받은 파일 크기와 요청된 파일 크기가 같으면 종료
return self.__filesize == len(self.__data);
# base64로 된 데이터를 파일로 저장하는 함수
def save(self):
# string을 byte로 변환(base64는 ascii코드로 구성되어 있음)
byte = self.__data.encode("ASCII");
# byte64를 binary로 디코딩
byte = base64.b64decode(byte);
# 파일 IO 오픈
with open("d:\\workd2\\"+self.__filename, "wb") as handle:
# 파일 작성
handle.write(byte);
# 콘솔 출력
print("craete file - d:\\workd2\\"+self.__filename);
# 웹 소켓 클라이언트가 접속이 되면 호출된다.
async def accept(websocket, path):
# 데이터 정보에 관한 클래스 할당
node = Node();
# 무한 루프, 파일 전송이 끝나면 종료한다.
while True:
# cmd를 받는다.
cmd = await websocket.recv();
# 처음 접속시 웹소켓에서 START 명령어가 온다.
if cmd == 'START':
# 파일 이름을 요청한다.
await websocket.send("FILENAME");
# 파일 이름에 대한 명령어가 오면,
elif cmd == 'FILENAME':
# 파일 이름을 받는다.
node.filename = await websocket.recv();
# 파일 사이즈를 요청한다.
await websocket.send("FILESIZE");
# 파일 사이즈에 대한 명령어가 오면
elif cmd == 'FILESIZE':
# 파일 사이즈를 설정한다.
node.filesize = await websocket.recv();
# 파일 데이터를 요청한다.
await websocket.send("DATA");
# 파일 데이터에 대한 명령어가 오면
elif cmd == 'DATA':
# 파일을 받아서 데이터를 추가한다.
node.add_data(await websocket.recv());
# 파일 전송이 끝나지 않으면
if node.is_complate() == False:
# 파일 데이터를 요청한다.
await websocket.send("DATA");
else:
# 파일 전송이 끝나면 저장한다.
node.save();
# 웹 소켓을 닫는다.
await websocket.close();
# 종료!
break;
# 웹 소켓 서버 생성.호스트는 localhost에 port는 9998로 생성한다.
start_server = websockets.serve(accept, "localhost", 9998);
# 비동기로 서버를 대기한다.
asyncio.get_event_loop().run_until_complete(start_server);
asyncio.get_event_loop().run_forever();
클라이언트 측의 소스입니다.
<!DOCTYPE html>
<html>
<head>
<title>python websocket ex</title>
</head>
<body>
<!-- 파일 업로드할 파일 요소 -->
<input type="file" id="fileupload">
<!-- 버튼을 누르면 파일 개시 -->
<button id="uploadClick">파일 업로드</button>
<!-- 진행 상황 표시 -->
<span id="progress"></span>
<!-- Jquery 연결 -->
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script>
// 파일 업로드 버튼을 누르면
$('#uploadClick').on('click', function(){
// 파일 요소로 부터 파일 정보 취득
var file = $("#fileupload")[0].files[0];
// 파일 이름 취득
var filename = file.name;
// 파일을 base64로 변환할 FileReader 선언
var reader = new FileReader();
// 파일을 읽으면 요청되는 이벤트
reader.onload = function(e) {
// base64코드
var base64data = reader.result;
var data = base64data.split(',')[1];
// 전송 파일 버퍼
var sendsize = 1024;
// 파일 사이즈
var filelength = data.length;
// 전송되는 파일 조각 위치
var pos = 0;
setTimeout(function() {
// 웹 소켓 접속
var webSocket = new WebSocket("ws://localhost:9998");
// 접속이 되면 START 명령어를 전송한다.
webSocket.onopen = function(){
webSocket.send("START");
};
// START -> FILENAME -> FILESIZE -> DATA -> DATA -> ... 순으로 파일 요청이 온다.
webSocket.onmessage = function(message){
// 명령어 echo
webSocket.send(message.data);
// 파일 이름 요청시
if(message.data === 'FILENAME'){
// 파일 이름 전송.
webSocket.send(filename);
// 파일 사이즈 요청시
} else if(message.data === 'FILESIZE'){
// 파일 사이즈 전송
webSocket.send(filelength);
// 파일 데이터 요청시
} else if(message.data === 'DATA'){
// buffer 크기만큼 파일을 나누어서 전송한다.
webSocket.send(data.substring(pos, pos + sendsize));
// 조각 위치 이동
pos = pos + sendsize;
// 파일 조각 위치가 파일 크기를 넘어서면 종료
if (pos > filelength) {
pos = filelength;
}
// 프로그래스 상태바 설정
$('#progress').text(pos + ' / ' + filelength);
}
};
// 파일 전송이 완료되면 서버로 부터 웹 소켓 종료 요청이 온다.
webSocket.onclose = function(){
// 콘솔 출력
console.log("completed");
}
});
}
// FileReader로 파일을 읽어 옴.
reader.readAsDataURL(file);
});
</script>
</body>
</html>
먼저 python의 웹 소켓을 기동합니다.
Python 소스에서 콘솔 출력을 아무 것도 하지 않았기 때문에 아무런 메시지가 안나오네요.
웹 페이지를 접속합니다.
클라이언트도 파일 업로드하기 전까지 웹 소켓을 접속하는 건 아니기 때문에 아무런 액션도 나오지 않습니다.
이 이미지 파일을 업로드 하겠습니다.
파일 크기도 크지 않고 로컬 안에서 통신이기 때문에 거의 순식간에 업로드가 끝납니다.
console 창에는 completed 로그가 남았습니다. onclose 이벤트가 발생해서 웹 소켓 접속이 종료가 된 것입니다.
Python 콘솔 창에도 파일이 저장이 되었다고 로그가 남았네요.
업로드가 완료되었습니다.
웹 프로그램에서 파일 업로드를 하는 페이지는 이렇게 웹 서버가 아닌 Python으로 독립된 서버를 두어서 업로드 파일을 따로 분리하면 성능 개선에도 많은 도움이 될 것 같습니다.
여기까지 Python의 웹소켓을 이용해서 웹 페이지에서 파일 업로드하는 방법에 대한 글이었습니다.
궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.
'Development note > Python' 카테고리의 다른 글
[Python] Redis 데이터베이스를 접속해서 사용하는 방법 (0) | 2022.02.21 |
---|---|
[Python] Hash 암호화 md5와 바이너리 구조체 base64를 다루는 방법 (0) | 2020.04.02 |
[Python] wsgi를 이용해서 apache에 웹 서버를 구축하는 방법 (0) | 2020.02.21 |
[Python] FTP에 접속하여 파일 다운로드, 업로드하는 방법(ftplib) (0) | 2020.02.19 |
[Python] 웹 서버를 구축하는 방법(bottle 모듈) (0) | 2020.02.17 |
[Python] Html 및 XML에서 데이터를 가져오는 모듈(Beautiful Soup) (0) | 2020.02.12 |
[Python] byte 타입을 다루는 방법(bytearray, struct 모듈) (0) | 2020.02.06 |
[Python] Selenium 라이브러리 (자동 웹 테스팅, 스크래핑) (0) | 2020.02.05 |