I'm implementing an example in which 2 ESP32's communicate each other using the HTTP Protocol. One acts as Server the other as Client. In the examples provided, I am curious about the declaration on the server, as its implementation is declared in the setup() function (I am using the standard Arduino IDE), which is supposed to have code that executes only once, rather than the loop() function (at least in this example), which is supposed to refresh the call every cycle, yet it remains empty:

  Complete project details at https://RandomNerdTutorials.com/esp32-client-server-wi-fi/
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.

// Import required libraries
#include "WiFi.h"
#include "ESPAsyncWebServer.h"

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

// Set your access point network credentials
const char* ssid = "ESP32-Access-Point";
const char* password = "123456789";

/*#include <SPI.h>
#define BME_SCK 18
#define BME_MISO 19
#define BME_MOSI 23
#define BME_CS 5*/

Adafruit_BME280 bme; // I2C
//Adafruit_BME280 bme(BME_CS); // hardware SPI
//Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); // software SPI

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);

String readTemp() {
  return String(bme.readTemperature());
  //return String(1.8 * bme.readTemperature() + 32);

String readHumi() {
  return String(bme.readHumidity());

String readPres() {
  return String(bme.readPressure() / 100.0F);

void setup(){
  // Serial port for debugging purposes
  // Setting the ESP as an access point
  Serial.print("Setting AP (Access Point)…");
  // Remove the password parameter, if you want the AP (Access Point) to be open
  WiFi.softAP(ssid, password);

  IPAddress IP = WiFi.softAPIP();
  Serial.print("AP IP address: ");

  server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", readTemp().c_str());
  server.on("/humidity", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", readHumi().c_str());
  server.on("/pressure", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", readPres().c_str());
  bool status;

  // default settings
  // (you can also pass in a Wire library object like &Wire2)
  status = bme.begin(0x76);  
  if (!status) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  // Start server
void loop(){

You can see the "server.on(....)" declarations in the setup() side of the code, and the empty loop() function in the end.

server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", readTemp().c_str());

So, my assumption is that the server is already running in the background when executing server.begin() and does not need to be explicitly called, but it still confuses me because there is no explicit declaration on the loop() such as server.loop() or something similar. Can you explain how this works and why it is not necessary to explicitly declare the function on the loop()?

Thanks -EZ

3 Answers 3


Server.on() sets a callback function. That registers a function to be executed when a certain event happens at a later date. In this case it's when a certain URL is requested with a GET method.

Any time a request is made to the web server a list of "on" registered events is examined for one that matches the request, then the associated function (in this case a lambda function) is executed.

And all that is done asynchronously (hence the name) in the background in another thread so you don't need to explicitly do anything.

  • Thanks, I figured as much, but I wanted confirmation. The Asynchronous part seems to be doing the trick for the callback operation without the explicit mention.
    – Ed Zamper
    Apr 19, 2022 at 4:02

The construct with a lambda function

server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", readTemp().c_str());

is same as

server.on("/temperature", HTTP_GET, onGetTemperature);

where onGetTemperature is a function defined before void setup()

void onGetTemperature(AsyncWebServerRequest *request) {
  request->send_P(200, "text/plain", readTemp().c_str());

Would you in this case ask why it is "executed in the setup()?"

  • Well, it seems to me that you are just separating the lambda function outside of the setup() scope and calling it from inside setup(), but my question would not be "why is it executed in the setup" but rather, why the server.on function is not explicitly called from within the loop function? Which I believe Majenko just confirmed it is a callback() and thus not required to be explicitly called, as it is triggered by the server and compared to match one of the multiple "on" functions that could be listed in setup().
    – Ed Zamper
    Apr 19, 2022 at 4:09

Yes, server.on() is a callback function, but that's not the entirety of the story.

You don't see anything related to the server in loop() either, because somewhere in the backend of all that code, probably in server.begin(), the server attaches a callback or ISR of its own to the networking stack of the ESP. Now, when the ESP gets a message across the network of the right type, the networking stack redirects the message into the server's internal callback, at which point the server checks the page that was viewed and executes the appropriate server.on-specified function that you put in setup().

I haven't yet had the chance to look into exactly how the server's callback is attached to the networking stack, but that's how it operates asynchronously. Either it's originally activated by a Wi-Fi hardware ISR (allowing it to temporarily pause your code's execution to run the server) or it's run by a small scheduler-like section of code that is executed outside of loop() (meaning that between iterations of loop(), it might be handling the server. It's probably this case since this is when some parts of the networking stack are handled anyway).

One minor note: this operation has a potential side effect of causing memory use or CPU use by the server when you might have intended to allocate either to your own application code, so if you start seeing odd timing errors or memory corruption on a really large application, you may want to look into this as a potential cause.

  • it uses SDK callbacks
    – Juraj
    Apr 21, 2022 at 4:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.