Help with crafting a HTTP POST response

Hi,

I’m trying to send a HTTP POST request to a Mastodon instance.

This is being done on an old Nanode, which has an ATmega328 microcontroller and built in ENC28J60 ethernet.

Nanode is an Arduino like 8 bit microcontroller board with integrated Ethernet connectivity.

As it is the same chip as the Arduino, it uses the Arduino IDE and has a dedicated library for ethernet connectivity: EtherCard.

I have all of this working; getting the network up, connecting to the remote server, receiving a response etc, but the POST seems malformed and I cannot figure out why.

There are numerous forum posts about how to do this, and all of them look like my sketch, yet mine doesn’t work and I cannot figure out why.

Sketch

//
// Based on the EtherCard library example 'twitter.ino'
//
// License: GPLv2
//
// POSTs to Mastodon
//

#include <EtherCard.h>

// Mastodon info
const char website[] PROGMEM = "botsin.space";
#define TOKEN "dgpAlS.........._Ai_gVTWf4M"

// ethernet interface mac address
static byte mymac[] = { 0x4e, 0x61, 0x6e, 0x6f, 0x64, 0x65 };
// ethernet interface ip address
static byte myip[] = { 192, 168, 0, 250 };
// gateway ip address
static byte gwip[] = { 192, 168, 0, 1 };
// DNS address
static byte dnsip[] = { 194, 168, 4, 100 };
// network mask
static byte netmask[] = { 255, 255, 255, 0 };

static byte session;

byte Ethernet::buffer[700];
Stash stash;

static void sendToMastodon() {
  Serial.println("Sending toot...");
  byte sd = stash.create();

  const char toot[] = "hello";
  stash.print("Authorization: Bearer ");
  stash.print(TOKEN);
  stash.print("&status=");
  stash.println(toot);
  stash.save();
  int stash_size = stash.size();

  Serial.println("");
  Serial.println("[Debugging:]");
  Serial.print("stash_size:");
  Serial.println(stash_size);
  Serial.println("");

  // Compose the http POST request, taking the headers below and appending
  // previously created stash in the sd holder.
    Stash::prepare(PSTR("POST /api/v1/statuses HTTP/1.1" "\r\n"
    "Host: $F" "\r\n"
    "Content-Type: application/x-www-form-urlencoded" "\r\n"
    "Content-Length: $D" "\r\n"
    "\r\n"
    "$H"),
  website, stash_size, sd);

  // send the packet - this also releases all stash buffers once done
  // Save the session ID so we can watch for it in the main loop.
  session = ether.tcpSend();
}

void setup() {
  Serial.begin(57600);
  Serial.println("\n[Mastodon Client]");

  // Change 'SS' to your Slave Select pin, if you arn't using the default pin
  // Use pin8 for a Nanode
  if (ether.begin(sizeof Ethernet::buffer, mymac, 8) == 0)
    Serial.println(F("Failed to access Ethernet controller"));
  if (!ether.dhcpSetup())
    Serial.println(F("DHCP failed"));

  //Static IP
  //ether.staticSetup(myip, gwip, dnsip, netmask);
  //Dynamic IP
  ether.dhcpSetup();

  ether.printIp("My IP:  ", ether.myip);
  ether.printIp("Gateway:  ", ether.gwip);
  ether.printIp("My DNS: ", ether.dnsip);
  ether.printIp("Subnet Mask: ", ether.netmask);

  if (!ether.dnsLookup(website))
    Serial.println(F("Remote DNS lookup failed"));
  ether.printIp("Remote DNS: ", ether.hisip);

  sendToMastodon();
}

void loop() {
  ether.packetLoop(ether.packetReceive());

  const char* reply = ether.tcpReply(session);
  if (reply != 0) {
    Serial.println("Got a response!");
    Serial.println(reply);
  }
}

Serial Output

[Mastodon Client]
My IP: 192.168.0.124
Gateway: 192.168.0.1
My DNS: 194.168.4.100
Subnet Mask: 255.255.255.0
Remote DNS: 147.182.217.82

Sending toot...

[Debugging:]
stash_size:80

Got a response!

HTTP/1.1 301 Moved Permanently
Server: nginx/1.18.0 (Ubuntu)
Date: Sat, 10 Dec 2022 10:10:25 GMT
Content-Type: text/html
Content-Length: 178
Connection: keep-alive
Location: https://botsin.space/api/v1/statuses

<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx/1.18.0 (Ubuntu)</center>
</body>
</html>

The response suggets that the URL is incorrect but the documentation states to POST to:

POST https://mastodon.example/api/v1/statuses HTTP/1.1

I think the error is somewhere in here, but cannot find it.

  // Compose the http POST request, taking the headers below and appending
  // previously created stash in the sd holder.
    Stash::prepare(PSTR("POST /api/v1/statuses HTTP/1.1" "\r\n"
    "Host: $F" "\r\n"
    "Content-Type: application/x-www-form-urlencoded" "\r\n"
    "Content-Length: $D" "\r\n"
    "\r\n"
    "$H"),
  website, stash_size, sd);

Any help gratefully appreciated.

Simon.

hmmm, it looks as if POST’ing to HTTPS is the problem here.

“Host: https://botsin.space

  // Compose the http POST request, taking the headers below and appending
  // previously created stash in the sd holder.
    Stash::prepare(PSTR("POST /api/v1/statuses HTTP/1.1" "\r\n"
    "Host: https://botsin.space" "\r\n"
    "Content-Type: application/x-www-form-urlencoded" "\r\n"
    "Content-Length: $D" "\r\n"
    "\r\n"
    "$H" "\r\n"),
    stash_size, sd);

Produces:

HTTP/1.1 400 Bad Request

While the non-HTTPS POST:

“Host: botsin.space”


  // Compose the http POST request, taking the headers below and appending
  // previously created stash in the sd holder.
    Stash::prepare(PSTR("POST /api/v1/statuses HTTP/1.1" "\r\n"
    "Host: botsin.space" "\r\n"
    "Content-Type: application/x-www-form-urlencoded" "\r\n"
    "Content-Length: $D" "\r\n"
    "\r\n"
    "$H" "\r\n"),
    stash_size, sd);

Produces:

HTTP/1.1 301 Moved Permanently

And points me to POST to:

Location: https://botsin.space/api/v1/statuses

Not sure what to do about this.

I guess more rabbit holes have to be investigated.

oh.

// WARNING: This example uses insecure HTTP and not HTTPS.

From the actual documentation for the library.

image