Question

I'm learning dart and have created a simple contact form - based on the forms tutorial on the dart site, using mailer to send a mail.

So far so good - the client post the message, the server picks up the post data, creates an envelope and sends the mail... but I want to display a comforting message to the person filling out the form, or warn them if it were not sent.

The problem is that the http response is sent before the future that sends the email is completed -

Chris Storm talks about using completers to solve this problem, I think - here: testing error conditions with dart and again in a chapter in Dart for Hipsters, but i am finding it hard to work out how to apply it here.

Here is the complete server code - the comments in the sendMail function show the problem.

import 'dart:io';
import 'dart:convert';
import 'package:mailer/mailer.dart';

final HOST = '127.0.0.1'; // eg: localhost
final PORT = 4040;        // a port, must match the client program

void main() {
    HttpServer.bind(HOST, PORT).then(gotMessage, onError: printError);
}

void gotMessage(_server) {
    _server.listen((HttpRequest request) {
    switch (request.method) {
      case 'POST':
        handlePost(request);
        break;
     case 'OPTIONS':
        handleOptions(request);
        break;
     default: defaultHandler(request);
     }
   },
  onError: printError); // .listen failed
  print('Listening for GET and POST on http://$HOST:$PORT');
}

/**
 * Handle POST requests
 *
 */


void handlePost(HttpRequest req) {
  HttpResponse res = req.response;
  print('${req.method}: ${req.uri.path}');

  addCorsHeaders(res);

  req.listen((List<int> buffer) {
     // Create a new string from the characters in the buffer and convert this into a map
    Map postData = JSON.decode(new String.fromCharCodes(buffer));
    res.write(sendMail(postData));
    res.close();
  },

  onError: printError);
}

/**
 * Add Cross-site headers to enable accessing this server from pages
 * not served by this server
 *
 * See: http://www.html5rocks.com/en/tutorials/cors/
 * and http://enable-cors.org/server.html
 */
void addCorsHeaders(HttpResponse res) {
  res.headers.add('Access-Control-Allow-Origin', '*, ');
  res.headers.add('Access-Control-Allow-Methods', 'POST, OPTIONS');
  res.headers.add('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
}

void handleOptions(HttpRequest req) {
  HttpResponse res = req.response;
  addCorsHeaders(res);
  print('${req.method}: ${req.uri.path}');
  res.statusCode = HttpStatus.NO_CONTENT;
  res.close();
}

void defaultHandler(HttpRequest req) {
  HttpResponse res = req.response;
  addCorsHeaders(res);
  res.statusCode = HttpStatus.NOT_FOUND;
  res.write('Not found: ${req.method}, ${req.uri.path}');
  res.close();
}

void printError(error) => print(error);

sendMail(postData) {

  String sentMsg = 'I am waiting for a result to report'; //initialised value

  var options = new GmailSmtpOptions()
  ..username = 'my.mailt@gmail.com' //shove real values in here to test
  ..password = 'my_password';

  var transport = new SmtpTransport(options);

  // Create the envelope to send.
  var envelope = new Envelope()
  ..fromName = postData['name']
  ..from = postData['fromEmail']
  ..recipients = ['the_recipient@somedomain.com']
  ..subject = 'Message from contact form'
  ..text = postData['contactMessage'];


  transport.send(envelope)
    .then((success) => sentMsg = 'Message sent, thank you.' ) //this will not be returned...
    .catchError((e) => sentMsg = 'Message not sent; the reported error was: $e'); // nor will this

  return sentMsg; // Sadly the initial value will be returned as the future (transport.send) will not have completed

}
Was it helpful?

Solution

If I get it right, you want to send a response based on the result on transport.send(). If so you can just return it, like

  return transport.send(envelope)
    .then((success) => sentMsg = 'Message sent, thank you.' )
    .catchError((e) => sentMsg = 'Message not sent; the reported error was: $e');

Or using Completer

  var c = new Completer();

  transport.send(envelope)
    .then((success) => sentMsg = 'Message sent, thank you.' )
    .then(c.complete)
    .catchError((e) => sentMsg = 'Message not sent; the reported error was: $e');

  return c.future;

And since you are returning a Future, you have to change how you send your response to client, from

res.write(sendMail(postData));
res.close();

to

sendMail(postData)
  .then((result) {
    res.write(result);
    res.close();
  });

OTHER TIPS

Your code doesn't look bad :)

As far as I can see there isn't missing much (haven't tried to execute it though).

  request.response.write('Hello, world'); 
  request.response.close();

either in handlePost() (where you have req available or
return the result that you want to return to the client from handlePost() to gotMessage() and add above lines there.

Also take a look at An Introduction to the dart:io Library - Writing web servers

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top