더북(TheBook)

④ 파일을 첨부해 메일 전송

이제 실제로 파일이 첨부된 이메일을 전송해 보자. 이번에도 익명 블록 형태로 예제를 선보일 텐데, 이 절에서 지금까지 다뤘던 모든 내용이 코드로 집약되어 있고 좀 복잡하므로 차근차근 살펴보자.

입력

    DECLARE
      vv_host    VARCHAR2(30) := 'localhost'; -- SMTP 서버명
      vn_port    NUMBER := 25;                -- 포트번호
      vv_domain  VARCHAR2(30) := 'hong.com';
      vv_from    VARCHAR2(50) := 'charieh@hong.com';  -- 보내는 주소
      vv_to      VARCHAR2(50) := 'charieh@hong.com';  -- 받는 주소

      c            utl_smtp.connection;
      vv_html      VARCHAR2(200); -- HTML 메시지를 담을 변수
      -- boundary 표시를 위한 변수, unique한 임의의 값을 사용하면 된다.
      vv_boundary  VARCHAR2(50) := 'DIFOJSLKDFO.WEFOWJFOWE';

      vv_directory  VARCHAR2(30) := 'SMTP_FILE'; --파일이 있는 디렉토리명
      vv_filename   VARCHAR2(30) := 'ch18_txt_file.txt';  -- 파일명
      vf_file_buff  RAW(32767);   -- 실제 파일을 담을 RAW타입 변수
      vf_temp_buff  RAW(54);
      vn_file_len   NUMBER := 0;  -- 파일 길이

      -- 한 줄당 올 수 있는 BASE64 변환된 데이터 최대 길이
      vn_base64_max_len  NUMBER := 54; --76 * (3/4);
      vn_pos             NUMBER := 1; --파일 위치를 담는 변수
      -- 파일을 한 줄씩 자를 때 사용할 단위 바이트 수
      vn_divide          NUMBER := 0;
    BEGIN
      c := UTL_SMTP.OPEN_CONNECTION(vv_host, vn_port);

      UTL_SMTP.HELO(c, vv_domain); -- HELO
      UTL_SMTP.MAIL(c, vv_from);   -- 보내는사람
      UTL_SMTP.RCPT(c, vv_to);     -- 받는사람

      UTL_SMTP.OPEN_DATA(c); -- 메일 본문 작성 시작
      UTL_SMTP.WRITE_DATA(c,'MIME-Version: 1.0' || UTL_TCP.CRLF ); -- MIME 버전
      -- Content-Type: multipart/mixed, boundary 입력
      UTL_SMTP.WRITE_DATA(c,'Content-Type: multipart/mixed; boundary="' || vv_boundary || '"' || UTL_TCP.CRLF);

      UTL_SMTP.WRITE_RAW_DATA(c, UTL_RAW.CAST_TO_RAW('From: ' || '"홍길동" <charieh@hong.com>' || UTL_TCP.CRLF) );
      UTL_SMTP.WRITE_RAW_DATA(c, UTL_RAW.CAST_TO_RAW('To: ' || '"홍길동" <charieh@hong.com>' || UTL_TCP.CRLF) );
      UTL_SMTP.WRITE_RAW_DATA(c, UTL_RAW.CAST_TO_RAW('Subject: HTML 첨부파일 테스트' || UTL_TCP.CRLF) );
      UTL_SMTP.WRITE_DATA(c, UTL_TCP.CRLF );

      -- HTML 본문을 작성
      vv_html := '<HEAD>
       <TITLE>HTML 테스트</TITLE>
     </HEAD>
     <BDOY>
        <p>이 메일은 <b>HTML</b> <i>버전</i> 으로 </p>
        <p>첨부파일까지 들어간 <strong>메일</strong>입니다. </p>
     </BODY>
    </HTML>';

      -- 메일 본문, Content-Type이 바뀌므로 boundary 추가
      UTL_SMTP.WRITE_DATA(c, '--' || vv_boundary || UTL_TCP.CRLF );
      UTL_SMTP.WRITE_DATA(c, 'Content-Type: text/html;' || UTL_TCP.CRLF );
      UTL_SMTP.WRITE_DATA(c, 'charset=euc-kr' || UTL_TCP.CRLF );
      UTL_SMTP.WRITE_DATA( c, UTL_TCP.CRLF );
      UTL_SMTP.WRITE_RAW_DATA(c, UTL_RAW.CAST_TO_RAW(vv_html || UTL_TCP.CRLF)  );
      UTL_SMTP.WRITE_DATA( c, UTL_TCP.CRLF );

      -- 첨부파일 추가
      UTL_SMTP.WRITE_DATA(c, '--' || vv_boundary || UTL_TCP.CRLF );
      -- 파일의 Content-Type은 application/octet-stream
      UTL_SMTP.WRITE_DATA(c,'Content-Type: application/octet-stream; name="' || vv_filename || '"' || UTL_TCP.CRLF);
      UTL_SMTP.WRITE_DATA(c,'Content-Transfer-Encoding: base64' || UTL_TCP.CRLF);
      UTL_SMTP.WRITE_DATA(c,'Content-Disposition: attachment; filename="' || vv_filename || '"' || UTL_TCP.CRLF);
      UTL_SMTP.WRITE_DATA(c, UTL_TCP.CRLF);

      -- fn_get_raw_file 함수를 사용해 실제 파일을 읽어 온다
      vf_file_buff := fn_get_raw_file(vv_directory, vv_filename);
      -- 파일의 총 크기를 가져온다.
      vn_file_len := DBMS_LOB.GETLENGTH(vf_file_buff);

      -- 파일전체 크기가 vn_base64_max_len 보다 작다면, 분할단위수인 vn_divide 값은 파일크기로 설정
      IF vn_file_len <= vn_base64_max_len THEN
         vn_divide := vn_file_len;
      ELSE -- 그렇지 않다면 BASE64 분할단위인 vn_base64_max_len로 설정
         vn_divide := vn_base64_max_len;
      END IF;

      -- 루프를 돌며 파일을 BASE64로 변환해 한 쭐씩 찍는다.
      vn_pos := 0;
      WHILE vn_pos < vn_file_len
      LOOP

        -- (파일 전체 크기 - 현재 크기)가 분할 단위보다 크면
        IF (vn_file_len - vn_pos) >= vn_divide then
           vn_divide := vn_divide;
        ELSE -- 그렇지 않으면 분할단위 = (파일전체크기 - 현재크기)
           vn_divide := vn_file_len - vn_pos;
        END IF ;

        -- 파일을 54 단위로 자른다.
        vf_temp_buff := UTL_RAW.SUBSTR ( vf_file_buff, vn_pos, vn_divide);
        -- BASE64 인코딩을 한 후 파일내용 첨부
        UTL_SMTP.WRITE_RAW_DATA(c, UTL_ENCODE.BASE64_ENCODE ( vf_temp_buff));
        UTL_SMTP.WRITE_DATA(c,  UTL_TCP.CRLF );

        -- vn_pos는 vn_base64_max_len 값 단위로 증가
        vn_pos := vn_pos + vn_divide;
      END LOOP;

        -- 맨 마지막 boundary에는 앞과 뒤에 '--'를 반드시 붙여야 한다.
      UTL_SMTP.WRITE_DATA(c, '--' ||  vv_boundary || '--' || UTL_TCP.CRLF );

      UTL_SMTP.CLOSE_DATA(c); -- 메일 본문 작성 종료
      UTL_SMTP.QUIT(c);       -- 메일 세션 종료

    EXCEPTION
      WHEN UTL_SMTP.INVALID_OPERATION THEN
           dbms_output.put_line(' Invalid Operation in Mail attempt using UTL_SMTP.');
           dbms_output.put_line(sqlerrm);
           UTL_SMTP.QUIT(c);
      WHEN UTL_SMTP.TRANSIENT_ERROR THEN
           dbms_output.put_line(' Temporary e-mail issue - try again');
           UTL_SMTP.QUIT(c);
      WHEN UTL_SMTP.PERMANENT_ERROR THEN
           dbms_output.put_line(' Permanent Error Encountered.');
           dbms_output.put_line(sqlerrm);
           UTL_SMTP.QUIT(c);
      WHEN OTHERS THEN
         dbms_output.put_line(sqlerrm);
         UTL_SMTP.QUIT(c);
    END;

결과

    익명 블록이 완료되었습니다.
신간 소식 구독하기
뉴스레터에 가입하시고 이메일로 신간 소식을 받아 보세요.