Libmicrohttpd est une vieille librairie de GNU, sans doute pas la plus évoluée mais après avoir fait le tour des solutions équivalentes, il m'a semblé qu'en termes techniques, de durée et de licence, elle n'avait pas trop à rougir, et méritait qu'on s'y intéresse.
L'implémentation qui suit a été testée sur un serveur Ubuntu 20.04 LTS (Focal Fossa), elle devrait aussi bien fonctionner sur n'importe quelle distribution debian-based équivalente.
Il serait un peu long de tout détailler, voici d'abord le code principal réduit à un maximum raisonnable:
//---------------------------------------------------------------------------------------- // WebServer.c #include <sys/types.h> #include <sys/select.h> #include <sys/socket.h> #include <sys/stat.h> #include <unistd.h> #include <syslog.h> #include <signal.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <libgen.h> #include <dirent.h> #include <fcntl.h> #include <microhttpd.h> #include "strptr.h" #include "log.h" #define PORT_HTTP 80 #define PORT_HTTPS 443 #ifndef TRUE #define TRUE 1 #endif #ifndef FALSE #define FALSE 0 #endif //---------------------------------------------------------------------------------------- // Globals static struct MHD_Daemon *gMHDDaemonHTTP = NULL; static struct MHD_Daemon *gMHDDaemonHTTPS = NULL; static int gMHDRunning = FALSE; static char *gAppDirPathPtr = NULL; static char *gHostsDirPathPtr = NULL; static char *gTLSPublicKeyPtr = NULL; static char *gTLSPrivateKeyPtr = NULL; //---------------------------------------------------------------------------------------- // Prototypes void AppInit (void); void AppExit (void); //---------------------------------------------------------------------------------------- // Signals void handle_sigterm () { log_timedwriteln(LOG_TYPE_SIGNAL, "Signal : TERM"); gMHDRunning = FALSE; } void handle_sigint () { log_timedwriteln(LOG_TYPE_SIGNAL, "Signal : INT"); gMHDRunning = FALSE; } void handle_sigsegv () { log_timedwriteln(LOG_TYPE_SIGNAL, "Signal : SEGV"); } //---------------------------------------------------------------------------------------- // MimeType from Extension char *get_mimetype_from_extension (const char *theExtension) { // https://developer.mozilla.org/fr/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types if (strcmp(theExtension, "html") == 0) return strptr_new("text/html"); if (strcmp(theExtension, "pl") == 0) return strptr_new("text/html"); if (strcmp(theExtension, "js") == 0) return strptr_new("application/javascript"); if (strcmp(theExtension, "css") == 0) return strptr_new("text/css"); if (strcmp(theExtension, "ico") == 0) return strptr_new("image/x-icon"); if (strcmp(theExtension, "jpg") == 0) return strptr_new("image/jpeg"); if (strcmp(theExtension, "jpeg") == 0) return strptr_new("image/jpeg"); if (strcmp(theExtension, "gif") == 0) return strptr_new("image/gif"); if (strcmp(theExtension, "png") == 0) return strptr_new("image/png"); if (strcmp(theExtension, "pdf") == 0) return strptr_new("application/pdf"); return strptr_new("application/octet-stream"); }; //---------------------------------------------------------------------------------------- // Response File Callbacks /* static ssize_t response_file_reader (void *theFilePtr, uint64_t pos, char *buf, size_t max) { fseek((FILE *)theFilePtr, pos, SEEK_SET); return fread(buf, 1, max, (FILE *)theFilePtr); } static void response_file_free (void *theFilePtr) { fclose((FILE *)theFilePtr); } */ //---------------------------------------------------------------------------------------- // Request Callback static int answer_to_connection (void *cls, struct MHD_Connection *theConnection, const char *theRequestURL, const char *theMethod, const char *version, const char *upload_data, size_t *upload_data_size, void **con_cls) { char *my400PageHTML = "<html><body>400</body></html>"; char *my404PageHTML = "<html><body>404</body></html>"; struct MHD_Response *myResponse = NULL; int myResponseQueueResult; unsigned int myResponseStatusCode = MHD_HTTP_NO_CONTENT; const char *myHeaderHostPtr; char *myRequestHostDirPathPtr = NULL; char *myRequestFilePathPtr = NULL; char *myRequestFileMimetypePtr = NULL; struct stat myRequestFileStat; void *myResponseBufferPtr = NULL; size_t myResponseBufferSize = 0; struct sockaddr **myClientSockAddrPtrPtr; char myClientIPAddress[20]; (void)cls; /* Unused. Silent compiler warning. */ // (void)theRequestURL; /* Unused. Silent compiler warning. */ // (void)theMethod; /* Unused. Silent compiler warning. */ (void)version; /* Unused. Silent compiler warning. */ (void)upload_data; /* Unused. Silent compiler warning. */ (void)upload_data_size; /* Unused. Silent compiler warning. */ // (void)con_cls; /* Unused. Silent compiler warning. */ if (strcmp(theMethod, MHD_HTTP_METHOD_GET) != 0) return MHD_NO; // unexpected method if (*con_cls == NULL) // first call { *con_cls = theConnection; return MHD_YES; }; myClientSockAddrPtrPtr = (struct sockaddr **)MHD_get_connection_info(theConnection, MHD_CONNECTION_INFO_CLIENT_ADDRESS); log_sockaddr_to_ipstr(*myClientSockAddrPtrPtr, myClientIPAddress, sizeof(myClientIPAddress)); myHeaderHostPtr = MHD_lookup_connection_value(theConnection, MHD_HEADER_KIND, MHD_HTTP_HEADER_HOST); if (myHeaderHostPtr != NULL) { char *myRequestRevDomainPtr = strptr_new(myHeaderHostPtr); strptr_revert_domain(myRequestRevDomainPtr); myRequestHostDirPathPtr = strptr_new("/opt/WebServer/hosts"); myRequestHostDirPathPtr = strptr_cat(myRequestHostDirPathPtr, "/"); myRequestHostDirPathPtr = strptr_cat(myRequestHostDirPathPtr, myRequestRevDomainPtr); myRequestRevDomainPtr = strptr_free(myRequestRevDomainPtr); }; if (myRequestHostDirPathPtr != NULL) { int myRequestHostDirPathOK = FALSE; if (stat(myRequestHostDirPathPtr, &myRequestFileStat) == 0) { if (S_ISDIR(myRequestFileStat.st_mode)) { myRequestHostDirPathOK = TRUE; } else log_timedwriteln(LOG_TYPE_ERROR, "(%s) RequestHostDirPath not a dir : %s", myClientIPAddress, myRequestHostDirPathPtr); } else log_timedwriteln(LOG_TYPE_ERROR, "(%s) RequestHostDirPath not found : %s", myClientIPAddress, myRequestHostDirPathPtr); if (!myRequestHostDirPathOK) myRequestHostDirPathPtr = strptr_free(myRequestHostDirPathPtr); }; if (myRequestHostDirPathPtr != NULL) { myRequestFilePathPtr = strptr_new(myRequestHostDirPathPtr); myRequestFilePathPtr = strptr_cat(myRequestFilePathPtr, theRequestURL); if (stat(myRequestFilePathPtr, &myRequestFileStat) == 0) { if (S_ISDIR(myRequestFileStat.st_mode)) { char *myRequestFileDirPathPtr = strptr_new(myRequestFilePathPtr); myRequestFileDirPathPtr = strptr_rtrim(myRequestFileDirPathPtr, '/'); myRequestFilePathPtr = strptr_cpy(myRequestFilePathPtr, myRequestFileDirPathPtr); myRequestFilePathPtr = strptr_cat(myRequestFilePathPtr, "/index.html"); if (stat(myRequestFilePathPtr, &myRequestFileStat) == 0) { int myRequestFileDesc = open(myRequestFilePathPtr, O_RDONLY); myResponse = MHD_create_response_from_fd((uint64_t)myRequestFileStat.st_size, myRequestFileDesc); myResponseStatusCode = MHD_HTTP_OK; }; if (myResponseStatusCode != MHD_HTTP_OK) { log_timedwriteln(LOG_TYPE_ERROR, "(%s) Couldn't stat : %s", myClientIPAddress, myRequestFilePathPtr); myResponse = MHD_create_response_from_buffer(strlen(my404PageHTML), my404PageHTML, MHD_RESPMEM_MUST_COPY); myResponseStatusCode = MHD_HTTP_NOT_FOUND; }; strptr_free(myRequestFileDirPathPtr); } else { int myRequestFileDesc = open(myRequestFilePathPtr, O_RDONLY); myResponse = MHD_create_response_from_fd((uint64_t)myRequestFileStat.st_size, myRequestFileDesc); myResponseStatusCode = MHD_HTTP_OK; }; } else { log_timedwriteln(LOG_TYPE_ERROR, "(%s) Couldn't stat : %s", myClientIPAddress, myRequestFilePathPtr); myResponse = MHD_create_response_from_buffer(strlen(my404PageHTML), my404PageHTML, MHD_RESPMEM_MUST_COPY); myResponseStatusCode = MHD_HTTP_NOT_FOUND; }; } else { myResponse = MHD_create_response_from_buffer(strlen(my400PageHTML), my400PageHTML, MHD_RESPMEM_MUST_COPY); myResponseStatusCode = MHD_HTTP_BAD_REQUEST; }; if ((myRequestFileMimetypePtr == NULL) && (myResponseStatusCode == MHD_HTTP_OK)) { char *myFileNamePtr = strrchr(myRequestFilePathPtr, '/'); if (myFileNamePtr != NULL) myFileNamePtr++; char *myExtensionPtr = strrchr(myFileNamePtr, '.'); if (myExtensionPtr != NULL) myExtensionPtr++; myRequestFileMimetypePtr = get_mimetype_from_extension(myExtensionPtr); }; if ((myRequestFilePathPtr != NULL) && (myResponseStatusCode == MHD_HTTP_OK)) { log_timedwriteln(LOG_TYPE_ACCESS, "(%s) Serving file : %s (MimeType: %s)", myClientIPAddress, myRequestFilePathPtr, myRequestFileMimetypePtr); }; if (myResponse != NULL) { if (myRequestFileMimetypePtr != NULL) { MHD_add_response_header(myResponse, "Content-Type", myRequestFileMimetypePtr); }; myResponseQueueResult = MHD_queue_response(theConnection, myResponseStatusCode, myResponse); MHD_destroy_response(myResponse); }; strptr_free(myRequestHostDirPathPtr); strptr_free(myRequestFilePathPtr); strptr_free(myRequestFileMimetypePtr); return myResponseQueueResult; } //---------------------------------------------------------------------------------------- // AppInit void AppInit (void) { char myAppFilePath[256]; int myAppFilePathLen; char *myLogsDirPathPtr = NULL; if ((myAppFilePathLen = readlink("/proc/self/exe", myAppFilePath, 255)) < 0) { syslog(LOG_INFO, "WebServer> readlink failed"); exit(1); }; myAppFilePath[myAppFilePathLen] = '\0'; gAppDirPathPtr = strptr_new(dirname(myAppFilePath)); syslog(LOG_INFO, "WebServer> App dir path: %s", gAppDirPathPtr); myLogsDirPathPtr = strptr_new(gAppDirPathPtr); myLogsDirPathPtr = strptr_cat(myLogsDirPathPtr, "/logs"); syslog(LOG_INFO, "WebServer> Logs dir path: %s", myLogsDirPathPtr); log_init(myLogsDirPathPtr); myLogsDirPathPtr = strptr_free(myLogsDirPathPtr); gHostsDirPathPtr = strptr_new(gAppDirPathPtr); gHostsDirPathPtr = strptr_cat(gHostsDirPathPtr, "/hosts"); syslog(LOG_INFO, "WebServer> Hosts dir path: %s", gHostsDirPathPtr); } //---------------------------------------------------------------------------------------- // AppExit void AppExit (void) { strptr_free(gAppDirPathPtr); strptr_free(gHostsDirPathPtr); strptr_free(gTLSPrivateKeyPtr); strptr_free(gTLSPublicKeyPtr); log_exit(); } //---------------------------------------------------------------------------------------- // Main int main (void) { syslog(LOG_INFO, "WebServer> Starting"); AppInit(); signal(SIGTERM, handle_sigterm); signal(SIGINT, handle_sigint); signal(SIGSEGV, handle_sigsegv); gMHDDaemonHTTP = MHD_start_daemon(MHD_USE_INTERNAL_POLLING_THREAD, PORT_HTTP, NULL, NULL, &answer_to_connection, NULL, MHD_OPTION_END); gMHDRunning = (gMHDDaemonHTTP != NULL) && (gMHDDaemonHTTPS != NULL); syslog(LOG_INFO, gMHDRunning ? "WebServer> Daemons started" : "WebServer> Daemons NOT started"); while (gMHDRunning) { sleep(1); }; if (gMHDDaemonHTTP != NULL) { syslog(LOG_INFO, "WebServer> MHD_stop_daemon(gMHDDaemonHTTP)..."); MHD_stop_daemon(gMHDDaemonHTTP); gMHDDaemonHTTP = NULL; }; syslog(LOG_INFO, "WebServer> Now Quit"); AppExit(); return 0; } //---------------------------------------------------------------------------------------- // EOFPour les besoins du test, créez un dossier "/root/webserver" dans lequel vous enregistrerez le code précédent sous "webserver.c". On va créer en même temps deux sous-dossiers nommés "logs" et "settings".
Si vous n'avez pas encore installé les outils de compilation, lancez:
apt-get install build-essentialInstallons maintenant libmicrohttpd:
apt-get install libmicrohttpd-devCet exemple utilise deux petites librairies "log" et "strptr" dont les sources sont à la fin de cet article, enregistrez-les au même niveau que "webserver.c".
Auquel on va ajouter le Makefile ci-dessous:
all : webserver strptr.o : strptr.c gcc -g -c strptr.c -o strptr.o log.o : log.c gcc -g -c log.c -o log.o webserver.o : webserver.c gcc -g -c webserver.c -o webserver.o webserver : webserver.o strptr.o log.o g++ -g -o webserver webserver.o strptr.o log.o -lmicrohttpd -lpthread -ldl clean : rm -f webserver rm -f *.o install: systemctl stop webserver mkdir -p /opt/webserver mkdir -p /opt/webserver/hosts mkdir -p /opt/webserver/settings mkdir -p /opt/webserver/logs cp -f webserver /opt/webserver/webserver cp -f webserver.service /etc/systemd/system/webserver.service systemctl daemon-reload ls -lah /opt/webserver/ systemctl start webserver systemctl status webserverLa section install mérite quelques explications, elle installe (comme son nom l'indique) le programme dans la section /opt/ du serveur, avec un fichier de configuration systemd "webserver.service" défini comme suit:
[Unit] Description=WebServer Daemon [Service] Type=simple ExecStart=/opt/fappserver/webserver [Install] WantedBy=multi-user.target