Code Monkey home page Code Monkey logo

webmua's Introduction

[Graha 응용프로그램]webmua

0. notice

최근에 Graha 소스코드는 새롭게 작성되었고, 이를 반영하기 위한 약간의 수정이 있었다.

1. about

Graha를 활용한 Web 기반의 이메일 클라이언트 프로그램이다.

2. 기능

webmua 프로그램는 웹메일 프로그램과 유사한 외관이지만, pop3 혹은 imap 서버에서 가져온 이메일만 처리하기 때문에, 전통적인 웹메일 프로그램과는 다르고, Outlook 이나 Thunderbird와 유사한 프로그램이라고 할 수 있다.

이 프로그램의 주요 목표는 다음과 같다.

  • 이메일의 기본기능을 제공한다.
  • pop3 혹은 imap 서버의 메시지의 사본을 보관한다.
  • 프로그래머인 사용자가 필요한 나머지 기능을 추가할 수 있도록 한다.

3. 실행환경

이 프로그램은 다음과 같은 환경에서 개발되고 테스트 되었다.

  • Apache Tomcat 7.x 이상
  • PostgreSQL 9.1.4 이상
  • JDBC 4.0 이상
  • Graha 0.5.1.187 이상
  • 최신버전의 JavaMail API

4. 테스트한 메일 서버

  • Apache James 2.x 의 pop3 계정
  • Apache James 3.x 의 imap 계정
  • Kakao(Daum) 의 imap 계정
  • Naver 의 imap 계정
  • Aol. 의 imap 계정 (Account Security 에서 "App Password" 을 설정해야 한다)
  • Gmail 의 imap 계정 (2단계 인증을 설정하고, "App Password" 을 설정해야 한다)

대부분의 이메일 서비스 업체들은 사용자가 imap 이나 pop3 기능을 사용할지 선택하도록 하고 있으므로, 이에 대한 별도의 설정이 필요하다.

Aol. 과 Gmail에서 "App Password" 는 2단계 인증을 사용할수 없는 경우에 임시 패스워드를 발급받아 사용하는 것을 말하는데, 다른 이메일 서비스 업체들도 유사한 방식으로 나아갈 것으로 예상된다.

4. 일반적이지 않은 구현

4.1. 이메일을 발송하는 사용자 인터페이스

  • 메일쓰기 기능에서는 (이메일을 발송하지 않고) 메시지를 Draft 폴더에만 저장하고,
  • Draft 폴더의 메일 상세보기 화면에서 이메일 전송 버튼을 클릭해야만 이메일이 전송된다.

미리보기를 한 이후에만 전송할 수 있다고 보면 된다. 사용자의 과거 경험과 조화를 이루지 못하지만, 메일의 내용이나 첨부파일을 확인한 이후에 이메일을 전송하기 때문에, 잘못된 파일을 첨부하는 등의 사소한 실수를 줄일 수 있다.

4.2. HTML 이메일 작성을 지원하지 않는다.

5. 비표준 지원

이 프로그램은 표준에서 벗어난 메시지로 부터 발생하는 문제(화면에서 한글이 깨지는 현상)를 해결하기 위해 다음과 같이 처리한다.

5.1. 헤더 정보에서 얻어온 charset을 이용하고, 이메일 계정, 폴더에 각각 기본 문자셋을 지정할 수 있다.

  • 메일 헤더의 경우 US-ASCII를 벗어난 경우,
  • 메일 내용은 charset 이 지정되지 않는 경우,
  • 헤더 정보에서 얻어온 charset, 메일의 기본 문자셋, 폴더의 기본 문자셋, 이메일 계정의 기본 문자셋을 우선순위로 처리한다.
  • Thunderbird 와 유사한 방식이지만, 헤더 정보에서 얻어온 charset을 사용한다는 것만 다르다.

5.2. 이메일 상세보기 화면에서 charset 을 변경 할 수 있다.

  • 앞으로 이 메일은 여기서 지정된 charset 을 기본 문자셋으로 사용하게 될 것이다.
  • Thunderbird 에서도 이와 같은 방식을 지원한다.

5.3. Subject를 가져오기 위한 특별한 처리

  • Subject 헤더가 Quotation mark 로 묶여 있는 경우, 이를 제거하고 처리한다(US-ASCII 범위인 경우에만).
  • Subject 헤더가 base64로 인코딩 되어 있고, 여러 줄인 경우, Message.getSubject() 대신에 MimeUtility.decodeText 를 본 떠 만든 것을 사용한다.
  • JavaMail API 에서는 매 줄마다 decodeWord 메소드를 호출하는데, byte array 를 문자열로 변경 할 때(new String()) 한글이 깨지는 경우가 있다.

5.4. 이메일 주소를 가져오기 위한 특별한 처리

  • JavaMail API는 이메일 주소 형식이 잘못된 것이 명백한 경우, 예외를 발생시키는데, 여러 개의 이메일 주소가 지정되어 있고, 그 중 1개의 이메일 주소 형식이 잘못된 경우 전체가 에러가 난다.
  • 이를 처리하기 위해서 JavaMail API 로 이메일 주소 처리가 실패한 경우, 이메일 주소를 1개씩 읽어, 실패한 것만 skip 하는 방식으로 처리한다.
  • (스팸메일일 가능성이 매우 높지만) 이메일 주소가 매우 긴 경우, 이메일 주소 사이에 줄바꿈이 발생하는 경우도 있고, 이 경우 JavaMail API 에서 처리할 수 없으므로, 프로그램적으로 처리한다.

6. 제한

6.1. imap 혹은 pop3 서버에서 이메일 가져오는 기능이 느리게 동작하는 경우에도 인내심을 가지고 실행이 끝날때가지 기다려야 한다.

imap 혹은 pop3 서버에서 받아와야 할 이메일이 많은 경우 매우 느리게 동작할 수 있으며, 다음과 같은 상황에서 심각할 수 있다.

  • 계정을 추가하고 처음 이메일을 받아오는데, imap 혹은 pop3 서버에 많은 메시지가 있는 경우
  • 계정을 추가한 이후에 오래동안 메시지를 받아오지 않았는데, 그 사이에 imap 혹은 pop3 서버에 많은 메시지가 쌓인 경우

일반적인 상황에서도 (연결을 pooling 할 수 없으므로) imap 혹은 pop3 연결을 맺는데 높은 비용이 발생한다.

6.2. imap 혹은 pop3 서버에서 메시지를 가져오는데 실패하면 다음번에도 실패할 가능성이 높다.

webmua 프로그램은 한번 가져온 이메일은 다시는 가져오지 않는데, 서버에서 최근 이메일부터 가져오다가, 이미 받아온 이메일을 발견하면, 그 이후의 것들은 가져오지 않는다.

만약 imap 혹은 pop3 서버에서 1개의 메시지만 받아오고, 내부적인 에러가 발생해서 나머지 메시지를 받아오지 못했다면, 나머지 메시지는 앞으로도 가져오지 못할 가능성이 높다.

이미 받아온 이메일인지 판정하기 위해 다음과 같은 방식을 사용한다.

  • pop3 서버의 경우 : UID로만 비교한다(MessageID 로만 비교하도록 설정할 수 있다).
  • imap 서버의 경우 : UID 와 modseq 혹은 UID 와 메시지 크기, MessageID 로 비교한다(MessageID 로만 비교하도록 설정할 수 있다).
  • imap 서버가 modseq를 지원하는 경우 imap 서버의 이메일 폴더의 가장 큰 modseq 와 최근에 받아온 이메일의 가장 큰 modseq을 비교한다.

대부분의 상업용 이메일 서비스 업체들은 modseq를 지원하지 않는다. modseq를 지원하는 메일서버는 Apache James 3.x 가 대표적이고, 이메일 서비스 업체중에는 Google 가 대표적이다. 이 글을 작성하는 시점에서 Aol, Daum(Kakao), Naver 는 modseq을 지원하지 않는 것이 확인되었다.

6.3. imap 서버와 통신하는 방식이 다르다.

webmua 프로그램은 imap 서버와 통신할 때 Thunderbird나 Outlook 과 같은 전통적인 이메일 클라이언트들과 달리 pop3 서버에 접근하는 것과 거의 유사한 방식을 사용한다.

전통적인 이메일 클라이언트들은 imap 서버와 통신하면서 클라이언트의 역활만 담당하고, imap 서버를 이메일 저장소로 사용하면서, 클라이언트에는 이메일 목록을 표시하기 위한 정도의 cache 정도만을 보관한다.

모든 변경사항은 imap 서버에 저장되고, 클라이언트와 서버가 서로 다른 데이타를 가지고 있는 경우 언제나 서버가 우선이 된다.

반면 webmua 프로그램은 pop3 와 통신하는 것과 매우 유사한 방식으로 통신하며, 구체적인 사항은 다음과 같다.

  • 서버에서 이메일을 가져올 때, 메시지 전체를 다운로드 받아서, eml 파일로 저장하고, 데이타베이스와 파일시스템에 서비스용 cache 데이타를 생성한다.
  • 먼저 메시지 헤더만 가져오고, 이메일 내용을 확인할 때, 전체 메시지를 가져오도록 설정할 수 있지만, 일부 imap 서버에서 오류가 발견되었으므로 추천하지 않는다.
  • 이메일을 발송하거나 메시지를 다른 폴더로 이동하거나 삭제한다고 하더라도 imap 서버에 반영하지 않는다.
  • imap 서버에서 삭제된 메시지를 확인하지 않는다(다만, imap 서버의 최신 메일 중 uid 가 동일하지만, MessageID 나 size 가 다른 경우 삭제된 메일로 취급한다).

6.4. 가급적 pop3 서버를 이용한다.

pop3 서버를 이용하면 불편한 점도 있다.

  • 받은편지함(Inbox)만 받아올 수 있다.

반면, imap 서버를 이용할 경우 다음과 같이 성능저하와 기능상의 제약을 감수해야 한다.

  • webmua 프로그램은 pop3 나 imap 서버에서 메시지 전체를 다운로드해서, webmua 서버에 저장한다. 이런 방식은 소규모의 트렌젝션을 빈번하게 발생시키는 imap 통신규약과 조화롭지 못하기 때문에, 성능에 부정적인 영향이 있다.
  • webmua는 서버용 프로그램이기 때문에, 다른 imap 클라이언트와 같은 방식으로 구현하는 것은 한계가 있고, imap 클라이언트라고 할수 없을 정도로 대부분의 기능이 누락되었다.

6.4. 이메일을 한번에 업로드하는 기능도 있다.

대부분의 이메일 서비스 업체들은 이메일 전체(Gmail)나 폴더(Daum(Kakao), Naver) 내려받기 기능을 제공한다.

imap/pop3 서버에 이메일이 많은 경우 이메일을 한번에 내려 받은 다음 webmua 서버에 한번에 업로드 할 수 있다.

webmua의 폴더 정보를 수정하는 화면에서 파일을 업로드 하면 해당 폴더에 이메일을 업로드 한다.

이 기능은 다음의 상황에서 테스트 했다.

  • 1개의 eml 파일 혹은 eml 파일이 gzip으로 묶여 있는 경우
  • 여러개의 eml 파일이 zip으로 묶여 있는 경우
  • 1개의 mbox 파일 혹은 mbox 파일이 gzip으로 묶여 있는 경우
  • 1개의 Thunderbird mbox 파일 혹은 이것이 gzip으로 묶여 있는 경우
  • 1개 이상의 mbox 파일 혹은 Thunderbird mbox 파일이 zip으로 묶여 있는 경우

다음과 같은 구체적인 상황에서 테스트 했다.

  • Thunderbird mbox 파일 (폴더를 Compact 한 이후가 아니라면, 지운 이메일도 같이 업로드 된다)
  • Linux의 mbox 파일
  • Daum(Kakao) 에서 다운로드 받은 파일
  • Naver 에서 다운로드 받은 파일
  • Gmail 에서 다운로드 받은 파일(폴더가 자동으로 분류되지 않는다)

이 기능을 이용하면 pop3나 imap 서버에서 대량의 이메일을 받아오는 일을 우회하는 방법이 있다.

7. 알려진 버그 및 이슈

이 프로그램의 알려진 버그는 다음과 같다.

  • UTF-7은 처리하지 못한다.

8. Apache James를 위한 tip

이 프로그램에 메일서버가 더해지면, 웹메일 서비스를 구축할 수 있는데, 메일서버로 Apache James 를 검토하고 있다면, 필수적인 tip 을 소개하기로 한다.

Apache James 에서 비표준 메시지를 정상적으로 수신하기 위해서는(한글이 깨지지 않기 위해서는), Apache James 를 기동할 때 다음과 같은 파라미터가 추가되어야 한다.

-Dfile.encoding=ISO-8859-1

US-ASCII 가 아니다. US-ASCII와 ISO-8859-1 는 바이트 수가 다르다.

이를 위해서, Apache James 2 에서는 다음과 같은 환경변수를 설정한 이후에, Apache James 를 기동하면 된다.

export PHOENIX_JVM_OPTS="-Dfile.encoding=ISO-8859-1"

Apache James 3 에서는 conf/wrapper.conf 파일에 다음과 같은 내용을 추가해야 한다.

Apache James 2 에서도 Apache James 3 와 같은 방법을 사용할 수 있다.

wrapper.java.additional.15=-Dfile.encoding=ISO-8859-1

15는 일련번호 이므로 적당히 변경한다.

Apache James 를 기동하고, 다음의 명령어로 프로세스를 검사했을 때, Apache James 프로세스가 보여야 한다.

ps -ef | grep james | grep ISO-8859-1

9. 배포하는 곳

webmua's People

Watchers

김헌직 avatar

webmua's Issues

이메일 전달(Forwarding) 기능에서 원본 이메일에 첨부된 파일이름과 새롭게 첨부한 파일이름이 동일한 경우 예외가 발생한다.

  1. 위치

메일목록 > 메일 조회 > 전달 > 저장

  1. 상황

이메일 전달(Forwarding) 기능에서 원본 이메일에 첨부된 파일이름과 새롭게 첨부한 파일이름이 동일한 경우 예외가 발생한다.

  1. 에러 메시지

kr.graha.sample.webmua.ForwardMailProcessorImpl.execute java.nio.file.FileAlreadyExistsException:
at java.base/sun.nio.fs.UnixCopyFile.copy(UnixCopyFile.java:573)
at java.base/sun.nio.fs.UnixFileSystemProvider.copy(UnixFileSystemProvider.java:258)
at java.base/java.nio.file.Files.copy(Files.java:1294)
at kr.graha.sample.webmua.ForwardMailProcessorImpl.execute(ForwardMailProcessorImpl.java:117)
at kr.graha.post.model.Processor.execute(Processor.java:278)
at kr.graha.post.model.QueryXMLImpl.executeProcessor(QueryXMLImpl.java:77)
at kr.graha.post.model.QueryXMLImpl.document(QueryXMLImpl.java:534)
at kr.graha.post.model.QueryXMLImpl.execute(QueryXMLImpl.java:616)
at kr.graha.post.servlet.PostGeneratorServlet.execute(PostGeneratorServlet.java:99)
at kr.graha.post.servlet.PostGeneratorServlet.doGet(PostGeneratorServlet.java:54)
at kr.graha.post.servlet.PostGeneratorServlet.doPost(PostGeneratorServlet.java:50)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:660)

  1. 원인

4.1. 직접적인 원인

이메일 전달(Forwarding) 기능에서 이메일을 저장할 때,
모든 처리가 완료된 후에,
원본 이메일에 첨부되어 있던 첨부파일을 복사한다.
(kr.graha.post.interfaces.Processor 를 구현한 kr.graha.sample.webmua.ForwardMailProcessorImpl 에서 처리)

이 때 파일이름이 중복되는지 검사하고, 그렇다면 (일련번호 따위를 붙이는 등의 방법으로) 처리해야 하는데, 이것이 누락되어서 발생하는 예외이다.

4.2. 간접적인 원인

이메일 전달(Forwarding) 화면에서 원본 이메일에 첨부되어 있던 파일목록이 표시되지 않기 때문에
사용자는 같은 파일을 다시 첨부할 가능성이 있다.

  1. 해결방안

5.1. 원본 이메일에 첨부되어 있던 파일은 파일이름을 유지하고, 새롭게 업로드한 파일의 파일이름을 변경하는 방식으로 처리하는 방안이 있을 수 있다.

5.2. 이메일 전달(Forwarding) 화면에서 파일을 첨부하지 못하도록 막고, 원본 이메일에 첨부되어 있던 파일이 자동으로 첨부된다는 안내 메시지를 표시하는 방안도 있을 수 있다.
이메일 전달(Forwarding) 기능에서 새로운 파일을 첨부하는 것이 부적절하다는 것을 전제한 것이다.
사용자가 이메일을 저장 후에, 이메일 수정 화면에서 새로운 파일을 첨부할 수 있을 것이다.

5.3. 이메일 전달(Forwarding) 화면에서 원본 이메일에 첨부되어 있던 파일 목록을 표시하는 방안도 있을 수 있다.
다만, Graha 환경에서는 이메일을 Draft 폴더 등에 저장한 후에 원본 이메일에 첨부되어 있던 파일을 복사해야 하는데, 쓰레기 데이타가 쌓이는 문제가 발생할 수 있다.

decodeFileName(URI uri) 메소드의 내부구현은 검토할 필요가 있다.

  1. 현재의 방식
  • java.net.URI 를 toString()
  • "+" 를 "%2B" 로 replace
  • URLDecoder.decode 로 변환
  • 파일이름만 얻기 위해서 마지막 "/" 이후의 문자열만 추출
  1. 새로운 방식
  • java.net.URI 의 getPath() 로 전체 파일 경로를 얻어와서,
  • 파일이름만 얻기 위해서 마지막 "/" 이후의 문자열만 추출

메일쓰기 기능이 참을 수 없을 만큼 느린 경우가 있다.

메일 목록에서 메일쓰기로 이동하면, 메일작성 화면이 매우 느리게 열리는 경우가 있다.

확인은 필요하겠지만,
sql 실행시간과는 관련이 없을 가능성이 높다.

과 관련된 sql은 실행되지 않을 것이고, 와 로 정의한 sql 만 실행될 것인데, 최적화된 sql 은 아니지만, 데이타의 양과 인덱스 현황에 비추어, 다른 원인이 있을 가능성이 높다.

WAS 의 file.encoding 이 UTF-8 이 아닌 경우, 이메일 전달(Forwarding) 기능에서 한글로 된 첨부파일의 이름이 깨질것이다.

WAS 의 file.encoding 을 UTF-8 로 할 수 없는 사정이 있다면,
file.encoding 이 US-ASCII 에서 UTF-8 파일 이름 처리방법
에 따라 처리해야 한다.

graha 응용프로그램이 graha 전용 WAS 에 올라가는 것이 아니라면,
WAS 에서 돌아가는 다른 응용프로그램이 UTF-8 이 아닌 다른 file.encoding 을 요구하는 경우를 고려해야 한다.

이메일을 발송할 때, 파일이름에 "+" 가 포함되어 있다면, "+" 는 공백(" ") 으로 변경될 것이다.

kr/graha/sample/webmua/MailSendProcessorImpl.javadecodeFileName 메소드는
kr/graha/post/xml/GFile.javadecodeFileName 메소드의 legacy 버전에서 복사해온 것인데,
복사한 이후의 변경사항이 반영되지 않았다.

kr/graha/post/xml/GFile.java 에서
webmua 에 그대로 복사된 사례가 있는 getUniqueFileURI 와 decodeFileName 메소드를
kr.graha.helper 아래에 FILE 와 같은 class 로 모을지는 검토할 가치가 있는 것으로 보인다.

kr.graha.sample.webmua.MailSendProcessorImpl 의 getMessage 에서 디렉토리가 아닌 파일인지 검사하는 코드는 검토해야 한다.

검토대상 코드는 다음과 같다.

if(file.toFile().isFile()) {

java.nio.file.Path 를 java.io.File 로 변경한 후에, isFile() 메소드로 검사하는 코드인데,
다음과 같이 java.nio.file.Files 로 검사하는 코드로 변경하는 것을 검토할 필요가 있다.

if(Files.isRegularFile(file)) {

java.io.File.isFile() 은 Java 응용프로그램에서 생성한 파일에 대해서 true 를 보장하는데,
O/S 의 종류와 버전 혹은 JDK 의 버전에 따라
파일이름이 한글이고, file.encoding 이 US-ASCII(ANSI_X3.4-1968) 인 경우 false 를 리턴하는 경우도 있는 것으로 보인다.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.