Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GSoC: finagle-smtp - initial #287

Closed
wants to merge 50 commits into from

Conversation

suncelesta
Copy link

This is initial version of SMTP client for Finagle, which I am working on during GSoC'14.
For now it supports sending independent commands to SMTP server and sending a simple text email to one ore more addresses (without cc, bcc and reply-to). Usage is described in README.md.

suncelesta and others added 25 commits March 13, 2014 00:37
Handling only text messages; no tests
Add simple support for Finagle 6 client API
Added the ability to send any existing SMTP command + two types of
client: one just sending emails and the other sending discrete commands
+ client dispatcher
+ transporter
+ reply decoder
Added handling of server greeting and multiline extension list
+ command encoder
+ refactoring of request and reply types
+Duplicating leading dot
+Adding headers
(Not working with copies for now)
+Some different factories for EmailMessage
Added connection phase, removed Greeting and handlers constructing it
Commands are dispatched in the correct order
Corrected MAIL FROM command
+ every recipient goes to its own RCPT TO command
Got rid of Result in favor of Reply in Smtp and Unit in SmtpSimple (so
when simply sending one email, it results either in a Future.Done or a
failed Future in case of exception)
Extended multiline support on all reply types, removed unused helper
classes like ComposedRequest and EmptyReply
Added unit tests for filters
Created some inner packages, moved Smtp out of package smtp
Tests for SmtpClientDispatcher and ReplyDecoder
+ In case of error in multiline reply the resulting reply is now
multiline, holding previously received information
+ A test case for that matter
+ Session state is now reset before sending email (for the ability to
try again in case of error)
+ Cleanup of SimpleSmtpClient (renamed to Example)
+ README with usage information
fix README to appropriate markdown format and syntax highlighting
@selvin
Copy link

selvin commented Jun 26, 2014

I see that this build failure is not a result of these proposed code changes

error sbt.ResolveException: unresolved dependency: com.twitter#util-core_2.9.2;6.18.0: not found

at https://travis-ci.org/twitter/finagle/jobs/28439637

and http://mvnrepository.com/artifact/com.twitter/util-core_2.9.2 does not have 6.18.0

Any ideas on how to proceed?

+support for cc, bcc, reply-to
+EmailBuilder for composing emails
+features for creating MailingAddress and retreiving mailbox string from
it
val second = rep(1)
val third = rep(2)

//Standart reply: three-digit-code SP info
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Standart => Standard

@mosesn
Copy link
Contributor

mosesn commented Aug 6, 2014

This looks generally good, but there are a few pieces that seem like they might fit better in the dispatcher than where they are right now. I'd be interested in seeing some more end to end tests. The testing could definitely be a little more thorough.

However, most of my suggestions are nitpicks. In general, this looks quite good! Great job. 👍

if (buf == null) null
else buf match {
case cb: ChannelBuffer => {
val rep = cb.toString(CharsetUtil.UTF_8)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be ascii?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I must have missed that one, thanks.

+ Fixed style
+ EmailBuilder implements EmailMessage
+ Aggregating multiline requests moved to the dispatcher
override def close(deadline: Time): Future[Unit] = {
if (service.isAvailable)
service(Request.Quit)
service.close(deadline)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think service(Request.Quit) before service.close(deadline) will work, because we want to close even if we can't Quit properly, but maybe ensure would be the right semantic. Can you use wireshark and check that the tcp connection is actually being torn down when you expect it to be torn down?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to wireshark:

  1. QUIT is sent
  2. Server responds
  3. Server closes connection
  4. Client closes connection

Seems like what should be expected.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, I thought you were saying that it wasn't behaving properly in the dispatcher? I think I got confused as to what was going on . . . I was trying to reply to a message you posted that said,

Yes, when testing with SMTP server stub and looking at its logs, I found out that this request is not actually sent from dispatcher, so I sought the way to enforce service(Request.Quit) before service.close(deadline)

but I couldn't find the actual message (github swallowed it maybe) so I just put it here.

Is the behavior correct now then?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, sorry, I thought you were asking to check the behavior it has right
now, not with sending QUIT in the dispatcher. I'll check the latter, but I
can say that the server doesn't receive the request in that case, because
in its logs the connection is torn down by client and QUIT isn't received.

2014-08-11 18:45 GMT+04:00 Moses Nakamura [email protected]:

In finagle-smtp/src/main/scala/com/twitter/finagle/Smtp.scala:

  • * Constructs an SMTP client.
  • * Upon closing the connection this client sends QUIT command;
  • * it also performs dot stuffing.
  • */
  • override def newClient(dest: Name, label: String): ServiceFactory[Request, Reply] = {
  • val quitOnCloseClient = {
  •  new ServiceFactoryProxy[Request, Reply](defaultClient.newClient%28dest, label%29) {
    
  •    override def apply(conn: ClientConnection): Future[ServiceProxy[Request, Reply]] = {
    
  •      self.apply(conn) map { service =>
    
  •        val quitOnClose = new ServiceProxy[Request, Reply](service) {
    
  •          override def close(deadline: Time): Future[Unit] = {
    
  •            if (service.isAvailable)
    
  •              service(Request.Quit)
    
  •            service.close(deadline)
    

Wait, I thought you were saying that it wasn't behaving properly in the
dispatcher? I think I got confused as to what was going on . . . I was
trying to reply to a message you posted that said,

Yes, when testing with SMTP server stub and looking at its logs, I found
out that this request is not actually sent from dispatcher, so I sought the
way to enforce service(Request.Quit) before service.close(deadline)

but I couldn't find the actual message (github swallowed it maybe) so I
just put it here.

Is the behavior correct now then?


Reply to this email directly or view it on GitHub
https://github.com/twitter/finagle/pull/287/files#r16056704.

С уважением,
Валерия Дымбицкая

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right--if you do service(Quit) ensure service.close(deadline) I think that will have the correct behavior, since it will wait for a response before tearing down the connection. the two tcp connections will race to tear down, but I think that's OK.

@suncelesta
Copy link
Author

Thank you for all the comments! I've made some changes and updated README, so you can check it out.

else from.head
}

def to: Seq[MailingAddress] = getAddresses("to")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another way of doing this would be to use an extractor, then you can keep the more natural case style. just a style choice though, it's up to you.

@caniszczyk
Copy link
Contributor

@mosesn @travisbrown @selvin now that GSOC is closing up, what can we do to make sure this code gets merged or survives in the github.com/finagle project? Rumor on the street was that @selvin or someone was interested in taking ownership :)

@mosesn
Copy link
Contributor

mosesn commented Aug 18, 2014

Oh yeah, this LGTM by the way. I'd be happy to see this get merged.

@mosesn
Copy link
Contributor

mosesn commented Mar 3, 2015

This finally graduated to the finagle organization, and can be found here: https://github.com/finagle/finagle-smtp

@mosesn mosesn closed this Mar 3, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

6 participants