使用Akka Http,ActiveMQ搭建一個郵件發送服務器

代碼地址:https://github.com/yangbajing/scala-applications/tree/master/email-serverjava

應用功能是實現一個基於隊列的郵件發送服務,每一個郵件發送者(使用smtp協議)做爲一個sender。多個sender能夠在同一個組(group)中,每一個組中的sender將串行發送郵件。git

郵件內容能夠經過REST API提交,以可使用JMS發佈到ActiveMQ中,郵件服務器將從中讀取郵件內容。github

快速開始

配置:apache

推薦在啓動時使用-Dapplication.file=/usr/app/etc/emailserver/application.conf指定配置文件json

emailserver {
  server {
    interface = "0.0.0.0"
    port = 9999
  }

  emails {
    email1 {
      userName = "email1@email.com"
      password = ""
      smtp = ""
      smtpPort = 0
      ssl = true

      # 同一個組內的郵件會串行發送,此key可忽略
      group = "1"
    }

    email2 {
      userName = "email2@email.com"
      password = ""
      smtp = ""
      smtpPort = 0
      ssl = true
      group = "2"
    }
  }

  activemq {
    url = "tcp://127.0.0.1:61616"
    emailQueueName = "EmailQueue"
  }
}

emails定義郵件發送者(可用於stmp服務進行郵件發送的郵箱信息)。能夠定義多個郵件發送者,但每一個郵件發送者的key不能相同。好比:email1email2服務器

activemq定義了ActiveMQ服務的鏈接參數。併發

編譯與運行:app

# 編譯
./sbt assembly

# 運行
java -Dapplication.file=/usr/app/etc/emailserver/application.conf -jar target/scala-2.11/email-server.jar

測試REST服務:框架

# 查詢存在的郵箱發送者:
curl http://localhost:9999/email/users

# 發送測試郵件
curl -v -XPOST -H "Content-Type: application/json" \
  -d '{"sender":"Info@email.cn", "subject":"測試郵件","to":["user1@email.cn", "user2@email.cn"],"content":"測試郵件內容咯~"}' \
  http://localhost:9999/email/send

使用JMS發送郵件:curl

安裝activemq

Downloads:http://mirrors.hust.edu.cn/apache/activemq/5.11.1/apache-activemq-5.11.1-bin.tar.gz

tar zxf ~/Downloads/apache-activemq-5.11.1-bin.tar.gz
cd apache-activemq-5.11.1/
./bin/activemq-admin start

activemq管理控制檯地址:http://localhost:8161/admin/,帳號:admin,密碼:admin

JMS TCP地址:tcp://localhost:61616

生產測試郵件

修改EmailProducers.scalaactiveMqUrlmapMessage參數,運行EmailProducers生產一個郵件發送請求。

Akka Http

Akka Http是一個完整的serverclient端HTTP開發棧,基於akka-actorakka-stream。它不是一個WEB框架,而是提供了能夠構建Http服務的工具包。

Akka Http有一套很直觀的DSL來定義路由,天然的造成了一個樹型的路由結構。如Routes

pathPrefix("email") {
  path("send") {
    post {
      entity(as[JsValue].map(_.as[SendEmail])) { sendEmail =>
        onComplete(emailService.sendEmail(sendEmail)) {
          case Success(value) =>
            value match {
              case Right(msg) => complete(msg)
              case Left(msg) => complete(StatusCodes.Forbidden, msg)
            }

          case Failure(e) => complete(StatusCodes.InternalServerError, "SendEmail an error occurred: " + e.getMessage)
        }
      }
    }
  } ~
    path("users") {
      get {
        onComplete(emailService.getEmailSenders) {
          case Success(emailSenders) => complete(Json.toJson(emailSenders))
          case Failure(e) => complete(StatusCodes.InternalServerError, "An error occurred: " + e.getMessage)
        }
      }
    }
}

path -> post -> entity -> complete 式的函數嵌套,很直觀的定義出了聲明式的樹型REST URI結構,井井有條、邏輯清晰。entity函數用於解析Http Body,將其映射成但願的數據類型,可自定義映射方法。

onComplete函數用在返回類型是一個Future[T]時,提供了快捷的方式把一個Future[T]類型的響應轉換到complete

郵件發送

郵件的發送採用了串行發送的方式,這個模式恰好契合Actor默認郵箱的FIFO處理形式。把收到的郵件發送請求告訴一個actoractor再從郵箱裏取出,並組裝成XXXXEmail(郵件發送使用了commons-email)後發送出去。

首先,程序將收到的郵件發送請求交給EmailMasterEmailMaster再根據郵件發送者(鏈接SMTP的郵箱用戶名)來決定將這個發送請求交給哪個具體的EmailGroupActor

這裏,程序對郵件發送者(簡稱:sender)作了一個分組。由於對於使用相同smtp郵件發送服務提供的sender,程序中最後對此類的sender作串行發送。而對於不一樣smtp郵件發送服務提供的sender,咱們能夠併發的發送郵件。這個能夠經過在定義配置文件的時候指定特定sender屬於的郵件發送組。

emails {
    email1 {
      userName = "email1@email.com"
      password = ""
      smtp = ""
      smtpPort = 0
      ssl = true

      # 同一個組內的郵件會串行發送,此key可忽略
      group = "1"
    }
  }

鏈接ActiveMQ

鏈接ActiveMQ使用了JMS協議,這是一個Java EE標準實現的消息隊列。代碼在:MQConsumerService

JMS裏,郵件使用MapMessage消息發送,程序使用case match來匹配指望的消息格式。

val listener = new MessageListener {
  override def onMessage(message: Message): Unit = message match {
    case msg: MapMessage => {
      val subject = msg.getString("subject")
      val sender = msg.getString("sender")
      val content = msg.getString("content")
      val to = msg.getString("to").split(";")
      val mimeType = Option(msg.getString("mimeType")).map(MimeType.withName).getOrElse(MimeType.Text)
      val sendEmail = SendEmail(sender, subject, to, content, None, mimeType)
      emailService.sendEmail(sendEmail).onComplete(result => logger.debug(result.toString))
    }

總結

本文簡單的演示了Akka Http構建一個REST服務,並支持鏈接JMS Server來獲取發送郵件消息。

演示了文件郵件和HTML格式郵件的發送。

接下來

接下來能夠添加對郵件附件的支持,這個功能能夠留給讀者去實現。

相關文章
相關標籤/搜索