Skip to content

edge-http: Server doesn't allow more clients #80

@Luni-4

Description

@Luni-4

This issue might be related to #62, though I'm not certain. I'm on an esp32c3 and when I try to access an edge-http server from two different clients using curl, I encounter the following error:

curl: (7) Failed to connect to 192.168.178.142 port 3000 after 7 ms: Couldn't connect to server
Hello world!⏎

I get the correct answer only the first time, but only when timeouts are disabled. At other times, the result is the same above.

Hello world!⏎
Hello world!⏎

This is the script I have used to create the clients:

#!/bin/bash
for i in {1..2}; do
  curl -X GET http://192.168.178.142:3000 \
       -H "Connection: close" \
       -H "User-Agent: client$i" &
done
wait

Below the code

Toml
[package]
name = "example"
version = "0.1.0"
rust-version = "1.89"
edition = "2024"

[dependencies]
# Embassy framework
embassy-executor.version = "0.9.1"
embassy-executor.features = ["log"]

embassy-net.version = "0.7.1"
embassy-net.features = ["dhcpv4", "log", "medium-ethernet", "tcp", "udp"]

embassy-sync.version = "0.7.2"

embassy-time.version = "0.5.0"
embassy-time.features = ["log"]

embassy-embedded-hal = "0.5.0"

# HTTP server
edge-http.version = "0.6.1"
edge-http.git = "https://github.com/Luni-4/edge-net.git"
edge-http.branch = "deps"

# Networking traits
edge-nal.version = "0.5.0"
edge-nal.git = "https://github.com/Luni-4/edge-net.git"
edge-nal.branch = "deps"

edge-nal-embassy.version = "0.7.0"
edge-nal-embassy.git = "https://github.com/Luni-4/edge-net.git"
edge-nal-embassy.branch = "deps"

# Esp dependencies
esp-alloc.version = "0.9.0"

esp-bootloader-esp-idf.version = "0.4.0"
esp-bootloader-esp-idf.features = ["esp32c3", "log-04"]

esp-hal.version = "1.0.0"
esp-hal.features = ["esp32c3", "log-04", "unstable"]

esp-rtos.version = "0.2.0"
esp-rtos.features = ["embassy", "esp-alloc", "esp-radio", "esp32c3", "log-04"]

esp-println.version = "0.16.1"
esp-println.features = ["esp32c3", "log-04"]

esp-radio.version = "0.17.0"
esp-radio.features = ["esp-alloc", "esp32c3", "log-04", "wifi"]

# Logging utilities
log.version = "0.4.28"

# Asynchronous read and write traits
embedded-io-async.version = "0.7.0"

# Static cell
static_cell.version = "2.1.1"
static_cell.features = ["nightly"]

# Toml dependency
toml-cfg.version = "0.2.0"
toml-cfg.default-features = false

[build-dependencies]
toml-cfg.version = "0.2.0"
toml-cfg.default-features = false

[profile.dev]
opt-level = "s"

[profile.release]
codegen-units    = 1  
debug            = 2
debug-assertions = false
incremental      = false
lto              = 'fat'
opt-level        = 's'
overflow-checks  = false
Rust
#![no_std]
#![no_main]
#![deny(
    clippy::mem_forget,
    reason = "mem::forget is generally not safe to do with esp_hal types, especially those \
    holding buffers for the duration of a data transfer."
)]

extern crate alloc;

use core::net::{IpAddr, Ipv4Addr, SocketAddr};

use alloc::boxed::Box;

use esp_hal::clock::CpuClock;
use esp_hal::interrupt::software::SoftwareInterruptControl;
use esp_hal::rng::Rng;
use esp_hal::timer::timg::TimerGroup;
use esp_hal::Config;

use log::{error, info};

use embassy_executor::Spawner;
use embassy_net::{Config as NetConfig, DhcpConfig, Runner, Stack, StackResources};
use embassy_time::Timer;

use esp_radio::wifi::{
    sta_state, ClientConfig, Config as WifiConfig, ModeConfig, WifiController, WifiDevice,
    WifiEvent, WifiStaState,
};
use esp_radio::Controller;

use edge_http::io::server::{Connection, Handler, Server};
use edge_http::io::Error;
use edge_http::Method;
use edge_nal::TcpBind;
use edge_nal_embassy::{Tcp, TcpBuffers};

use embedded_io_async::{Read, Write};

macro_rules! mk_static {
    ($t:ty,$val:expr) => {{
        static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new();
        #[deny(unused_attributes)]
        let x = STATIC_CELL.uninit().write($val);
        x
    }};
}

// This creates a default app-descriptor required by the esp-idf bootloader.
// For more information see: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description>
esp_bootloader_esp_idf::esp_app_desc!();

#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
    loop {}
}

#[embassy_executor::task]
async fn connect(mut wifi_controller: WifiController<'static>) {
    info!("Wi-Fi connection task started");
    loop {
        if sta_state() == WifiStaState::Connected {
            wifi_controller
                .wait_for_event(WifiEvent::StaDisconnected)
                .await;
            embassy_time::Timer::after_secs(2).await;
        }

        if !matches!(wifi_controller.is_started(), Ok(true)) {
            info!("Starting Wi-Fi...");
            wifi_controller
                .start_async()
                .await
                .expect("Impossible to start Wi-Fi");
            info!("Wi-Fi started");
        }

        info!("Attempting to connect...");
        if let Err(e) = wifi_controller.connect_async().await {
            error!("Wi-Fi connect failed: {e:?}");
            embassy_time::Timer::after_secs(2).await;
        } else {
            info!("Wi-Fi connected!");
        }
    }
}

#[embassy_executor::task]
async fn task(mut runner: Runner<'static, WifiDevice<'static>>) {
    runner.run().await;
}

#[inline]
pub(crate) async fn get_ip(stack: Stack<'static>) -> Ipv4Addr {
    info!("Waiting till the link is up...");
    loop {
        if stack.is_link_up() {
            break;
        }
        Timer::after_millis(100).await;
    }

    info!("Waiting to get IP address...");
    loop {
        if let Some(config) = stack.config_v4() {
            return config.address.address();
        }
        Timer::after_millis(100).await;
    }
}

#[toml_cfg::toml_config]
struct DeviceConfig {
    #[default("")]
    ssid: &'static str,
    #[default("")]
    password: &'static str,
    #[default("")]
    broker_address: &'static str,
    #[default(0)]
    broker_port: u16,
}

#[esp_rtos::main]
async fn main(spawner: Spawner) {
    esp_println::logger::init_logger_from_env();

    let config = Config::default().with_cpu_clock(CpuClock::max());
    let peripherals = esp_hal::init(config);

    esp_alloc::heap_allocator!(size: 128 * 1024);

    let timg0 = TimerGroup::new(peripherals.TIMG0);
    let sw_int = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);
    esp_rtos::start(timg0.timer0, sw_int.software_interrupt0);

    info!("ESP RTOS started!");

    // Retrieve device configuration.
    let device_config = DEVICE_CONFIG;

    let esp_radio_controller = &*mk_static!(Controller<'static>, esp_radio::init().unwrap());
    let (mut controller, interfaces) = esp_radio::wifi::new(
        esp_radio_controller,
        peripherals.WIFI,
        WifiConfig::default(),
    )
    .unwrap();

    if device_config.ssid.is_empty() {
        panic!("Missing Wi-Fi SSID");
    }

    if device_config.password.is_empty() {
        panic!("Missing Wi-Fi password");
    }

    let client_config = ModeConfig::Client(
        ClientConfig::default()
            .with_ssid(device_config.ssid.into())
            .with_password(device_config.password.into()),
    );

    controller.set_config(&client_config).unwrap();

    spawner.spawn(connect(controller)).unwrap();

    // Wait until Wi-Fi is connected.
    while sta_state() != WifiStaState::Connected {
        embassy_time::Timer::after_millis(100).await;
    }

    info!("Starting network stack...");

    let rng = Rng::new();

    let net_config = NetConfig::dhcpv4(DhcpConfig::default());
    let seed = u64::from(rng.random()) << 32 | u64::from(rng.random());

    let resources = Box::leak(Box::new(StackResources::<12>::new()));

    let (stack, runner) = embassy_net::new(interfaces.sta, net_config, resources, seed);

    spawner.spawn(task(runner)).unwrap();

    // Wait until the stack has a valid IP configuration.
    while !stack.is_config_up() {
        Timer::after_millis(100).await;
    }

    let mut server = Server::<2, 4096, 32>::new();
    let buffers = TcpBuffers::<2, 2048, 4096>::new();
    let tcp = Tcp::new(stack, &buffers);

    let ip = get_ip(stack).await;

    info!("Ip: {ip}:3000");

    let acceptor = tcp
        .bind(SocketAddr::new(IpAddr::V4(ip), 3000))
        .await
        .unwrap();

    loop {
        match server
            .run(
                Some(15 * 1000),
                edge_nal::WithTimeout::new(15_000, &acceptor),
                HttpHandler,
            )
            .await
        {
            Ok(_) => {}
            Err(error) => {
                // panic!("{:?}", error);
                log::error!("{:?}", error);
            }
        }
    }
}

struct HttpHandler;

impl Handler for HttpHandler {
    type Error<E>
        = Error<E>
    where
        E: core::fmt::Debug;

    async fn handle<T, const N: usize>(
        &self,
        _task_id: impl core::fmt::Display + Copy,
        connection: &mut Connection<'_, T, N>,
    ) -> Result<(), Self::Error<T::Error>>
    where
        T: Read + Write,
    {
        info!("Got new connection");
        let headers = connection.headers()?;

        if headers.method != Method::Get {
            connection
                .initiate_response(405, Some("Method Not Allowed"), &[])
                .await?;
        } else if headers.path != "/" {
            connection
                .initiate_response(404, Some("Not Found"), &[])
                .await?;
        } else {
            connection
                .initiate_response(200, Some("OK"), &[("Content-Type", "text/plain")])
                .await?;

            connection.write_all(b"Hello world!").await?;
        }

        Ok(())
    }
}
build.rs
#[toml_cfg::toml_config]
pub struct DeviceConfig {
    #[default("")]
    ssid: &'static str,
    #[default("")]
    password: &'static str,
}

fn main() {
    let cfg = std::path::Path::new("cfg.toml");

    // Checks whether device configuration exists
    assert!(
        cfg.exists(),
        "A `cfg.toml` file with Wi-Fi credentials and broker configuration is required! Use `cfg.toml.example` as a template."
    );

    let device_config = DEVICE_CONFIG;
    assert!(
        !device_config.ssid.trim().is_empty() || !device_config.password.trim().is_empty(),
        "All config fields should be set in `cfg.toml` file!"
    );

    linker_be_nice();
    // make sure linkall.x is the last linker script (otherwise might cause problems with flip-link)
    println!("cargo:rustc-link-arg=-Tlinkall.x");
}

fn linker_be_nice() {
    let args: Vec<String> = std::env::args().collect();
    if args.len() > 1 {
        let kind = &args[1];
        let what = &args[2];

        match kind.as_str() {
            "undefined-symbol" => match what.as_str() {
                "_defmt_timestamp" => {
                    eprintln!();
                    eprintln!(
                        "💡 `defmt` not found - make sure `defmt.x` is added as a linker script and you have included `use defmt_rtt as _;`"
                    );
                    eprintln!();
                }
                "_stack_start" => {
                    eprintln!();
                    eprintln!("💡 Is the linker script `linkall.x` missing?");
                    eprintln!();
                }
                "esp_rtos_initialized" | "esp_rtos_yield_task" | "esp_rtos_task_create" => {
                    eprintln!();
                    eprintln!(
                        "💡 `esp-radio` has no scheduler enabled. Make sure you have initialized `esp-rtos` or provided an external scheduler."
                    );
                    eprintln!();
                }
                "embedded_test_linker_file_not_added_to_rustflags" => {
                    eprintln!();
                    eprintln!(
                        "💡 `embedded-test` not found - make sure `embedded-test.x` is added as a linker script for tests"
                    );
                    eprintln!();
                }
                _ => (),
            },
            // we don't have anything helpful for "missing-lib" yet
            _ => {
                std::process::exit(1);
            }
        }

        std::process::exit(0);
    }

    println!(
        "cargo:rustc-link-arg=--error-handling-script={}",
        std::env::current_exe().unwrap().display()
    );
}

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions