2 * Copyright (c) 2017-2022 Cisco and/or its affiliates.
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 <http_static/http_static.h>
18 #include <sys/types.h>
22 /** @file static_server.c
23 * Static http server, sufficient to serve .html / .css / .js content.
25 /*? %%clicmd:group_label Static HTTP Server %% ?*/
27 #define HSS_FIFO_THRESH (16 << 10)
31 static hss_session_t *
32 hss_session_alloc (u32 thread_index)
34 hss_main_t *hsm = &hss_main;
37 pool_get_zero (hsm->sessions[thread_index], hs);
38 hs->session_index = hs - hsm->sessions[thread_index];
39 hs->thread_index = thread_index;
40 hs->cache_pool_index = ~0;
44 static hss_session_t *
45 hss_session_get (u32 thread_index, u32 hs_index)
47 hss_main_t *hsm = &hss_main;
48 if (pool_is_free_index (hsm->sessions[thread_index], hs_index))
50 return pool_elt_at_index (hsm->sessions[thread_index], hs_index);
54 hss_session_free (hss_session_t *hs)
56 hss_main_t *hsm = &hss_main;
58 pool_put (hsm->sessions[hs->thread_index], hs);
62 u32 save_thread_index;
63 save_thread_index = hs->thread_index;
64 /* Poison the entry, preserve timer state and thread index */
65 memset (hs, 0xfa, sizeof (*hs));
66 hs->thread_index = save_thread_index;
70 /** \brief Disconnect a session
73 hss_session_disconnect_transport (hss_session_t *hs)
75 vnet_disconnect_args_t _a = { 0 }, *a = &_a;
76 a->handle = hs->vpp_session_handle;
77 a->app_index = hss_main.app_index;
78 vnet_disconnect_session (a);
82 start_send_data (hss_session_t *hs, http_status_code_t status)
88 ts = session_get (hs->vpp_session_index, hs->thread_index);
90 msg.type = HTTP_MSG_REPLY;
92 msg.content_type = HTTP_CONTENT_TEXT_HTML;
93 msg.data.len = hs->data_len;
95 if (hs->data_len > hss_main.use_ptr_thresh)
97 msg.data.type = HTTP_MSG_DATA_PTR;
98 rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (msg), (u8 *) &msg);
99 ASSERT (rv == sizeof (msg));
101 uword data = pointer_to_uword (hs->data);
102 rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (data), (u8 *) &data);
103 ASSERT (rv == sizeof (sizeof (data)));
108 msg.data.type = HTTP_MSG_DATA_INLINE;
110 rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (msg), (u8 *) &msg);
111 ASSERT (rv == sizeof (msg));
116 rv = svm_fifo_enqueue (ts->tx_fifo, hs->data_len, hs->data);
118 if (rv != hs->data_len)
120 hs->data_offset = rv;
121 svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
126 if (svm_fifo_set_event (ts->tx_fifo))
127 session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
131 hss_session_send_data (hss_url_handler_args_t *args)
135 hs = hss_session_get (args->sh.thread_index, args->sh.session_index);
137 if (hs->data && hs->free_data)
140 hs->data = args->data;
141 hs->data_len = args->data_len;
142 hs->free_data = args->free_vec_data;
143 start_send_data (hs, args->sc);
147 try_url_handler (hss_main_t *hsm, hss_session_t *hs, http_req_method_t rt,
150 http_status_code_t sc = HTTP_STATUS_OK;
151 hss_url_handler_args_t args = {};
152 uword *p, *url_table;
155 if (!hsm->enable_url_handlers || !request)
158 /* Look for built-in GET / POST handlers */
160 (rt == HTTP_REQ_GET) ? hsm->get_url_handlers : hsm->post_url_handlers;
162 p = hash_get_mem (url_table, request);
168 hs->cache_pool_index = ~0;
170 if (hsm->debug_level > 0)
171 clib_warning ("%s '%s'", (rt == HTTP_REQ_GET) ? "GET" : "POST", request);
174 args.request = request;
175 args.sh.thread_index = hs->thread_index;
176 args.sh.session_index = hs->session_index;
178 rv = ((hss_url_handler_fn) p[0]) (&args);
180 /* Wait for data from handler */
181 if (rv == HSS_URL_HANDLER_ASYNC)
184 if (rv == HSS_URL_HANDLER_ERROR)
186 clib_warning ("builtin handler %llx hit on %s '%s' but failed!", p[0],
187 (rt == HTTP_REQ_GET) ? "GET" : "POST", request);
188 sc = HTTP_STATUS_NOT_FOUND;
191 hs->data = args.data;
192 hs->data_len = args.data_len;
193 hs->free_data = args.free_vec_data;
195 start_send_data (hs, sc);
198 hss_session_disconnect_transport (hs);
204 file_path_is_valid (u8 *path)
206 struct stat _sb, *sb = &_sb;
208 if (stat ((char *) path, sb) < 0 /* can't stat the file */
209 || (sb->st_mode & S_IFMT) != S_IFREG /* not a regular file */)
216 try_index_file (hss_main_t *hsm, hss_session_t *hs, u8 *path)
218 u8 *port_str = 0, *redirect;
219 transport_endpoint_t endpt;
220 transport_proto_t proto;
226 /* Remove the trailing space */
227 vec_dec_len (path, 1);
228 plen = vec_len (path);
230 /* Append "index.html" */
231 if (path[plen - 1] != '/')
232 path = format (path, "/index.html%c", 0);
234 path = format (path, "index.html%c", 0);
236 if (hsm->debug_level > 0)
237 clib_warning ("trying to find index: %s", path);
239 if (!file_path_is_valid (path))
240 return HTTP_STATUS_NOT_FOUND;
243 * We found an index.html file, build a redirect
245 vec_delete (path, vec_len (hsm->www_root) - 1, 0);
247 ts = session_get (hs->vpp_session_index, hs->thread_index);
248 session_get_endpoint (ts, &endpt, 1 /* is_local */);
250 local_port = clib_net_to_host_u16 (endpt.port);
251 proto = session_type_transport_proto (ts->session_type);
253 if ((proto == TRANSPORT_PROTO_TCP && local_port != 80) ||
254 (proto == TRANSPORT_PROTO_TLS && local_port != 443))
257 port_str = format (0, ":%u", (u32) local_port);
262 "HTTP/1.1 301 Moved Permanently\r\n"
263 "Location: http%s://%U%s%s\r\n\r\n",
264 proto == TRANSPORT_PROTO_TLS ? "s" : "", format_ip46_address,
265 &endpt.ip, endpt.is_ip4, print_port ? port_str : (u8 *) "", path);
267 if (hsm->debug_level > 0)
268 clib_warning ("redirect: %s", redirect);
273 hs->data_len = vec_len (redirect);
276 return HTTP_STATUS_OK;
280 try_file_handler (hss_main_t *hsm, hss_session_t *hs, http_req_method_t rt,
283 http_status_code_t sc = HTTP_STATUS_OK;
287 /* Feature not enabled */
292 * Construct the file to open
293 * Browsers are capable of sporadically including a leading '/'
296 path = format (0, "%s%c", hsm->www_root, 0);
297 else if (request[0] == '/')
298 path = format (0, "%s%s%c", hsm->www_root, request, 0);
300 path = format (0, "%s/%s%c", hsm->www_root, request, 0);
302 if (hsm->debug_level > 0)
303 clib_warning ("%s '%s'", (rt == HTTP_REQ_GET) ? "GET" : "POST", path);
305 if (hs->data && hs->free_data)
312 hss_cache_lookup_and_attach (&hsm->cache, path, &hs->data, &hs->data_len);
315 if (!file_path_is_valid (path))
317 sc = try_index_file (hsm, hs, path);
321 hss_cache_add_and_attach (&hsm->cache, path, &hs->data, &hs->data_len);
324 sc = HTTP_STATUS_INTERNAL_ERROR;
329 hs->cache_pool_index = ce_index;
333 start_send_data (hs, sc);
335 hss_session_disconnect_transport (hs);
341 handle_request (hss_session_t *hs, http_req_method_t rt, u8 *request)
343 hss_main_t *hsm = &hss_main;
345 if (!try_url_handler (hsm, hs, rt, request))
348 if (!try_file_handler (hsm, hs, rt, request))
351 /* Handler did not find anything return 404 */
352 start_send_data (hs, HTTP_STATUS_NOT_FOUND);
353 hss_session_disconnect_transport (hs);
359 hss_ts_rx_callback (session_t *ts)
366 hs = hss_session_get (ts->thread_index, ts->opaque);
368 /* Read the http message header */
369 rv = svm_fifo_dequeue (ts->rx_fifo, sizeof (msg), (u8 *) &msg);
370 ASSERT (rv == sizeof (msg));
372 if (msg.type != HTTP_MSG_REQUEST ||
373 (msg.method_type != HTTP_REQ_GET && msg.method_type != HTTP_REQ_POST))
376 start_send_data (hs, HTTP_STATUS_METHOD_NOT_ALLOWED);
383 vec_validate (request, msg.data.len - 1);
384 rv = svm_fifo_dequeue (ts->rx_fifo, msg.data.len, request);
385 ASSERT (rv == msg.data.len);
388 /* Find and send data */
389 handle_request (hs, msg.method_type, request);
397 hss_ts_tx_callback (session_t *ts)
403 hs = hss_session_get (ts->thread_index, ts->opaque);
404 if (!hs || !hs->data)
407 to_send = hs->data_len - hs->data_offset;
408 rv = svm_fifo_enqueue (ts->tx_fifo, to_send, hs->data + hs->data_offset);
412 svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
418 hs->data_offset += rv;
419 svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
422 if (svm_fifo_set_event (ts->tx_fifo))
423 session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
428 /** \brief Session accept callback
431 hss_ts_accept_callback (session_t *ts)
436 hs = hss_session_alloc (ts->thread_index);
438 hs->vpp_session_index = ts->session_index;
439 hs->vpp_session_handle = session_handle (ts);
441 /* The application sets a threshold for it's fifo to get notified when
442 * additional data can be enqueued. We want to keep the TX fifo reasonably
443 * full, however avoid entering a state where the
444 * fifo is full all the time and small chunks of data are being enqueued
445 * each time. If the fifo is small (under 16K) we set
446 * the threshold to it's size, meaning a notification will be given when the
449 thresh = clib_min (svm_fifo_size (ts->tx_fifo), HSS_FIFO_THRESH);
450 svm_fifo_set_deq_thresh (ts->tx_fifo, thresh);
452 ts->opaque = hs->session_index;
453 ts->session_state = SESSION_STATE_READY;
458 hss_ts_disconnect_callback (session_t *ts)
460 hss_main_t *hsm = &hss_main;
461 vnet_disconnect_args_t _a = { 0 }, *a = &_a;
463 a->handle = session_handle (ts);
464 a->app_index = hsm->app_index;
465 vnet_disconnect_session (a);
469 hss_ts_reset_callback (session_t *ts)
471 hss_main_t *hsm = &hss_main;
472 vnet_disconnect_args_t _a = { 0 }, *a = &_a;
474 a->handle = session_handle (ts);
475 a->app_index = hsm->app_index;
476 vnet_disconnect_session (a);
480 hss_ts_connected_callback (u32 app_index, u32 api_context, session_t *ts,
483 clib_warning ("called...");
488 hss_add_segment_callback (u32 client_index, u64 segment_handle)
494 hss_ts_cleanup (session_t *s, session_cleanup_ntf_t ntf)
496 hss_main_t *hsm = &hss_main;
499 if (ntf == SESSION_CLEANUP_TRANSPORT)
502 hs = hss_session_get (s->thread_index, s->opaque);
506 if (hs->cache_pool_index != ~0)
508 hss_cache_detach_entry (&hsm->cache, hs->cache_pool_index);
509 hs->cache_pool_index = ~0;
519 hss_session_free (hs);
522 static session_cb_vft_t hss_cb_vft = {
523 .session_accept_callback = hss_ts_accept_callback,
524 .session_disconnect_callback = hss_ts_disconnect_callback,
525 .session_connected_callback = hss_ts_connected_callback,
526 .add_segment_callback = hss_add_segment_callback,
527 .builtin_app_rx_callback = hss_ts_rx_callback,
528 .builtin_app_tx_callback = hss_ts_tx_callback,
529 .session_reset_callback = hss_ts_reset_callback,
530 .session_cleanup_callback = hss_ts_cleanup,
536 vnet_app_add_cert_key_pair_args_t _ck_pair, *ck_pair = &_ck_pair;
537 hss_main_t *hsm = &hss_main;
538 u64 options[APP_OPTIONS_N_OPTIONS];
539 vnet_app_attach_args_t _a, *a = &_a;
540 u32 segment_size = 128 << 20;
542 clib_memset (a, 0, sizeof (*a));
543 clib_memset (options, 0, sizeof (options));
545 if (hsm->private_segment_size)
546 segment_size = hsm->private_segment_size;
548 a->api_client_index = ~0;
549 a->name = format (0, "http_static_server");
550 a->session_cb_vft = &hss_cb_vft;
551 a->options = options;
552 a->options[APP_OPTIONS_SEGMENT_SIZE] = segment_size;
553 a->options[APP_OPTIONS_ADD_SEGMENT_SIZE] = segment_size;
554 a->options[APP_OPTIONS_RX_FIFO_SIZE] =
555 hsm->fifo_size ? hsm->fifo_size : 8 << 10;
556 a->options[APP_OPTIONS_TX_FIFO_SIZE] =
557 hsm->fifo_size ? hsm->fifo_size : 32 << 10;
558 a->options[APP_OPTIONS_FLAGS] = APP_OPTIONS_FLAGS_IS_BUILTIN;
559 a->options[APP_OPTIONS_PREALLOC_FIFO_PAIRS] = hsm->prealloc_fifos;
560 a->options[APP_OPTIONS_TLS_ENGINE] = CRYPTO_ENGINE_OPENSSL;
562 if (vnet_application_attach (a))
565 clib_warning ("failed to attach server");
569 hsm->app_index = a->app_index;
571 clib_memset (ck_pair, 0, sizeof (*ck_pair));
572 ck_pair->cert = (u8 *) test_srv_crt_rsa;
573 ck_pair->key = (u8 *) test_srv_key_rsa;
574 ck_pair->cert_len = test_srv_crt_rsa_len;
575 ck_pair->key_len = test_srv_key_rsa_len;
576 vnet_app_add_cert_key_pair (ck_pair);
577 hsm->ckpair_index = ck_pair->index;
583 hss_transport_needs_crypto (transport_proto_t proto)
585 return proto == TRANSPORT_PROTO_TLS || proto == TRANSPORT_PROTO_DTLS ||
586 proto == TRANSPORT_PROTO_QUIC;
592 hss_main_t *hsm = &hss_main;
593 session_endpoint_cfg_t sep = SESSION_ENDPOINT_CFG_NULL;
594 vnet_listen_args_t _a, *a = &_a;
595 char *uri = "tcp://0.0.0.0/80";
599 clib_memset (a, 0, sizeof (*a));
600 a->app_index = hsm->app_index;
603 uri = (char *) hsm->uri;
605 if (parse_uri (uri, &sep))
608 need_crypto = hss_transport_needs_crypto (sep.transport_proto);
610 sep.transport_proto = TRANSPORT_PROTO_HTTP;
611 clib_memcpy (&a->sep_ext, &sep, sizeof (sep));
615 session_endpoint_alloc_ext_cfg (&a->sep_ext,
616 TRANSPORT_ENDPT_EXT_CFG_CRYPTO);
617 a->sep_ext.ext_cfg->crypto.ckpair_index = hsm->ckpair_index;
620 rv = vnet_listen (a);
623 clib_mem_free (a->sep_ext.ext_cfg);
629 hss_url_handlers_init (hss_main_t *hsm)
631 if (!hsm->get_url_handlers)
633 hsm->get_url_handlers = hash_create_string (0, sizeof (uword));
634 hsm->post_url_handlers = hash_create_string (0, sizeof (uword));
637 hss_builtinurl_json_handlers_init ();
641 hss_create (vlib_main_t *vm)
643 vlib_thread_main_t *vtm = vlib_get_thread_main ();
644 hss_main_t *hsm = &hss_main;
647 num_threads = 1 /* main thread */ + vtm->n_threads;
648 vec_validate (hsm->sessions, num_threads - 1);
652 clib_warning ("failed to attach server");
657 clib_warning ("failed to start listening");
662 hss_cache_init (&hsm->cache, hsm->cache_size, hsm->debug_level);
664 if (hsm->enable_url_handlers)
665 hss_url_handlers_init (hsm);
670 static clib_error_t *
671 hss_create_command_fn (vlib_main_t *vm, unformat_input_t *input,
672 vlib_cli_command_t *cmd)
674 unformat_input_t _line_input, *line_input = &_line_input;
675 hss_main_t *hsm = &hss_main;
676 clib_error_t *error = 0;
680 if (hsm->app_index != (u32) ~0)
681 return clib_error_return (0, "http server already running...");
683 hsm->prealloc_fifos = 0;
684 hsm->private_segment_size = 0;
686 hsm->cache_size = 10 << 20;
688 /* Get a line of input. */
689 if (!unformat_user (input, unformat_line_input, line_input))
692 while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
694 if (unformat (line_input, "www-root %s", &hsm->www_root))
697 if (unformat (line_input, "prealloc-fifos %d", &hsm->prealloc_fifos))
699 else if (unformat (line_input, "private-segment-size %U",
700 unformat_memory_size, &seg_size))
701 hsm->private_segment_size = seg_size;
702 else if (unformat (line_input, "fifo-size %d", &hsm->fifo_size))
703 hsm->fifo_size <<= 10;
704 else if (unformat (line_input, "cache-size %U", unformat_memory_size,
707 else if (unformat (line_input, "uri %s", &hsm->uri))
709 else if (unformat (line_input, "debug %d", &hsm->debug_level))
711 else if (unformat (line_input, "debug"))
712 hsm->debug_level = 1;
713 else if (unformat (line_input, "ptr-thresh %U", unformat_memory_size,
714 &hsm->use_ptr_thresh))
716 else if (unformat (line_input, "url-handlers"))
717 hsm->enable_url_handlers = 1;
720 error = clib_error_return (0, "unknown input `%U'",
721 format_unformat_error, line_input);
726 unformat_free (line_input);
733 if (hsm->www_root == 0 && !hsm->enable_url_handlers)
735 error = clib_error_return (0, "Must set www-root or url-handlers");
739 if (hsm->cache_size < (128 << 10))
741 error = clib_error_return (0, "cache-size must be at least 128kb");
742 vec_free (hsm->www_root);
746 vnet_session_enable_disable (vm, 1 /* turn on TCP, etc. */ );
748 if ((rv = hss_create (vm)))
750 error = clib_error_return (0, "server_create returned %d", rv);
751 vec_free (hsm->www_root);
760 * Enable the static http server
763 * This command enables the static http server. Only the www-root
764 * parameter is required
766 * http static server www-root /tmp/www uri tcp://0.0.0.0/80 cache-size 2m
768 * @cliexcmd{http static server www-root <path> [prealloc-fios <nn>]
769 * [private-segment-size <nnMG>] [fifo-size <nbytes>] [uri <uri>]}
771 VLIB_CLI_COMMAND (hss_create_command, static) = {
772 .path = "http static server",
774 "http static server www-root <path> [prealloc-fifos <nn>]\n"
775 "[private-segment-size <nnMG>] [fifo-size <nbytes>] [uri <uri>]\n"
776 "[ptr-thresh <nn>] [url-handlers] [debug [nn]]\n",
777 .function = hss_create_command_fn,
781 format_hss_session (u8 *s, va_list *args)
783 hss_session_t *hs = va_arg (*args, hss_session_t *);
784 int __clib_unused verbose = va_arg (*args, int);
786 s = format (s, "\n path %s, data length %u, data_offset %u",
787 hs->path ? hs->path : (u8 *) "[none]", hs->data_len,
792 static clib_error_t *
793 hss_show_command_fn (vlib_main_t *vm, unformat_input_t *input,
794 vlib_cli_command_t *cmd)
796 int verbose = 0, show_cache = 0, show_sessions = 0;
797 hss_main_t *hsm = &hss_main;
799 if (hsm->www_root == 0)
800 return clib_error_return (0, "Static server disabled");
802 while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
804 if (unformat (input, "verbose %d", &verbose))
806 else if (unformat (input, "verbose"))
808 else if (unformat (input, "cache"))
810 else if (unformat (input, "sessions"))
816 if ((show_cache + show_sessions) == 0)
817 return clib_error_return (0, "specify one or more of cache, sessions");
820 vlib_cli_output (vm, "%U", format_hss_cache, &hsm->cache, verbose);
824 u32 *session_indices = 0;
829 for (i = 0; i < vec_len (hsm->sessions); i++)
831 pool_foreach (hs, hsm->sessions[i])
832 vec_add1 (session_indices, hs - hsm->sessions[i]);
834 for (j = 0; j < vec_len (session_indices); j++)
837 vm, "%U", format_hss_session,
838 pool_elt_at_index (hsm->sessions[i], session_indices[j]),
841 vec_reset_length (session_indices);
843 vec_free (session_indices);
849 * Display static http server cache statistics
852 * This command shows the contents of the static http server cache
854 * show http static server
856 * @cliexcmd{show http static server sessions cache [verbose [nn]]}
858 VLIB_CLI_COMMAND (hss_show_command, static) = {
859 .path = "show http static server",
860 .short_help = "show http static server sessions cache [verbose [<nn>]]",
861 .function = hss_show_command_fn,
864 static clib_error_t *
865 hss_clear_cache_command_fn (vlib_main_t *vm, unformat_input_t *input,
866 vlib_cli_command_t *cmd)
868 hss_main_t *hsm = &hss_main;
871 if (hsm->www_root == 0)
872 return clib_error_return (0, "Static server disabled");
874 busy_items = hss_cache_clear (&hsm->cache);
877 vlib_cli_output (vm, "Note: %d busy items still in cache...", busy_items);
879 vlib_cli_output (vm, "Cache cleared...");
884 * Clear the static http server cache, to force the server to
885 * reload content from backing files
888 * This command clear the static http server cache
890 * clear http static cache
892 * @cliexcmd{clear http static cache}
894 VLIB_CLI_COMMAND (clear_hss_cache_command, static) = {
895 .path = "clear http static cache",
896 .short_help = "clear http static cache",
897 .function = hss_clear_cache_command_fn,
900 static clib_error_t *
901 hss_main_init (vlib_main_t *vm)
903 hss_main_t *hsm = &hss_main;
911 VLIB_INIT_FUNCTION (hss_main_init);
914 * fd.io coding-style-patch-verification: ON
917 * eval: (c-set-style "gnu")