2 * Copyright (c) 2017-2019 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 <vnet/vnet.h>
17 #include <vnet/session/application.h>
18 #include <vnet/session/application_interface.h>
19 #include <vnet/session/session.h>
20 #include <vppinfra/unix.h>
21 #include <sys/types.h>
24 #include <http_static/http_static.h>
26 #include <vppinfra/bihash_template.c>
28 /** @file static_server.c
29 * Static http server, sufficient to
30 * serve .html / .css / .js content.
32 /*? %%clicmd:group_label Static HTTP Server %% ?*/
34 http_static_server_main_t http_static_server_main;
36 /** \brief Format the called-from enum
40 format_state_machine_called_from (u8 * s, va_list * args)
42 http_state_machine_called_from_t cf =
43 va_arg (*args, http_state_machine_called_from_t);
44 char *which = "bogus!";
54 case CALLED_FROM_TIMER:
62 s = format (s, "%s", which);
67 /** \brief Acquire reader lock on the sessions pools
70 http_static_server_sessions_reader_lock (void)
72 clib_rwlock_reader_lock (&http_static_server_main.sessions_lock);
75 /** \brief Drop reader lock on the sessions pools
78 http_static_server_sessions_reader_unlock (void)
80 clib_rwlock_reader_unlock (&http_static_server_main.sessions_lock);
83 /** \brief Acquire writer lock on the sessions pools
86 http_static_server_sessions_writer_lock (void)
88 clib_rwlock_writer_lock (&http_static_server_main.sessions_lock);
91 /** \brief Drop writer lock on the sessions pools
94 http_static_server_sessions_writer_unlock (void)
96 clib_rwlock_writer_unlock (&http_static_server_main.sessions_lock);
99 /** \brief Start a session cleanup timer
102 http_static_server_session_timer_start (http_session_t * hs)
104 http_static_server_main_t *hsm = &http_static_server_main;
107 /* The session layer may fire a callback at a later date... */
108 if (!pool_is_free (hsm->sessions[hs->thread_index], hs))
110 hs_handle = hs->thread_index << 24 | hs->session_index;
111 clib_spinlock_lock (&http_static_server_main.tw_lock);
112 hs->timer_handle = tw_timer_start_2t_1w_2048sl
113 (&http_static_server_main.tw, hs_handle, 0, 60);
114 clib_spinlock_unlock (&http_static_server_main.tw_lock);
118 /** \brief stop a session cleanup timer
121 http_static_server_session_timer_stop (http_session_t * hs)
123 if (hs->timer_handle == ~0)
125 clib_spinlock_lock (&http_static_server_main.tw_lock);
126 tw_timer_stop_2t_1w_2048sl (&http_static_server_main.tw, hs->timer_handle);
127 clib_spinlock_unlock (&http_static_server_main.tw_lock);
130 /** \brief Allocate an http session
132 static http_session_t *
133 http_static_server_session_alloc (u32 thread_index)
135 http_static_server_main_t *hsm = &http_static_server_main;
137 pool_get_aligned_zero_numa (hsm->sessions[thread_index], hs,
138 0 /* not aligned */ ,
140 os_get_numa_index ());
141 hs->session_index = hs - hsm->sessions[thread_index];
142 hs->thread_index = thread_index;
143 hs->timer_handle = ~0;
144 hs->cache_pool_index = ~0;
148 /** \brief Get an http session by index
150 static http_session_t *
151 http_static_server_session_get (u32 thread_index, u32 hs_index)
153 http_static_server_main_t *hsm = &http_static_server_main;
154 if (pool_is_free_index (hsm->sessions[thread_index], hs_index))
156 return pool_elt_at_index (hsm->sessions[thread_index], hs_index);
159 /** \brief Free an http session
162 http_static_server_session_free (http_session_t * hs)
164 http_static_server_main_t *hsm = &http_static_server_main;
166 /* Make sure the timer is stopped... */
167 http_static_server_session_timer_stop (hs);
168 pool_put (hsm->sessions[hs->thread_index], hs);
172 u32 save_thread_index;
173 save_thread_index = hs->thread_index;
174 /* Poison the entry, preserve timer state and thread index */
175 memset (hs, 0xfa, sizeof (*hs));
176 hs->timer_handle = ~0;
177 hs->thread_index = save_thread_index;
181 /** \brief add a session to the vpp < -- > http session index map
184 http_static_server_session_lookup_add (u32 thread_index, u32 s_index,
187 http_static_server_main_t *hsm = &http_static_server_main;
188 vec_validate (hsm->session_to_http_session[thread_index], s_index);
189 hsm->session_to_http_session[thread_index][s_index] = hs_index;
192 /** \brief Remove a session from the vpp < -- > http session index map
195 http_static_server_session_lookup_del (u32 thread_index, u32 s_index)
197 http_static_server_main_t *hsm = &http_static_server_main;
198 hsm->session_to_http_session[thread_index][s_index] = ~0;
201 /** \brief lookup a session in the vpp < -- > http session index map
204 static http_session_t *
205 http_static_server_session_lookup (u32 thread_index, u32 s_index)
207 http_static_server_main_t *hsm = &http_static_server_main;
210 if (s_index < vec_len (hsm->session_to_http_session[thread_index]))
212 hs_index = hsm->session_to_http_session[thread_index][s_index];
213 return http_static_server_session_get (thread_index, hs_index);
218 /** \brief Detach cache entry from session
222 http_static_server_detach_cache_entry (http_session_t * hs)
224 http_static_server_main_t *hsm = &http_static_server_main;
225 file_data_cache_t *ep;
228 * Decrement cache pool entry reference count
229 * Note that if e.g. a file lookup fails, the cache pool index
232 if (hs->cache_pool_index != ~0)
234 ep = pool_elt_at_index (hsm->cache_pool, hs->cache_pool_index);
236 if (hsm->debug_level > 1)
237 clib_warning ("index %d refcnt now %d", hs->cache_pool_index,
240 hs->cache_pool_index = ~0;
249 /** \brief Disconnect a session
252 http_static_server_session_disconnect (http_session_t * hs)
254 vnet_disconnect_args_t _a = { 0 }, *a = &_a;
255 a->handle = hs->vpp_session_handle;
256 a->app_index = http_static_server_main.app_index;
257 vnet_disconnect_session (a);
261 /** \brief http error boilerplate
263 static const char *http_error_template =
266 "Content-Type: text/html\r\n"
267 "Connection: close\r\n"
268 "Pragma: no-cache\r\n"
269 "Content-Length: 0\r\n\r\n";
271 /** \brief http response boilerplate
273 static const char *http_response_template =
275 "Expires: %U GMT\r\n"
276 "Server: VPP Static\r\n"
277 "Content-Type: %s\r\n"
278 "Content-Length: %d\r\n\r\n";
282 /** \brief send http data
283 @param hs - http session
284 @param data - the data vector to transmit
285 @param length - length of data
286 @param offset - transmit offset for this operation
287 @return offset for next transmit operation, may be unchanged w/ full fifo
291 static_send_data (http_session_t * hs, u8 * data, u32 length, u32 offset)
294 http_static_server_main_t *hsm = &http_static_server_main;
296 bytes_to_send = length - offset;
298 while (bytes_to_send > 0)
302 actual_transfer = svm_fifo_enqueue
303 (hs->tx_fifo, bytes_to_send, data + offset);
305 /* Made any progress? */
306 if (actual_transfer <= 0)
308 if (hsm->debug_level > 0 && bytes_to_send > 0)
309 clib_warning ("WARNING: still %d bytes to send", bytes_to_send);
314 offset += actual_transfer;
315 bytes_to_send -= actual_transfer;
317 if (hsm->debug_level && bytes_to_send > 0)
318 clib_warning ("WARNING: still %d bytes to send", bytes_to_send);
320 if (svm_fifo_set_event (hs->tx_fifo))
321 session_send_io_evt_to_thread (hs->tx_fifo,
322 SESSION_IO_EVT_TX_FLUSH);
330 /** \brief Send an http error string
331 @param hs - the http session
332 @param str - the error string, e.g. "404 Not Found"
335 send_error (http_session_t * hs, char *str)
337 http_static_server_main_t *hsm = &http_static_server_main;
341 now = clib_timebase_now (&hsm->timebase);
342 data = format (0, http_error_template, str, format_clib_timebase_time, now);
343 static_send_data (hs, data, vec_len (data), 0);
347 /** \brief Retrieve data from the application layer
350 session_rx_request (http_session_t * hs)
352 u32 max_dequeue, cursize;
355 cursize = vec_len (hs->rx_buf);
356 max_dequeue = svm_fifo_max_dequeue (hs->rx_fifo);
357 if (PREDICT_FALSE (max_dequeue == 0))
360 vec_validate (hs->rx_buf, cursize + max_dequeue - 1);
361 n_read = app_recv_stream_raw (hs->rx_fifo, hs->rx_buf + cursize,
362 max_dequeue, 0, 0 /* peek */ );
363 ASSERT (n_read == max_dequeue);
364 if (svm_fifo_is_empty (hs->rx_fifo))
365 svm_fifo_unset_event (hs->rx_fifo);
367 _vec_len (hs->rx_buf) = cursize + n_read;
371 /** \brief Sanity-check the forward and reverse LRU lists
374 lru_validate (http_static_server_main_t * hsm)
380 file_data_cache_t *ep;
382 last_timestamp = 1e70;
383 for (i = 1, index = hsm->first_index; index != ~0;)
385 ep = pool_elt_at_index (hsm->cache_pool, index);
386 index = ep->next_index;
387 /* Timestamps should be smaller (older) as we walk the fwd list */
388 if (ep->last_used > last_timestamp)
390 clib_warning ("%d[%d]: last used %.6f, last_timestamp %.6f",
391 ep - hsm->cache_pool, i,
392 ep->last_used, last_timestamp);
394 last_timestamp = ep->last_used;
398 last_timestamp = 0.0;
399 for (i = 1, index = hsm->last_index; index != ~0;)
401 ep = pool_elt_at_index (hsm->cache_pool, index);
402 index = ep->prev_index;
403 /* Timestamps should be larger (newer) as we walk the rev list */
404 if (ep->last_used < last_timestamp)
406 clib_warning ("%d[%d]: last used %.6f, last_timestamp %.6f",
407 ep - hsm->cache_pool, i,
408 ep->last_used, last_timestamp);
410 last_timestamp = ep->last_used;
416 /** \brief Remove a data cache entry from the LRU lists
419 lru_remove (http_static_server_main_t * hsm, file_data_cache_t * ep)
421 file_data_cache_t *next_ep, *prev_ep;
426 ep_index = ep - hsm->cache_pool;
428 /* Deal with list heads */
429 if (ep_index == hsm->first_index)
430 hsm->first_index = ep->next_index;
431 if (ep_index == hsm->last_index)
432 hsm->last_index = ep->prev_index;
435 if (ep->next_index != ~0)
437 next_ep = pool_elt_at_index (hsm->cache_pool, ep->next_index);
438 next_ep->prev_index = ep->prev_index;
441 if (ep->prev_index != ~0)
443 prev_ep = pool_elt_at_index (hsm->cache_pool, ep->prev_index);
444 prev_ep->next_index = ep->next_index;
449 /** \brief Add an entry to the LRU lists, tag w/ supplied timestamp
453 lru_add (http_static_server_main_t * hsm, file_data_cache_t * ep, f64 now)
455 file_data_cache_t *next_ep;
460 ep_index = ep - hsm->cache_pool;
463 * Re-add at the head of the forward LRU list,
464 * tail of the reverse LRU list
466 if (hsm->first_index != ~0)
468 next_ep = pool_elt_at_index (hsm->cache_pool, hsm->first_index);
469 next_ep->prev_index = ep_index;
474 /* ep now the new head of the LRU forward list */
475 ep->next_index = hsm->first_index;
476 hsm->first_index = ep_index;
478 /* single session case: also the tail of the reverse LRU list */
479 if (hsm->last_index == ~0)
480 hsm->last_index = ep_index;
486 /** \brief Remove and re-add a cache entry from/to the LRU lists
490 lru_update (http_static_server_main_t * hsm, file_data_cache_t * ep, f64 now)
492 lru_remove (hsm, ep);
493 lru_add (hsm, ep, now);
496 /** \brief Session-layer (main) data rx callback.
497 Parse the http request, and reply to it.
498 Future extensions might include POST processing, active content, etc.
501 /* svm_fifo_add_want_deq_ntf (tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF_IF_FULL)
502 get shoulder-tap when transport dequeues something, set in
505 /** \brief closed state - should never really get here
508 state_closed (session_t * s, http_session_t * hs,
509 http_state_machine_called_from_t cf)
511 clib_warning ("WARNING: http session %d, called from %U",
512 hs->session_index, format_state_machine_called_from, cf);
517 close_session (http_session_t * hs)
519 http_static_server_session_disconnect (hs);
522 /** \brief Register a builtin GET or POST handler
524 __clib_export void http_static_server_register_builtin_handler
525 (void *fp, char *url, int request_type)
527 http_static_server_main_t *hsm = &http_static_server_main;
528 uword *p, *builtin_table;
530 builtin_table = (request_type == HTTP_BUILTIN_METHOD_GET)
531 ? hsm->get_url_handlers : hsm->post_url_handlers;
533 p = hash_get_mem (builtin_table, url);
537 clib_warning ("WARNING: attempt to replace handler for %s '%s' ignored",
538 (request_type == HTTP_BUILTIN_METHOD_GET) ?
539 "GET" : "POST", url);
543 hash_set_mem (builtin_table, url, (uword) fp);
546 * Need to update the hash table pointer in http_static_server_main
547 * in case we just expanded it...
549 if (request_type == HTTP_BUILTIN_METHOD_GET)
550 hsm->get_url_handlers = builtin_table;
552 hsm->post_url_handlers = builtin_table;
556 v_find_index (u8 * vec, char *str)
559 u32 slen = (u32) strnlen_s_inline (str, 8);
560 u32 vlen = vec_len (vec);
567 for (start_index = 0; start_index < (vlen - slen); start_index++)
569 if (!memcmp (vec, str, slen))
576 /** \brief established state - waiting for GET, POST, etc.
579 state_established (session_t * s, http_session_t * hs,
580 http_state_machine_called_from_t cf)
582 http_static_server_main_t *hsm = &http_static_server_main;
586 struct stat _sb, *sb = &_sb;
588 u8 request_type = HTTP_BUILTIN_METHOD_GET;
590 uword *p, *builtin_table;
592 /* Read data from the sessison layer */
593 rv = session_rx_request (hs);
595 /* No data? Odd, but stay in this state and await further instructions */
599 /* Process the client request */
600 request = hs->rx_buf;
601 if (vec_len (request) < 8)
603 send_error (hs, "400 Bad Request");
608 if ((i = v_find_index (request, "GET ")) >= 0)
610 else if ((i = v_find_index (request, "POST ")) >= 0)
612 request_type = HTTP_BUILTIN_METHOD_POST;
616 if (hsm->debug_level > 1)
617 clib_warning ("Unknown http method");
619 send_error (hs, "405 Method Not Allowed");
625 /* Lose "GET " or "POST " */
626 vec_delete (request, i + 5 + request_type, 0);
628 /* Temporarily drop in a NULL byte for lookup purposes */
629 for (i = 0; i < vec_len (request); i++)
631 if (request[i] == ' ' || request[i] == '?')
633 save_byte = request[i];
640 * Now we can construct the file to open
641 * Browsers are capable of sporadically including a leading '/'
643 if (request[0] == '/')
644 path = format (0, "%s%s%c", hsm->www_root, request, 0);
646 path = format (0, "%s/%s%c", hsm->www_root, request, 0);
648 if (hsm->debug_level > 0)
649 clib_warning ("%s '%s'", (request_type) == HTTP_BUILTIN_METHOD_GET ?
650 "GET" : "POST", path);
652 /* Look for built-in GET / POST handlers */
653 builtin_table = (request_type == HTTP_BUILTIN_METHOD_GET) ?
654 hsm->get_url_handlers : hsm->post_url_handlers;
656 p = hash_get_mem (builtin_table, request);
659 request[i] = save_byte;
664 int (*fp) (http_builtin_method_type_t, u8 *, http_session_t *);
667 rv = (*fp) (request_type, request, hs);
670 clib_warning ("builtin handler %llx hit on %s '%s' but failed!",
671 p[0], (request_type == HTTP_BUILTIN_METHOD_GET) ?
672 "GET" : "POST", request);
673 send_error (hs, "404 Not Found");
677 vec_reset_length (hs->rx_buf);
680 vec_reset_length (hs->rx_buf);
681 /* poison request, it's not valid anymore */
683 /* The static server itself doesn't do POSTs */
684 if (request_type == HTTP_BUILTIN_METHOD_POST)
686 send_error (hs, "404 Not Found");
691 /* Try to find the file. 2x special cases to find index.html */
692 if (stat ((char *) path, sb) < 0 /* cant even stat the file */
693 || sb->st_size < 20 /* file too small */
694 || (sb->st_mode & S_IFMT) != S_IFREG /* not a regular file */ )
696 u32 save_length = vec_len (path) - 1;
697 /* Try appending "index.html"... */
698 _vec_len (path) -= 1;
699 path = format (path, "index.html%c", 0);
700 if (stat ((char *) path, sb) < 0 /* cant even stat the file */
701 || sb->st_size < 20 /* file too small */
702 || (sb->st_mode & S_IFMT) != S_IFREG /* not a regular file */ )
704 _vec_len (path) = save_length;
705 path = format (path, "/index.html%c", 0);
707 /* Send a redirect, otherwise the browser will confuse itself */
708 if (stat ((char *) path, sb) < 0 /* cant even stat the file */
709 || sb->st_size < 20 /* file too small */
710 || (sb->st_mode & S_IFMT) != S_IFREG /* not a regular file */ )
713 send_error (hs, "404 Not Found");
719 transport_endpoint_t endpoint;
720 transport_proto_t proto;
726 * To make this bit work correctly, we need to know our local
727 * IP address, etc. and send it in the redirect...
731 vec_delete (path, vec_len (hsm->www_root) - 1, 0);
733 session_get_endpoint (s, &endpoint, 1 /* is_local */ );
735 local_port = clib_net_to_host_u16 (endpoint.port);
737 proto = session_type_transport_proto (s->session_type);
739 if ((proto == TRANSPORT_PROTO_TCP && local_port != 80)
740 || (proto == TRANSPORT_PROTO_TLS && local_port != 443))
743 port_str = format (0, ":%u", (u32) local_port);
746 redirect = format (0, "HTTP/1.1 301 Moved Permanently\r\n"
747 "Location: http%s://%U%s%s\r\n\r\n",
748 proto == TRANSPORT_PROTO_TLS ? "s" : "",
749 format_ip46_address, &endpoint.ip,
751 print_port ? port_str : (u8 *) "", path);
752 if (hsm->debug_level > 0)
753 clib_warning ("redirect: %s", redirect);
757 static_send_data (hs, redirect, vec_len (redirect), 0);
758 hs->session_state = HTTP_STATE_CLOSED;
768 /* find or read the file if we haven't done so yet. */
771 BVT (clib_bihash_kv) kv;
772 file_data_cache_t *dp;
776 /* First, try the cache */
777 kv.key = (u64) hs->path;
778 if (BV (clib_bihash_search) (&hsm->name_to_data, &kv, &kv) == 0)
780 if (hsm->debug_level > 1)
781 clib_warning ("lookup '%s' returned %lld", kv.key, kv.value);
783 /* found the data.. */
784 dp = pool_elt_at_index (hsm->cache_pool, kv.value);
786 /* Update the cache entry, mark it in-use */
787 lru_update (hsm, dp, vlib_time_now (vlib_get_main ()));
788 hs->cache_pool_index = dp - hsm->cache_pool;
790 if (hsm->debug_level > 1)
791 clib_warning ("index %d refcnt now %d", hs->cache_pool_index,
796 if (hsm->debug_level > 1)
797 clib_warning ("lookup '%s' failed", kv.key, kv.value);
798 /* Need to recycle one (or more cache) entries? */
799 if (hsm->cache_size > hsm->cache_limit)
801 int free_index = hsm->last_index;
803 while (free_index != ~0)
806 dp = pool_elt_at_index (hsm->cache_pool, free_index);
807 free_index = dp->prev_index;
808 /* Which could be in use... */
811 if (hsm->debug_level > 1)
812 clib_warning ("index %d in use refcnt %d",
813 dp - hsm->cache_pool, dp->inuse);
816 kv.key = (u64) (dp->filename);
818 if (BV (clib_bihash_add_del) (&hsm->name_to_data, &kv,
819 0 /* is_add */ ) < 0)
821 clib_warning ("LRU delete '%s' FAILED!", dp->filename);
823 else if (hsm->debug_level > 1)
824 clib_warning ("LRU delete '%s' ok", dp->filename);
826 lru_remove (hsm, dp);
827 hsm->cache_size -= vec_len (dp->data);
828 hsm->cache_evictions++;
829 vec_free (dp->filename);
831 if (hsm->debug_level > 1)
832 clib_warning ("pool put index %d", dp - hsm->cache_pool);
833 pool_put (hsm->cache_pool, dp);
834 if (hsm->cache_size < hsm->cache_limit)
840 error = clib_file_contents ((char *) (hs->path), &hs->data);
843 clib_warning ("Error reading '%s'", hs->path);
844 clib_error_report (error);
849 /* Create a cache entry for it */
850 pool_get (hsm->cache_pool, dp);
851 memset (dp, 0, sizeof (*dp));
852 dp->filename = vec_dup (hs->path);
854 hs->cache_pool_index = dp - hsm->cache_pool;
856 if (hsm->debug_level > 1)
857 clib_warning ("index %d refcnt now %d", hs->cache_pool_index,
859 lru_add (hsm, dp, vlib_time_now (vlib_get_main ()));
860 kv.key = (u64) vec_dup (hs->path);
861 kv.value = dp - hsm->cache_pool;
862 /* Add to the lookup table */
863 if (hsm->debug_level > 1)
864 clib_warning ("add '%s' value %lld", kv.key, kv.value);
866 if (BV (clib_bihash_add_del) (&hsm->name_to_data, &kv,
867 1 /* is_add */ ) < 0)
869 clib_warning ("BUG: add failed!");
871 hsm->cache_size += vec_len (dp->data);
875 /* send 200 OK first */
877 static_send_data (hs, (u8 *) "HTTP/1.1 200 OK\r\n", 17, 0);
878 hs->session_state = HTTP_STATE_OK_SENT;
883 state_send_more_data (session_t * s, http_session_t * hs,
884 http_state_machine_called_from_t cf)
887 /* Start sending data */
888 hs->data_offset = static_send_data (hs, hs->data, vec_len (hs->data),
892 if (hs->data_offset < vec_len (hs->data))
894 /* No: ask for a shoulder-tap when the tx fifo has space */
895 svm_fifo_add_want_deq_ntf (hs->tx_fifo,
896 SVM_FIFO_WANT_DEQ_NOTIF_IF_FULL);
897 hs->session_state = HTTP_STATE_SEND_MORE_DATA;
900 /* Finished with this transaction, back to HTTP_STATE_ESTABLISHED */
902 /* Let go of the file cache entry */
903 http_static_server_detach_cache_entry (hs);
904 hs->session_state = HTTP_STATE_ESTABLISHED;
909 state_sent_ok (session_t * s, http_session_t * hs,
910 http_state_machine_called_from_t cf)
912 http_static_server_main_t *hsm = &http_static_server_main;
919 /* What kind of dog food are we serving? */
920 suffix = (char *) (hs->path + vec_len (hs->path) - 1);
921 while ((u8 *) suffix >= hs->path && *suffix != '.')
924 http_type = "text/html";
925 if (!clib_strcmp (suffix, "css"))
926 http_type = "text/css";
927 else if (!clib_strcmp (suffix, "js"))
928 http_type = "text/javascript";
929 else if (!clib_strcmp (suffix, "json"))
930 http_type = "application/json";
934 clib_warning ("BUG: hs->data not set for session %d",
941 * Send an http response, which needs the current time,
942 * the expiration time, and the data length
944 now = clib_timebase_now (&hsm->timebase);
945 http_response = format (0, http_response_template,
947 format_clib_timebase_time, now,
949 format_clib_timebase_time, now + 600.0,
950 http_type, vec_len (hs->data));
951 offset = static_send_data (hs, http_response, vec_len (http_response), 0);
952 if (offset != vec_len (http_response))
954 clib_warning ("BUG: couldn't send response header!");
958 vec_free (http_response);
960 /* Send data from the beginning... */
962 hs->session_state = HTTP_STATE_SEND_MORE_DATA;
966 static void *state_funcs[HTTP_STATE_N_STATES] = {
968 /* Waiting for GET, POST, etc. */
973 state_send_more_data,
977 http_static_server_rx_tx_callback (session_t * s,
978 http_state_machine_called_from_t cf)
981 int (*fp) (session_t *, http_session_t *, http_state_machine_called_from_t);
984 /* Acquire a reader lock on the session table */
985 http_static_server_sessions_reader_lock ();
986 hs = http_static_server_session_lookup (s->thread_index, s->session_index);
990 clib_warning ("No http session for thread %d session_index %d",
991 s->thread_index, s->session_index);
992 http_static_server_sessions_reader_unlock ();
996 /* Execute state machine for this session */
999 fp = state_funcs[hs->session_state];
1000 rv = (*fp) (s, hs, cf);
1002 goto session_closed;
1006 /* Reset the session expiration timer */
1007 http_static_server_session_timer_stop (hs);
1008 http_static_server_session_timer_start (hs);
1011 http_static_server_sessions_reader_unlock ();
1016 http_static_server_rx_callback (session_t * s)
1018 return http_static_server_rx_tx_callback (s, CALLED_FROM_RX);
1022 http_static_server_tx_callback (session_t * s)
1024 return http_static_server_rx_tx_callback (s, CALLED_FROM_TX);
1028 /** \brief Session accept callback
1032 http_static_server_session_accept_callback (session_t * s)
1034 http_static_server_main_t *hsm = &http_static_server_main;
1037 hsm->vpp_queue[s->thread_index] =
1038 session_main_get_vpp_event_queue (s->thread_index);
1040 http_static_server_sessions_writer_lock ();
1042 hs = http_static_server_session_alloc (s->thread_index);
1043 http_static_server_session_lookup_add (s->thread_index, s->session_index,
1045 hs->rx_fifo = s->rx_fifo;
1046 hs->tx_fifo = s->tx_fifo;
1047 hs->vpp_session_index = s->session_index;
1048 hs->vpp_session_handle = session_handle (s);
1049 hs->session_state = HTTP_STATE_ESTABLISHED;
1050 http_static_server_session_timer_start (hs);
1052 http_static_server_sessions_writer_unlock ();
1054 s->session_state = SESSION_STATE_READY;
1058 /** \brief Session disconnect callback
1062 http_static_server_session_disconnect_callback (session_t * s)
1064 http_static_server_main_t *hsm = &http_static_server_main;
1065 vnet_disconnect_args_t _a = { 0 }, *a = &_a;
1067 a->handle = session_handle (s);
1068 a->app_index = hsm->app_index;
1069 vnet_disconnect_session (a);
1072 /** \brief Session reset callback
1076 http_static_server_session_reset_callback (session_t * s)
1078 http_static_server_main_t *hsm = &http_static_server_main;
1079 vnet_disconnect_args_t _a = { 0 }, *a = &_a;
1081 a->handle = session_handle (s);
1082 a->app_index = hsm->app_index;
1083 vnet_disconnect_session (a);
1087 http_static_server_session_connected_callback (u32 app_index, u32 api_context,
1089 session_error_t err)
1091 clib_warning ("called...");
1096 http_static_server_add_segment_callback (u32 client_index, u64 segment_handle)
1098 clib_warning ("called...");
1103 http_static_session_cleanup (session_t * s, session_cleanup_ntf_t ntf)
1107 if (ntf == SESSION_CLEANUP_TRANSPORT)
1110 http_static_server_sessions_writer_lock ();
1112 hs = http_static_server_session_lookup (s->thread_index, s->session_index);
1116 http_static_server_detach_cache_entry (hs);
1117 http_static_server_session_lookup_del (hs->thread_index,
1118 hs->vpp_session_index);
1119 vec_free (hs->rx_buf);
1120 http_static_server_session_free (hs);
1123 http_static_server_sessions_writer_unlock ();
1126 /** \brief Session-layer virtual function table
1128 static session_cb_vft_t http_static_server_session_cb_vft = {
1129 .session_accept_callback = http_static_server_session_accept_callback,
1130 .session_disconnect_callback =
1131 http_static_server_session_disconnect_callback,
1132 .session_connected_callback = http_static_server_session_connected_callback,
1133 .add_segment_callback = http_static_server_add_segment_callback,
1134 .builtin_app_rx_callback = http_static_server_rx_callback,
1135 .builtin_app_tx_callback = http_static_server_tx_callback,
1136 .session_reset_callback = http_static_server_session_reset_callback,
1137 .session_cleanup_callback = http_static_session_cleanup,
1141 http_static_server_attach ()
1143 vnet_app_add_cert_key_pair_args_t _ck_pair, *ck_pair = &_ck_pair;
1144 http_static_server_main_t *hsm = &http_static_server_main;
1145 u64 options[APP_OPTIONS_N_OPTIONS];
1146 vnet_app_attach_args_t _a, *a = &_a;
1147 u32 segment_size = 128 << 20;
1149 clib_memset (a, 0, sizeof (*a));
1150 clib_memset (options, 0, sizeof (options));
1152 if (hsm->private_segment_size)
1153 segment_size = hsm->private_segment_size;
1155 a->api_client_index = ~0;
1156 a->name = format (0, "test_http_static_server");
1157 a->session_cb_vft = &http_static_server_session_cb_vft;
1158 a->options = options;
1159 a->options[APP_OPTIONS_SEGMENT_SIZE] = segment_size;
1160 a->options[APP_OPTIONS_RX_FIFO_SIZE] =
1161 hsm->fifo_size ? hsm->fifo_size : 8 << 10;
1162 a->options[APP_OPTIONS_TX_FIFO_SIZE] =
1163 hsm->fifo_size ? hsm->fifo_size : 32 << 10;
1164 a->options[APP_OPTIONS_FLAGS] = APP_OPTIONS_FLAGS_IS_BUILTIN;
1165 a->options[APP_OPTIONS_PREALLOC_FIFO_PAIRS] = hsm->prealloc_fifos;
1166 a->options[APP_OPTIONS_TLS_ENGINE] = CRYPTO_ENGINE_OPENSSL;
1168 if (vnet_application_attach (a))
1171 clib_warning ("failed to attach server");
1175 hsm->app_index = a->app_index;
1177 clib_memset (ck_pair, 0, sizeof (*ck_pair));
1178 ck_pair->cert = (u8 *) test_srv_crt_rsa;
1179 ck_pair->key = (u8 *) test_srv_key_rsa;
1180 ck_pair->cert_len = test_srv_crt_rsa_len;
1181 ck_pair->key_len = test_srv_key_rsa_len;
1182 vnet_app_add_cert_key_pair (ck_pair);
1183 hsm->ckpair_index = ck_pair->index;
1189 http_static_transport_needs_crypto (transport_proto_t proto)
1191 return proto == TRANSPORT_PROTO_TLS || proto == TRANSPORT_PROTO_DTLS ||
1192 proto == TRANSPORT_PROTO_QUIC;
1196 http_static_server_listen ()
1198 http_static_server_main_t *hsm = &http_static_server_main;
1199 session_endpoint_cfg_t sep = SESSION_ENDPOINT_CFG_NULL;
1200 vnet_listen_args_t _a, *a = &_a;
1201 char *uri = "tcp://0.0.0.0/80";
1204 clib_memset (a, 0, sizeof (*a));
1205 a->app_index = hsm->app_index;
1208 uri = (char *) hsm->uri;
1210 if (parse_uri (uri, &sep))
1213 clib_memcpy (&a->sep_ext, &sep, sizeof (sep));
1214 if (http_static_transport_needs_crypto (a->sep_ext.transport_proto))
1216 session_endpoint_alloc_ext_cfg (&a->sep_ext,
1217 TRANSPORT_ENDPT_EXT_CFG_CRYPTO);
1218 a->sep_ext.ext_cfg->crypto.ckpair_index = hsm->ckpair_index;
1221 rv = vnet_listen (a);
1222 if (a->sep_ext.ext_cfg)
1223 clib_mem_free (a->sep_ext.ext_cfg);
1228 http_static_server_session_close_cb (void *hs_handlep)
1230 http_static_server_main_t *hsm = &http_static_server_main;
1233 hs_handle = pointer_to_uword (hs_handlep);
1235 http_static_server_session_get (hs_handle >> 24, hs_handle & 0x00FFFFFF);
1237 if (hsm->debug_level > 1)
1238 clib_warning ("terminate thread %d index %d hs %llx",
1239 hs_handle >> 24, hs_handle & 0x00FFFFFF, hs);
1242 hs->timer_handle = ~0;
1243 http_static_server_session_disconnect (hs);
1246 /** \brief Expired session timer-wheel callback
1249 http_expired_timers_dispatch (u32 * expired_timers)
1254 for (i = 0; i < vec_len (expired_timers); i++)
1256 /* Get session handle. The first bit is the timer id */
1257 hs_handle = expired_timers[i] & 0x7FFFFFFF;
1258 session_send_rpc_evt_to_thread (hs_handle >> 24,
1259 http_static_server_session_close_cb,
1260 uword_to_pointer (hs_handle, void *));
1264 /** \brief Timer-wheel expiration process
1267 http_static_server_process (vlib_main_t * vm, vlib_node_runtime_t * rt,
1270 http_static_server_main_t *hsm = &http_static_server_main;
1271 f64 now, timeout = 1.0;
1272 uword *event_data = 0;
1273 uword __clib_unused event_type;
1277 vlib_process_wait_for_event_or_clock (vm, timeout);
1278 now = vlib_time_now (vm);
1279 event_type = vlib_process_get_events (vm, (uword **) & event_data);
1282 clib_spinlock_lock (&http_static_server_main.tw_lock);
1283 tw_timer_expire_timers_2t_1w_2048sl (&hsm->tw, now);
1284 clib_spinlock_unlock (&http_static_server_main.tw_lock);
1286 vec_reset_length (event_data);
1292 VLIB_REGISTER_NODE (http_static_server_process_node) =
1294 .function = http_static_server_process,
1295 .type = VLIB_NODE_TYPE_PROCESS,
1296 .name = "static-http-server-process",
1297 .state = VLIB_NODE_STATE_DISABLED,
1302 http_static_server_create (vlib_main_t * vm)
1304 vlib_thread_main_t *vtm = vlib_get_thread_main ();
1305 http_static_server_main_t *hsm = &http_static_server_main;
1309 num_threads = 1 /* main thread */ + vtm->n_threads;
1310 vec_validate (hsm->vpp_queue, num_threads - 1);
1311 vec_validate (hsm->sessions, num_threads - 1);
1312 vec_validate (hsm->session_to_http_session, num_threads - 1);
1314 clib_rwlock_init (&hsm->sessions_lock);
1315 clib_spinlock_init (&hsm->tw_lock);
1317 if (http_static_server_attach ())
1319 clib_warning ("failed to attach server");
1322 if (http_static_server_listen ())
1324 clib_warning ("failed to start listening");
1328 /* Init path-to-cache hash table */
1329 BV (clib_bihash_init) (&hsm->name_to_data, "http cache", 128, 32 << 20);
1331 hsm->get_url_handlers = hash_create_string (0, sizeof (uword));
1332 hsm->post_url_handlers = hash_create_string (0, sizeof (uword));
1334 /* Init timer wheel and process */
1335 tw_timer_wheel_init_2t_1w_2048sl (&hsm->tw, http_expired_timers_dispatch,
1336 1.0 /* timer interval */ , ~0);
1337 vlib_node_set_state (vm, http_static_server_process_node.index,
1338 VLIB_NODE_STATE_POLLING);
1339 n = vlib_get_node (vm, http_static_server_process_node.index);
1340 vlib_start_process (vm, n->runtime_index);
1345 /** \brief API helper function for vl_api_http_static_enable_t messages
1348 http_static_server_enable_api (u32 fifo_size, u32 cache_limit,
1350 u32 private_segment_size,
1351 u8 * www_root, u8 * uri)
1353 http_static_server_main_t *hsm = &http_static_server_main;
1356 hsm->fifo_size = fifo_size;
1357 hsm->cache_limit = cache_limit;
1358 hsm->prealloc_fifos = prealloc_fifos;
1359 hsm->private_segment_size = private_segment_size;
1360 hsm->www_root = format (0, "%s%c", www_root, 0);
1361 hsm->uri = format (0, "%s%c", uri, 0);
1363 if (vec_len (hsm->www_root) < 2)
1364 return VNET_API_ERROR_INVALID_VALUE;
1366 if (hsm->my_client_index != ~0)
1367 return VNET_API_ERROR_APP_ALREADY_ATTACHED;
1369 vnet_session_enable_disable (hsm->vlib_main, 1 /* turn on TCP, etc. */ );
1371 rv = http_static_server_create (hsm->vlib_main);
1377 vec_free (hsm->www_root);
1378 vec_free (hsm->uri);
1379 return VNET_API_ERROR_INIT_FAILED;
1384 static clib_error_t *
1385 http_static_server_create_command_fn (vlib_main_t * vm,
1386 unformat_input_t * input,
1387 vlib_cli_command_t * cmd)
1389 http_static_server_main_t *hsm = &http_static_server_main;
1390 unformat_input_t _line_input, *line_input = &_line_input;
1395 hsm->prealloc_fifos = 0;
1396 hsm->private_segment_size = 0;
1398 /* 10mb cache limit, before LRU occurs */
1399 hsm->cache_limit = 10 << 20;
1401 /* Get a line of input. */
1402 if (!unformat_user (input, unformat_line_input, line_input))
1405 while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
1407 if (unformat (line_input, "www-root %s", &www_root))
1410 if (unformat (line_input, "prealloc-fifos %d", &hsm->prealloc_fifos))
1412 else if (unformat (line_input, "private-segment-size %U",
1413 unformat_memory_size, &seg_size))
1415 if (seg_size >= 0x100000000ULL)
1417 vlib_cli_output (vm, "private segment size %llu, too large",
1421 hsm->private_segment_size = seg_size;
1423 else if (unformat (line_input, "fifo-size %d", &hsm->fifo_size))
1424 hsm->fifo_size <<= 10;
1425 else if (unformat (line_input, "cache-size %U", unformat_memory_size,
1428 if (hsm->cache_limit < (128 << 10))
1430 return clib_error_return (0,
1431 "cache-size must be at least 128kb");
1435 else if (unformat (line_input, "uri %s", &hsm->uri))
1437 else if (unformat (line_input, "debug %d", &hsm->debug_level))
1439 else if (unformat (line_input, "debug"))
1440 hsm->debug_level = 1;
1442 return clib_error_return (0, "unknown input `%U'",
1443 format_unformat_error, line_input);
1445 unformat_free (line_input);
1450 return clib_error_return (0, "Must specify www-root <path>");
1453 if (hsm->my_client_index != (u32) ~ 0)
1455 vec_free (www_root);
1456 return clib_error_return (0, "http server already running...");
1459 hsm->www_root = www_root;
1461 vnet_session_enable_disable (vm, 1 /* turn on TCP, etc. */ );
1463 rv = http_static_server_create (vm);
1469 vec_free (hsm->www_root);
1470 return clib_error_return (0, "server_create returned %d", rv);
1476 * Enable the static http server
1479 * This command enables the static http server. Only the www-root
1480 * parameter is required
1482 * http static server www-root /tmp/www uri tcp://0.0.0.0/80 cache-size 2m
1484 * @cliexcmd{http static server www-root <path> [prealloc-fios <nn>]
1485 * [private-segment-size <nnMG>] [fifo-size <nbytes>] [uri <uri>]}
1488 VLIB_CLI_COMMAND (http_static_server_create_command, static) =
1490 .path = "http static server",
1491 .short_help = "http static server www-root <path> [prealloc-fifos <nn>]\n"
1492 "[private-segment-size <nnMG>] [fifo-size <nbytes>] [uri <uri>]\n"
1494 .function = http_static_server_create_command_fn,
1498 /** \brief format a file cache entry
1501 format_hsm_cache_entry (u8 * s, va_list * args)
1503 file_data_cache_t *ep = va_arg (*args, file_data_cache_t *);
1504 f64 now = va_arg (*args, f64);
1509 s = format (s, "%40s%12s%20s", "File", "Size", "Age");
1512 s = format (s, "%40s%12lld%20.2f", ep->filename, vec_len (ep->data),
1513 now - ep->last_used);
1518 format_http_session_state (u8 * s, va_list * args)
1520 http_session_state_t state = va_arg (*args, http_session_state_t);
1521 char *state_string = "bogus!";
1525 case HTTP_STATE_CLOSED:
1526 state_string = "closed";
1528 case HTTP_STATE_ESTABLISHED:
1529 state_string = "established";
1531 case HTTP_STATE_OK_SENT:
1532 state_string = "ok sent";
1534 case HTTP_STATE_SEND_MORE_DATA:
1535 state_string = "send more data";
1541 return format (s, "%s", state_string);
1545 format_http_session (u8 * s, va_list * args)
1547 http_session_t *hs = va_arg (*args, http_session_t *);
1548 int verbose = va_arg (*args, int);
1550 s = format (s, "[%d]: state %U", hs->session_index,
1551 format_http_session_state, hs->session_state);
1554 s = format (s, "\n path %s, data length %u, data_offset %u",
1555 hs->path ? hs->path : (u8 *) "[none]",
1556 vec_len (hs->data), hs->data_offset);
1561 static clib_error_t *
1562 http_show_static_server_command_fn (vlib_main_t * vm,
1563 unformat_input_t * input,
1564 vlib_cli_command_t * cmd)
1566 http_static_server_main_t *hsm = &http_static_server_main;
1567 file_data_cache_t *ep, **entries = 0;
1570 int show_sessions = 0;
1574 if (hsm->www_root == 0)
1575 return clib_error_return (0, "Static server disabled");
1577 while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
1579 if (unformat (input, "verbose %d", &verbose))
1581 else if (unformat (input, "verbose"))
1583 else if (unformat (input, "cache"))
1585 else if (unformat (input, "sessions"))
1591 if ((show_cache + show_sessions) == 0)
1592 return clib_error_return (0, "specify one or more of cache, sessions");
1599 (vm, "www_root %s, cache size %lld bytes, limit %lld bytes, "
1601 hsm->www_root, hsm->cache_size, hsm->cache_limit,
1602 hsm->cache_evictions);
1606 now = vlib_time_now (vm);
1608 vlib_cli_output (vm, "%U", format_hsm_cache_entry, 0 /* header */ ,
1611 for (index = hsm->first_index; index != ~0;)
1613 ep = pool_elt_at_index (hsm->cache_pool, index);
1614 index = ep->next_index;
1615 vlib_cli_output (vm, "%U", format_hsm_cache_entry, ep, now);
1618 vlib_cli_output (vm, "%40s%12lld", "Total Size", hsm->cache_size);
1625 u32 *session_indices = 0;
1629 http_static_server_sessions_reader_lock ();
1631 for (i = 0; i < vec_len (hsm->sessions); i++)
1634 pool_foreach (hs, hsm->sessions[i])
1636 vec_add1 (session_indices, hs - hsm->sessions[i]);
1640 for (j = 0; j < vec_len (session_indices); j++)
1642 vlib_cli_output (vm, "%U", format_http_session,
1644 (hsm->sessions[i], session_indices[j]),
1647 vec_reset_length (session_indices);
1649 http_static_server_sessions_reader_unlock ();
1650 vec_free (session_indices);
1656 * Display static http server cache statistics
1659 * This command shows the contents of the static http server cache
1661 * show http static server
1663 * @cliexcmd{show http static server sessions cache [verbose [nn]]}
1666 VLIB_CLI_COMMAND (http_show_static_server_command, static) =
1668 .path = "show http static server",
1669 .short_help = "show http static server sessions cache [verbose [<nn>]]",
1670 .function = http_show_static_server_command_fn,
1674 static clib_error_t *
1675 http_clear_static_cache_command_fn (vlib_main_t * vm,
1676 unformat_input_t * input,
1677 vlib_cli_command_t * cmd)
1679 http_static_server_main_t *hsm = &http_static_server_main;
1680 file_data_cache_t *dp;
1683 BVT (clib_bihash_kv) kv;
1685 if (hsm->www_root == 0)
1686 return clib_error_return (0, "Static server disabled");
1688 http_static_server_sessions_reader_lock ();
1690 /* Walk the LRU list to find active entries */
1691 free_index = hsm->last_index;
1692 while (free_index != ~0)
1694 dp = pool_elt_at_index (hsm->cache_pool, free_index);
1695 free_index = dp->prev_index;
1696 /* Which could be in use... */
1700 free_index = dp->next_index;
1703 kv.key = (u64) (dp->filename);
1705 if (BV (clib_bihash_add_del) (&hsm->name_to_data, &kv,
1706 0 /* is_add */ ) < 0)
1708 clib_warning ("BUG: cache clear delete '%s' FAILED!", dp->filename);
1711 lru_remove (hsm, dp);
1712 hsm->cache_size -= vec_len (dp->data);
1713 hsm->cache_evictions++;
1714 vec_free (dp->filename);
1715 vec_free (dp->data);
1716 if (hsm->debug_level > 1)
1717 clib_warning ("pool put index %d", dp - hsm->cache_pool);
1718 pool_put (hsm->cache_pool, dp);
1719 free_index = hsm->last_index;
1721 http_static_server_sessions_reader_unlock ();
1723 vlib_cli_output (vm, "Note: %d busy items still in cache...", busy_items);
1725 vlib_cli_output (vm, "Cache cleared...");
1730 * Clear the static http server cache, to force the server to
1731 * reload content from backing files
1734 * This command clear the static http server cache
1736 * clear http static cache
1738 * @cliexcmd{clear http static cache}
1741 VLIB_CLI_COMMAND (clear_http_static_cache_command, static) =
1743 .path = "clear http static cache",
1744 .short_help = "clear http static cache",
1745 .function = http_clear_static_cache_command_fn,
1749 static clib_error_t *
1750 http_static_server_main_init (vlib_main_t * vm)
1752 http_static_server_main_t *hsm = &http_static_server_main;
1754 hsm->my_client_index = ~0;
1755 hsm->vlib_main = vm;
1756 hsm->first_index = hsm->last_index = ~0;
1758 clib_timebase_init (&hsm->timebase, 0 /* GMT */ ,
1759 CLIB_TIMEBASE_DAYLIGHT_NONE,
1760 &vm->clib_time /* share the system clock */ );
1765 VLIB_INIT_FUNCTION (http_static_server_main_init);
1768 * fd.io coding-style-patch-verification: ON
1771 * eval: (c-set-style "gnu")