0

I'm building a flutter application that talks to a Particle Photon device through the Particle Cloud and I'm having trouble making a correctly formatted request to the Particle cloud.

When I try to send my request with the particle Access Token encoded as form data, my code looks like this:

void setStatus(int idx) async {
  print('ParticleService: setStatus($idx)');
  var url = particleHost + _deviceID + particleVerb1;
  var formData = 'access_token=$_accessToken&params=$idx';
  var bodyLen = formData.length;
  var headers = {
    "Content-Type": "application/x-www-form-urlencoded",
    "Content-Length": "$bodyLen"
  };
  var response = await http.post(url, body: formData, headers: headers);
}

With it, I get an error indicating that it can't find the Access Token:

Response body: {"error":"invalid_request","error_description":"The access token was not found"}

When I try to send the access token in an authorization header it looks like this:

    var url = particleHost + _deviceID + particleVerb1;
    var body = json.encode({"params": "$idx"});
    var bodyLen = body.length;
    var headers = {
      HttpHeaders.authorizationHeader: _accessToken,
      HttpHeaders.contentTypeHeader: 'application/json',
      "Content-Length": "$bodyLen"
    };

    print('URL: $url');
    print('Body: $body');
    print('Headers: $headers');

    var response = await http.post(url, body: body, headers: headers);

The particle cloud replies that the auth header is malformed:

Response body: {"error":"invalid_request","error_description":"Malformed auth header"}

I've done this many times before and it worked fine, but something about the Flutter implementation is killing me. Here is it in TypeScrtipt in an Ionic application:

  setStatus(currentStatus: number): Promise<any> {
    console.log(`ParticleService: setStatus(${currentStatus})`);

    const url: string = PARTICLE_HOST + this.deviceID + PARTICLE_VERB_1;
    const body = `access_token=${this.accessToken}&params=${currentStatus}`;
    const headers: any = {
      'Content-Type': 'application/x-www-form-urlencoded',
      'Content-Length': body.length
    };

    return new Promise((resolve, reject) => {
      this.http.post(url, body, { headers }).subscribe(data => {
        resolve(data);
      }, error => {
        console.error(error.message);
        reject(error.message);
      });
    });
  }

Here it is in JavaScript for the browser:

console.log(`setRemoteStatus(${btnName})`);
        // Get the color code based on the status being set
        let colorCode = buttons.indexOf(btnName);
        // Build the URL we'll use to talk to the Particle API
        let postURL = particleHost + particlePath + config.getDeviceID() + particleVerb1;
        // Craft the POST request body
        let postBody = `access_token=${config.getAccessToken()}&params=${colorCode}`;
        // Call the API
        fetch(postURL, {
            method: 'POST',
            mode: 'cors',
            headers: {
                'Content-Type': "application/x-www-form-urlencoded",
                "Content-Length": postBody.length
            },
            referrerPolicy: 'no-referrer',
            body: postBody
        }).then(res => {
            if (res.status == 200) {
                displayAlert('Status Update', 'info', 'Successfully set remote status', 500);
            } else {
                displayAlert('Update Error', 'warning', `Status not set (result code ${res.status})`);
            }
        }).catch(err => {
            console.error('Unable to set status');
            displayAlert('Update Error', 'error', `Unable to set status (${err.message})`);
        });

Can someone please help me understand where I'm going wrong in the Dart code?

johnwargo
  • 601
  • 2
  • 7
  • 22

1 Answers1

0

Change setStatus to this:

Future<void> setStatus(int idx) async {
  print('ParticleService: setStatus($idx)');
  var url = particleHost + _deviceID + particleVerb1;
  var formData = <String, String>{
    'access_token': _accessToken,
    'params': idx.toString(),
  };
  var response = await http.post(url, body: formData);
}

Don't try to encode the body yourself, when post will do it for you if you give it a Map<String, String>. (It also sets the content type and content length for you. If you need to add other headers you can re-instate the headers map, but that's not needed if you just need the two content headers.)

Richard Heap
  • 48,344
  • 9
  • 130
  • 112
  • Thank you so much, that was stumping me. – johnwargo Apr 07 '20 at 17:23
  • Incidentally, I don't thing you need to set the content length header on the other request either. Here your body is a single string - the encoded JSON. `post` will encode it to bytes and set the length header for you. Note that the encoding might cause the number of bytes to differ from the string length depending on the encoding. It's generally safest to do the character encoding yourself - `body: utf8.encode(json.encode(someMap)),` – Richard Heap Apr 07 '20 at 20:09