/ components / esp_http_server / src / httpd_main.c
httpd_main.c
  1  // Copyright 2018 Espressif Systems (Shanghai) PTE LTD
  2  //
  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
  6  //
  7  //     http://www.apache.org/licenses/LICENSE-2.0
  8  //
  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.
 14  
 15  #include <string.h>
 16  #include <sys/socket.h>
 17  #include <sys/param.h>
 18  #include <errno.h>
 19  #include <esp_log.h>
 20  #include <esp_err.h>
 21  #include <assert.h>
 22  
 23  #include <esp_http_server.h>
 24  #include "esp_httpd_priv.h"
 25  #include "ctrl_sock.h"
 26  
 27  static const char *TAG = "httpd";
 28  
 29  static esp_err_t httpd_accept_conn(struct httpd_data *hd, int listen_fd)
 30  {
 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
 41               */
 42         }
 43      }
 44  
 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);
 48      if (new_fd < 0) {
 49          ESP_LOGW(TAG, LOG_FMT("error in accept (%d)"), errno);
 50          return ESP_FAIL;
 51      }
 52      ESP_LOGD(TAG, LOG_FMT("newfd = %d"), new_fd);
 53  
 54      struct timeval tv;
 55      /* Set recv timeout of this fd as per config */
 56      tv.tv_sec = hd->config.recv_wait_timeout;
 57      tv.tv_usec = 0;
 58      setsockopt(new_fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));
 59  
 60      /* Set send timeout of this fd as per config */
 61      tv.tv_sec = hd->config.send_wait_timeout;
 62      tv.tv_usec = 0;
 63      setsockopt(new_fd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv));
 64  
 65      if (ESP_OK != httpd_sess_new(hd, new_fd)) {
 66          ESP_LOGW(TAG, LOG_FMT("session creation failed"));
 67          close(new_fd);
 68          return ESP_FAIL;
 69      }
 70      httpd_sess_update_lru_counter(hd->hd_sd->handle, new_fd);
 71  
 72      ESP_LOGD(TAG, LOG_FMT("complete"));
 73      return ESP_OK;
 74  }
 75  
 76  struct httpd_ctrl_data {
 77      enum httpd_ctrl_msg {
 78          HTTPD_CTRL_SHUTDOWN,
 79          HTTPD_CTRL_WORK,
 80      } hc_msg;
 81      httpd_work_fn_t hc_work;
 82      void *hc_work_arg;
 83  };
 84  
 85  esp_err_t httpd_queue_work(httpd_handle_t handle, httpd_work_fn_t work, void *arg)
 86  {
 87      if (handle == NULL || work == NULL) {
 88          return ESP_ERR_INVALID_ARG;
 89      }
 90  
 91      struct httpd_data *hd = (struct httpd_data *) handle;
 92      struct httpd_ctrl_data msg = {
 93          .hc_msg = HTTPD_CTRL_WORK,
 94          .hc_work = work,
 95          .hc_work_arg = arg,
 96      };
 97  
 98      int ret = cs_send_to_ctrl_sock(hd->msg_fd, hd->config.ctrl_port, &msg, sizeof(msg));
 99      if (ret < 0) {
100          ESP_LOGW(TAG, LOG_FMT("failed to queue work"));
101          return ESP_FAIL;
102      }
103  
104      return ESP_OK;
105  }
106  
107  esp_err_t httpd_get_client_list(httpd_handle_t handle, size_t *fds, int *client_fds)
108  {
109      struct httpd_data *hd = (struct httpd_data *) handle;
110      if (hd == NULL || fds == NULL || *fds == 0 || client_fds == NULL || *fds < hd->config.max_open_sockets) {
111          return ESP_ERR_INVALID_ARG;
112      }
113      size_t max_fds = *fds;
114      *fds = 0;
115      for (int i = 0; i < hd->config.max_open_sockets; ++i) {
116          if (hd->hd_sd[i].fd != -1) {
117              if (*fds < max_fds) {
118                  client_fds[(*fds)++] = hd->hd_sd[i].fd;
119              } else {
120                  return ESP_ERR_INVALID_ARG;
121              }
122          }
123      }
124      return ESP_OK;
125  }
126  
127  void *httpd_get_global_user_ctx(httpd_handle_t handle)
128  {
129      return ((struct httpd_data *)handle)->config.global_user_ctx;
130  }
131  
132  void *httpd_get_global_transport_ctx(httpd_handle_t handle)
133  {
134      return ((struct httpd_data *)handle)->config.global_transport_ctx;
135  }
136  
137  static void httpd_close_all_sessions(struct httpd_data *hd)
138  {
139      int fd = -1;
140      while ((fd = httpd_sess_iterate(hd, fd)) != -1) {
141          ESP_LOGD(TAG, LOG_FMT("cleaning up socket %d"), fd);
142          httpd_sess_delete(hd, fd);
143          close(fd);
144      }
145  }
146  
147  static void httpd_process_ctrl_msg(struct httpd_data *hd)
148  {
149      struct httpd_ctrl_data msg;
150      int ret = recv(hd->ctrl_fd, &msg, sizeof(msg), 0);
151      if (ret <= 0) {
152          ESP_LOGW(TAG, LOG_FMT("error in recv (%d)"), errno);
153          return;
154      }
155      if (ret != sizeof(msg)) {
156          ESP_LOGW(TAG, LOG_FMT("incomplete msg"));
157          return;
158      }
159  
160      switch (msg.hc_msg) {
161      case HTTPD_CTRL_WORK:
162          if (msg.hc_work) {
163              ESP_LOGD(TAG, LOG_FMT("work"));
164              (*msg.hc_work)(msg.hc_work_arg);
165          }
166          break;
167      case HTTPD_CTRL_SHUTDOWN:
168          ESP_LOGD(TAG, LOG_FMT("shutdown"));
169          hd->hd_td.status = THREAD_STOPPING;
170          break;
171      default:
172          break;
173      }
174  }
175  
176  /* Manage in-coming connection or data requests */
177  static esp_err_t httpd_server(struct httpd_data *hd)
178  {
179      fd_set read_set;
180      FD_ZERO(&read_set);
181      if (hd->config.lru_purge_enable || httpd_is_sess_available(hd)) {
182          /* Only listen for new connections if server has capacity to
183           * handle more (or when LRU purge is enabled, in which case
184           * older connections will be closed) */
185          FD_SET(hd->listen_fd, &read_set);
186      }
187      FD_SET(hd->ctrl_fd, &read_set);
188  
189      int tmp_max_fd;
190      httpd_sess_set_descriptors(hd, &read_set, &tmp_max_fd);
191      int maxfd = MAX(hd->listen_fd, tmp_max_fd);
192      tmp_max_fd = maxfd;
193      maxfd = MAX(hd->ctrl_fd, tmp_max_fd);
194  
195      ESP_LOGD(TAG, LOG_FMT("doing select maxfd+1 = %d"), maxfd + 1);
196      int active_cnt = select(maxfd + 1, &read_set, NULL, NULL, NULL);
197      if (active_cnt < 0) {
198          ESP_LOGE(TAG, LOG_FMT("error in select (%d)"), errno);
199          httpd_sess_delete_invalid(hd);
200          return ESP_OK;
201      }
202  
203      /* Case0: Do we have a control message? */
204      if (FD_ISSET(hd->ctrl_fd, &read_set)) {
205          ESP_LOGD(TAG, LOG_FMT("processing ctrl message"));
206          httpd_process_ctrl_msg(hd);
207          if (hd->hd_td.status == THREAD_STOPPING) {
208              ESP_LOGD(TAG, LOG_FMT("stopping thread"));
209              return ESP_FAIL;
210          }
211      }
212  
213      /* Case1: Do we have any activity on the current data
214       * sessions? */
215      int fd = -1;
216      while ((fd = httpd_sess_iterate(hd, fd)) != -1) {
217          if (FD_ISSET(fd, &read_set) || (httpd_sess_pending(hd, fd))) {
218              ESP_LOGD(TAG, LOG_FMT("processing socket %d"), fd);
219              if (httpd_sess_process(hd, fd) != ESP_OK) {
220                  ESP_LOGD(TAG, LOG_FMT("closing socket %d"), fd);
221                  close(fd);
222                  /* Delete session and update fd to that
223                   * preceding the one being deleted */
224                  fd = httpd_sess_delete(hd, fd);
225              }
226          }
227      }
228  
229      /* Case2: Do we have any incoming connection requests to
230       * process? */
231      if (FD_ISSET(hd->listen_fd, &read_set)) {
232          ESP_LOGD(TAG, LOG_FMT("processing listen socket %d"), hd->listen_fd);
233          if (httpd_accept_conn(hd, hd->listen_fd) != ESP_OK) {
234              ESP_LOGW(TAG, LOG_FMT("error accepting new connection"));
235          }
236      }
237      return ESP_OK;
238  }
239  
240  /* The main HTTPD thread */
241  static void httpd_thread(void *arg)
242  {
243      int ret;
244      struct httpd_data *hd = (struct httpd_data *) arg;
245      hd->hd_td.status = THREAD_RUNNING;
246  
247      ESP_LOGD(TAG, LOG_FMT("web server started"));
248      while (1) {
249          ret = httpd_server(hd);
250          if (ret != ESP_OK) {
251              break;
252          }
253      }
254  
255      ESP_LOGD(TAG, LOG_FMT("web server exiting"));
256      close(hd->msg_fd);
257      cs_free_ctrl_sock(hd->ctrl_fd);
258      httpd_close_all_sessions(hd);
259      close(hd->listen_fd);
260      hd->hd_td.status = THREAD_STOPPED;
261      httpd_os_thread_delete();
262  }
263  
264  static esp_err_t httpd_server_init(struct httpd_data *hd)
265  {
266      int fd = socket(PF_INET6, SOCK_STREAM, 0);
267      if (fd < 0) {
268          ESP_LOGE(TAG, LOG_FMT("error in socket (%d)"), errno);
269          return ESP_FAIL;
270      }
271  
272      struct in6_addr inaddr_any = IN6ADDR_ANY_INIT;
273      struct sockaddr_in6 serv_addr = {
274          .sin6_family  = PF_INET6,
275          .sin6_addr    = inaddr_any,
276          .sin6_port    = htons(hd->config.server_port)
277      };
278  
279      /* Enable SO_REUSEADDR to allow binding to the same
280       * address and port when restarting the server */
281      int enable = 1;
282      if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) < 0) {
283          /* This will fail if CONFIG_LWIP_SO_REUSE is not enabled. But
284           * it does not affect the normal working of the HTTP Server */
285          ESP_LOGW(TAG, LOG_FMT("error enabling SO_REUSEADDR (%d)"), errno);
286      }
287  
288      int ret = bind(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
289      if (ret < 0) {
290          ESP_LOGE(TAG, LOG_FMT("error in bind (%d)"), errno);
291          close(fd);
292          return ESP_FAIL;
293      }
294  
295      ret = listen(fd, hd->config.backlog_conn);
296      if (ret < 0) {
297          ESP_LOGE(TAG, LOG_FMT("error in listen (%d)"), errno);
298          close(fd);
299          return ESP_FAIL;
300      }
301  
302      int ctrl_fd = cs_create_ctrl_sock(hd->config.ctrl_port);
303      if (ctrl_fd < 0) {
304          ESP_LOGE(TAG, LOG_FMT("error in creating ctrl socket (%d)"), errno);
305          close(fd);
306          return ESP_FAIL;
307      }
308  
309      int msg_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
310      if (msg_fd < 0) {
311          ESP_LOGE(TAG, LOG_FMT("error in creating msg socket (%d)"), errno);
312          close(fd);
313          close(ctrl_fd);
314          return ESP_FAIL;
315      }
316  
317      hd->listen_fd = fd;
318      hd->ctrl_fd = ctrl_fd;
319      hd->msg_fd  = msg_fd;
320      return ESP_OK;
321  }
322  
323  static struct httpd_data *httpd_create(const httpd_config_t *config)
324  {
325      /* Allocate memory for httpd instance data */
326      struct httpd_data *hd = calloc(1, sizeof(struct httpd_data));
327      if (!hd) {
328          ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP server instance"));
329          return NULL;
330      }
331      hd->hd_calls = calloc(config->max_uri_handlers, sizeof(httpd_uri_t *));
332      if (!hd->hd_calls) {
333          ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP URI handlers"));
334          free(hd);
335          return NULL;
336      }
337      hd->hd_sd = calloc(config->max_open_sockets, sizeof(struct sock_db));
338      if (!hd->hd_sd) {
339          ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP session data"));
340          free(hd->hd_calls);
341          free(hd);
342          return NULL;
343      }
344      struct httpd_req_aux *ra = &hd->hd_req_aux;
345      ra->resp_hdrs = calloc(config->max_resp_headers, sizeof(struct resp_hdr));
346      if (!ra->resp_hdrs) {
347          ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP response headers"));
348          free(hd->hd_sd);
349          free(hd->hd_calls);
350          free(hd);
351          return NULL;
352      }
353      hd->err_handler_fns = calloc(HTTPD_ERR_CODE_MAX, sizeof(httpd_err_handler_func_t));
354      if (!hd->err_handler_fns) {
355          ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP error handlers"));
356          free(ra->resp_hdrs);
357          free(hd->hd_sd);
358          free(hd->hd_calls);
359          free(hd);
360          return NULL;
361      }
362      /* Save the configuration for this instance */
363      hd->config = *config;
364      return hd;
365  }
366  
367  static void httpd_delete(struct httpd_data *hd)
368  {
369      struct httpd_req_aux *ra = &hd->hd_req_aux;
370      /* Free memory of httpd instance data */
371      free(hd->err_handler_fns);
372      free(ra->resp_hdrs);
373      free(hd->hd_sd);
374  
375      /* Free registered URI handlers */
376      httpd_unregister_all_uri_handlers(hd);
377      free(hd->hd_calls);
378      free(hd);
379  }
380  
381  esp_err_t httpd_start(httpd_handle_t *handle, const httpd_config_t *config)
382  {
383      if (handle == NULL || config == NULL) {
384          return ESP_ERR_INVALID_ARG;
385      }
386  
387      /* Sanity check about whether LWIP is configured for providing the
388       * maximum number of open sockets sufficient for the server. Though,
389       * this check doesn't guarantee that many sockets will actually be
390       * available at runtime as other processes may use up some sockets.
391       * Note that server also uses 3 sockets for its internal use :
392       *     1) listening for new TCP connections
393       *     2) for sending control messages over UDP
394       *     3) for receiving control messages over UDP
395       * So the total number of required sockets is max_open_sockets + 3
396       */
397      if (CONFIG_LWIP_MAX_SOCKETS < config->max_open_sockets + 3) {
398          ESP_LOGE(TAG, "Configuration option max_open_sockets is too large (max allowed %d)\n\t"
399                        "Either decrease this or configure LWIP_MAX_SOCKETS to a larger value",
400                        CONFIG_LWIP_MAX_SOCKETS - 3);
401          return ESP_ERR_INVALID_ARG;
402      }
403  
404      struct httpd_data *hd = httpd_create(config);
405      if (hd == NULL) {
406          /* Failed to allocate memory */
407          return ESP_ERR_HTTPD_ALLOC_MEM;
408      }
409  
410      if (httpd_server_init(hd) != ESP_OK) {
411          httpd_delete(hd);
412          return ESP_FAIL;
413      }
414  
415      httpd_sess_init(hd);
416      if (httpd_os_thread_create(&hd->hd_td.handle, "httpd",
417                                 hd->config.stack_size,
418                                 hd->config.task_priority,
419                                 httpd_thread, hd,
420                                 hd->config.core_id) != ESP_OK) {
421          /* Failed to launch task */
422          httpd_delete(hd);
423          return ESP_ERR_HTTPD_TASK;
424      }
425  
426      *handle = (httpd_handle_t *)hd;
427      return ESP_OK;
428  }
429  
430  esp_err_t httpd_stop(httpd_handle_t handle)
431  {
432      struct httpd_data *hd = (struct httpd_data *) handle;
433      if (hd == NULL) {
434          return ESP_ERR_INVALID_ARG;
435      }
436  
437      struct httpd_ctrl_data msg;
438      memset(&msg, 0, sizeof(msg));
439      msg.hc_msg = HTTPD_CTRL_SHUTDOWN;
440      cs_send_to_ctrl_sock(hd->msg_fd, hd->config.ctrl_port, &msg, sizeof(msg));
441  
442      ESP_LOGD(TAG, LOG_FMT("sent control msg to stop server"));
443      while (hd->hd_td.status != THREAD_STOPPED) {
444          httpd_os_thread_sleep(100);
445      }
446  
447      /* Release global user context, if not NULL */
448      if (hd->config.global_user_ctx) {
449          if (hd->config.global_user_ctx_free_fn) {
450              hd->config.global_user_ctx_free_fn(hd->config.global_user_ctx);
451          } else {
452              free(hd->config.global_user_ctx);
453          }
454          hd->config.global_user_ctx = NULL;
455      }
456  
457      /* Release global transport context, if not NULL */
458      if (hd->config.global_transport_ctx) {
459          if (hd->config.global_transport_ctx_free_fn) {
460              hd->config.global_transport_ctx_free_fn(hd->config.global_transport_ctx);
461          } else {
462              free(hd->config.global_transport_ctx);
463          }
464          hd->config.global_transport_ctx = NULL;
465      }
466  
467      ESP_LOGD(TAG, LOG_FMT("server stopped"));
468      httpd_delete(hd);
469      return ESP_OK;
470  }