1 // Copyright 2018 Espressif Systems (Shanghai) PTE LTD
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
16 #include <sys/socket.h>
17 #include <sys/param.h>
23 #include <http_server.h>
24 #include "httpd_priv.h"
25 #include "ctrl_sock.h"
27 static const char *TAG = "httpd";
29 static esp_err_t httpd_accept_conn(struct httpd_data *hd, int listen_fd)
31 /* If no space is available for new session, close the least recently used one */
32 if (hd->config.lru_purge_enable == true) {
33 if (!httpd_is_sess_available(hd)) {
34 /* Queue asynchronous closure of the least recently used session */
35 return httpd_sess_close_lru(hd);
36 /* Returning from this allowes the main server thread to process
37 * the queued asynchronous control message for closing LRU session.
38 * Since connection request hasn't been addressed yet using accept()
39 * therefore httpd_accept_conn() will be called again, but this time
40 * with space available for one session
45 struct sockaddr_in addr_from;
46 socklen_t addr_from_len = sizeof(addr_from);
47 int new_fd = accept(listen_fd, (struct sockaddr *)&addr_from, &addr_from_len);
49 ESP_LOGW(TAG, LOG_FMT("error in accept (%d)"), errno);
52 ESP_LOGD(TAG, LOG_FMT("newfd = %d"), new_fd);
55 /* Set recv timeout of this fd as per config */
56 tv.tv_sec = hd->config.recv_wait_timeout;
58 setsockopt(new_fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));
60 /* Set send timeout of this fd as per config */
61 tv.tv_sec = hd->config.send_wait_timeout;
63 setsockopt(new_fd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv));
65 if (httpd_sess_new(hd, new_fd)) {
66 ESP_LOGW(TAG, LOG_FMT("no slots left for launching new session"));
70 ESP_LOGD(TAG, LOG_FMT("complete"));
74 struct httpd_ctrl_data {
79 httpd_work_fn_t hc_work;
83 esp_err_t httpd_queue_work(httpd_handle_t handle, httpd_work_fn_t work, void *arg)
85 if (handle == NULL || work == NULL) {
86 return ESP_ERR_INVALID_ARG;
89 struct httpd_data *hd = (struct httpd_data *) handle;
90 struct httpd_ctrl_data msg = {
91 .hc_msg = HTTPD_CTRL_WORK,
96 int ret = cs_send_to_ctrl_sock(hd->msg_fd, hd->config.ctrl_port, &msg, sizeof(msg));
98 ESP_LOGW(TAG, LOG_FMT("failed to queue work"));
105 static void httpd_close_all_sessions(struct httpd_data *hd)
108 while ((fd = httpd_sess_iterate(hd, fd)) != -1) {
109 ESP_LOGD(TAG, LOG_FMT("cleaning up socket %d"), fd);
110 httpd_sess_delete(hd, fd);
115 static void httpd_process_ctrl_msg(struct httpd_data *hd)
117 struct httpd_ctrl_data msg;
118 int ret = recv(hd->ctrl_fd, &msg, sizeof(msg), 0);
120 ESP_LOGW(TAG, LOG_FMT("error in recv (%d)"), errno);
123 if (ret != sizeof(msg)) {
124 ESP_LOGW(TAG, LOG_FMT("incomplete msg"));
128 switch (msg.hc_msg) {
129 case HTTPD_CTRL_WORK:
131 ESP_LOGD(TAG, LOG_FMT("work"));
132 (*msg.hc_work)(msg.hc_work_arg);
135 case HTTPD_CTRL_SHUTDOWN:
136 ESP_LOGD(TAG, LOG_FMT("shutdown"));
137 hd->hd_td.status = THREAD_STOPPING;
144 /* Manage in-coming connection or data requests */
145 static esp_err_t httpd_server(struct httpd_data *hd)
149 FD_SET(hd->listen_fd, &read_set);
150 FD_SET(hd->ctrl_fd, &read_set);
153 httpd_sess_set_descriptors(hd, &read_set, &tmp_max_fd);
154 int maxfd = MAX(hd->listen_fd, tmp_max_fd);
156 maxfd = MAX(hd->ctrl_fd, tmp_max_fd);
158 ESP_LOGD(TAG, LOG_FMT("doing select maxfd+1 = %d"), maxfd + 1);
159 int active_cnt = select(maxfd + 1, &read_set, NULL, NULL, NULL);
160 if (active_cnt < 0) {
161 ESP_LOGE(TAG, LOG_FMT("error in select (%d)"), errno);
162 /* Assert, as it's not possible to recover from this point onwards,
163 * and there is no way to notify the main thread that server handle
164 * has become invalid */
169 /* Case0: Do we have a control message? */
170 if (FD_ISSET(hd->ctrl_fd, &read_set)) {
171 ESP_LOGD(TAG, LOG_FMT("processing ctrl message"));
172 httpd_process_ctrl_msg(hd);
173 if (hd->hd_td.status == THREAD_STOPPING) {
174 ESP_LOGD(TAG, LOG_FMT("stopping thread"));
179 /* Case1: Do we have any activity on the current data
182 while ((fd = httpd_sess_iterate(hd, fd)) != -1) {
183 if (FD_ISSET(fd, &read_set) || (httpd_sess_pending(hd, fd))) {
184 ESP_LOGD(TAG, LOG_FMT("processing socket %d"), fd);
185 if (httpd_sess_process(hd, fd) != ESP_OK) {
186 ESP_LOGD(TAG, LOG_FMT("closing socket %d"), fd);
188 /* Delete session and update fd to that
189 * preceding the one being deleted */
190 fd = httpd_sess_delete(hd, fd);
195 /* Case2: Do we have any incoming connection requests to
197 if (FD_ISSET(hd->listen_fd, &read_set)) {
198 ESP_LOGD(TAG, LOG_FMT("processing listen socket %d"), hd->listen_fd);
199 if (httpd_accept_conn(hd, hd->listen_fd) != ESP_OK) {
200 ESP_LOGW(TAG, LOG_FMT("error accepting new connection"));
206 /* The main HTTPD thread */
207 static void httpd_thread(void *arg)
210 struct httpd_data *hd = (struct httpd_data *) arg;
211 hd->hd_td.status = THREAD_RUNNING;
213 ESP_LOGD(TAG, LOG_FMT("web server started"));
215 ret = httpd_server(hd);
221 ESP_LOGD(TAG, LOG_FMT("web server exiting"));
223 cs_free_ctrl_sock(hd->ctrl_fd);
224 httpd_close_all_sessions(hd);
225 close(hd->listen_fd);
226 hd->hd_td.status = THREAD_STOPPED;
227 httpd_os_thread_delete();
230 static esp_err_t httpd_server_init(struct httpd_data *hd)
232 int fd = socket(PF_INET6, SOCK_STREAM, 0);
234 ESP_LOGE(TAG, LOG_FMT("error in socket (%d)"), errno);
238 struct in6_addr inaddr_any = IN6ADDR_ANY_INIT;
239 struct sockaddr_in6 serv_addr = {
240 .sin6_family = PF_INET6,
241 .sin6_addr = inaddr_any,
242 .sin6_port = htons(hd->config.server_port)
245 int ret = bind(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
247 ESP_LOGE(TAG, LOG_FMT("error in bind (%d)"), errno);
252 ret = listen(fd, hd->config.backlog_conn);
254 ESP_LOGE(TAG, LOG_FMT("error in listen (%d)"), errno);
259 int ctrl_fd = cs_create_ctrl_sock(hd->config.ctrl_port);
261 ESP_LOGE(TAG, LOG_FMT("error in creating ctrl socket (%d)"), errno);
266 int msg_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
268 ESP_LOGE(TAG, LOG_FMT("error in creating msg socket (%d)"), errno);
275 hd->ctrl_fd = ctrl_fd;
280 static struct httpd_data *httpd_create(const httpd_config_t *config)
282 /* Allocate memory for httpd instance data */
283 struct httpd_data *hd = calloc(1, sizeof(struct httpd_data));
285 hd->hd_calls = calloc(config->max_uri_handlers, sizeof(httpd_uri_t *));
286 if (hd->hd_calls == NULL) {
290 hd->hd_sd = calloc(config->max_open_sockets, sizeof(struct sock_db));
291 if (hd->hd_sd == NULL) {
296 struct httpd_req_aux *ra = &hd->hd_req_aux;
297 ra->resp_hdrs = calloc(config->max_resp_headers, sizeof(struct resp_hdr));
298 if (ra->resp_hdrs == NULL) {
304 /* Save the configuration for this instance */
305 hd->config = *config;
307 ESP_LOGE(TAG, "mem alloc failed");
312 static void httpd_delete(struct httpd_data *hd)
314 struct httpd_req_aux *ra = &hd->hd_req_aux;
315 /* Free memory of httpd instance data */
319 /* Free registered URI handlers */
320 httpd_unregister_all_uri_handlers(hd);
325 esp_err_t httpd_start(httpd_handle_t *handle, const httpd_config_t *config)
327 if (handle == NULL || config == NULL) {
328 return ESP_ERR_INVALID_ARG;
331 struct httpd_data *hd = httpd_create(config);
333 /* Failed to allocate memory */
334 return ESP_ERR_HTTPD_ALLOC_MEM;
337 if (httpd_server_init(hd) != ESP_OK) {
343 if (httpd_os_thread_create(&hd->hd_td.handle, "httpd",
344 hd->config.stack_size,
345 hd->config.task_priority,
346 httpd_thread, hd) != ESP_OK) {
347 /* Failed to launch task */
349 return ESP_ERR_HTTPD_TASK;
352 *handle = (httpd_handle_t *)hd;
356 esp_err_t httpd_stop(httpd_handle_t handle)
358 struct httpd_data *hd = (struct httpd_data *) handle;
360 return ESP_ERR_INVALID_ARG;
363 struct httpd_ctrl_data msg;
364 memset(&msg, 0, sizeof(msg));
365 msg.hc_msg = HTTPD_CTRL_SHUTDOWN;
366 cs_send_to_ctrl_sock(hd->msg_fd, hd->config.ctrl_port, &msg, sizeof(msg));
368 ESP_LOGD(TAG, LOG_FMT("sent control msg to stop server"));
369 while (hd->hd_td.status != THREAD_STOPPED) {
370 httpd_os_thread_sleep(1000);
373 ESP_LOGD(TAG, LOG_FMT("server stopped"));