[译]5分钟内我自己的Mailinator
By robot-v1.0
本文链接 https://www.kyfws.com/applications/my-own-mailinator-in-5-minutes-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 11 分钟阅读 - 5269 个词 阅读量 0[译]5分钟内我自己的Mailinator
原文地址:https://www.codeproject.com/Articles/495282/My-own-Mailinator-in-5-minutes
原文作者:TheQult
译文由本站 robot-v1.0 翻译
前言
A simple Mailinator clone developed in five minutes with the NetFluid framework
NetFluid框架在五分钟内开发了一个简单的Mailinator克隆
介绍(Introduction)
当另一个站点拒绝让我与mailinator注册时,我想"为什么我不能拥有我的?"(When yet another site has refused to let me signup with mailinator, I thought “Why I can’t have mine ?")
原始Mailinator需要两天的工作时间和一些改进((Original Mailinator required two days of working and some refinements () 这里详细(detail here) ),因此,如果您不想在网站上留下真实的电子邮件,那么这款游戏就不值钱了.(), so the game is not worth the candle if you just don’t want to leave your real email on a website.)
幸运的是,三年前,我开发了一个应用程序服务器,该服务器旨在快速开发Web应用程序并以可观的性能为其提供服务,那么为什么不尝试它呢?(Fortunately, three years ago I developed an application server just designed to quickly develop web apps and serve them with respectable performance, so why not try it?)
(可以找到应用程序服务器免费软件的版本和文档((Application server freeware version and documentation can be found) 这里(here) ).().)
背景(Background)
显然,我没有在五分钟之内开发出SMTP服务器以及该网站.邮件服务器来自LumiSoft.Net库.(Obviously I haven’t developed a SMTP server plus the website in five minutes. Mail server come from LumiSoft.Net library.)
与原始Mailinator有所不同,从Web应用程序到邮件服务器无需下载或检查电子邮件.邮件服务器是webapp的一部分,所有数据都从ram到ram流动,目前我不认为有必要在磁盘上开发永久消息存储.(Differently from the original Mailinator, there is no downloading or email checking from the webapp to the mail server. The mail server is part of the webapp and all the data flow from the ram to the ram, for the moment I don’t see any necessity to develop a permanent message store on the disk.)
重要(Important):我们使用的是NetFluid Application Server,其中Web应用程序是真实的应用程序,因此,如果对其进行更改,则需要重新编译.工具和帮助脚本将在随附的源代码中找到.(: We are using the NetFluid Application Server, where web applications are real applications, so if you change it, you need to recompile.Tools and helping script will be found in the source attached.)
使用代码(Using the code)
步骤零:如何运行(Step zero: How to run)
要运行NetFluid应用服务器,因此,此代码需要.Net Framework 4或更高版本或Mono 2.10或更高版本.(To run NetFluid Application Server and consequently this code you need the .Net Framework 4 or higher or Mono 2.10 or higher.)
本质上,有两个可用命令:(Essentially essentially there are two available commands:)
-
编译webapp:Compiler.exe <*.csproj的路径>(*Compile the webapp : Compiler.exe <path to the .csproj>)
-
运行Web应用程序:FluidPlayer.exe <*.csproj的路径>(*Run the webapp: FluidPlayer.exe <path to the .csproj>) 正确的过程将更改应用于NetFluid Webapp:(Correct procedure to apply changes to a NetFluid Webapp:)
-
调用编译器(Call the Compiler)
-
重新启动播放器(Restart the Player) 在附件中,您将找到帮助脚本来在每个平台上运行代码.(In the attachment you will find helping script to run the code on every platform.)
默认情况下,NetFluid App Server会在端口8080上监听正在运行的计算机的每个IP,您可以通过编辑配置文件 .json来更改此设置.(By default NetFluid App Server listen on every ip of running machine on port 8080, you can chage this settings by editing the configuration file .json)(在这种情况下((in this case)Mailinator/Mailinator.json(Mailinator/Mailinator.json))())
要立即查看应用程序正在运行:(To immediately see the application running :)
- 选择与您的平台相对应的启动脚本(Choose the start script corresponding to your platform)
- 等到[主机正在运行](Wait until the [Host Running])信息(message)出现(appear)
- 在该地址打开您喜欢的浏览器(Open your favorite browser at the address) http://本地主机:8080(http://localhost:8080) 该代码是如此之小,非常愚蠢,由一个页面组成((The code is so small and quite stupid, composed by a single page ()Mailinator.cs(Mailinator.cs))和三个视图,让我们看看它是如何工作的.() and three view, so let’s see how does it works.)
第一步:SMTP服务器(Step one: The STMP server)
我们需要一些东西来接收电子邮件(We need something to recieve the emails)
static SMTP_Server Server;
public override void OnServerStart()
{
Server = new SMTP_Server
{
Bindings = new[]{new IPBindInfo("localhost",
System.Net.IPAddress.Parse("0.0.0.0"), 25,SslMode.None, null)},
MaxConnections = 10,
MaxConnectionsPerIP = 1,
MaxRecipients = 5,
MaxBadCommands = 2,
ServiceExtentions = new[]
{
SMTP_ServiceExtensions.PIPELINING,
SMTP_ServiceExtensions.SIZE,
SMTP_ServiceExtensions._8BITMIME,
SMTP_ServiceExtensions.BINARYMIME,
SMTP_ServiceExtensions.CHUNKING
},
};
Server.SessionCreated += ServerSessionCreated;
Server.Start();
}
现在,我们得到了一个空的Web应用程序,该应用程序在其自身中嵌入了一个无底的邮件服务器,非常无用.(Now we a got an empty web application that embed in it self a bottom less mail server, quite useless.)
ASPGuy:静态成员?我不会工作..(ASPGuy: Static member ? I will not work..)
那是NetFluid,不是ASP.静态成员实际上是静态的,并且在服务器停止之前一直保持活动状态.(That’s NetFluid, not ASP. Static members are really static and kept alive until the server stop.)
第二步:收集消息(Step two: Collect the messages)
在实际的Mailinator中,有大量的注册域指向相同的邮箱.(In the real Mailinator there is a bulk of registred domain pointing to the same mailboxes.)
示例:netfluid@mailinator.com和netfluid@not-mailinator.com链接同一个邮箱(Example: netfluid@mailinator.com and netfluid@not-mailinator.com link the same mailbox)
因此,我们将执行相同的操作,每当收到邮件时,服务器都会调用一个代表,该代表控制每个收件人以检查其是否属于我们的域,如果属于我们,则将其存储在我们的内存集合中.(So we gonna do the same, whenever a mail is recieved the server call a delegate which controls each recipient to check if it belong to our domains, if it belongs to us it will be stored in our inmemory collection.)
对于内存收集,我选择了(For the memory collection I opted for a) ConcurrentDictionary
(而不是列表)(键:本地名称,值:已接收的消息)((key: local name, value : recieved messages) of List instead of) ConcurrentBag
因为(because) ConcurrentBag
提供良好的线程,但是如果您想根据条件删除元素,这将非常痛苦.(offer a good threading but it’s quite painful if you want to delete elements upon conditions.)
static ConcurrentDictionary<string, List<MailMessage>> MyHosts =
new[] { "localhost" , "spam.netfluid.org" };
static void ServerSessionCreated(object sender,
LumiSoft.Net.TCP.TCP_ServerSessionEventArgs<SMTP_Session> e)
{
e.Session.MessageStoringCompleted += (s, arg) =>
{
arg.Stream.Position = 0;
var msg = MailMessage.ParseFromStream(arg.Stream);
foreach (var to in msg.To)
foreach (var host in MyHosts)
if (to.Domain == host)
{
var folder = GetFolder(to.LocalPart);
lock (folder)
{
folder.Add(msg);
}
}
}
}
上面的嵌套的foreach可以简化为更快,可读性更强的单个LINQ表达式.(The nested foreachs above can be simplified into a faster and less readable single LINQ expression.)
第三步:检查收件箱!(Step three: Check your inbox !)
至此,我们的系统已经可以运行了,我们有一个正在运行的Web应用程序,可以接收邮件并将其存储在其他主机上.(At this point our system is already working, we a got a running webapp that can recieve and store mails on differents host.)
但是我们看不懂它们.因此,让我们向我们的Web应用程序添加一个视图.(But we can’t read them. So let’s add a view to our webapp.)
像真正的Mailinator一样,我们将从URI中获取邮箱名称和消息ID,为此,我们需要在页面上添加一个方法.(Like the real Mailinator, we gonna take mailbox name and message id from the URI, to do this all we need it’s to add a method to our page.)
[Route("/",true)]
public void Box(string box,string msg)
{
//No mailbox specified, display the Index
if (string.IsNullOrEmpty(box))
{
render("Mailinator.View.Index");
return;
}
//If there is a folder with this name his taken, otherwise it's created
var folder = GetFolder(box);
//No message id specified, display the mailbox
if (string.IsNullOrEmpty(msg))
{
render("Mailinator.View.Box", box, folder);
return;
}
//Mailbox and message specified, take it and show the message
render("Mailinator.View.Message", box, folder.FirstOrDefault(x=>x.Id==msg));
}
Route属性定义必须在该URI上调用该方法,在本例中为URI/'',或者作为Webapp的
索引''.(Route attribute defines that the method must be called on that URI, in our case on the URI “/” or rather as “index” of our webapp.)
布尔标志表明将从位置的URI中获取参数,否则从请求中以其名称为基础获取参数.(The bool flag indicates that parameters will be taken from URI in base of their position, otherwise they will be taken from the request in base of their name.)
此时,我们的Web应用程序将响应以下模式:(At this point our webapp respond to the following schema:)
||http://spam.netfluid.org/<邮箱>/<消息>(http://spam.netfluid.org//)
在缺少参数的情况下,将其替换为默认值(When an argument is missing it’s replaced by default value, in this case)空值(null).(.)
因此,我们很容易决定在两个索引都为null时显示索引,并在两个索引都被指定时显示消息.(So, easily we can decide to show the index when both are null and show the message when both are specified.)
但是,仅在指定邮箱时?如何区分邮箱参数和公共URI?(But when just the mailbox it’s specified ? How we can distinguish the mailbox parameter from a public URI ?)
**style.css(style.css)**可以参考我们应用程序的样式表或邮箱(could be referred to the stylesheet of our application or to the mailbox)style.css@spam.netfluid.org(style.css@spam.netfluid.org)
这是因为我们的公用文件夹和主页指向相同的URI(可以从 .json文件配置公用文件夹URI),但是即使我们移动公用文件夹,问题仍然存在:(This because our public folder and our main page points to the same URI (public folder URIs are configurable from the .json file) but the problem persist even if we move the public folders:)
**/public/style.css(/public/style.css)**可以参考样式表或消息(could be referred to the stylesheet or to the message)**style.css(style.css)**在文件夹中(in the folder)上市(public)
因此,决定我们不想将我们的Web应用程序移动到子文件夹中,唯一可用的选择是执行检查.(So, decided that we don’t want to move our webapp into a subfolder the only available choice is to perform a check.)
在这种情况下,我选择了一个简单的(In this case I opted for a simple)**如果(if)**代替功能(instead of the function)**is_public_file(is_public_file)**使其尽可能简单.(to keep it simplest as possible.)
关于渲染(About the rendering)
渲染功能可打印出被调用页面的输出.(The render function print out the output of the called page.)
在此示例中,我使用的是首页((In this example I’m using a main page ()/查看/Index.htm(/View/Index.htm)),其他所有页面都通过指令继承了这一页() and all others pages inherit this one through the instruction)%page继承(%page inherit)覆盖字段Content.(overriding the field Content.)
可以通过位置将参数传递给调用的函数(如示例中所示)(Arguments can be passed to the called function via position (like in the exampe))
render(” MyPage",arg1,arg2,arg3 …);(render(“MyPage”, arg1, arg2, arg3 … );)
或通过名称(Or via name)
render(" MyPage",t(" mybox",arg1),t(" message",arg2)…);(render(“MyPage”, t(“mybox”,arg1), t(“message”,arg2) … );)
并通过函数在调用中恢复(And recovered in the called via the function)args(对象)(args(object))
****注意:(NOTE:)避免在HTML内使用<>两个(to avoid the use of <> inside the HTML two)**args(args)**函数定义:(functions are defined:)
- 动态参数(对象)(dynamic args(object))****
- T args (对象)(T args(object)) 当您使用第一个时,您不能直接调用扩展名,例如LINQ(请不要怪我,也不要怪.net dynamics),这就是在Box.htm中将其转换为IEnumerable 的原因.(When you use the first one you cannot directly call extensions like LINQ (don’t blame me, blame .net dynamics ) and this is the reason of the cast to IEnumerable in Box.htm)
有关页面继承,覆盖字段和模板说明的更多文档,可以在这里找到:(More documentation about page inheritance, overriding fields and template instructions can be found here:)
索引(Index.htm)
<!doctype html>
<html>
<head>
<title>
Fluidanator - Mailinator clone powered by NetFluid
</title>
<link rel="stylesheet" type="text/css"
href="http://www.codeproject.com/style.css" media="screen" />
</head>
<body>
<div id="content">
% define Content
<h1>let them eat spam, again :)</h1>
<form method="post">
<h2>Check your Inbox!</h2>
<input type="text" name="box" />
<input type="submit" value="GO" />
</form>
% end define
</div>
</body>
</html>
Box.htm(Box.htm)
% page inherit Index
% redefine Content
% IEnumerable<MailMessage> box = args(1);
% if(box.Count()==0)
<div>
<h2>Empty mailbox</h2>
</div>
% else
% foreach(var msg in args(1))
<div>
<a href="http://www.codeproject.com/{% args(0) %}/{% msg.Id %}">
<span>
{% msg.From %}
</span>
<span>
{% msg.Subject %}
</span>
<span>
{% msg.Date %}
</span>
</a>
</div>
% end foreach
% end if
% end redefine
第四步:取出垃圾(Step four: Take out the trash)
现在,我们有了自己的完全工作的mailinator.(Now we got our fully working own mailinator.)
但是,它将接收并存储大量的垃圾邮件,并将其存储到ram中,因此让我们添加一个名为的用户,以及一种自动清洁方法.(But it will recieve and store into the ram a huge and eternal dose of spam, so let’s add a user called and an automated cleaning method.)
允许用户删除他的电子邮件:(Allow the user to delete his emails:)
消息视图中的简单表单将触发删除当前消息的操作(A simple form in the message view will trigger the action for delete the current message)
<span lang="en" class="short_text" id="result_box"></span><div>
<form method="post">
<input type="submit" name="action" value="Delete" />
</form>
</div>
我没有在表单中指定任何操作,因此它将在URL中发布消息的数据.(I didn’t specified any action in the form so it will post the data of the message in URL.)
Box方法中的一个简单操作定义如下:(And a simple action in the Box method define above:)
if (request("action")=="Delete")
{
lock (folder)
{
folder.RemoveAll(x => x.Id == msg);
}
render("Mailinator.View.Box", box, folder);
return;
}
触发过期邮件的自动删除(Trigger auto delete on expired mails)
static Timer Cleaner;
public override void OnServerStart()
{
Cleaner=new Timer(10*60*1000); /*10 minutes*/
Cleaner.AutoReset = true;
Cleaner.Elapsed += (s, e) =>
{
foreach (var box in Messages)
{
lock (box.Value)
{
//Remove expired messages
box.Value.RemoveAll(x => (DateTime.Now - x.Date) >= TimeSpan.FromMinutes(10));
}
}
//Remove empty box
var empty = Messages.Where(x => x.Value.Count == 0).ToArray();
List<MailMessage> trash;
foreach (var pair in empty)
{
Messages.TryRemove(pair.Key, out trash);
}
};
Cleaner.Start();
}
就像我在SMTP服务器上所做的一样,计时器上的自动清除是由静态计时器(在页面的所有实例中都是常见的)定义的,该计时器每次10分钟触发触发删除已过期邮件的操作.(Like what i did whit the SMTP server, autocleaning on timer is defined by a static timer (common to all instances of the page) that evbery 10 minutes trigger the action of delete exipired mails.)
最初,我使用ConcurrentBag的ConcurrentDictionary,但是从包中删除过期的元素非常痛苦,因此我更喜欢带锁的Nomal List.(Initally I used a CuncurrentDictionary of ConcurrentBag but remove expired elements from the bag was quite painful so i preferred a nomal List with a lock.)
兴趣点(Points of Interest)
我花了数小时的时间来寻找一个好的嵌入式.NET SMTP服务器,花了五分钟来编写webapp,花了四个小时来写这篇文章.我发现我很幸运能当程序员而不是记者.(I spent thrre hours for finding a good embeddable .NET SMTP server, five minutes to write the webapp and four hours to write this article. I discovered that I’m very lucky to be a programmer instead of a journalist.)
我还对LumiSoft.Net.Mail命名空间进行了重大重构,发现它是一个功能强大的框架,具有非常难以理解的结构.(I also heavily refactored the LumiSoft.Net.Mail namespace discovering that it’s a powerful framework with a very incomprehensible structure.)
您可以在附件中找到修改后的代码.(You can find the modified code in the attachments.)
历史(History)
第一个版本在这里制作.(First version made here.)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
HTML C#3.5 C# Dev Architect 新闻 翻译