aiohttp에서 큰 파일을 업로드하는 법

파일 업로드는 보통 요청의 body에 인코딩된 파일 데이터를 넣어서 POST 요청으로 서버에 전달되는데, aiohttp에서는 다음과 같이 post를 처리하는 핸들러를 사용해서 이를 처리할 수 있다.

async def store_mp3_handler(request):
  data = await request.post()
  mp3 = data['mp3']
  filename = mp3.filename
  mp3_file = mp3.file
  content = mp3_file.read()
  return web.Response(body=content, headers=
     MultiDict({ 'CONTENT-DISPOSITION': mp3_file}))

여기서 문제는 request.post() 메소드가 요청 데이터를 한꺼번에 메모리로 읽어들이기 때문에 메모리 부족으로 서버가 죽을 수 있는 상황이 있다는 것이다. 따라서 aiohttp에서 일반적으로 처리할 수 있는 요청의 크기는 2MB로 제한된다. 하지만 이 크기는 어지간한 사진 하나의 용량도 감당하기 어렵기 때문에 뭔가 다른 방법이 필요하다. (보통은 일종의 옵션 값 같은 걸로 최대 처리 요청 크기를 변경할 수 있을 줄 알았는데, 없었다.)

request.multipart는 이러한 문제를 피하는 멀티파트 리더로 기능할 수 있다. 리더 객체를 생성한 다음에는 멀티파트 요청을 단위 콘텐츠 별로 읽어들일 수 있다. 개별 파트의 헤더를 먼저 읽고, 다시 최종적으로 file 필드로부터 버퍼로 파일의 일부 내용을 순차적으로 읽어서 처리할 수 있다.

async def store_mp3_handler(request):
  reader = await request.multipart()

  field = await reader.next()
  assert field.name == 'name'
  name = await field.read(decode=True)
  
  field = await reader.next()
  assert field.name == 'mp3'
  filename = filed.filename

  size = 0
  with open(os.path.join('/spool/yarrr-media/mp3/', filename), 'wb') as f:
    while True:
      chunk = await field.read_chunk()
      if not chunck:
        break
      size += len(chunk)
      f.write(chunk)
  return web.Response(text=f'{filename} sized of {size} successfully stored.')

웹서버란 무엇인가

What is a web server?

웹서버는 많은 웹개발자들에게는 블랙박스나 마법의 상자 같은 물건인데, 실상 그것은 하나의 프로그램이다. 이 프로그램은 80번 포트에 소켓을 열고 이 포트를 통해 들어오는 HTTP 요청을 받아, 다시 응답을 보내주는 프로그램이다. 요즘의 웹서버들은 이런 기본적인 기능에 덧붙여 여러가지 부가기능들을 제공한다. 하지만 이러한 기능들은 모두 부가적인 것이며, 웹 서버의 본질은 HTTP 응답을 내보배는 것이다. 만약 에코서버를 작성해본 경험이 있다면 웹서버 역시 이러한 에코 서버와 크게 다르지 않다. 단지 들어오는 입력에 대해 좀 더 많은 손질을 해서 내보내는 것일 뿐이다. 웹서버란 무엇인가 더보기