④ 파일을 첨부해 메일 전송

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

    입력

        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;
    

    결과

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