/* ESP8266 web server - platform-dependent routines, FreeRTOS version Thanks to my collague at Espressif for writing the foundations of this code. */ #include "httpd.h" #include "httpd-platform.h" #include #include #include #include #include #include #include #include #include "httpd-logging.h" #include "httpd-types.h" struct HttpdConnType { int fd; bool needWriteDoneNotif; bool needsClose; uint16_t port; httpd_ipaddr_t ip; }; static HttpdConnType s_rconn[HTTPD_MAX_CONNECTIONS]; static uint8_t s_recv_buf[HTTPD_RECV_BUF_LEN]; static volatile bool s_shutdown_requested = false; static int fd_is_valid(int fd) { return fcntl(fd, F_GETFD) != -1 || errno != EBADF; } int httpdConnSendData(ConnTypePtr conn, const uint8_t *buff, size_t len) { conn->needWriteDoneNotif = 1; if (!fd_is_valid(conn->fd)) { return 0; } return write(conn->fd, buff, len) == (int) len; } void httpdConnDisconnect(ConnTypePtr conn) { conn->needsClose = 1; conn->needWriteDoneNotif = 1; //because the real close is done in the writable select code } void httpdServerTask(void *pvParameters) { int32_t listenfd; int32_t len; int32_t ret; //char *precvbuf; fd_set readset, writeset; struct sockaddr_in server_addr; struct sockaddr_in remote_addr; uint16_t httpPort; s_shutdown_requested = false; const struct httpd_init_options *options = pvParameters; if (options == NULL) { httpPort = 80; } else { httpPort = options->port; } for (int i = 0; i < HTTPD_MAX_CONNECTIONS; i++) { s_rconn[i].fd = -1; } /* Construct local address structure */ memset(&server_addr, 0, sizeof(server_addr)); /* Zero out structure */ server_addr.sin_family = AF_INET; /* Internet address family */ server_addr.sin_addr.s_addr = INADDR_ANY; /* Any incoming interface */ server_addr.sin_port = htons(httpPort); /* Local port */ /* Create socket for incoming connections */ do { listenfd = socket(AF_INET, SOCK_STREAM, 0); if (listenfd == -1) { error("httpdServerTask: failed to create sock!"); httpdPlatDelayMs(1000); } } while (listenfd == -1); /* * Allow taking over an old socket after the server was killed or crashed. * https://stackoverflow.com/questions/5592747/bind-error-while-recreating-socket */ const int yes = 1; if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1) { perror("setsockopt"); } /* Bind to the local port */ do { ret = bind(listenfd, (struct sockaddr *) &server_addr, sizeof(server_addr)); if (ret != 0) { error("httpdServerTask: failed to bind!"); httpdPlatDelayMs(1000); } } while (ret != 0); do { /* Listen to the local connection */ ret = listen(listenfd, HTTPD_MAX_CONNECTIONS); if (ret != 0) { error("httpdServerTask: failed to listen!"); httpdPlatDelayMs(1000); } } while (ret != 0); info("httpd: listening on http://0.0.0.0:%d/", httpPort); while (1) { if (s_shutdown_requested) { info("httpd: Shutting down"); break; } //dbg("httpd: loop running"); // clear fdset, and set the select function wait time int socketsFull = 1; int maxfdp = 0; FD_ZERO(&readset); FD_ZERO(&writeset); // shutdown flag polling timeout struct timeval timeout; timeout.tv_sec = 1; timeout.tv_usec = 0; for (int i = 0; i < HTTPD_MAX_CONNECTIONS; i++) { if (s_rconn[i].fd != -1) { FD_SET(s_rconn[i].fd, &readset); if (s_rconn[i].needWriteDoneNotif) { FD_SET(s_rconn[i].fd, &writeset); } if (s_rconn[i].fd > maxfdp) { maxfdp = s_rconn[i].fd; } } else { socketsFull = 0; } } if (!socketsFull) { FD_SET(listenfd, &readset); if (listenfd > maxfdp) { maxfdp = listenfd; } } //polling all exist client handle,wait until readable/writable ret = select(maxfdp + 1, &readset, &writeset, NULL, &timeout); if (ret > 0) { //See if we need to accept a new connection if (FD_ISSET(listenfd, &readset)) { len = sizeof(struct sockaddr_in); const int remotefd = accept(listenfd, (struct sockaddr *) &remote_addr, (socklen_t *) &len); if (remotefd < 0) { warn("httpdServerTask: Huh? Accept failed."); continue; } // Find a free slot int socknum; for (socknum = 0; socknum < HTTPD_MAX_CONNECTIONS; socknum++) { if (s_rconn[socknum].fd == -1) { break; } } if (socknum >= HTTPD_MAX_CONNECTIONS) { warn("httpdServerTask: Huh? Got accept with all slots full."); continue; } const int keepAlive = 1; //enable keepalive const int keepIdle = 60; //60s const int keepInterval = 5; //5s const int keepCount = 3; //retry times setsockopt(remotefd, SOL_SOCKET, SO_KEEPALIVE, (void *) &keepAlive, sizeof(keepAlive)); setsockopt(remotefd, IPPROTO_TCP, TCP_KEEPIDLE, (void *) &keepIdle, sizeof(keepIdle)); setsockopt(remotefd, IPPROTO_TCP, TCP_KEEPINTVL, (void *) &keepInterval, sizeof(keepInterval)); setsockopt(remotefd, IPPROTO_TCP, TCP_KEEPCNT, (void *) &keepCount, sizeof(keepCount)); s_rconn[socknum].fd = remotefd; s_rconn[socknum].needWriteDoneNotif = 0; s_rconn[socknum].needsClose = 0; struct sockaddr name; len = sizeof(name); getpeername(remotefd, &name, (socklen_t *) &len); struct sockaddr_in *piname = (struct sockaddr_in *) &name; s_rconn[socknum].port = piname->sin_port; memcpy(&s_rconn[socknum].ip, &piname->sin_addr.s_addr, sizeof(s_rconn[socknum].ip)); httpdConnectCb(&s_rconn[socknum], s_rconn[socknum].ip, s_rconn[socknum].port); } //See if anything happened on the existing connections. for (int i = 0; i < HTTPD_MAX_CONNECTIONS; i++) { //Skip empty slots if (s_rconn[i].fd == -1) { continue; } //Check for write availability first: the read routines may write needWriteDoneNotif while //the select didn't check for that. if (s_rconn[i].needWriteDoneNotif && FD_ISSET(s_rconn[i].fd, &writeset)) { s_rconn[i].needWriteDoneNotif = 0; //Do this first, httpdSentCb may write something making this 1 again. if (s_rconn[i].needsClose) { //Do callback and close fd. httpdDisconCb(&s_rconn[i], s_rconn[i].ip, s_rconn[i].port); close(s_rconn[i].fd); s_rconn[i].fd = -1; } else { httpdSentCb(&s_rconn[i], s_rconn[i].ip, s_rconn[i].port); } } if (FD_ISSET(s_rconn[i].fd, &readset)) { ret = (int) recv(s_rconn[i].fd, s_recv_buf, HTTPD_RECV_BUF_LEN, 0); if (ret > 0) { //Data received. Pass to httpd. httpdRecvCb(&s_rconn[i], s_rconn[i].ip, s_rconn[i].port, s_recv_buf, (size_t) ret); } else { //recv error,connection close httpdDisconCb(&s_rconn[i], s_rconn[i].ip, s_rconn[i].port); httpdConnRelease(&s_rconn[i]); } } } } } httpdInternalCloseAllSockets(); /*release listen socket*/ close(listenfd); httpdPlatTaskEnd(); } void httpdConnRelease(ConnTypePtr conn) { if (conn && conn->fd >= 0) { close(conn->fd); conn->fd = -1; } // Don't free it - it's a pointer into the static struct! } void httpdShutdown(httpd_thread_handle_t *handle) { s_shutdown_requested = true; httpdJoin(handle); }