Inconsistent Vercel Serverless Functions Behavior for CuRL and ESP32

When I run a CuRL POST request to my Vercel app, I get a 200 response code. When I do the same thing with HTTPClient on my ESP32, I get a 308 Permanent Redirect, with the redirect URL being the same URL I requested, causing an infinite loop.

Curl request: curl -X POST https://soil-moisture-monitor-phi.vercel.app/api/updateMeasurement -H “Content-Type: application/json” -d ‘{“measurement”: 123.45}’
ESP32 code:

#include <WiFi.h>
#include <HTTPClient.h>
#include <LiquidCrystal_I2C.h>


#define SENSOR_INPUT 36//15
#define LIGHT_PORT 12

LiquidCrystal_I2C lcd(0x27,16,2);
const String ssid = "REDACTED";
const String password = "REDACTED";
const String serverName = "https://soil-moisture-monitor-phi.vercel.app/api/updateMeasurement/";
int prevHumidity = 0;

void setup() {
  Serial.begin(9600);
  pinMode(LIGHT_PORT, OUTPUT);
  lcd.init();
  lcd.clear();         
  lcd.backlight(); 
  WiFi.begin(ssid, password);
  while(WiFi.status() != WL_CONNECTED){
    Serial.print("Connecting...");
    delay(100);
  }
  Serial.println("Connected!");
  // delay(1000);
  pinMode(LIGHT_PORT, OUTPUT);
  if(WiFi.status() == WL_CONNECTED){
    WiFiClient client;
    HTTPClient http;
  
    // Your Domain name with URL path or IP address with path
    http.begin(client, serverName);
    const char* headerNames[] = { "Location", "Last-Modified" };
    http.collectHeaders(headerNames, sizeof(headerNames)/sizeof(headerNames[0]));
    // Data to send with HTTP POST
    String payload = "{\"measurement\": "+String(12345) + "}";
    // Authorization
    // Send HTTP POST request
    http.addHeader("Content-Type", "application/json");
    http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
    int httpResponseCode = http.POST(payload);
    Serial.print("HTTP Response code: ");
    Serial.println(httpResponseCode);
    if (httpResponseCode == 308) {
      String location = http.header("Location");  // Get redirect target
      http.end();

      Serial.println("Redirected to: " + location);

      // Try the new location
      http.begin(client, location);
      http.addHeader("Content-Type", "application/json");
      httpResponseCode = http.POST(payload);
      Serial.print("New code: ");
      Serial.println(httpResponseCode);
    }
    // Free resources
    http.end();
  }
  else {
    Serial.println("WiFi Disconnected");
  }
}

void loop() {
  // put your main code here, to run repeatedly:
  int sensorOutput = analogRead(SENSOR_INPUT);
  int humidityPercentage = map(sensorOutput, 0, 4095, 100, 0);
  if (humidityPercentage < 30) {
    digitalWrite(LIGHT_PORT, HIGH);
  } else {
    digitalWrite(LIGHT_PORT, LOW);
  }
  prevHumidity = humidityPercentage;
  lcd.setCursor(1,0);
  lcd.print("Soil Humidity:");
  lcd.setCursor(6,1);
  lcd.print(String(humidityPercentage)+"%");
  // When the probe is not in soil it reports moisture as 0 or 1%
  // if (humidityPercentage != prevHumidity && humidityPercentage > 1){ 
  //   if(WiFi.status() == WL_CONNECTED){
  //     WiFiClient client;
  //     HTTPClient http;
    
  //     // Your Domain name with URL path or IP address with path
  //     http.begin(client, serverName);

  //     // Data to send with HTTP POST
  //     String httpRequestData = "";
  //     // Authorization
  //     // Send HTTP POST request
  //     http.addHeader("Content-Type", "application/json");
  //     int httpResponseCode = http.POST("{\"measurement\": "+String(humidityPercentage) + "}");
  //     Serial.print("HTTP Response code: ");
  //     Serial.println(httpResponseCode);
  //     // Free resources
  //     http.end();
  //   }
  //   else {
  //     Serial.println("WiFi Disconnected");
  //   }
  // }
  if (humidityPercentage < 30) {
    digitalWrite(LIGHT_PORT, HIGH);
  } else {
    digitalWrite(LIGHT_PORT, LOW);
  }
  prevHumidity = humidityPercentage;
}

My webapp code is here: GitHub - sr5434/soil-moisture-monitor

Hi @samirrangwalla-gmail, welcome to the Vercel Community!

Sorry that you’re facing this issue. In your code, I see you have a trailing slash / in the URL. Can you try removing that and then checking if the issue exists?

Removing the trailing slash does not change anything. I noticed that in the logs on my ESP32 it said that it received an HTTP 1.0 response but in CuRL it said it was running HTTP 2.0. Could that be the reason why I am getting redirected?

Hi @samirrangwalla-gmail, all traffic from Vercel is served by h2. Are you using some VPN or firewall software on your computer or the local network? I’ve noticed in the past that such programs can affect the outcome in some rare cases.

No, I’m not using a vpn or firewall.

I see. Can you share the response you get in your ESP32 code? I mean the response headers and status code. That’ll help us narrow down.

Can you also share the updated copy of the ESP32 code?

I am getting a 308 response code. Below are the response headers:
Header Name: Location
Header Value: https://soil-moisture-monitor-phi.vercel.app/api/updateMeasurement
Header Name: Last-Modified
Header Value:

Below is my updated code:

#include <WiFi.h>
#include <HTTPClient.h>
#include <LiquidCrystal_I2C.h>
#include <esp_log.h>


#define SENSOR_INPUT 36//15
#define LIGHT_PORT 12

LiquidCrystal_I2C lcd(0x27,16,2);
const String ssid = "REDACTED";
const String password = "REDACTED";
const String serverName = "https://soil-moisture-monitor-phi.vercel.app/api/updateMeasurement";
int prevHumidity = 0;

void setup() {
  Serial.begin(9600);
  esp_log_level_set("*", ESP_LOG_VERBOSE);
  pinMode(LIGHT_PORT, OUTPUT);
  lcd.init();
  lcd.clear();         
  lcd.backlight(); 
  WiFi.begin(ssid, password);
  while(WiFi.status() != WL_CONNECTED){
    Serial.print("Connecting...");
    delay(100);
  }
  Serial.println("Connected!");
  // delay(1000);
  pinMode(LIGHT_PORT, OUTPUT);
  if(WiFi.status() == WL_CONNECTED){
    WiFiClient client;
    HTTPClient http;
  
    // Your Domain name with URL path or IP address with path
    http.begin(client, serverName);
    const char* headerNames[] = { "Location", "Last-Modified" };
    http.collectHeaders(headerNames, sizeof(headerNames)/sizeof(headerNames[0]));
    // Data to send with HTTP POST
    String payload = "{\"measurement\": "+String(12345) + "}";
    // Authorization
    // Send HTTP POST request
    http.addHeader("Content-Type", "application/json");
    http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
    int httpResponseCode = http.POST(payload);
    Serial.print("HTTP Response code: ");
    Serial.println(httpResponseCode);
    int headerCount = http.headers();
    Serial.printf("Header count: %d\n", headerCount);

    for (int i = 0; i < headerCount; i++) {
      Serial.printf("Header Name: %s\n", http.headerName(i).c_str());
      Serial.printf("Header Value: %s\n", http.header(i).c_str());
    }
    if (httpResponseCode == 308) {
      String location = http.header("Location");  // Get redirect target
      http.end();

      Serial.println("Redirected to: " + location);

      // Try the new location
      http.begin(client, location);
      const char* headerNames[] = { "Location", "Last-Modified" };
      http.collectHeaders(headerNames, sizeof(headerNames)/sizeof(headerNames[0]));
      http.addHeader("Content-Type", "application/json");
      httpResponseCode = http.POST(payload);
      Serial.print("New code: ");
      Serial.println(httpResponseCode);
      int headerCount = http.headers();
      Serial.printf("Header count: %d\n", headerCount);

      for (int i = 0; i < headerCount; i++) {
        Serial.printf("Header Name: %s\n", http.headerName(i).c_str());
        Serial.printf("Header Value: %s\n", http.header(i).c_str());
      }
    }
    // Free resources
    http.end();
  }
  else {
    Serial.println("WiFi Disconnected");
  }
}

void loop() {}```

Hi @samirrangwalla-gmail, thanks for sharing additional details. I think the issue/limitation lies into the HTTP Client we’re using here because Vercel responses are be same as reflected by cURL.

Are there alternative HTTP client that you can use for your program? Maybe just for testing purpose to see what could be the actual issue.

I found some relevant posts:

It seems that the tutorials you sent are out of date, as they require libraries that are no longer available.

Oh, I see. I’d suggest to look up for alternatives and share if the problem persists. Because this is hard to reproduce without the same codebase/hardware client.