åºæãšããŠ
ãããããããªãã®å®è·µã®å€ãã¯ãå€ãã®ã¡ãŒã«ããã¯ã¹ããã¡ãŒã«ãåéããã¿ã¹ã¯ã«çŽé¢ããŠããã§ãããã ãªããããå¿
èŠãªã®ã§ããããïŒ ããã¯ãããããã·ã¹ãã éã§ããŒã¿ã亀æããããã®æ®éçãªã¡ã«ããºã ã ããã§ãã SMTPãPOP3ãIMAPãã¡ãã»ãŒãžã¹ã¿ãã¯ãå®è£
ããããã®æ¢è£œã®ãœãªã¥ãŒã·ã§ã³ïŒã¡ãŒã«ããã¯ã¹ãšåŒãã§ããããã«...ïŒãªã©ãå®è£
ããèšèªçšã®ã©ã€ãã©ãªã倿°ãããŸãã
å€ãã®çµ±åã¿ã¹ã¯ãã¡ãŒã«ãä»ããŠå®è£
ãããŠããããšã¯é©ãããšã§ã¯ãããŸããã ããã§ã¯ãå¿
èŠãªã¢ã¯ã·ã§ã³ããã°ããååŸãåé¡ãããã³å®è¡ããæ¹æ³ãç¥ã£ãŠããç¹å®ã®ãµãŒãã¹ãç»å ŽããŸãã
以äžã®ã³ãŒãã§ååãªäºº-圌ãã¯ããã«èªãããšã¯ã§ããŸããïŒ
foreach (var mailbox in mailboxes) using (var client = new Pop3Client()) { client.Connect(Hostname, Port, false); client.Authenticate(User, Password); var count = client.GetMessageCount(); for (var i = 0; i < count; i++) { Mail = client.GetMessage(i + 1); var cat = SortMail(Mail); DoSomething(Mail, cat); } }
ç§ãã¡ã¯äœãããŸãã
ããã«ããã€ãã®ä»®å®ãè¡ããŸãã
1ïŒè€æ°ã®ã·ã¹ãã ã®ã¡ãŒã«ãåéããå¿
èŠããããŸãã å°æ¥çã«ã¯ããã«ããã€ãã®å¯èœæ§ããããŸãã ããã§ã...äžè¬ã«ã解決çã¯æ®éçã§ãªããã°ãªããŸããã
2ïŒããããã®ã¡ãŒã«ããããããããŸãã-ããã¯ãã©ã°ã©ã1ããç¶ããŸãïŒããã§ãªããã°ç§ã¯ãã®æçš¿ãæžããŸããïŒã
3ïŒã¡ãŒã«ã¯è§£æããå¿
èŠããããŸãã
4ïŒãã¹ãŠã®ãµãŒãã¹ããã¯ã¹-ãŠãŒã¶ãŒã¯ããã«è¡ããŸããã
䜿çšãããã®
ã·ã¹ãã ã¯å¹Žäžç¡äŒã§åäœããã¯ããªã®ã§ãWindowsãµãŒãã¹ã®åœ¢åŒã§å®è£
ããŸãã ãããã®ç®çã®ããã«ãããã«
TopShelfã䜿çšããããšããå§ãã
ãŸã ã
ãã¡ããããã¹ãŠã䞊ååããå¿
èŠããããŸãã
ããã§ ãç§ã®ãæ°ã«å
¥ãã®
TPL DataFlowã©ã€ãã©ãªãç»å ŽããŸãã
POP3çµç±ã§ã¡ãŒã«ãåãåããŸãã ãã®ã¿ã¹ã¯ã§ã®IMAPã®ããã¡ãã·ã§ããã«ãªãã®ãã¯ãã¹ãŠäžèŠã§ããã§ããã ãæ©ãæçŽã®ãœãŒã¹ãåãäžããŠããµãŒããŒäžã§åé€ããå¿
èŠããããŸãã POP3ã¯ç®ã«ãšã£ãŠååã§ãã
OpenPop.NETã䜿çšã
ãŸã ã
ãã§ã«è¿°ã¹ãããã«ãã¡ãŒã«ãè§£æããŸãã ãã¶ããæ£èŠè¡šçŸãä»ããŠãããããã«ã¹ã¿ã ããžãã¯ã...ããªãã¯äœãç¥ããªãã®ã§ãããã ãã©ã°ã€ã³ã®å©ããåããŠæè»ãã€è¿
éã«æ°ããã«ãŒã«ãããã·ã¥ããå¿
èŠãããã®ã¯ãã®ããã§ãã ããã§ã¯ã
Managed Extensibility Frameworkã圹ç«ã¡ãŸãã
NLogãä»ããŠãã°ãæžã蟌ã¿ãŸãã
ãªãã·ã§ã³ãšããŠã
Zabbixã§ç£èŠãåºå®ããŸãã ïŒ24æé幎äžç¡äŒã§äœæ¥ããèªæ
¢ã®ã¹ããŒããæäŸããŸã-ããã«åŸãå¿
èŠããããŸãïŒã
è¡ãã
éåžžã®ã³ã³ãœãŒã«ã¢ããªã±ãŒã·ã§ã³ãäœæããŸãã NuGetã³ã³ãœãŒã«ãéããå¿
èŠãªãã¹ãŠã®ããã±ãŒãžãã€ã³ã¹ããŒã«ããŸãã
Install-Package Nlog Install-Package OpenPop.NET Install-Package TopShelf Install-Package Microsoft.TPL.DataFlow
ãããžã§ã¯ããã©ã«ããŒã«ç§»åããApp.Debug.configãšApp.Release.configãäœæããŸãã ã¹ã¿ãžãªãããããžã§ã¯ããã¢ã³ããŒãããã³ãŒããéããŸãïŒä»¥äžTopCrawler.csprojïŒã æ§æãå«ãã»ã¯ã·ã§ã³ã«è¿œå ããŸãã
æ§æ <None Include="App.Debug.config"> <DependentUpon>App.config</DependentUpon> </None> <None Include="App.Release.config"> <DependentUpon>App.config</DependentUpon> </None>
次ã«ãMSBuildã®ã«ã¹ã¿ã ã¿ãŒã²ããã瀺ããŸãã
倿ã¿ãŒã²ãã <UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Web\Microsoft.Web.Publishing.Tasks.dll" /> <Target Name="AfterCompile" Condition="Exists('App.$(Configuration).config')"> <TransformXml Source="App.config" Destination="$(IntermediateOutputPath)$(TargetFileName).config" Transform="App.$(Configuration).config" /> <ItemGroup> <AppConfigWithTargetPath Remove="App.config" /> <AppConfigWithTargetPath Include="$(IntermediateOutputPath)$(TargetFileName).config"> <TargetPath>$(TargetFileName).config</TargetPath> </AppConfigWithTargetPath> </ItemGroup> </Target>
å人çã«ã¯ããã®æ¹æ³ã§-æãªããã®æ¹æ³ã§-æ§æã®å€æãå¥ã
ã®ç°å¢ã«è¿œå ããããšã«æ
£ããŸããã䟿å®äžãç§ã¯åŒ·ãã¿ã€ããããæ§æãæäŸããŸãã å¥ã®ã¯ã©ã¹ãæ§æãèªã¿åããŸãã ïŒã³ã¡ã³ãã§ãã®ãããªãœãªã¥ãŒã·ã§ã³ã®çè«çãªåŽé¢ã«ã€ããŠè©±ãããšãã§ããŸãïŒã æ§æããã°ãç£èŠã¯ãã·ã³ã°ã«ãã³ãã¿ãŒã³ãå®è£
ããåªããçç±ã§ãã
ãããžã§ã¯ãå
ã«åãååã®ãã©ã«ããŒãäœæããŸãïŒé åºãå¿
èŠã§ãïŒã å
éšã«3ã€ã®ã¯ã©ã¹-ConfigãLoggerãZabbixãäœæããŸãã ãã¬ãŒïŒ
ãã¬ãŒ static class Logger { public static NLog.Logger Log { get; private set; } public static NLog.Logger Archive { get; private set; } static Logger() { Log = LogManager.GetLogger("Global"); Archive = LogManager.GetLogger("Archivator"); } }
Zabbixã䜿çšããç£èŠã¯å¥ã®æçš¿ã«å€ããã®ã§ããšãŒãžã§ã³ããå®è£
ããã¯ã©ã¹ãããã«æ®ããŸãã
ã¶ããã¯ã¹ namespace TopCrawler.Singleton { /// <summary> /// Singleton: zabbix sender class /// </summary> static class Zabbix { public static ZabbixSender Sender { get; private set; } static Zabbix() { Sender = new ZabbixSender(Config.ZabbixServer, Config.ZabbixPort); } } struct ZabbixItem { public string Host; public string Key; public string Value; } class ZabbixSender { internal struct SendItem { // ReSharper disable InconsistentNaming - Zabbix is case sensitive public string host; public string key; public string value; public string clock; // ReSharper restore InconsistentNaming } #pragma warning disable 0649 internal struct ZabbixResponse { public string Response; public string Info; } #pragma warning restore 0649 #region
æ§æ...å°ãªããšãäœãé¢çœãããšãããæãæ¥ãŸããã æåã«ãèšå®ã«ãããŒãªã³ã°ããããã¯ã¹ãä¿åããŸãã 第äºã«ãDataFlowèšå®ã ããããå§ãããŸãïŒ
æ§æ <CredentialsList> <credentials hostname="8.8.8.8" username="popka@example.com" password="123" port="110" type="fbl" /> <credentials hostname="8.8.8.8" username="kesha@example.com" password="123" port="110" type="bounce" /> </CredentialsList> <DataFlowOptionsList> <datablockoptions name="_sortMailDataBlock" maxdop="4" boundedcapacity="4" /> <datablockoptions name="_spamFilterDataBlock" maxdop="4" boundedcapacity="4" /> <datablockoptions name="_checkBounceDataBlock" maxdop="16" boundedcapacity="16" /> <datablockoptions name="_identifyDataBlock" maxdop="16" boundedcapacity="16" /> <datablockoptions name="_addToCrmDataBlock" maxdop="16" boundedcapacity="16" /> <datablockoptions name="_addToFblDataBlock" maxdop="16" boundedcapacity="16" /> <datablockoptions name="_addToBounceDataBlock" maxdop="16" boundedcapacity="16" /> </DataFlowOptionsList>
ãããã£ãŠãæ¥ç¶å
ã®ãã¹ããšããŒãããŠãŒã¶ãŒãšãã¹ã¯ãŒã-ããã§ã¯ãã¹ãŠãæç¢ºã§ãã æ¬¡ã¯ããã¯ã¹ã®çš®é¡ã§ãã ãµãŒãã¹ãããŒã±ãã£ã³ã°éšéïŒããã³ä»ã®éšéïŒã«ãã£ãŠäœ¿çšãããŠãããšããŸãã ãããã«ã¯ãã¡ãŒã«ãžã®èªåè¿ä¿¡ãš
FBLã¹ãã ã¬ããŒãããã³ããããã¡ãŒã«ããã¯ã¹ããããŸãã ç®±èªäœã¯ãã§ã«æçŽãåé¡ããŠããã®ã§ããã®ãããªç¶æ³ã§ã¯ããã«ç®±ã®çš®é¡ãèšå®ããŸãã DataFlowèšå®ã䜿çšãããšããªããžã§ã¯ãã®äœæãéå§ãããšããã«æç¢ºã«ãªããŸãã ããã§ã¯ãèšå®ã«ç¬èªã®ã»ã¯ã·ã§ã³ããããŸãã å€ãã®ããã¥ã¢ã«ã§ãããè¡ãæ¹æ³ããããŸãã®ã§ãçµæã衚瀺ããŠãã ããïŒ
ã¿ã€ããå®çŸ©ãã #region --- Types --- static class MailboxType { public const string Bo = "bo"; public const string Crm = "crm"; public const string Fbl = "fbl"; public const string Bounce = "bounce"; } class MailboxInfo { public string Type { get; set; } public string Hostname { get; set; } public string User { get; set; } public string Password { get; set; } public int Port { get; set; } } class DataBlockOptions { public int Maxdop { get; set; } public int BoundedCapacity { get; set; } public DataBlockOptions() { Maxdop = 1; BoundedCapacity = 1; } } #endregion
ã»ã¯ã·ã§ã³ãäœæãã /// <summary> /// Custom config section /// </summary> public class CustomSettingsConfigSection : ConfigurationSection { [ConfigurationProperty("CredentialsList")] public CredentialsCollection CredentialItems { get { return base["CredentialsList"] as CredentialsCollection; } } [ConfigurationProperty("DataFlowOptionsList")] public DataBlockOptionsCollection DataFlowOptionsItems { get { return base["DataFlowOptionsList"] as DataBlockOptionsCollection; } } }
ãããã®ã»ã¯ã·ã§ã³ããå€ãèªãããšãåŠã¶ /// <summary> /// Custom collection - credentials list /// </summary> [ConfigurationCollection(typeof(CredentialsElement), AddItemName = "credentials")] public class CredentialsCollection : ConfigurationElementCollection, IEnumerable<CredentialsElement> { protected override ConfigurationElement CreateNewElement() { return new CredentialsElement(); } protected override object GetElementKey(ConfigurationElement element) { return ((CredentialsElement)element).Username; } public CredentialsElement this[int index] { get { return BaseGet(index) as CredentialsElement; } } public new IEnumerator<CredentialsElement> GetEnumerator() { for (var i = 0; i < Count; i++) { yield return BaseGet(i) as CredentialsElement; } } } /// <summary> /// Custom credentials item /// </summary> public class CredentialsElement : ConfigurationElement { [ConfigurationProperty("hostname", DefaultValue = "")] public string Hostname { get { return base["hostname"] as string; } } [ConfigurationProperty("username", DefaultValue = "", IsKey = true)] public string Username { get { return base["username"] as string; } } [ConfigurationProperty("password", DefaultValue = "")] public string Password { get { return base["password"] as string; } } [ConfigurationProperty("type", DefaultValue = "")] public string Type { get { return base["type"] as string; } } [ConfigurationProperty("port", DefaultValue = "")] public string Port { get { return base["port"] as string; } } } /// <summary> /// Custom collection - DataBlock options list /// </summary> [ConfigurationCollection(typeof(DataBlockOptionsElement), AddItemName = "datablockoptions")] public class DataBlockOptionsCollection : ConfigurationElementCollection, IEnumerable<DataBlockOptionsElement> { protected override ConfigurationElement CreateNewElement() { return new DataBlockOptionsElement(); } protected override object GetElementKey(ConfigurationElement element) { return ((DataBlockOptionsElement)element).Name; } public CredentialsElement this[int index] { get { return BaseGet(index) as CredentialsElement; } } public new IEnumerator<DataBlockOptionsElement> GetEnumerator() { for (var i = 0; i < Count; i++) { yield return BaseGet(i) as DataBlockOptionsElement; } } } /// <summary> /// Custom DataBlock options item /// </summary> public class DataBlockOptionsElement : ConfigurationElement { [ConfigurationProperty("name", DefaultValue = "", IsKey = true)] public string Name { get { return base["name"] as string; } } [ConfigurationProperty("maxdop", DefaultValue = "")] public string Maxdop { get { return base["maxdop"] as string; } } [ConfigurationProperty("boundedcapacity", DefaultValue = "")] public string BoundedCapacity { get { return base["boundedcapacity"] as string; } } }
configã®å®å
šãªå®è£
ã¯èšè¿°ããŸãããéçºããã»ã¹äžã«å¿
èŠãªãã©ã¡ãŒã¿ãŒãããã«è¿œå ãããããšãçè§£ãããŠããŸãã
次ã®ãããªã«ã¹ã¿ã èšå®ãèªã¿åããŸãã
èªã public List<MailboxInfo> CredentialsList { get; private set; } public Dictionary<string, DataBlockOptions> DataFlowOptionsList { get; private set; } ... static Config() { try { var customConfig = (CustomSettingsConfigSection)ConfigurationManager.GetSection("CustomSettings");
ã©ããããããéåžžã«é·åŒãããšã倿ããŸããããç§ãã¡ã¯æãè峿·±ãéšåã«ããéããŠããŸãããTopShelfããã®ãã€ã³ãã£ã³ã°ãããã©ãŒãã³ã¹ã«ãŠã³ã¿ãŒãããŒã¿ããŒã¹ãšã®éä¿¡ãçç¥ããããžãã¹ã«åãããããŸãïŒ Crawlerã¯ã©ã¹ïŒã³ã¢ïŒãäœæããŸãã ãŸããã¡ãŒã«ãèªãã§ãã ããïŒ
private volatile bool _stopPipeline; ... public void Start() { do { var getMailsTasks = _config.CredentialsList.Select(credentials => Task.Run(() => GetMails(credentials))).ToList(); foreach (var task in getMailsTasks) task.Wait(); Thread.Sleep(2000); } while (!_stopPipeline);
ããã§æ ãç ç²ã«ãªããç§ã¯æ°ã«ããªãããšã«æ±ºããŸãã-ããªããçŽ20-30ã®ããã¯ã¹ãæã£ãŠãããªããããªãã¯ããããã®äžã§ã¿ã¹ã¯ãå®è¡ã§ããã¹ã¬ããã®æ°ãå¿é
ããŸããã ïŒããããæ¯ããããèš±å¯ãïŒèªã¿åãèªäœã«é²ã¿ãŸãã
private void GetMails(MailboxInfo info) { try { using (var client = new Pop3Client()) {
ããã¯ã¹ã®ã¢ã¯ã»ã¹ã¿ã€ãã³ã°ãããã«èšç®ããŸã-ãããã¯ãŒã¯èšºæãšãµãŒããŒã®è² è·ã«åœ¹ç«ã¡ãŸãã
//Get Zabbix metrics var stopwatch = new Stopwatch(); stopwatch.Start(); //Get mail count client.Connect(info.Hostname, info.Port, false); client.Authenticate(info.User, info.Password); stopwatch.Stop();
Zabbixã«ããŒã¿ãéä¿¡ããŸãã ç°¡åã§ã-ãã¹ãåïŒZabbixã®å ŽåïŒãããŒïŒå³å¯ã«ã¯ãZabbixã®å Žåãåæ§ïŒãããã³æååå€ãæå®ããŸãã
//Send it to Zabbix Zabbix.Sender.SendData(new ZabbixItem { Host = Config.HostKey, Key = info.Type + Config.TimingKey, Value = stopwatch.ElapsedMilliseconds.ToString() }); Logger.Log.Debug("Send [{0}] timing to Zabbix: connected to '{1}' as '{2}', timing {3}ms", info.Type, info.Hostname, info.User, stopwatch.ElapsedMilliseconds); var count = client.GetMessageCount(); if (count == 0) return; Logger.Log.Debug("We've got new {0} messages in '{1}'", count, info.User); //Send messages to sorting block for (var i = 0; i < count; i++) { try { var mailInfo = new MessageInfo { IsSpam = false, Mail = client.GetMessage(i + 1), Type = MessageType.UNKNOWN, Subtype = null, Recipient = null, Mailbox = info }; Logger.Log.Debug("Download message from '{0}'. Size: {1}b", info.User, mailInfo.Mail.RawMessage.Length);
DataFlowãã€ãã©ã€ã³ã¯ãCrawlerã¯ã©ã¹ãäœæããããšãã«äœæãããŸãã ç§ãã¡ã®æåã®ã¹ãããã¯æçŽãåé¡ããããšã ãšä¿¡ããŠããŸãã
while (!_sortMailDataBlock.Post(mailInfo)) Thread.Sleep(500);
ããªãã¯ãããã©ãã»ã©ç°¡åããèŠã-ã³ã³ãã€ãŒèªäœã¯äžã€ã§ãã ã¡ãŒã«ãèªããã¹ãŠã®ã¿ã¹ã¯ã¯ãäžåºŠã«1ã€ãã€ã¡ãã»ãŒãžãéä¿¡ããŸãã ãããã¯ãããžãŒã®å ŽåãPostã¯falseãè¿ããè§£æŸããããŸã§åŸ
æ©ããŸãã ãã®æç¹ã§ã黿µã¯åŒãç¶ãæ©èœããŸãã ããã¯ç§ãå¿é
ãªãäžŠè¡æ§ãšåŒãã§ãããã®ã§ãã
ã¡ãã»ãŒãžã¯ã³ã³ãã¢ã«éãããRAWã¢ãŒã«ã€ãã«å®å
šã«ä¿åã§ããããã«ãªããŸããïŒã¯ããã¯ããèªã¿åã£ããã®ã¯ãã¹ãŠãã¡ã€ã«ã¢ãŒã«ã€ãã«ä¿åãããŸãããµããŒãããŒã ã¯åŸã§æè¬ããŸãïŒã
ããšãã°ãã¢ãŒã«ã€ãã®ããŒããŒã·ã§ã³ãæ§æããŸãã
Nlog.config <targets> <target name="logfile" xsi:type="File" fileName="${basedir}\logs\${shortdate}-message.log" /> <target name="Archivefile" xsi:type="File" fileName="${basedir}\archive\${shortdate}-archive.dat" /> </targets>
次ã«ã
logStashãèšå®ã§ããŸãããããã¯å¥ã®è©±ã§ã...
//Save every mail to archive Logger.Log.Debug("Archive message"); Logger.Archive.Info(Functions.MessageToString(mailInfo.Mail)); } catch (Exception ex) { Logger.Log.Error("Parse email error: {0}", ex.Message); Functions.ErrorsCounters[info.Type].Increment(); //Archive mail anyway Logger.Log.Debug("Archive message"); Logger.Archive.Info(Encoding.Default.GetString(client.GetMessageAsBytes(i + 1))); } if (_config.DeleteMail) client.DeleteMessage(i + 1); if (_stopPipeline) break; } Logger.Log.Debug("Done with '{0}'", info.User); } } catch (Exception ex) { Logger.Log.Error("General error - type: {0}, message: {1}", ex, ex.Message); Functions.ErrorsCounters[info.Type].Increment(); } }
ããã§ã¯ãErrorsCountersãæ¬¡ã®éçãšã©ãŒã«ãŠã³ã¿ãŒã䜿çšããŸããïŒããã¯ã¹ã®çš®é¡å¥ïŒã
public static Dictionary<string, Counter> ErrorsCounters = new Dictionary<string, Counter>();
ã«ãŠã³ã¿ãŒèªäœã¯æ¬¡ã®ããã«å®è¡ã§ããŸãã
Counter.cs class Counter { private long _counter; public Counter() { _counter = 0; } public void Increment() { Interlocked.Increment(ref _counter); } public long Read() { return _counter; } public long Refresh() { return Interlocked.Exchange(ref _counter, 0); } public void Add(long value) { Interlocked.Add(ref _counter, value); } public void Set(long value) { Interlocked.Exchange(ref _counter, value); } }
ãã€ãã©ã€ã³ã®äœæã«ç§»ããŸãããã çå®çªé»è©±ã泚ãããããã¯ã¹ããããšããŸãããã ãã®ãããªæåã¯è§£æããïŒã©ã®ãããªçš®é¡ã®èªåè¿ä¿¡ã誰ãããã©ã®ã¡ãŒã«ã§ããªã©ïŒãçµæããªããžããªïŒDBïŒã«å
¥ããå¿
èŠããããŸãã FBLã¬ããŒãã該åœããããã¯ã¹ããããšããŸãã ãã®ãããªæçŽãããã«ããŒã¿ããŒã¹ã«è¿œå ããŸãã ä»ã®ãã¹ãŠã®æåã¯ãæçšãã§ãããšèŠãªããŸããã¹ãã ããã§ãã¯ããCRMãªã©ã®å€éšã·ã¹ãã ã«éä¿¡ããå¿
èŠããããŸãã
ãã§ã«çè§£ããŠããããã«ããã®äŸã§ã¯äž»ã«ããŒã±ãã£ã³ã°ã¿ã¹ã¯ã§ã®ã³ã¬ã¯ã¿ãŒã®äœ¿çšãèæ
®ããŠããŸããã¡ãŒã«é
ä¿¡ã«é¢ããçµ±èšæ
å ±ãã¹ãã ã«é¢ããæ
å ±ãåéããŸããããã§ãã¯ãŒã¯ãããŒã決å®ããŸããã Crawlerã¯ã©ã¹ã§å¿
èŠãªãããã¯ã宣èšããŸãã
class MessageInfo { public bool IsSpam { get; set; } public Message Mail { get; set; } public string Subtype { get; set; } public string Recipient { get; set; } public MessageType Type { get; set; } public MailboxInfo Mailbox { get; set; } } class Crawler {
åæåã¡ãœãããäœæãããã€ãã©ã€ã³ãããã¯ãäœæããŸãïŒconfigsã®çŽ æŽãããã»ã¯ã·ã§ã³ã䜿çšããŠãããã¯ãåæåããŸãïŒïŒ
public void Init() { //*** Create pipeline *** //Create TransformBlock to get message type var blockOptions = _config.GetDataBlockOptions("_sortMailDataBlock"); _sortMailDataBlock = new TransformBlock<MessageInfo, MessageInfo>(mail => SortMail(mail), new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = blockOptions.Maxdop, BoundedCapacity = blockOptions.BoundedCapacity }); //Create TransformBlock to filter spam blockOptions = _config.GetDataBlockOptions("_spamFilterDataBlock"); _spamFilterDataBlock = new TransformBlock<MessageInfo, MessageInfo>(mail => FilterSpam(mail), new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = blockOptions.Maxdop, BoundedCapacity = blockOptions.BoundedCapacity }); //Create TransformBlock to sort bounces blockOptions = _config.GetDataBlockOptions("_checkBounceDataBlock"); _checkBounceDataBlock = new TransformBlock<MessageInfo, MessageInfo>(mail => BounceTypeCheck(mail), new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = blockOptions.Maxdop, BoundedCapacity = blockOptions.BoundedCapacity }); //Create TransformBlock to identify bounce owner blockOptions = _config.GetDataBlockOptions("_identifyDataBlock"); _identifyDataBlock = new TransformBlock<MessageInfo, MessageInfo>(mail => GetRecipient(mail), new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = blockOptions.Maxdop, BoundedCapacity = blockOptions.BoundedCapacity }); //Create ActionBlock to send mail to CRM blockOptions = _config.GetDataBlockOptions("_addToCrmDataBlock"); _addToCrmDataBlock = new ActionBlock<MessageInfo>(mail => AddToCrm(mail), new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = blockOptions.Maxdop, BoundedCapacity = blockOptions.BoundedCapacity }); //Create ActionBlock to send FBL to MailWH blockOptions = _config.GetDataBlockOptions("_addToFblDataBlock"); _addToFblDataBlock = new ActionBlock<MessageInfo>(mail => AddToFbl(mail), new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = blockOptions.Maxdop, BoundedCapacity = blockOptions.BoundedCapacity }); //Create ActionBlock to send Bounce to MailWH blockOptions = _config.GetDataBlockOptions("_addToBounceDataBlock"); _addToBounceDataBlock = new ActionBlock<MessageInfo>(mail => AddToBounce(mail), new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = blockOptions.Maxdop, BoundedCapacity = blockOptions.BoundedCapacity });
ã¹ããŒã ã«åŸã£ãŠã³ã³ãã¢ãçµã¿ç«ãŠãŸãã
/ _checkBounceDataBlock.LinkTo(_identifyDataBlock); _identifyDataBlock.LinkTo(_addToBounceDataBlock); _spamFilterDataBlock.LinkTo(_addToCrmDataBlock, info => !info.IsSpam); _spamFilterDataBlock.LinkTo(DataflowBlock.NullTarget<MessageInfo>(), info => info.IsSpam);
ã芧ã®ããã«ããã¹ãŠãéåžžã«åçŽã§ã-ãããã¯ã以äžã«é¢é£ä»ããŸãïŒéä¿¡æ¡ä»¶ãèšå®ããå¯èœæ§ãããïŒã ãã¹ãŠã®ãããã¯ã¯äžŠè¡ããŠå®è¡ãããŸãã åãããã¯ã«ã¯ããçšåºŠã®äžŠåæ§ãšå®¹éããããŸãïŒå®¹éã䜿çšãããšããããã¯ã®åã®ãã¥ãŒã調æŽã§ããŸããã€ãŸãããããã¯ã¯ã¡ãã»ãŒãžãåä¿¡ããŸãããããŸã åŠçããŠããŸããïŒã ãããã£ãŠãã¬ã¿ãŒã®å
容ãè§£æãããªã©ããè€éãªãé·æéã®æäœã«å¯ŸããŠé«åºŠãªäžŠååŠçãèšå®ã§ããŸãã
DataFlowãããªã¢ã«ã«ã€ããŠã¯èª¬æããŸãããTPLDataFlowãœãŒã¹ã®ãã¹ãŠãèªãæ¹ãè¯ãã§ããã ã
次ã«ããããã¯ãçµäºããããã®ã«ãŒã«ãèšå®ããŸãã
_sortMailDataBlock.Completion.ContinueWith(t => { if (t.IsFaulted) ((IDataflowBlock)_spamFilterDataBlock).Fault(t.Exception); else _spamFilterDataBlock.Complete(); }); _sortMailDataBlock.Completion.ContinueWith(t => { if (t.IsFaulted) ((IDataflowBlock)_addToFblDataBlock).Fault(t.Exception); else _addToFblDataBlock.Complete(); }); _sortMailDataBlock.Completion.ContinueWith(t => { if (t.IsFaulted) ((IDataflowBlock)_checkBounceDataBlock).Fault(t.Exception); else _checkBounceDataBlock.Complete(); }); _spamFilterDataBlock.Completion.ContinueWith(t => { if (t.IsFaulted) ((IDataflowBlock)_addToCrmDataBlock).Fault(t.Exception); else _addToCrmDataBlock.Complete(); }); _checkBounceDataBlock.Completion.ContinueWith(t => { if (t.IsFaulted) ((IDataflowBlock)_identifyDataBlock).Fault(t.Exception); else _identifyDataBlock.Complete(); }); _identifyDataBlock.Completion.ContinueWith(t => { if (t.IsFaulted) ((IDataflowBlock)_addToBounceDataBlock).Fault(t.Exception); else _addToBounceDataBlock.Complete(); }); }
å®éããã€ãã©ã€ã³ã¯ãã¹ãŠæ©èœããŠããã®ã§ãã¡ãã»ãŒãžãæçš¿ã§ããŸãã Startã¡ãœããã远å ããŠåæ¢ããã ãã§ãã
éå§ãã public void Start() { do { var getMailsTasks = _config.CredentialsList.Select(credentials => Task.Run(() => GetMails(credentials))).ToList(); foreach (var task in getMailsTasks) task.Wait(); Thread.Sleep(2000); } while (!_stopPipeline); //Stop pipeline - wait for completion of all endpoints _sortMailDataBlock.Complete(); _addToCrmDataBlock.Completion.Wait(); _addToFblDataBlock.Completion.Wait(); _addToBounceDataBlock.Completion.Wait(); if (_stopPipeline) Logger.Log.Warn("Pipeline has been stopped by user"); }
ããªã²ãŒãã«æž¡ããŸãã
äžŠã¹æ¿ã...ãŸãããã¹ãŠãç§ãã¡ãšç°¡åã ãšããŸãããïŒç§ãã¡ã¯åžžã«è€éã«ããæéããããŸãïŒ
private MessageInfo SortMail(MessageInfo mail) { switch (mail.Mailbox.Type) { case MailboxType.Crm: mail.Type = MessageType.GENERAL; break; case MailboxType.Bounce: mail.Type = MessageType.BOUNCE; break; case MailboxType.Fbl: mail.Type = MessageType.FBL; break; } return mail; }
ã¹ãã ãã£ã«ã¿ãŒã ããã¯å®¿é¡çšã§ã
-SpamAssassinã䜿çšããŸãã
代çäººã¯æ¬¡ã®ãšããã§ãã
private MessageInfo FilterSpam(MessageInfo mail) {
ãŸããSpamAssassin APIãæäœããããã®ã¯ã©ã¹ïŒ
ãããžã§ã¯ããžã®ãªã³ã¯ ïŒã
ãããŠãæåã®è§£æã«é²ã¿ãŸãã ParsimããåçããŸãã ãããMEFã®åºçªã§ãã
ãã©ã°ã€ã³ã®ã€ã³ã¿ãŒãã§ã€ã¹ã䜿çšããŠãããžã§ã¯ãïŒdllïŒãäœæããŸãïŒã€ã³ã¿ãŒãã§ã€ã¹ãåŒã³åºããŸãããïŒã
ã€ã³ã¿ãŒãã§ãŒã¹ã远å ïŒ
public interface ICondition { string Check(Message mimeMessage); } public interface IConditionMetadata { Type Type { get; } }
ãããŠ...ããã ãã§ãã TopCrawlerã¯ãã®ãããžã§ã¯ãã«äŸåããŠããããã©ã°ã€ã³ãå«ããããžã§ã¯ãã§ã䜿çšãããŸãã
æ°ãããããžã§ã¯ãïŒdllãïŒãäœæããConditionsãåŒã³åºããŸãã
èªåå¿çã¿ã€ãã远å ããŸãã
#region --- Types --- static class BounceType { public const string Full = "BounceTypeFull"; public const string Timeout = "BounceTypeTimeout"; public const string Refused = "BounceTypeRefused"; public const string NotFound = "BounceTypeNotFound"; public const string Inactive = "BounceTypeInactive"; public const string OutOfOffice = "BounceTypeOutOfOffice"; public const string HostNotFound = "BounceTypeHostNotFound"; public const string NotAuthorized = "BounceTypeNotAuthorized"; public const string ManyConnections = "BounceTypeManyConnections"; } #endregion
ãããŠãã€ã³ã¿ãŒãã§ãŒã¹ãå®è£
ããã¯ã©ã¹ïŒ
[Export(typeof(ICondition))] [ExportMetadata("Type", typeof(ConditionNotFound1))] public class ConditionNotFound1 : ICondition { public string Check(Message mimeMessage) { if (!mimeMessage.MessagePart.IsMultiPart) return null; const string pattern = "Diagnostic-Code:.+smtp.+550"; var regexp = new Regex(pattern, RegexOptions.IgnoreCase); return mimeMessage.MessagePart.MessageParts.Any(part => part.ContentType.MediaType == "message/delivery-status" && regexp.IsMatch(part.GetBodyAsText())) ? BounceType.NotFound : null; } } ... [Export(typeof(ICondition))] [ExportMetadata("Type", typeof(ConditionTimeout2))] public class ConditionTimeout2 : ICondition { return BounceType.Timeout; } ...
ãæ°ã¥ãã®ããã«ããã€ã³ãã¯å±æ§ã«ãããŸãã ãããã䜿çšããŠããã©ã°ã€ã³ãã¢ããããŒããããŸãã
ãããžã§ã¯ãã«æ»ãããã©ã°ã€ã³ãããŒãããŸãã class Crawler { ... //Plugins [ImportMany] public IEnumerable<Lazy<ICondition, IConditionMetadata>> BounceTypeConditions { get; set; } private void LoadPlugins() { try { var container = new CompositionContainer(new DirectoryCatalog(_config.PluginDirectory), true); container.ComposeParts(this); } catch (Exception ex) { Logger.Log.Error("Unable to load plugins: {0}", ex.Message); } } ...
ã¯ã©ã¹ã®ã³ã³ã¹ãã©ã¯ã¿ãŒã§LoadPluginsããã«ããŸããããŒãã¡ã«ããºã ã«ã€ããŠã¯è©³ãã説æããŸãããGoogleã®æ¹ãããŸããããŸããããŠã³ã¹ã®ãããªãã§ãã¯ã®ããªã²ãŒãã«æž¡ããŸããæåã®æ¡ä»¶ãæ©èœãããŸã§æ¡ä»¶ãé çªã«é©çšãããŸã-æä»çæ¹æ³ïŒ private MessageInfo BounceTypeCheck(MessageInfo mailInfo) { try { foreach (var condition in BounceTypeConditions) { var res = condition.Value.Check(mailInfo.Mail); if (res == null) continue; mailInfo.Subtype = res; Logger.Log.Debug("Bounce type condition [{0}] triggered for message [{1}]", condition.Metadata.Type, mailInfo.Mail.Headers.MessageId); break; } } catch (Exception ex) { Logger.Log.Error("Failed to determine bounce type for message '{0}': {1}", mailInfo.Mail.Headers.MessageId, ex.Message); Logger.ErrorsCounters[MailboxType.Bounce].Increment(); } return mailInfo; }
ãããã£ãŠãæ°ããlogicabã衚瀺ãããå Žåã¯ãã€ã³ã¿ãŒãã§ã€ã¹ãå®è£
ãããã©ã°ã€ã³ãå«ããããžã§ã¯ãã«æ°ããã¯ã©ã¹ã远å ããã ãã§ååã§ããã¬ã¿ãŒã®éä¿¡è
ã®å®çŸ©ã«ãã£ãŠ2çªç®ã®ãã©ã°ã€ã³ã®äŸãé©çšããŸãããæ¢ã«é·ãæçš¿ã§ãïŒãµãŒããŒèªäœãèªåå¿çãçæãããããéä¿¡è
ãã¡ãã»ãŒãžããããŒãã解決ããå¿
èŠããããŸãïŒãããŒã¿ããŒã¹ã«çµæãèšé²ããããšãçããããšã§ã¯ãããŸãããããšãã°ã次ã®ããã«ïŒ
private void AddToBounce(MessageInfo mail) { try { MailWH.BounceAdd(mail); Functions.ProcessedCounters[MailboxType.Bounce].Increment(); Functions.Log.Debug("Send Bounce to MailWH"); } catch (Exception ex) { Functions.Log.Error("Error saving Bounce message '{0}' to MailWH: {1}", mail.Mail.Headers.MessageId, ex.Message); Functions.ErrorsCounters[MailboxType.Bounce].Increment(); } }
ããŠã³ã¹ã¢ã public static long BounceAdd(MessageInfo message) { using (var conn = new SqlConnection(ConnectionString)) using (var cmd = new SqlDataAdapter("BounceAdd", conn)) { var body = message.Mail.FindFirstPlainTextVersion() == null ? message.Mail.FindFirstHtmlVersion().GetBodyAsText() : message.Mail.FindFirstPlainTextVersion().GetBodyAsText(); var outId = new SqlParameter("@ID", SqlDbType.BigInt) { Direction = ParameterDirection.Output }; cmd.SelectCommand.CommandType = CommandType.StoredProcedure; cmd.SelectCommand.Parameters.Add(new SqlParameter("@RawMessage", message.Mail.RawMessage)); cmd.SelectCommand.Parameters.Add(new SqlParameter("@Message", body)); cmd.SelectCommand.Parameters.Add(new SqlParameter("@Subject", message.Mail.Headers.Subject ?? "")); cmd.SelectCommand.Parameters.Add(new SqlParameter("@MessageID", message.Mail.Headers.MessageId ?? "")); cmd.SelectCommand.Parameters.Add(new SqlParameter("@AddressTo", message.Mail.Headers.To[0].Address ?? "")); cmd.SelectCommand.Parameters.Add(new SqlParameter("@AddressFrom", message.Mail.Headers.From.Address ?? "")); cmd.SelectCommand.Parameters.Add(new SqlParameter("@DateRecieved", DateTime.Now)); cmd.SelectCommand.Parameters.Add(new SqlParameter("@BounceTypeSysName", (object)message.Subtype ?? DBNull.Value)); cmd.SelectCommand.Parameters.Add(new SqlParameter("@SourceFrom", (object)message.Recipient ?? DBNull.Value));
TopShelfã衚瀺ããæéããªãããããããªãã-æçš¿ã¯ãã§ã«è¥å€§åããããŠããŸããçµè«
ãã®ã¬ãã¹ã³ã§ã¯ãã¡ãŒã«ãåéããã¿ã¹ã¯ã¯ããã»ã©åçŽã§ã¯ãªãå¯èœæ§ãããããšãåŠã³ãŸãããéçºãããã«ãŒãã«ã«ãããæ¢åã®ããžãã¯ã«åœ±é¿ãäžããããšãªããæ°ããããã»ã¹ã¹ãããã§ããDataFlowãããã¯ããã°ãã远å ã§ããŸãããã©ã°ã€ã³ãµãã·ã¹ãã ã䜿çšãããšãã¹ã¯ãªããã®ãããªè§£æããžãã¯ããã°ããæ§ç¯ã§ããDataFlowèªäœããã¹ãŠã®èšç®ã䞊ååããŸãïŒç¹å®ã®ãã·ã³ã«å¯ŸããŠæè»ã«ãã«ãã¹ã¬ãããæ§æã§ããŸãïŒãTopShelfã¯ããããã°ã容æã«ããããã«ããµãŒãã¹ã¢ãŒããšã³ã³ãœãŒã«ã¢ãŒãã®äž¡æ¹ã§ãµãŒãã¹ãå®è¡ããæ©èœãæäŸããŸããFuh ...è峿·±ãå Žåã¯ãContinious Integrationã®ã¬ãŒã«ã«é
眮ããVSãªãªãŒã¹ç®¡çã䜿çšããŠèªåãã«ããšãªãªãŒã¹ãæ§æããæ¹æ³ãããã«èª¬æããŸãã