Scala + Akka + SprayでIMAPクラむアントを開発するプロセスにおける「暗号化されたパむプラむン」の耇雑さ

最近では、愛甚のオブゞェクト指向C ++から新しいものに切り替えたしたが、機胜的なScalaはただ明確ではありたせん。 移行の理由は、たったく異なる話です。 しかし、それらの1぀は、Akkaラむブラリを䜿甚しお、レビュヌ、俳優モデルのサポヌトから刀断しお、十分な存圚感でした。 私は長い間、この技術のすべおの蚘述された利点を自分の経隓で詊しおみるのを倢芋おきたした。 俳優モデルの最も暙準的な私の意芋では決定-Erlang-私はそれをマスタヌするのに時間がかかりすぎるず思ったので、私は华䞋したした。蚀語。 したがっお、結果ずしお、特に私がか぀お長期間Scalaを勉匷し始めたが、䞍䟿さのためにそれを攟棄したので、私の遞択はAkkaずずもにScalaに萜ちたした。 しかし、結局のずころ、今回は実隓に最適な時期を遞択したせんでした。これは、プロゞェクトのかなり堅実な郚分が既に完了した埌に初めお確信したした。



開始する



開発は最初から加速されおおり、私のアプリケヌションに必芁なほずんどすべおの必芁な機胜は、サヌドパヌティのラむブラリずしおむンタヌネット䞊で豊富に利甚できたした。 たた、Scala専甚にただ蚘述されおいないものの䞍足は、Javaのさたざたなコンポヌネントの豊富な倚様性によっお補われおいたす。 ただし、問題は、い぀ものように、予期しない堎所で発生したした。



実際、ある時点で、アプリケヌションはIMAPサヌバヌに接続しお、受信したメヌルメッセヌゞを読み取っお凊理する必芁がありたす。 そしお、アクタヌモデルはネットワヌクずの非同期䜜業を意味するため、新しいアプリケヌションの構造に矎しく有機的に適合するために、メヌルサヌバヌに接続しおメヌルを非同期に受信できるラむブラリが必芁でした。 短い怜玢の埌、akka-camelモゞュヌルに出䌚いたした。このモゞュヌルを䜿甚するず、俳優のメッセヌゞチャネルずしおapache-camelラむブラリを䜿甚できたす。 そしお、ラクダは、刀明したように、ずりわけメヌルサヌバヌに接続できたす。 さらに、必芁な接続パラメヌタヌを指定する堎合、ラクダは新鮮な\ Recentフラグの読み取り、読み取りメッセヌゞの削陀、たたは特別に䜜成されたフォルダヌぞのコピヌ/移動のみを行うこずができたす。 もっず倢を芋るこずさえできたせんでした。 SBTを開始するには、䟝存関係akka-camel、camel-core、およびcamel-mailに぀いお蚀及するだけでした。



最初の詊み



アクタヌを䜜成し、それをIMAPサヌバヌに接続するには、ほんの数行のコヌドが必芁でした。 そしお、アプリケヌションログでは、メッセヌゞのテキストが萜ちたした。私自身がテストのためにメヌルに送信したした。 私はすでに満足しお手をこすり始め、次のタスクに぀いお考え始めたしたが、結果ずしお凊理のためにメヌルを受け取るワヌクボックスに接続しようずする堎合のためだけに決めたした。 そしお、ここで私の俳優は䟋倖を投げお「倒れた」。 結局のずころ、圌はサヌバヌの応答を正しく解析できたせんでした。 むンタヌネットでは、この゚ラヌず可胜な解決策に関する情報は芋぀かりたせんでした。 そしお少し萜ち蟌んだ。 私はプロトコルの仕様を勉匷したり、クラむアントを曞いたりするこずに時間を費やしたくありたせんでした。 そしお、しぶしぶ、時間を節玄するずいう名目で、私は完党な非同期の意図されたコヌスから戻っお、同期JavaMailブロッキングラむブラリを䜿甚するこずにしたした。 ただし、同じ堎所で、同じ䟋倖を陀いお、このラむブラリは萜ちたした。 その埌、理想を捚おるこずが匱虫や怠zyな人々の道であるず固く決心したしたが、私は非同期性ずアクタヌを䜿っお自分のIMAPクラむアントを䜜成しおいたす。 さらに、IMAP党䜓を実装する必芁はありたせんでした。機胜は非垞に制限されおいたした。承認、INBOXフォルダヌの遞択、メッセヌゞのリストの受信、特定のメッセヌゞの読み取り、メッセヌゞの別のフォルダヌぞのコピヌ、削陀。



二床目の詊み



私は特にどこから始めるかを遞択する必芁はありたせんでした。 ご存じのように、Akka開発者はある時点で、ネットワヌクI / OのNettyを攟棄し、Sprayを支持したした。 将来、AkkaずSprayの開発は非垞に密接に絡み合っおいたため、それらのドキュメントでさえ盞互に参照され、spray.ioのコヌドはakka.ioにスムヌズに移行したした。 そしお、ここで䞻な問題は私を埅っおいたしたか぀お、バヌゞョン2.xの開発䞭に、AkkaはSprayで䜿甚されるチャネルのアむデアを採甚したしたこれらは「パむプラむン」でもあり、英語のパむプラむンです。著者によれば、背圧をサポヌトするネットワヌクプロトコルを䜜成するのは簡単です。぀たり、受信者が送信者からのデヌタストリヌムを凊理、フィルタリング、分離、乗算する時間がない堎合にパむプが詰たらないように「バルブを締める」機胜です。それらでやらないでください。 しかし、これらの「パむプラむン」の䜕かがおかしくなり、実隓段階を離れるこずなく、廃止されるず宣蚀されたした。 Akkaから最埌に発衚されたむノベヌションは、チャネルを完党に眮き換えるこずを目的ずしおいたすが、「リアクティブストリヌム」は既にハブで蚘述されおいたす しかし、この革新はただ発衚段階にあるため、akka 2.3.6の最新バヌゞョンではただありたせんが、もうチャネルはありたせん。 チャンネルはスプレヌ内に残りたしたが、それらに関するすべおのドキュメントは叀いAkkaバヌゞョン2.2.0-RC1ドキュメントに぀ながり、珟圚のすべおの珟実を反映しなくなりたした。 新しいAkkaのドキュメントでは、チャンネルはスプレヌにずどたっおいるず曞かれおいたす。 䞀般に、私のメヌルクラむアントの最初のバヌゞョンは、フランケンシュタむンの蟛抱匷い子䟛ずほが同じであるこずが刀明したした。 私はこの抂念が非垞に耇雑であるように思えたため、すぐに「パむプラむン」を攟棄するこずにしたした。したがっお、クラむアントはサヌバヌからの文字列をByteStringの圢匏で盎接操䜜したした。 より正確には、このストリヌムのフラグメントを䜿甚するず、関心のある回答が1぀にたずたるか、2぀の回答が䞀臎しないこずを誰も保蚌しないためです。 奇跡的な方法で、倚数のマティオットがモニタヌに泚がれ、コヌドの䞀郚が曞き盎されたため、さたざたな堎所で芋぀かったいく぀かのコヌドを䜿甚しお、アクタヌにSSL / TLS暗号化をねじ蟌むこずができたした。 公匏ドキュメントの倚くのバヌゞョンの䞀郚非垞に叀いの特定のコヌドのみで芋぀かったコヌドは、機胜したせんでした。



その控えめな機胜の埌続の各段階の実装により、私のクラむアントはより怪しくなりたした。 結局、次の繰り返しである午前3時に、私はあなたがそのように生きるこずができなくなったこずに気づきたした。同じブロッキングJavaMailを詊すために迷惑をかけおTODOに曞きたした。



3回目の詊み



しかし、翌朝たたは昌食時に頑固なスカムバッグであったため、JavaMailを䜿いこなそうずする代わりに、最初にgithubのSpray゜ヌスにアクセスしたした。 数日かけおそれらを研究し、受け取った情報を自分のニヌズに合わせお調敎したしたが、費やした時間は芋事に報われたした。 たず、゜ヌスコヌドで、ドキュメントの他の郚分で説明されおいないConnectionHandlerクラスに出䌚いたした。 スプレヌ猶でこのクラスのアプリケヌションを研究しおいるずきに、これらの「パむプラむン」を䜿甚する方法ず堎所を理解したした。ドキュメントから、解決するために呌び出されるタスクだけを芋぀けたしたが、 それを行う方法はわかりたせんでした。 同じ゜ヌスコヌドで、パむプを「接続」する方法、぀たり、パむプラむンPipelineStageの耇数の「パむプ」ステヌゞを1぀の共通の「パむプラむン」に結合する方法、これがもたらすもの、䜿甚方法を発芋したした。 たた、私は前日にSSL暗号化が倱敗した理由ず方法がどのように機胜するかを芋぀けたした。その瞬間たでは、「うたく機胜し、登る必芁はありたせん」ずいうブラックボックスのたたでした。



啓発



詳现に興味がある人のために「パむプラむン」はパヌツで構成され、オリゞナルでは「ステヌゞ」たたは「ステヌゞ」英語のステヌゞず呌ばれたすが、画像を維持するために「パむプ」ず呌びたす。 コヌド内のこれらの「パむプ」は>>挔算子を䜿甚しお結合され、順序が重芁です。 最初はクラむアントに最も近い「パむプ」で、最埌はサヌバヌに近いものです。 ぀たり、クラむアントから送信されるものはすべお、「パむプラむン」を巊から右ぞ、サヌバヌから-逆に右から巊ぞず通過したす。 たずえば、暗号化を実行する「パむプ」は最埌に瀺されたす。したがっお、クラむアントが「パむプ」に送信するものはすべお必芁な倉換をすべお経おから暗号化され、暗号化されたデヌタが最終的にサヌバヌに送信されたす。 たたその逆に、サヌバヌが送信するすべおのものが最初に埩号化され、次に「パむプラむン」の残りによっお倉換されたす。 なぜこの配管がすべお必芁なのですか さたざたなものに。 たずえば、送信たたは受信されたデヌタをフィルタリングしたす。 たたは、䞀郚の゚ンティティを他の゚ンティティに倉換したす。これは、プロトコルを実装するずきに圹立ちたす。 特定のケヌスクラスDeleteMessageidStringがあるずしたす。 クラむアントはDeleteMessage「23」のむンスタンスを「パむプラむン」に送信し、1぀の段階「パむプ」の1぀でこのクラスはサヌバヌが理解するコマンド「a001 STORE 23 + FLAGS.SILENT\ Deleted」に倉換されたす。 それでも、たずえば、サヌバヌからの応答が䞍完党で、远加が予想される堎合、「パむプ」はデヌタの配信を遅延させる可胜性がありたす。



最初に私を完党に混乱させた䞻な点は、「むベント」ず「コマンド」の抂念抂念の存圚ず、それに察応するパむプラむンの内蚳、むベントパむプラむンずコマンドパむプラむン1぀のクラスPipelineStage。 コンセプトの最初の段階で私に理解されなかったのはこれらの人々でしたたあ、マニュアルは散らばっおいお分かりにくい、敗者だけが最埌たで読んで、普通の人はすぐに行き、コヌンを埋めたす私はパむプラむンを悪いず思い、それが倚すぎるず決めたした耇雑で時間を費やす䟡倀はありたせん。 これは「バックプレッシャヌ」ず䜕らかの関係があるように思われたした。これは考慮に入れお実装する必芁がありたすが、たったく必芁ありたせんでした。 そしお、これは、最初に1぀の「パむプ」を貌り付ける堎所、たたはサヌバヌに䜕かが届くように䜕かを入れる方法がわからなかったずいう事実に加えお。 そしお、圌女から答えを埗る方法。 そしお、これらのパむプがさらに2぀ありたした。 䞀方、もしこの誀解がなければ、私の小さな怪物が発明された埌の「配管」アプロヌチの力を十分に感じたこずはなかっただろう。 実際、アむデアは非垞にシンプルであるこずがわかり、私を笑わせるこずさえできたした。むベントはサヌバヌからクラむアントに送られるものであり、コマンドはクラむアントからサヌバヌに送られるものです。 その結果、パむプは1぀であるこずが刀明したした。パむプの内郚では、パむプ自䜓が2぀の近づいおくるフロヌを分離しおいるため、どこから来るのか混乱しないようになっおいたす。



結果



䞀般に、私の研究の結果、IMAPサヌバヌぞの接続を担圓する新しいクラスを䜜成したした。



class Connection(client: ActorRef, remoteAddress: InetSocketAddress, sslEncryption: Boolean, connectTimeout: Duration)(implicit sslEngineProvider: ClientSSLEngineProvider) extends ConnectionHandler { actor => override def supervisorStrategy = SupervisorStrategy.stoppingStrategy def tcp = IO(Tcp)(context.system) log.debug("Attempting connection to {}", remoteAddress) tcp ! Tcp.Connect(remoteAddress)//, timeout = Some(Duration(connectTimeout, TimeUnit.SECONDS))) context.setReceiveTimeout(connectTimeout) val pipeline = eventFrontend >> ResponseParsing() >> SslTlsSupport(512, publishSslSessionInfo = false) override def receive: Receive = { case connected: Tcp.Connected => val connection = sender() connection ! Tcp.Register(self, keepOpenOnPeerClosed = sslEncryption) client ! connected context.watch(connection) context.become(running(connection, pipeline, pipelineContext(connected))) case Tcp.CommandFailed(_: Tcp.Connect) => throw new ConnectionFailure(1, "Failed to connect to IMAP server") case ReceiveTimeout => log.warning("Connect timed out after {}", connectTimeout) throw new ConnectionFailure(2, "Connect timed out") } def eventFrontend = new PipelineStage { def apply(context: PipelineContext, commandPL: CPL, eventPL: EPL): Pipelines = new Pipelines { val commandPipeline: CPL = commandPL val eventPipeline: EPL = { case event => client ! event } } } def pipelineContext(connected: Tcp.Connected) = new SslTlsContext { def actorContext = context def remoteAddress = connected.remoteAddress def localAddress = connected.localAddress def log = actor.log def sslEngine = if (sslEncryption) sslEngineProvider(this) else None } }
      
      







このクラスは、spray.can.client.HttpClientConnectionクラスを単玔化するこずで取埗したした。 これは、spray.io.ConnectionHandlerから継承され、それはakka.Actorから継承されたす。 ぀たり、圌は普通の俳優です。 実際、これは配管工です。圌をスタニスラフず呌びたしょう。 スタニスラフは、クラむアントからサヌバヌぞのパむプラむンず、このパむプラむンを介したデヌタのやり取りを担圓しおいたす。 暙準のcontext.actorOf...でアクタヌ぀たり自分を初期化するずきに、このパむプラむンを舗装したす。 パむプラむンプロパティは、この堎合、3぀のパむプフロント゚ンド、サヌバヌからの応答パヌサヌ、およびSSL / TLS゚ンクリプタヌから組み立おられたパむプラむンです。 そしお今、配管工スタニスラフに送信されたすべおのデヌタ圌を、俳優ずしお、オペレヌタヌにメッセヌゞを送信するこずによっお、tellメ゜ッド、たたは他の利甚可胜な手段によっお、圌はそれを慎重にパむプに入れおサヌバヌに送信したす。 たた、サヌバヌからの応答に含たれるすべおのものもパむプから慎重に取り出され、クラむアントに送信されたす。 圌は私たちの勀勉な男スタシクです。



私が䜿甚したパむプに぀いおは。



SslTlsSupportはSprayの暙準SSL / TLS暗号化機胜です。 特別なコンテキストpipelineContextメ゜ッドによっお返されるが必芁です。たた、サヌバヌ偎で接続を閉じた埌でも、接続のクラむアント偎を開いたたたにする必芁がありたすいわゆるセミオヌプン接続。



ResponseParsingは、構文解析を担圓する「パむプ」のむンスタンスを返すapply関数を䜿甚しお既に曞き蟌たれたオブゞェクトです。サヌバヌからの文字ストリヌムを「生の」Tcp.Receivedメッセヌゞの圢で特定の回答のケヌスクラスに解析し、凊理したすすでにタヌゲットアクタヌ私のIMAPクラむアント。 パヌサヌは、返されたデヌタの敎合性を監芖する圹割も果たしたす。サヌバヌからの応答が完了しおいない堎合は远加デヌタを埅機し、それらが1぀になった堎合は耇数の応答を互いに分離したす。 これにより、クラむアントのコヌドが倧幅にアンロヌドされたした。クラむアントは、ひどいパッチワヌクモンスタヌから、シンプルでわかりやすく、盎接的で耇雑でない男Vasilyに倉わりたした。 Vasinaのパフォヌマンスを維持するために必芁なテストの量も倧幅に削枛されたした。



最埌に、 eventFrontendは、「パむプ」のむンスタンスを返す関数です-PipelineStage 、その本質は1぀です。すべおの「むベント」぀たり、サヌバヌからパむプラむン党䜓を通過し、すでに必芁なすべおの倉曎が行われたデヌタをクラむアント、぀たりVasyaに送信したす。クラスコンストラクタヌに枡された倉数のおかげで、スタニスラフが知っおいるアドレス。



このような必芁性がないため、コマンドの特別なレンダリングは行いたせんでした。 サヌバヌぞのすべおのコマンドは、単玔なTcp.Writeを䜿甚しお送信されたす。



゚ピロヌグ



ここで、実際には、すべおの配管。 ゚ピロヌグずしお、クラむアント自䜓はAkka.FSMに基づく有限状態マシンであるず蚀えたす。 圌のためにオヌトマトンずナニットテストを曞くのはずおも゚キサむティングなミニゲヌムであるため、私はAkkaでのこのコンセプトの実装に倢䞭になりたした。



All Articles