diff --git a/tutorials/http/singleton-client/Makefile b/tutorials/http/singleton-client/Makefile new file mode 100644 index 00000000..82aa7dcf --- /dev/null +++ b/tutorials/http/singleton-client/Makefile @@ -0,0 +1,28 @@ +PROG ?= example # Program we are building +DELETE = rm -rf # Command to remove files +OUT ?= -o $(PROG) # Compiler argument for output file +SOURCES = main.c mongoose.c # Source code files +CFLAGS = -W -Wall -Wextra -g -I. # Build options +#CFLAGS += -fsanitize=address,undefined,alignment + +# Mongoose build options. See https://mongoose.ws/documentation/#build-options +CFLAGS_MONGOOSE += -DMG_ENABLE_LINES=1 +CFLAGS_MONGOOSE += -DMG_TLS=MG_TLS_BUILTIN +#CFLAGS_MONGOOSE += -DMG_TLS=MG_TLS_OPENSSL -L/opt/homebrew/opt/openssl/lib -I/opt/homebrew/opt/openssl/include -lssl -lcrypto +#CFLAGS_MONGOOSE += -DMG_TLS=MG_TLS_MBED -L/opt/homebrew/opt/mbedtls/lib -I/opt/homebrew/opt/mbedtls/include -lmbedtls -lmbedcrypto -lmbedx509 + +ifeq ($(OS),Windows_NT) # Windows settings. Assume MinGW compiler. To use VC: make CC=cl CFLAGS=/MD OUT=/Feprog.exe + PROG ?= example.exe # Use .exe suffix for the binary + CC = gcc # Use MinGW gcc compiler + CFLAGS += -lws2_32 -lbcrypt # Link against Winsock library + DELETE = cmd /C del /Q /F /S # Command prompt command to delete files +endif + +all: $(PROG) # Default target. Build and run program + $(RUN) ./$(PROG) $(ARGS) + +$(PROG): $(SOURCES) # Build program from sources + $(CC) $(SOURCES) $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA) $(OUT) + +clean: # Cleanup. Delete built program and all build artifacts + $(DELETE) $(PROG) *.o *.obj *.exe *.dSYM diff --git a/tutorials/http/singleton-client/main.c b/tutorials/http/singleton-client/main.c new file mode 100644 index 00000000..61acf2c3 --- /dev/null +++ b/tutorials/http/singleton-client/main.c @@ -0,0 +1,111 @@ +// Copyright (c) 2026 Cesanta Software Limited +// All rights reserved +// +// This example shows how we can poll an external HTTP server periodically, and +// make sure that not more than one instance of the active request in flight. +// Each request has a configured timeout. + +#include "mongoose.h" + +static const char *s_url = "https://mongoose.ws/tls/api/ca"; +static const char *s_post_data = "hi"; +static const uint64_t s_connect_timeout_ms = 5500; // milliseconds +static struct mg_connection *s_conn = NULL; + +// TLS certificate, obtained using https://mongoose.ws/tls/ +#define TLS_CA \ + "-----BEGIN CERTIFICATE-----\n" \ + "MIIEVjCCAj6gAwIBAgIQY5WTY8JOcIJxWRi/w9ftVjANBgkqhkiG9w0BAQsFADBP\n" \ + "MQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFy\n" \ + "Y2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMTAeFw0yNDAzMTMwMDAwMDBa\n" \ + "Fw0yNzAzMTIyMzU5NTlaMDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBF\n" \ + "bmNyeXB0MQswCQYDVQQDEwJFODB2MBAGByqGSM49AgEGBSuBBAAiA2IABNFl8l7c\n" \ + "S7QMApzSsvru6WyrOq44ofTUOTIzxULUzDMMNMchIJBwXOhiLxxxs0LXeb5GDcHb\n" \ + "R6EToMffgSZjO9SNHfY9gjMy9vQr5/WWOrQTZxh7az6NSNnq3u2ubT6HTKOB+DCB\n" \ + "9TAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMB\n" \ + "MBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFI8NE6L2Ln7RUGwzGDhdWY4j\n" \ + "cpHKMB8GA1UdIwQYMBaAFHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEB\n" \ + "BCYwJDAiBggrBgEFBQcwAoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzATBgNVHSAE\n" \ + "DDAKMAgGBmeBDAECATAnBgNVHR8EIDAeMBygGqAYhhZodHRwOi8veDEuYy5sZW5j\n" \ + "ci5vcmcvMA0GCSqGSIb3DQEBCwUAA4ICAQBnE0hGINKsCYWi0Xx1ygxD5qihEjZ0\n" \ + "RI3tTZz1wuATH3ZwYPIp97kWEayanD1j0cDhIYzy4CkDo2jB8D5t0a6zZWzlr98d\n" \ + "AQFNh8uKJkIHdLShy+nUyeZxc5bNeMp1Lu0gSzE4McqfmNMvIpeiwWSYO9w82Ob8\n" \ + "otvXcO2JUYi3svHIWRm3+707DUbL51XMcY2iZdlCq4Wa9nbuk3WTU4gr6LY8MzVA\n" \ + "aDQG2+4U3eJ6qUF10bBnR1uuVyDYs9RhrwucRVnfuDj29CMLTsplM5f5wSV5hUpm\n" \ + "Uwp/vV7M4w4aGunt74koX71n4EdagCsL/Yk5+mAQU0+tue0JOfAV/R6t1k+Xk9s2\n" \ + "HMQFeoxppfzAVC04FdG9M+AC2JWxmFSt6BCuh3CEey3fE52Qrj9YM75rtvIjsm/1\n" \ + "Hl+u//Wqxnu1ZQ4jpa+VpuZiGOlWrqSP9eogdOhCGisnyewWJwRQOqK16wiGyZeR\n" \ + "xs/Bekw65vwSIaVkBruPiTfMOo0Zh4gVa8/qJgMbJbyrwwG97z/PRgmLKCDl8z3d\n" \ + "tA0Z7qq7fta0Gl24uyuB05dqI5J1LvAzKuWdIjT1tP8qCoxSE/xpix8hX2dt3h+/\n" \ + "jujUgFPFZ0EVZ0xSyBNRF3MboGZnYXFUxpNjTWPKpagDHJQmqrAcDmWJnMsFY3jS\n" \ + "u1igv3OefnWjSQ==\n" \ + "-----END CERTIFICATE-----" + +static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) { + uint64_t *start_time = (uint64_t *) c->data; // Stored start time + bool *success = (bool *) (start_time + 1); // Stored success flag + if (ev == MG_EV_OPEN) { + *start_time = mg_millis(); + MG_DEBUG(("%lu Connection started, time: %llu", c->id, *start_time)); + } else if (ev == MG_EV_POLL) { + if (*start_time + s_connect_timeout_ms < mg_millis()) { + mg_error(c, "Connect timeout"); + } + } else if (ev == MG_EV_CONNECT) { + // Connected to server. Extract host name from URL + struct mg_str host = mg_url_host(s_url); + if (c->is_tls) { + struct mg_tls_opts opts = {}; + opts.ca = mg_str(TLS_CA); + opts.name = host; + mg_tls_init(c, &opts); + } + // Send request + int content_length = s_post_data ? strlen(s_post_data) : 0; + mg_printf(c, + "%s %s HTTP/1.0\r\n" + "Host: %.*s\r\n" + "Content-Type: octet-stream\r\n" + "Content-Length: %d\r\n" + "\r\n", + s_post_data ? "POST" : "GET", mg_url_uri(s_url), host.len, + host.buf, content_length); + mg_send(c, s_post_data, content_length); + } else if (ev == MG_EV_HTTP_MSG) { + // Response is received. Print it + struct mg_http_message *hm = (struct mg_http_message *) ev_data; + MG_INFO(("%lu Got response %lu bytes %lu", c->id, hm->body.len, c->rtls.len)); + *success = true; + c->is_closing = 1; + } else if (ev == MG_EV_ERROR) { + MG_ERROR(("%lu Connection error: %s", c->id, ev_data)); + } else if (ev == MG_EV_CLOSE) { + MG_DEBUG(("%lu Connection closed %lu", c->id, mg_millis() - *start_time)); + if (*success == false) MG_ERROR(("%lu NO RESPONSE %lu", c->id, c->rtls.len)); + if (c->is_draining == 0) s_conn = NULL; + } +} + +static void timer_fn(void *arg) { + struct mg_mgr *mgr = (struct mg_mgr *) arg; + // If connection is already there, do nothing. Otherwise, start it + if (s_conn != NULL) { + MG_DEBUG(("Connection is already active...")); + } else { + s_conn = mg_http_connect(mgr, s_url, http_ev_handler, NULL); + } +} + +int main(void) { + struct mg_mgr mgr; + mg_log_set(MG_LL_INFO); + mg_mgr_init(&mgr); + + // Attempt to start a POST request frequently + mg_timer_add(&mgr, 50, MG_TIMER_REPEAT, timer_fn, &mgr); + + for (;;) mg_mgr_poll(&mgr, 10); + mg_mgr_free(&mgr); + + return 0; +} diff --git a/tutorials/http/singleton-client/mongoose.c b/tutorials/http/singleton-client/mongoose.c new file mode 120000 index 00000000..5e522bbc --- /dev/null +++ b/tutorials/http/singleton-client/mongoose.c @@ -0,0 +1 @@ +../../../mongoose.c \ No newline at end of file diff --git a/tutorials/http/singleton-client/mongoose.h b/tutorials/http/singleton-client/mongoose.h new file mode 120000 index 00000000..ee4ac823 --- /dev/null +++ b/tutorials/http/singleton-client/mongoose.h @@ -0,0 +1 @@ +../../../mongoose.h \ No newline at end of file