]> granicus.if.org Git - esp-idf/blob - components/http_server/src/httpd_main.c
Http Server : Add a simple light weight HTTP Server Component.
[esp-idf] / components / http_server / src / 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 <http_server.h>
24 #include "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 (httpd_sess_new(hd, new_fd)) {
66         ESP_LOGW(TAG, LOG_FMT("no slots left for launching new session"));
67         close(new_fd);
68         return ESP_FAIL;
69     }
70     ESP_LOGD(TAG, LOG_FMT("complete"));
71     return ESP_OK;
72 }
73
74 struct httpd_ctrl_data {
75     enum httpd_ctrl_msg {
76         HTTPD_CTRL_SHUTDOWN,
77         HTTPD_CTRL_WORK,
78     } hc_msg;
79     httpd_work_fn_t hc_work;
80     void *hc_work_arg;
81 };
82
83 esp_err_t httpd_queue_work(httpd_handle_t handle, httpd_work_fn_t work, void *arg)
84 {
85     if (handle == NULL || work == NULL) {
86         return ESP_ERR_INVALID_ARG;
87     }
88
89     struct httpd_data *hd = (struct httpd_data *) handle;
90     struct httpd_ctrl_data msg = {
91         .hc_msg = HTTPD_CTRL_WORK,
92         .hc_work = work,
93         .hc_work_arg = arg,
94     };
95
96     int ret = cs_send_to_ctrl_sock(hd->msg_fd, hd->config.ctrl_port, &msg, sizeof(msg));
97     if (ret < 0) {
98         ESP_LOGW(TAG, LOG_FMT("failed to queue work"));
99         return ESP_FAIL;
100     }
101
102     return ESP_OK;
103 }
104
105 static void httpd_close_all_sessions(struct httpd_data *hd)
106 {
107     int fd = -1;
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);
111         close(fd);
112     }
113 }
114
115 static void httpd_process_ctrl_msg(struct httpd_data *hd)
116 {
117     struct httpd_ctrl_data msg;
118     int ret = recv(hd->ctrl_fd, &msg, sizeof(msg), 0);
119     if (ret <= 0) {
120         ESP_LOGW(TAG, LOG_FMT("error in recv (%d)"), errno);
121         return;
122     }
123     if (ret != sizeof(msg)) {
124         ESP_LOGW(TAG, LOG_FMT("incomplete msg"));
125         return;
126     }
127
128     switch (msg.hc_msg) {
129     case HTTPD_CTRL_WORK:
130         if (msg.hc_work) {
131             ESP_LOGD(TAG, LOG_FMT("work"));
132             (*msg.hc_work)(msg.hc_work_arg);
133         }
134         break;
135     case HTTPD_CTRL_SHUTDOWN:
136         ESP_LOGD(TAG, LOG_FMT("shutdown"));
137         hd->hd_td.status = THREAD_STOPPING;
138         break;
139     default:
140         break;
141     }
142 }
143
144 /* Manage in-coming connection or data requests */
145 static esp_err_t httpd_server(struct httpd_data *hd)
146 {
147     fd_set read_set;
148     FD_ZERO(&read_set);
149     FD_SET(hd->listen_fd, &read_set);
150     FD_SET(hd->ctrl_fd, &read_set);
151
152     int tmp_max_fd;
153     httpd_sess_set_descriptors(hd, &read_set, &tmp_max_fd);
154     int maxfd = MAX(hd->listen_fd, tmp_max_fd);
155     tmp_max_fd = maxfd;
156     maxfd = MAX(hd->ctrl_fd, tmp_max_fd);
157
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 */
165         assert(false);
166         return ESP_FAIL;
167     }
168
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"));
175             return ESP_FAIL;
176         }
177     }
178
179     /* Case1: Do we have any activity on the current data
180      * sessions? */
181     int fd = -1;
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);
187                 close(fd);
188                 /* Delete session and update fd to that
189                  * preceding the one being deleted */
190                 fd = httpd_sess_delete(hd, fd);
191             }
192         }
193     }
194
195     /* Case2: Do we have any incoming connection requests to
196      * process? */
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"));
201         }
202     }
203     return ESP_OK;
204 }
205
206 /* The main HTTPD thread */
207 static void httpd_thread(void *arg)
208 {
209     int ret;
210     struct httpd_data *hd = (struct httpd_data *) arg;
211     hd->hd_td.status = THREAD_RUNNING;
212
213     ESP_LOGD(TAG, LOG_FMT("web server started"));
214     while (1) {
215         ret = httpd_server(hd);
216         if (ret != ESP_OK) {
217             break;
218         }
219     }
220
221     ESP_LOGD(TAG, LOG_FMT("web server exiting"));
222     close(hd->msg_fd);
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();
228 }
229
230 static esp_err_t httpd_server_init(struct httpd_data *hd)
231 {
232     int fd = socket(PF_INET6, SOCK_STREAM, 0);
233     if (fd < 0) {
234         ESP_LOGE(TAG, LOG_FMT("error in socket (%d)"), errno);
235         return ESP_FAIL;
236     }
237
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)
243     };
244
245     int ret = bind(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
246     if (ret < 0) {
247         ESP_LOGE(TAG, LOG_FMT("error in bind (%d)"), errno);
248         close(fd);
249         return ESP_FAIL;
250     }
251
252     ret = listen(fd, hd->config.backlog_conn);
253     if (ret < 0) {
254         ESP_LOGE(TAG, LOG_FMT("error in listen (%d)"), errno);
255         close(fd);
256         return ESP_FAIL;
257     }
258
259     int ctrl_fd = cs_create_ctrl_sock(hd->config.ctrl_port);
260     if (ctrl_fd < 0) {
261         ESP_LOGE(TAG, LOG_FMT("error in creating ctrl socket (%d)"), errno);
262         close(fd);
263         return ESP_FAIL;
264     }
265
266     int msg_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
267     if (msg_fd < 0) {
268         ESP_LOGE(TAG, LOG_FMT("error in creating msg socket (%d)"), errno);
269         close(fd);
270         close(ctrl_fd);
271         return ESP_FAIL;
272     }
273
274     hd->listen_fd = fd;
275     hd->ctrl_fd = ctrl_fd;
276     hd->msg_fd  = msg_fd;
277     return ESP_OK;
278 }
279
280 static struct httpd_data *httpd_create(const httpd_config_t *config)
281 {
282     /* Allocate memory for httpd instance data */
283     struct httpd_data *hd = calloc(1, sizeof(struct httpd_data));
284     if (hd != NULL) {
285         hd->hd_calls = calloc(config->max_uri_handlers, sizeof(httpd_uri_t *));
286         if (hd->hd_calls == NULL) {
287             free(hd);
288             return NULL;
289         }
290         hd->hd_sd = calloc(config->max_open_sockets, sizeof(struct sock_db));
291         if (hd->hd_sd == NULL) {
292             free(hd->hd_calls);
293             free(hd);
294             return NULL;
295         }
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) {
299             free(hd->hd_sd);
300             free(hd->hd_calls);
301             free(hd);
302             return NULL;
303         }
304         /* Save the configuration for this instance */
305         hd->config = *config;
306     } else {
307         ESP_LOGE(TAG, "mem alloc failed");
308     }
309     return hd;
310 }
311
312 static void httpd_delete(struct httpd_data *hd)
313 {
314     struct httpd_req_aux *ra = &hd->hd_req_aux;
315     /* Free memory of httpd instance data */
316     free(ra->resp_hdrs);
317     free(hd->hd_sd);
318
319     /* Free registered URI handlers */
320     httpd_unregister_all_uri_handlers(hd);
321     free(hd->hd_calls);
322     free(hd);
323 }
324
325 esp_err_t httpd_start(httpd_handle_t *handle, const httpd_config_t *config)
326 {
327     if (handle == NULL || config == NULL) {
328         return ESP_ERR_INVALID_ARG;
329     }
330
331     struct httpd_data *hd = httpd_create(config);
332     if (hd == NULL) {
333         /* Failed to allocate memory */
334         return ESP_ERR_HTTPD_ALLOC_MEM;
335     }
336
337     if (httpd_server_init(hd) != ESP_OK) {
338         httpd_delete(hd);
339         return ESP_FAIL;
340     }
341
342     httpd_sess_init(hd);
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 */
348         httpd_delete(hd);
349         return ESP_ERR_HTTPD_TASK;
350     }
351
352     *handle = (httpd_handle_t *)hd;
353     return ESP_OK;
354 }
355
356 esp_err_t httpd_stop(httpd_handle_t handle)
357 {
358     struct httpd_data *hd = (struct httpd_data *) handle;
359     if (hd == NULL) {
360         return ESP_ERR_INVALID_ARG;
361     }
362
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));
367
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);
371     }
372
373     ESP_LOGD(TAG, LOG_FMT("server stopped"));
374     httpd_delete(hd);
375     return ESP_OK;
376 }