http_static: incorporate builtinurl plugin
[vpp.git] / src / plugins / http_static / static_server.c
1 /*
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:
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
16 #include <http_static/http_static.h>
17 #include <vppinfra/bihash_template.c>
18 #include <vppinfra/unix.h>
19
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <unistd.h>
23
24 /** @file static_server.c
25  *  Static http server, sufficient to serve .html / .css / .js content.
26  */
27 /*? %%clicmd:group_label Static HTTP Server %% ?*/
28
29 #define HSS_FIFO_THRESH (16 << 10)
30
31 hss_main_t hss_main;
32
33 static void
34 hss_cache_lock (void)
35 {
36   clib_spinlock_lock (&hss_main.cache_lock);
37 }
38
39 static void
40 hss_cache_unlock (void)
41 {
42   clib_spinlock_unlock (&hss_main.cache_lock);
43 }
44
45 static hss_session_t *
46 hss_session_alloc (u32 thread_index)
47 {
48   hss_main_t *hsm = &hss_main;
49   hss_session_t *hs;
50
51   pool_get_zero (hsm->sessions[thread_index], hs);
52   hs->session_index = hs - hsm->sessions[thread_index];
53   hs->thread_index = thread_index;
54   hs->cache_pool_index = ~0;
55   return hs;
56 }
57
58 static hss_session_t *
59 hss_session_get (u32 thread_index, u32 hs_index)
60 {
61   hss_main_t *hsm = &hss_main;
62   if (pool_is_free_index (hsm->sessions[thread_index], hs_index))
63     return 0;
64   return pool_elt_at_index (hsm->sessions[thread_index], hs_index);
65 }
66
67 static void
68 hss_session_free (hss_session_t *hs)
69 {
70   hss_main_t *hsm = &hss_main;
71
72   pool_put (hsm->sessions[hs->thread_index], hs);
73
74   if (CLIB_DEBUG)
75     {
76       u32 save_thread_index;
77       save_thread_index = hs->thread_index;
78       /* Poison the entry, preserve timer state and thread index */
79       memset (hs, 0xfa, sizeof (*hs));
80       hs->thread_index = save_thread_index;
81     }
82 }
83
84 /** \brief Detach cache entry from session
85  */
86 static void
87 hss_detach_cache_entry (hss_session_t *hs)
88 {
89   hss_main_t *hsm = &hss_main;
90   hss_cache_entry_t *ep;
91
92   /*
93    * Decrement cache pool entry reference count
94    * Note that if e.g. a file lookup fails, the cache pool index
95    * won't be set
96    */
97   if (hs->cache_pool_index != ~0)
98     {
99       ep = pool_elt_at_index (hsm->cache_pool, hs->cache_pool_index);
100       ep->inuse--;
101       if (hsm->debug_level > 1)
102         clib_warning ("index %d refcnt now %d", hs->cache_pool_index,
103                       ep->inuse);
104     }
105   hs->cache_pool_index = ~0;
106   if (hs->free_data)
107     vec_free (hs->data);
108   hs->data = 0;
109   hs->data_offset = 0;
110   hs->free_data = 0;
111   vec_free (hs->path);
112 }
113
114 /** \brief Disconnect a session
115  */
116 static void
117 hss_session_disconnect_transport (hss_session_t *hs)
118 {
119   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
120   a->handle = hs->vpp_session_handle;
121   a->app_index = hss_main.app_index;
122   vnet_disconnect_session (a);
123 }
124
125 /** \brief Sanity-check the forward and reverse LRU lists
126  */
127 static inline void
128 lru_validate (hss_main_t *hsm)
129 {
130 #if CLIB_DEBUG > 0
131   f64 last_timestamp;
132   u32 index;
133   int i;
134   hss_cache_entry_t *ep;
135
136   last_timestamp = 1e70;
137   for (i = 1, index = hsm->first_index; index != ~0;)
138     {
139       ep = pool_elt_at_index (hsm->cache_pool, index);
140       index = ep->next_index;
141       /* Timestamps should be smaller (older) as we walk the fwd list */
142       if (ep->last_used > last_timestamp)
143         {
144           clib_warning ("%d[%d]: last used %.6f, last_timestamp %.6f",
145                         ep - hsm->cache_pool, i,
146                         ep->last_used, last_timestamp);
147         }
148       last_timestamp = ep->last_used;
149       i++;
150     }
151
152   last_timestamp = 0.0;
153   for (i = 1, index = hsm->last_index; index != ~0;)
154     {
155       ep = pool_elt_at_index (hsm->cache_pool, index);
156       index = ep->prev_index;
157       /* Timestamps should be larger (newer) as we walk the rev list */
158       if (ep->last_used < last_timestamp)
159         {
160           clib_warning ("%d[%d]: last used %.6f, last_timestamp %.6f",
161                         ep - hsm->cache_pool, i,
162                         ep->last_used, last_timestamp);
163         }
164       last_timestamp = ep->last_used;
165       i++;
166     }
167 #endif
168 }
169
170 /** \brief Remove a data cache entry from the LRU lists
171  */
172 static inline void
173 lru_remove (hss_main_t *hsm, hss_cache_entry_t *ep)
174 {
175   hss_cache_entry_t *next_ep, *prev_ep;
176   u32 ep_index;
177
178   lru_validate (hsm);
179
180   ep_index = ep - hsm->cache_pool;
181
182   /* Deal with list heads */
183   if (ep_index == hsm->first_index)
184     hsm->first_index = ep->next_index;
185   if (ep_index == hsm->last_index)
186     hsm->last_index = ep->prev_index;
187
188   /* Fix next->prev */
189   if (ep->next_index != ~0)
190     {
191       next_ep = pool_elt_at_index (hsm->cache_pool, ep->next_index);
192       next_ep->prev_index = ep->prev_index;
193     }
194   /* Fix prev->next */
195   if (ep->prev_index != ~0)
196     {
197       prev_ep = pool_elt_at_index (hsm->cache_pool, ep->prev_index);
198       prev_ep->next_index = ep->next_index;
199     }
200   lru_validate (hsm);
201 }
202
203 /** \brief Add an entry to the LRU lists, tag w/ supplied timestamp
204  */
205 static inline void
206 lru_add (hss_main_t *hsm, hss_cache_entry_t *ep, f64 now)
207 {
208   hss_cache_entry_t *next_ep;
209   u32 ep_index;
210
211   lru_validate (hsm);
212
213   ep_index = ep - hsm->cache_pool;
214
215   /*
216    * Re-add at the head of the forward LRU list,
217    * tail of the reverse LRU list
218    */
219   if (hsm->first_index != ~0)
220     {
221       next_ep = pool_elt_at_index (hsm->cache_pool, hsm->first_index);
222       next_ep->prev_index = ep_index;
223     }
224
225   ep->prev_index = ~0;
226
227   /* ep now the new head of the LRU forward list */
228   ep->next_index = hsm->first_index;
229   hsm->first_index = ep_index;
230
231   /* single session case: also the tail of the reverse LRU list */
232   if (hsm->last_index == ~0)
233     hsm->last_index = ep_index;
234   ep->last_used = now;
235
236   lru_validate (hsm);
237 }
238
239 /** \brief Remove and re-add a cache entry from/to the LRU lists
240  */
241 static inline void
242 lru_update (hss_main_t *hsm, hss_cache_entry_t *ep, f64 now)
243 {
244   lru_remove (hsm, ep);
245   lru_add (hsm, ep, now);
246 }
247
248 static void
249 start_send_data (hss_session_t *hs, http_status_code_t status)
250 {
251   http_msg_t msg;
252   session_t *ts;
253   int rv;
254
255   ts = session_get (hs->vpp_session_index, hs->thread_index);
256
257   msg.type = HTTP_MSG_REPLY;
258   msg.code = status;
259   msg.content_type = HTTP_CONTENT_TEXT_HTML;
260   msg.data.len = vec_len (hs->data);
261
262   if (vec_len (hs->data) > hss_main.use_ptr_thresh)
263     {
264       msg.data.type = HTTP_MSG_DATA_PTR;
265       rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (msg), (u8 *) &msg);
266       ASSERT (rv == sizeof (msg));
267
268       uword data = pointer_to_uword (hs->data);
269       rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (data), (u8 *) &data);
270       ASSERT (rv == sizeof (sizeof (data)));
271
272       goto done;
273     }
274
275   msg.data.type = HTTP_MSG_DATA_INLINE;
276
277   rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (msg), (u8 *) &msg);
278   ASSERT (rv == sizeof (msg));
279
280   if (!msg.data.len)
281     goto done;
282
283   rv = svm_fifo_enqueue (ts->tx_fifo, vec_len (hs->data), hs->data);
284
285   if (rv != vec_len (hs->data))
286     {
287       hs->data_offset = rv;
288       svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
289     }
290
291 done:
292
293   if (svm_fifo_set_event (ts->tx_fifo))
294     session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
295 }
296
297 static int
298 try_url_handler (hss_main_t *hsm, hss_session_t *hs, http_req_method_t rt,
299                  u8 *request, http_status_code_t *sc)
300 {
301   uword *p, *url_table;
302   hss_url_handler_t fp;
303   int rv;
304
305   if (!hsm->enable_url_handlers)
306     return -1;
307
308   /* Look for built-in GET / POST handlers */
309   url_table =
310     (rt == HTTP_REQ_GET) ? hsm->get_url_handlers : hsm->post_url_handlers;
311
312   p = hash_get_mem (url_table, request);
313   if (!p)
314     return -1;
315
316   if (hsm->debug_level > 0)
317     clib_warning ("%s '%s'", (rt == HTTP_REQ_GET) ? "GET" : "POST", request);
318
319   fp = (hss_url_handler_t) p[0];
320   hs->path = 0;
321   rv = fp (rt, request, hs);
322   if (rv)
323     {
324       clib_warning ("builtin handler %llx hit on %s '%s' but failed!", p[0],
325                     (rt == HTTP_REQ_GET) ? "GET" : "POST", request);
326       *sc = HTTP_STATUS_NOT_FOUND;
327     }
328
329   return 0;
330 }
331
332 static int
333 find_data (hss_session_t *hs, http_req_method_t rt, u8 *request)
334 {
335   http_status_code_t sc = HTTP_STATUS_OK;
336   hss_main_t *hsm = &hss_main;
337   struct stat _sb, *sb = &_sb;
338   clib_error_t *error;
339   u8 *path;
340
341   if (!try_url_handler (hsm, hs, rt, request, &sc))
342     goto done;
343
344   /*
345    * Construct the file to open
346    * Browsers are capable of sporadically including a leading '/'
347    */
348   if (request[0] == '/')
349     path = format (0, "%s%s%c", hsm->www_root, request, 0);
350   else
351     path = format (0, "%s/%s%c", hsm->www_root, request, 0);
352
353   if (hsm->debug_level > 0)
354     clib_warning ("%s '%s'", (rt == HTTP_REQ_GET) ? "GET" : "POST", path);
355
356   /* Try to find the file. 2x special cases to find index.html */
357   if (stat ((char *) path, sb) < 0      /* cant even stat the file */
358       || sb->st_size < 20       /* file too small */
359       || (sb->st_mode & S_IFMT) != S_IFREG /* not a regular file */ )
360     {
361       u32 save_length = vec_len (path) - 1;
362       /* Try appending "index.html"... */
363       _vec_len (path) -= 1;
364       path = format (path, "index.html%c", 0);
365       if (stat ((char *) path, sb) < 0  /* cant even stat the file */
366           || sb->st_size < 20   /* file too small */
367           || (sb->st_mode & S_IFMT) != S_IFREG /* not a regular file */ )
368         {
369           _vec_len (path) = save_length;
370           path = format (path, "/index.html%c", 0);
371
372           /* Send a redirect, otherwise the browser will confuse itself */
373           if (stat ((char *) path, sb) < 0      /* cant even stat the file */
374               || sb->st_size < 20       /* file too small */
375               || (sb->st_mode & S_IFMT) != S_IFREG /* not a regular file */ )
376             {
377               sc = HTTP_STATUS_NOT_FOUND;
378               goto done;
379             }
380           else
381             {
382               transport_endpoint_t endpoint;
383               transport_proto_t proto;
384               u16 local_port;
385               int print_port = 0;
386               u8 *port_str = 0;
387               session_t *ts;
388
389               /*
390                * To make this bit work correctly, we need to know our local
391                * IP address, etc. and send it in the redirect...
392                */
393               u8 *redirect;
394
395               vec_delete (path, vec_len (hsm->www_root) - 1, 0);
396
397               ts = session_get (hs->vpp_session_index, hs->thread_index);
398               session_get_endpoint (ts, &endpoint, 1 /* is_local */);
399
400               local_port = clib_net_to_host_u16 (endpoint.port);
401
402               proto = session_type_transport_proto (ts->session_type);
403
404               if ((proto == TRANSPORT_PROTO_TCP && local_port != 80)
405                   || (proto == TRANSPORT_PROTO_TLS && local_port != 443))
406                 {
407                   print_port = 1;
408                   port_str = format (0, ":%u", (u32) local_port);
409                 }
410
411               redirect = format (0, "HTTP/1.1 301 Moved Permanently\r\n"
412                                  "Location: http%s://%U%s%s\r\n\r\n",
413                                  proto == TRANSPORT_PROTO_TLS ? "s" : "",
414                                  format_ip46_address, &endpoint.ip,
415                                  endpoint.is_ip4,
416                                  print_port ? port_str : (u8 *) "", path);
417               if (hsm->debug_level > 0)
418                 clib_warning ("redirect: %s", redirect);
419
420               vec_free (port_str);
421
422               hs->data = redirect;
423               goto done;
424             }
425         }
426     }
427
428   /* find or read the file if we haven't done so yet. */
429   if (hs->data == 0)
430     {
431       BVT (clib_bihash_kv) kv;
432       hss_cache_entry_t *ce;
433
434       hs->path = path;
435
436       /* First, try the cache */
437       kv.key = (u64) hs->path;
438       if (BV (clib_bihash_search) (&hsm->name_to_data, &kv, &kv) == 0)
439         {
440           if (hsm->debug_level > 1)
441             clib_warning ("lookup '%s' returned %lld", kv.key, kv.value);
442
443           hss_cache_lock ();
444
445           /* found the data.. */
446           ce = pool_elt_at_index (hsm->cache_pool, kv.value);
447           hs->data = ce->data;
448           /* Update the cache entry, mark it in-use */
449           lru_update (hsm, ce, vlib_time_now (vlib_get_main ()));
450           hs->cache_pool_index = ce - hsm->cache_pool;
451           ce->inuse++;
452           if (hsm->debug_level > 1)
453             clib_warning ("index %d refcnt now %d", hs->cache_pool_index,
454                           ce->inuse);
455
456           hss_cache_unlock ();
457         }
458       else
459         {
460           hss_cache_lock ();
461
462           if (hsm->debug_level > 1)
463             clib_warning ("lookup '%s' failed", kv.key, kv.value);
464           /* Need to recycle one (or more cache) entries? */
465           if (hsm->cache_size > hsm->cache_limit)
466             {
467               int free_index = hsm->last_index;
468
469               while (free_index != ~0)
470                 {
471                   /* pick the LRU */
472                   ce = pool_elt_at_index (hsm->cache_pool, free_index);
473                   free_index = ce->prev_index;
474                   /* Which could be in use... */
475                   if (ce->inuse)
476                     {
477                       if (hsm->debug_level > 1)
478                         clib_warning ("index %d in use refcnt %d",
479                                       ce - hsm->cache_pool, ce->inuse);
480                     }
481                   kv.key = (u64) (ce->filename);
482                   kv.value = ~0ULL;
483                   if (BV (clib_bihash_add_del) (&hsm->name_to_data, &kv,
484                                                 0 /* is_add */ ) < 0)
485                     {
486                       clib_warning ("LRU delete '%s' FAILED!", ce->filename);
487                     }
488                   else if (hsm->debug_level > 1)
489                     clib_warning ("LRU delete '%s' ok", ce->filename);
490
491                   lru_remove (hsm, ce);
492                   hsm->cache_size -= vec_len (ce->data);
493                   hsm->cache_evictions++;
494                   vec_free (ce->filename);
495                   vec_free (ce->data);
496                   if (hsm->debug_level > 1)
497                     clib_warning ("pool put index %d", ce - hsm->cache_pool);
498                   pool_put (hsm->cache_pool, ce);
499                   if (hsm->cache_size < hsm->cache_limit)
500                     break;
501                 }
502             }
503
504           /* Read the file */
505           error = clib_file_contents ((char *) (hs->path), &hs->data);
506           if (error)
507             {
508               clib_warning ("Error reading '%s'", hs->path);
509               clib_error_report (error);
510               sc = HTTP_STATUS_INTERNAL_ERROR;
511               hss_cache_unlock ();
512               goto done;
513             }
514           /* Create a cache entry for it */
515           pool_get_zero (hsm->cache_pool, ce);
516           ce->filename = vec_dup (hs->path);
517           ce->data = hs->data;
518           hs->cache_pool_index = ce - hsm->cache_pool;
519           ce->inuse++;
520           if (hsm->debug_level > 1)
521             clib_warning ("index %d refcnt now %d", hs->cache_pool_index,
522                           ce->inuse);
523           lru_add (hsm, ce, vlib_time_now (vlib_get_main ()));
524           kv.key = (u64) vec_dup (hs->path);
525           kv.value = ce - hsm->cache_pool;
526           /* Add to the lookup table */
527           if (hsm->debug_level > 1)
528             clib_warning ("add '%s' value %lld", kv.key, kv.value);
529
530           if (BV (clib_bihash_add_del) (&hsm->name_to_data, &kv,
531                                         1 /* is_add */ ) < 0)
532             {
533               clib_warning ("BUG: add failed!");
534             }
535           hsm->cache_size += vec_len (ce->data);
536
537           hss_cache_unlock ();
538         }
539       hs->data_offset = 0;
540     }
541
542 done:
543
544   return sc;
545 }
546
547 static int
548 hss_ts_rx_callback (session_t *ts)
549 {
550   hss_session_t *hs;
551   u8 *request = 0;
552   http_msg_t msg;
553   int rv;
554   http_status_code_t sc;
555
556   hs = hss_session_get (ts->thread_index, ts->opaque);
557
558   /* Read the http message header */
559   rv = svm_fifo_dequeue (ts->rx_fifo, sizeof (msg), (u8 *) &msg);
560   ASSERT (rv == sizeof (msg));
561
562   if (msg.type != HTTP_MSG_REQUEST ||
563       (msg.method_type != HTTP_REQ_GET && msg.method_type != HTTP_REQ_POST))
564     {
565       hs->data = 0;
566       start_send_data (hs, HTTP_STATUS_METHOD_NOT_ALLOWED);
567       return 0;
568     }
569
570   /* Read request */
571   vec_validate (request, msg.data.len - 1);
572   rv = svm_fifo_dequeue (ts->rx_fifo, msg.data.len, request);
573   ASSERT (rv == msg.data.len);
574
575   /* Find and send data */
576   sc = find_data (hs, msg.method_type, request);
577   start_send_data (hs, sc);
578
579   vec_free (request);
580   if (!hs->data)
581     hss_session_disconnect_transport (hs);
582
583   return 0;
584 }
585
586 static int
587 hss_ts_tx_callback (session_t *ts)
588 {
589   hss_session_t *hs;
590   u32 to_send;
591   int rv;
592
593   hs = hss_session_get (ts->thread_index, ts->opaque);
594   if (!hs || !hs->data)
595     return 0;
596
597   to_send = vec_len (hs->data) - hs->data_offset;
598   rv = svm_fifo_enqueue (ts->tx_fifo, to_send, hs->data + hs->data_offset);
599
600   if (rv <= 0)
601     {
602       svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
603       return 0;
604     }
605
606   if (rv < to_send)
607     {
608       hs->data_offset += rv;
609       svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
610     }
611
612   if (svm_fifo_set_event (ts->tx_fifo))
613     session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
614
615   return 0;
616 }
617
618 /** \brief Session accept callback
619  */
620 static int
621 hss_ts_accept_callback (session_t *ts)
622 {
623   hss_session_t *hs;
624   u32 thresh;
625
626   hs = hss_session_alloc (ts->thread_index);
627
628   hs->vpp_session_index = ts->session_index;
629   hs->vpp_session_handle = session_handle (ts);
630
631   /* The application sets a threshold for it's fifo to get notified when
632    * additional data can be enqueued. We want to keep the TX fifo reasonably
633    * full, however avoid entering a state where the
634    * fifo is full all the time and small chunks of data are being enqueued
635    * each time. If the fifo is small (under 16K) we set
636    * the threshold to it's size, meaning a notification will be given when the
637    * fifo empties.
638    */
639   thresh = clib_min (svm_fifo_size (ts->tx_fifo), HSS_FIFO_THRESH);
640   svm_fifo_set_deq_thresh (ts->tx_fifo, thresh);
641
642   ts->opaque = hs->session_index;
643   ts->session_state = SESSION_STATE_READY;
644   return 0;
645 }
646
647 static void
648 hss_ts_disconnect_callback (session_t *ts)
649 {
650   hss_main_t *hsm = &hss_main;
651   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
652
653   a->handle = session_handle (ts);
654   a->app_index = hsm->app_index;
655   vnet_disconnect_session (a);
656 }
657
658 static void
659 hss_ts_reset_callback (session_t *ts)
660 {
661   hss_main_t *hsm = &hss_main;
662   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
663
664   a->handle = session_handle (ts);
665   a->app_index = hsm->app_index;
666   vnet_disconnect_session (a);
667 }
668
669 static int
670 hss_ts_connected_callback (u32 app_index, u32 api_context, session_t *ts,
671                            session_error_t err)
672 {
673   clib_warning ("called...");
674   return -1;
675 }
676
677 static int
678 hss_add_segment_callback (u32 client_index, u64 segment_handle)
679 {
680   return 0;
681 }
682
683 static void
684 hss_ts_cleanup (session_t *s, session_cleanup_ntf_t ntf)
685 {
686   hss_session_t *hs;
687
688   if (ntf == SESSION_CLEANUP_TRANSPORT)
689     return;
690
691   hs = hss_session_get (s->thread_index, s->opaque);
692   if (!hs)
693     return;
694
695   hss_detach_cache_entry (hs);
696   hss_session_free (hs);
697 }
698
699 static session_cb_vft_t hss_cb_vft = {
700   .session_accept_callback = hss_ts_accept_callback,
701   .session_disconnect_callback = hss_ts_disconnect_callback,
702   .session_connected_callback = hss_ts_connected_callback,
703   .add_segment_callback = hss_add_segment_callback,
704   .builtin_app_rx_callback = hss_ts_rx_callback,
705   .builtin_app_tx_callback = hss_ts_tx_callback,
706   .session_reset_callback = hss_ts_reset_callback,
707   .session_cleanup_callback = hss_ts_cleanup,
708 };
709
710 static int
711 hss_attach ()
712 {
713   vnet_app_add_cert_key_pair_args_t _ck_pair, *ck_pair = &_ck_pair;
714   hss_main_t *hsm = &hss_main;
715   u64 options[APP_OPTIONS_N_OPTIONS];
716   vnet_app_attach_args_t _a, *a = &_a;
717   u32 segment_size = 128 << 20;
718
719   clib_memset (a, 0, sizeof (*a));
720   clib_memset (options, 0, sizeof (options));
721
722   if (hsm->private_segment_size)
723     segment_size = hsm->private_segment_size;
724
725   a->api_client_index = ~0;
726   a->name = format (0, "http_static_server");
727   a->session_cb_vft = &hss_cb_vft;
728   a->options = options;
729   a->options[APP_OPTIONS_SEGMENT_SIZE] = segment_size;
730   a->options[APP_OPTIONS_ADD_SEGMENT_SIZE] = segment_size;
731   a->options[APP_OPTIONS_RX_FIFO_SIZE] =
732     hsm->fifo_size ? hsm->fifo_size : 8 << 10;
733   a->options[APP_OPTIONS_TX_FIFO_SIZE] =
734     hsm->fifo_size ? hsm->fifo_size : 32 << 10;
735   a->options[APP_OPTIONS_FLAGS] = APP_OPTIONS_FLAGS_IS_BUILTIN;
736   a->options[APP_OPTIONS_PREALLOC_FIFO_PAIRS] = hsm->prealloc_fifos;
737   a->options[APP_OPTIONS_TLS_ENGINE] = CRYPTO_ENGINE_OPENSSL;
738
739   if (vnet_application_attach (a))
740     {
741       vec_free (a->name);
742       clib_warning ("failed to attach server");
743       return -1;
744     }
745   vec_free (a->name);
746   hsm->app_index = a->app_index;
747
748   clib_memset (ck_pair, 0, sizeof (*ck_pair));
749   ck_pair->cert = (u8 *) test_srv_crt_rsa;
750   ck_pair->key = (u8 *) test_srv_key_rsa;
751   ck_pair->cert_len = test_srv_crt_rsa_len;
752   ck_pair->key_len = test_srv_key_rsa_len;
753   vnet_app_add_cert_key_pair (ck_pair);
754   hsm->ckpair_index = ck_pair->index;
755
756   return 0;
757 }
758
759 static int
760 hss_transport_needs_crypto (transport_proto_t proto)
761 {
762   return proto == TRANSPORT_PROTO_TLS || proto == TRANSPORT_PROTO_DTLS ||
763          proto == TRANSPORT_PROTO_QUIC;
764 }
765
766 static int
767 hss_listen (void)
768 {
769   hss_main_t *hsm = &hss_main;
770   session_endpoint_cfg_t sep = SESSION_ENDPOINT_CFG_NULL;
771   vnet_listen_args_t _a, *a = &_a;
772   char *uri = "tcp://0.0.0.0/80";
773   u8 need_crypto;
774   int rv;
775
776   clib_memset (a, 0, sizeof (*a));
777   a->app_index = hsm->app_index;
778
779   if (hsm->uri)
780     uri = (char *) hsm->uri;
781
782   if (parse_uri (uri, &sep))
783     return -1;
784
785   need_crypto = hss_transport_needs_crypto (a->sep_ext.transport_proto);
786
787   sep.transport_proto = TRANSPORT_PROTO_HTTP;
788   clib_memcpy (&a->sep_ext, &sep, sizeof (sep));
789
790   if (need_crypto)
791     {
792       session_endpoint_alloc_ext_cfg (&a->sep_ext,
793                                       TRANSPORT_ENDPT_EXT_CFG_CRYPTO);
794       a->sep_ext.ext_cfg->crypto.ckpair_index = hsm->ckpair_index;
795     }
796
797   rv = vnet_listen (a);
798
799   if (need_crypto)
800     clib_mem_free (a->sep_ext.ext_cfg);
801
802   return rv;
803 }
804
805 static void
806 hss_url_handlers_init (hss_main_t *hsm)
807 {
808   if (!hsm->get_url_handlers)
809     {
810       hsm->get_url_handlers = hash_create_string (0, sizeof (uword));
811       hsm->post_url_handlers = hash_create_string (0, sizeof (uword));
812     }
813
814   hss_builtinurl_json_handlers_init ();
815 }
816
817 int
818 hss_create (vlib_main_t *vm)
819 {
820   vlib_thread_main_t *vtm = vlib_get_thread_main ();
821   hss_main_t *hsm = &hss_main;
822   u32 num_threads;
823
824   num_threads = 1 /* main thread */  + vtm->n_threads;
825   vec_validate (hsm->sessions, num_threads - 1);
826
827   clib_spinlock_init (&hsm->cache_lock);
828
829   if (hss_attach ())
830     {
831       clib_warning ("failed to attach server");
832       return -1;
833     }
834   if (hss_listen ())
835     {
836       clib_warning ("failed to start listening");
837       return -1;
838     }
839
840   /* Init path-to-cache hash table */
841   BV (clib_bihash_init) (&hsm->name_to_data, "http cache", 128, 32 << 20);
842
843   if (hsm->enable_url_handlers)
844     hss_url_handlers_init (hsm);
845
846   return 0;
847 }
848
849 static clib_error_t *
850 hss_create_command_fn (vlib_main_t *vm, unformat_input_t *input,
851                        vlib_cli_command_t *cmd)
852 {
853   unformat_input_t _line_input, *line_input = &_line_input;
854   hss_main_t *hsm = &hss_main;
855   clib_error_t *error = 0;
856   u64 seg_size;
857   int rv;
858
859   if (hsm->app_index != (u32) ~0)
860     return clib_error_return (0, "http server already running...");
861
862   hsm->prealloc_fifos = 0;
863   hsm->private_segment_size = 0;
864   hsm->fifo_size = 0;
865   /* 10mb cache limit, before LRU occurs */
866   hsm->cache_limit = 10 << 20;
867
868   /* Get a line of input. */
869   if (!unformat_user (input, unformat_line_input, line_input))
870     goto no_input;
871
872   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
873     {
874       if (unformat (line_input, "www-root %s", &hsm->www_root))
875         ;
876       else
877         if (unformat (line_input, "prealloc-fifos %d", &hsm->prealloc_fifos))
878         ;
879       else if (unformat (line_input, "private-segment-size %U",
880                          unformat_memory_size, &seg_size))
881         hsm->private_segment_size = seg_size;
882       else if (unformat (line_input, "fifo-size %d", &hsm->fifo_size))
883         hsm->fifo_size <<= 10;
884       else if (unformat (line_input, "cache-size %U", unformat_memory_size,
885                          &hsm->cache_limit))
886         ;
887       else if (unformat (line_input, "uri %s", &hsm->uri))
888         ;
889       else if (unformat (line_input, "debug %d", &hsm->debug_level))
890         ;
891       else if (unformat (line_input, "debug"))
892         hsm->debug_level = 1;
893       else if (unformat (line_input, "ptr-thresh %U", unformat_memory_size,
894                          &hsm->use_ptr_thresh))
895         ;
896       else if (unformat (line_input, "url-handlers"))
897         hsm->enable_url_handlers = 1;
898       else
899         {
900           error = clib_error_return (0, "unknown input `%U'",
901                                      format_unformat_error, line_input);
902         }
903     }
904
905   unformat_free (line_input);
906
907 no_input:
908
909   if (error)
910     goto done;
911
912   if (hsm->www_root == 0 && !hsm->enable_url_handlers)
913     {
914       error = clib_error_return (0, "Must set www-root or url-handlers");
915       goto done;
916     }
917
918   if (hsm->cache_limit < (128 << 10))
919     {
920       error = clib_error_return (0, "cache-size must be at least 128kb");
921       vec_free (hsm->www_root);
922       goto done;
923     }
924
925   vnet_session_enable_disable (vm, 1 /* turn on TCP, etc. */ );
926
927   if ((rv = hss_create (vm)))
928     {
929       error = clib_error_return (0, "server_create returned %d", rv);
930       vec_free (hsm->www_root);
931     }
932
933 done:
934
935   return error;
936 }
937
938 /*?
939  * Enable the static http server
940  *
941  * @cliexpar
942  * This command enables the static http server. Only the www-root
943  * parameter is required
944  * @clistart
945  * http static server www-root /tmp/www uri tcp://0.0.0.0/80 cache-size 2m
946  * @cliend
947  * @cliexcmd{http static server www-root <path> [prealloc-fios <nn>]
948  *   [private-segment-size <nnMG>] [fifo-size <nbytes>] [uri <uri>]}
949 ?*/
950 VLIB_CLI_COMMAND (hss_create_command, static) = {
951   .path = "http static server",
952   .short_help =
953     "http static server www-root <path> [prealloc-fifos <nn>]\n"
954     "[private-segment-size <nnMG>] [fifo-size <nbytes>] [uri <uri>]\n"
955     "[ptr-thresh <nn>] [url-handlers] [debug [nn]]\n",
956   .function = hss_create_command_fn,
957 };
958
959 /** \brief format a file cache entry
960  */
961 static u8 *
962 format_hss_cache_entry (u8 *s, va_list *args)
963 {
964   hss_cache_entry_t *ep = va_arg (*args, hss_cache_entry_t *);
965   f64 now = va_arg (*args, f64);
966
967   /* Header */
968   if (ep == 0)
969     {
970       s = format (s, "%40s%12s%20s", "File", "Size", "Age");
971       return s;
972     }
973   s = format (s, "%40s%12lld%20.2f", ep->filename, vec_len (ep->data),
974               now - ep->last_used);
975   return s;
976 }
977
978 static u8 *
979 format_hss_session (u8 *s, va_list *args)
980 {
981   hss_session_t *hs = va_arg (*args, hss_session_t *);
982   int __clib_unused verbose = va_arg (*args, int);
983
984   s = format (s, "\n path %s, data length %u, data_offset %u",
985               hs->path ? hs->path : (u8 *) "[none]", vec_len (hs->data),
986               hs->data_offset);
987   return s;
988 }
989
990 static clib_error_t *
991 hss_show_command_fn (vlib_main_t *vm, unformat_input_t *input,
992                      vlib_cli_command_t *cmd)
993 {
994   hss_main_t *hsm = &hss_main;
995   hss_cache_entry_t *ep, **entries = 0;
996   int verbose = 0;
997   int show_cache = 0;
998   int show_sessions = 0;
999   u32 index;
1000   f64 now;
1001
1002   if (hsm->www_root == 0)
1003     return clib_error_return (0, "Static server disabled");
1004
1005   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
1006     {
1007       if (unformat (input, "verbose %d", &verbose))
1008         ;
1009       else if (unformat (input, "verbose"))
1010         verbose = 1;
1011       else if (unformat (input, "cache"))
1012         show_cache = 1;
1013       else if (unformat (input, "sessions"))
1014         show_sessions = 1;
1015       else
1016         break;
1017     }
1018
1019   if ((show_cache + show_sessions) == 0)
1020     return clib_error_return (0, "specify one or more of cache, sessions");
1021
1022   if (show_cache)
1023     {
1024       if (verbose == 0)
1025         {
1026           vlib_cli_output
1027             (vm, "www_root %s, cache size %lld bytes, limit %lld bytes, "
1028              "evictions %lld",
1029              hsm->www_root, hsm->cache_size, hsm->cache_limit,
1030              hsm->cache_evictions);
1031           return 0;
1032         }
1033
1034       now = vlib_time_now (vm);
1035
1036       vlib_cli_output (vm, "%U", format_hss_cache_entry, 0 /* header */, now);
1037
1038       for (index = hsm->first_index; index != ~0;)
1039         {
1040           ep = pool_elt_at_index (hsm->cache_pool, index);
1041           index = ep->next_index;
1042           vlib_cli_output (vm, "%U", format_hss_cache_entry, ep, now);
1043         }
1044
1045       vlib_cli_output (vm, "%40s%12lld", "Total Size", hsm->cache_size);
1046
1047       vec_free (entries);
1048     }
1049
1050   if (show_sessions)
1051     {
1052       u32 *session_indices = 0;
1053       hss_session_t *hs;
1054       int i, j;
1055
1056       hss_cache_lock ();
1057
1058       for (i = 0; i < vec_len (hsm->sessions); i++)
1059         {
1060           pool_foreach (hs, hsm->sessions[i])
1061            {
1062             vec_add1 (session_indices, hs - hsm->sessions[i]);
1063           }
1064
1065           for (j = 0; j < vec_len (session_indices); j++)
1066             {
1067               vlib_cli_output (
1068                 vm, "%U", format_hss_session,
1069                 pool_elt_at_index (hsm->sessions[i], session_indices[j]),
1070                 verbose);
1071             }
1072           vec_reset_length (session_indices);
1073         }
1074       hss_cache_unlock ();
1075       vec_free (session_indices);
1076     }
1077   return 0;
1078 }
1079
1080 /*?
1081  * Display static http server cache statistics
1082  *
1083  * @cliexpar
1084  * This command shows the contents of the static http server cache
1085  * @clistart
1086  * show http static server
1087  * @cliend
1088  * @cliexcmd{show http static server sessions cache [verbose [nn]]}
1089 ?*/
1090 VLIB_CLI_COMMAND (hss_show_command, static) = {
1091   .path = "show http static server",
1092   .short_help = "show http static server sessions cache [verbose [<nn>]]",
1093   .function = hss_show_command_fn,
1094 };
1095
1096 static clib_error_t *
1097 hss_clear_cache_command_fn (vlib_main_t *vm, unformat_input_t *input,
1098                             vlib_cli_command_t *cmd)
1099 {
1100   hss_main_t *hsm = &hss_main;
1101   hss_cache_entry_t *ce;
1102   u32 free_index;
1103   u32 busy_items = 0;
1104   BVT (clib_bihash_kv) kv;
1105
1106   if (hsm->www_root == 0)
1107     return clib_error_return (0, "Static server disabled");
1108
1109   hss_cache_lock ();
1110
1111   /* Walk the LRU list to find active entries */
1112   free_index = hsm->last_index;
1113   while (free_index != ~0)
1114     {
1115       ce = pool_elt_at_index (hsm->cache_pool, free_index);
1116       free_index = ce->prev_index;
1117       /* Which could be in use... */
1118       if (ce->inuse)
1119         {
1120           busy_items++;
1121           free_index = ce->next_index;
1122           continue;
1123         }
1124       kv.key = (u64) (ce->filename);
1125       kv.value = ~0ULL;
1126       if (BV (clib_bihash_add_del) (&hsm->name_to_data, &kv,
1127                                     0 /* is_add */ ) < 0)
1128         {
1129           clib_warning ("BUG: cache clear delete '%s' FAILED!", ce->filename);
1130         }
1131
1132       lru_remove (hsm, ce);
1133       hsm->cache_size -= vec_len (ce->data);
1134       hsm->cache_evictions++;
1135       vec_free (ce->filename);
1136       vec_free (ce->data);
1137       if (hsm->debug_level > 1)
1138         clib_warning ("pool put index %d", ce - hsm->cache_pool);
1139       pool_put (hsm->cache_pool, ce);
1140       free_index = hsm->last_index;
1141     }
1142   hss_cache_unlock ();
1143   if (busy_items > 0)
1144     vlib_cli_output (vm, "Note: %d busy items still in cache...", busy_items);
1145   else
1146     vlib_cli_output (vm, "Cache cleared...");
1147   return 0;
1148 }
1149
1150 /*?
1151  * Clear the static http server cache, to force the server to
1152  * reload content from backing files
1153  *
1154  * @cliexpar
1155  * This command clear the static http server cache
1156  * @clistart
1157  * clear http static cache
1158  * @cliend
1159  * @cliexcmd{clear http static cache}
1160 ?*/
1161 VLIB_CLI_COMMAND (clear_hss_cache_command, static) = {
1162   .path = "clear http static cache",
1163   .short_help = "clear http static cache",
1164   .function = hss_clear_cache_command_fn,
1165 };
1166
1167 static clib_error_t *
1168 hss_main_init (vlib_main_t *vm)
1169 {
1170   hss_main_t *hsm = &hss_main;
1171
1172   hsm->app_index = ~0;
1173   hsm->vlib_main = vm;
1174   hsm->first_index = hsm->last_index = ~0;
1175
1176   return 0;
1177 }
1178
1179 VLIB_INIT_FUNCTION (hss_main_init);
1180
1181 /*
1182  * fd.io coding-style-patch-verification: ON
1183  *
1184  * Local Variables:
1185  * eval: (c-set-style "gnu")
1186  * End:
1187  */