You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
263 lines
8.5 KiB
263 lines
8.5 KiB
/*
|
|
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 <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <unistd.h>
|
|
#include <netinet/tcp.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#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);
|
|
}
|
|
|