How to handle the POST request body without a framework

How to handle the POST request body without a framework

Following the positive response from a similar article based on Node, I found it fitting that I demonstrate how you can handle the payload and parameters sent as part of a POST request made to a Dart server backend. Long story short, I found it’s simpler.

This example works where the Content-Type of the request payload is application/x-www-form-urlencoded, which essentially means that the form data is formatted as a query string when sent to the server.

1. Create our server

Create a main.dart file and enter the snippet below:

import 'dart:io';

void main() async {
  var server = await HttpServer.bind('localhost', 9000);

  await for (HttpRequest req in server) {
    req.response
      ..headers.set('Content-Type', 'text/html')
      ..write('''
        <!doctype html>
        <html>
        <body>
          <form action="/" method="post">
            <input type="text" name="fname" /><br />
            <input type="number" name="age" /><br />
            <input type="file" name="photo" /><br />
            <button>Save</button>
          </form>
        </body>
        </html>
      ''')
      ..close();
  }
}

This bootstraps our server and responds with a form when a request is made to http://localhost:9000. The snippet begins by importing the dart:io library, since it contains the classes we’ll need to create our server. The whole bootstrapping process happens in a main() function, which is needed to run our Dart app.

To run this file, type the below command in the terminal:

$ dart main.dart

And you should be greeted with a form in the browser:

2. Capture the POSTed payload

Let’s now ensure that we are dealing with a POST request:

...
...
await for (HttpRequest req in server) {
  if (req.method == 'POST') {
   // deal with the payload  
  } else {
    req.response
      ..headers.set('Content-Type', 'text/html')
      ..write('''
        <!doctype html>
        <html>
        <body>
          <form action="/" method="post">
            <input type="text" name="fname" /><br />
            <input type="number" name="age" /><br />
            <input type="file" name="photo" /><br />
            <button>Save</button>
          </form>
        </body>
        </html>
      ''')
      ..close();
  }
}

Requests to the server are treated as a Stream, which means that we can use the request’s transform method to decode the content.

if (req.method == 'POST') {
  var content = await req.transform().join();
} ...

This won’t work straightaway because the transform method requires a StreamTransformer. A StreamTransformer contains a bind method that allows it to manipulate the streaming data somehow. We need one to transform our streaming request data into a readable format, and afterwards use the join method to combine our transformed chunks.

Beneath our dart:io import, let’s require the dart:convert library:

import 'dart:io';
import 'dart:convert';

And use the Utf8Decoder as our transformer:

var content = await req.transform(Utf8Decoder()).join();

Printing out content will give you the below result, provided you filled in the form with the relevant details:

fname=John&age=30&photo=file.jpg

However, we still need to extract our key/value pairs of information from the query string. Fortunately, we have a Uri class in the core Dart SDK:

var content = await req.transform(Utf8Decoder()).join();
var queryParams = Uri(query: content).queryParameters;

3. Respond to the POST

Now let’s send back a response:

var content = await req.transform(Utf8Decoder()).join();
var queryParams = Uri(query: content).queryParameters;

req.response
  ..write('Parsed data belonging to ${queryParams['fname']}')
  ..close();

UPDATE 20/10/2018: Tobe Osakwe(Creator of Angel) helpfully pointed out the use of splitQueryString static method to extract the query parameters:

// var queryParams = Uri(query: content).queryParameters;
var queryParams = Uri.splitQueryString(content);

Our query params are returned as a Map object, allowing us to pull our values like this:

queryParams['fname'];
queryParams['age'];
queryParams['photo'];

Here’s the full solution:

import 'dart:io';
import 'dart:convert';

void main() async {
  var server = await HttpServer.bind('127.0.0.1', 9000);

  await for (HttpRequest req in server) {
    if (req.method == 'POST' && req.headers.contentType.toString() == 'application/x-www-form-urlencoded') {
      var content = await req.transform(Utf8Decoder()).join();
      var queryParams = Uri(query: content).queryParameters;
      req.response
        ..write('Parsed data belonging to ${queryParams['fname']}')
        ..close();
    } else {
      req.response
        ..headers.set('Content-Type', 'text/html')
        ..write('''
          <!doctype html>
          <html>
          <body>
            <form action="/" method="post">
              <input type="text" name="fname" /><br />
              <input type="number" name="age" /><br />
              <input type="file" name="photo" /><br />
              <button>Save</button>
            </form>
          </body>
          </html>
        ''')
        ..close();
    }
  }
}

In closing…

Like the related article, this works for application/x-www-form-urlencoded content types and would require a different approach if parsing other content types. We’ll look at this in a future article.

Like, share and follow me for more content on Dart.

Further reading

  1. Hello world server in Dart

  2. Creating Streams in Dart

  3. Free Dart Screencasts on Egghead.io