prom: basic builtin prometheus stats exporter
[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 = hs->data_len;
261
262   if (hs->data_len > 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, hs->data_len, hs->data);
284
285   if (rv != hs->data_len)
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 __clib_export void
298 hss_session_send_data (hss_url_handler_args_t *args)
299 {
300   hss_session_t *hs;
301
302   hs = hss_session_get (args->sh.thread_index, args->sh.session_index);
303
304   if (hs->data && hs->free_data)
305     vec_free (hs->data);
306
307   hs->data = args->data;
308   hs->data_len = args->data_len;
309   hs->free_data = args->free_vec_data;
310   start_send_data (hs, args->sc);
311 }
312
313 static int
314 try_url_handler (hss_main_t *hsm, hss_session_t *hs, http_req_method_t rt,
315                  u8 *request)
316 {
317   http_status_code_t sc = HTTP_STATUS_OK;
318   hss_url_handler_args_t args = {};
319   uword *p, *url_table;
320   int rv;
321
322   if (!hsm->enable_url_handlers)
323     return -1;
324
325   /* Look for built-in GET / POST handlers */
326   url_table =
327     (rt == HTTP_REQ_GET) ? hsm->get_url_handlers : hsm->post_url_handlers;
328
329   p = hash_get_mem (url_table, request);
330   if (!p)
331     return -1;
332
333   hs->path = 0;
334   hs->data_offset = 0;
335   hs->cache_pool_index = ~0;
336
337   if (hsm->debug_level > 0)
338     clib_warning ("%s '%s'", (rt == HTTP_REQ_GET) ? "GET" : "POST", request);
339
340   args.reqtype = rt;
341   args.request = request;
342   args.sh.thread_index = hs->thread_index;
343   args.sh.session_index = hs->session_index;
344
345   rv = ((hss_url_handler_fn) p[0]) (&args);
346
347   /* Wait for data from handler */
348   if (rv == HSS_URL_HANDLER_ASYNC)
349     return 0;
350
351   if (rv == HSS_URL_HANDLER_ERROR)
352     {
353       clib_warning ("builtin handler %llx hit on %s '%s' but failed!", p[0],
354                     (rt == HTTP_REQ_GET) ? "GET" : "POST", request);
355       sc = HTTP_STATUS_NOT_FOUND;
356     }
357
358   hs->data = args.data;
359   hs->data_len = args.data_len;
360   hs->free_data = args.free_vec_data;
361
362   start_send_data (hs, sc);
363
364   if (!hs->data)
365     hss_session_disconnect_transport (hs);
366
367   return 0;
368 }
369
370 static int
371 handle_request (hss_session_t *hs, http_req_method_t rt, u8 *request)
372 {
373   http_status_code_t sc = HTTP_STATUS_OK;
374   hss_main_t *hsm = &hss_main;
375   struct stat _sb, *sb = &_sb;
376   clib_error_t *error;
377   u8 *path;
378
379   if (!try_url_handler (hsm, hs, rt, request))
380     return 0;
381
382   /*
383    * Construct the file to open
384    * Browsers are capable of sporadically including a leading '/'
385    */
386   if (request[0] == '/')
387     path = format (0, "%s%s%c", hsm->www_root, request, 0);
388   else
389     path = format (0, "%s/%s%c", hsm->www_root, request, 0);
390
391   if (hsm->debug_level > 0)
392     clib_warning ("%s '%s'", (rt == HTTP_REQ_GET) ? "GET" : "POST", path);
393
394   /* Try to find the file. 2x special cases to find index.html */
395   if (stat ((char *) path, sb) < 0      /* cant even stat the file */
396       || sb->st_size < 20       /* file too small */
397       || (sb->st_mode & S_IFMT) != S_IFREG /* not a regular file */ )
398     {
399       u32 save_length = vec_len (path) - 1;
400       /* Try appending "index.html"... */
401       _vec_len (path) -= 1;
402       path = format (path, "index.html%c", 0);
403       if (stat ((char *) path, sb) < 0  /* cant even stat the file */
404           || sb->st_size < 20   /* file too small */
405           || (sb->st_mode & S_IFMT) != S_IFREG /* not a regular file */ )
406         {
407           _vec_len (path) = save_length;
408           path = format (path, "/index.html%c", 0);
409
410           /* Send a redirect, otherwise the browser will confuse itself */
411           if (stat ((char *) path, sb) < 0      /* cant even stat the file */
412               || sb->st_size < 20       /* file too small */
413               || (sb->st_mode & S_IFMT) != S_IFREG /* not a regular file */ )
414             {
415               sc = HTTP_STATUS_NOT_FOUND;
416               goto done;
417             }
418           else
419             {
420               transport_endpoint_t endpoint;
421               transport_proto_t proto;
422               u16 local_port;
423               int print_port = 0;
424               u8 *port_str = 0;
425               session_t *ts;
426
427               /*
428                * To make this bit work correctly, we need to know our local
429                * IP address, etc. and send it in the redirect...
430                */
431               u8 *redirect;
432
433               vec_delete (path, vec_len (hsm->www_root) - 1, 0);
434
435               ts = session_get (hs->vpp_session_index, hs->thread_index);
436               session_get_endpoint (ts, &endpoint, 1 /* is_local */);
437
438               local_port = clib_net_to_host_u16 (endpoint.port);
439
440               proto = session_type_transport_proto (ts->session_type);
441
442               if ((proto == TRANSPORT_PROTO_TCP && local_port != 80)
443                   || (proto == TRANSPORT_PROTO_TLS && local_port != 443))
444                 {
445                   print_port = 1;
446                   port_str = format (0, ":%u", (u32) local_port);
447                 }
448
449               redirect = format (0, "HTTP/1.1 301 Moved Permanently\r\n"
450                                  "Location: http%s://%U%s%s\r\n\r\n",
451                                  proto == TRANSPORT_PROTO_TLS ? "s" : "",
452                                  format_ip46_address, &endpoint.ip,
453                                  endpoint.is_ip4,
454                                  print_port ? port_str : (u8 *) "", path);
455               if (hsm->debug_level > 0)
456                 clib_warning ("redirect: %s", redirect);
457
458               vec_free (port_str);
459
460               hs->data = redirect;
461               goto done;
462             }
463         }
464     }
465
466   /* find or read the file if we haven't done so yet. */
467   if (hs->data == 0)
468     {
469       BVT (clib_bihash_kv) kv;
470       hss_cache_entry_t *ce;
471
472       hs->path = path;
473
474       /* First, try the cache */
475       kv.key = (u64) hs->path;
476       if (BV (clib_bihash_search) (&hsm->name_to_data, &kv, &kv) == 0)
477         {
478           if (hsm->debug_level > 1)
479             clib_warning ("lookup '%s' returned %lld", kv.key, kv.value);
480
481           hss_cache_lock ();
482
483           /* found the data.. */
484           ce = pool_elt_at_index (hsm->cache_pool, kv.value);
485           hs->data = ce->data;
486           /* Update the cache entry, mark it in-use */
487           lru_update (hsm, ce, vlib_time_now (vlib_get_main ()));
488           hs->cache_pool_index = ce - hsm->cache_pool;
489           ce->inuse++;
490           if (hsm->debug_level > 1)
491             clib_warning ("index %d refcnt now %d", hs->cache_pool_index,
492                           ce->inuse);
493
494           hss_cache_unlock ();
495         }
496       else
497         {
498           hss_cache_lock ();
499
500           if (hsm->debug_level > 1)
501             clib_warning ("lookup '%s' failed", kv.key, kv.value);
502           /* Need to recycle one (or more cache) entries? */
503           if (hsm->cache_size > hsm->cache_limit)
504             {
505               int free_index = hsm->last_index;
506
507               while (free_index != ~0)
508                 {
509                   /* pick the LRU */
510                   ce = pool_elt_at_index (hsm->cache_pool, free_index);
511                   free_index = ce->prev_index;
512                   /* Which could be in use... */
513                   if (ce->inuse)
514                     {
515                       if (hsm->debug_level > 1)
516                         clib_warning ("index %d in use refcnt %d",
517                                       ce - hsm->cache_pool, ce->inuse);
518                     }
519                   kv.key = (u64) (ce->filename);
520                   kv.value = ~0ULL;
521                   if (BV (clib_bihash_add_del) (&hsm->name_to_data, &kv,
522                                                 0 /* is_add */ ) < 0)
523                     {
524                       clib_warning ("LRU delete '%s' FAILED!", ce->filename);
525                     }
526                   else if (hsm->debug_level > 1)
527                     clib_warning ("LRU delete '%s' ok", ce->filename);
528
529                   lru_remove (hsm, ce);
530                   hsm->cache_size -= vec_len (ce->data);
531                   hsm->cache_evictions++;
532                   vec_free (ce->filename);
533                   vec_free (ce->data);
534                   if (hsm->debug_level > 1)
535                     clib_warning ("pool put index %d", ce - hsm->cache_pool);
536                   pool_put (hsm->cache_pool, ce);
537                   if (hsm->cache_size < hsm->cache_limit)
538                     break;
539                 }
540             }
541
542           /* Read the file */
543           error = clib_file_contents ((char *) (hs->path), &hs->data);
544           if (error)
545             {
546               clib_warning ("Error reading '%s'", hs->path);
547               clib_error_report (error);
548               sc = HTTP_STATUS_INTERNAL_ERROR;
549               hss_cache_unlock ();
550               goto done;
551             }
552           /* Create a cache entry for it */
553           pool_get_zero (hsm->cache_pool, ce);
554           ce->filename = vec_dup (hs->path);
555           ce->data = hs->data;
556           hs->cache_pool_index = ce - hsm->cache_pool;
557           ce->inuse++;
558           if (hsm->debug_level > 1)
559             clib_warning ("index %d refcnt now %d", hs->cache_pool_index,
560                           ce->inuse);
561           lru_add (hsm, ce, vlib_time_now (vlib_get_main ()));
562           kv.key = (u64) vec_dup (hs->path);
563           kv.value = ce - hsm->cache_pool;
564           /* Add to the lookup table */
565           if (hsm->debug_level > 1)
566             clib_warning ("add '%s' value %lld", kv.key, kv.value);
567
568           if (BV (clib_bihash_add_del) (&hsm->name_to_data, &kv,
569                                         1 /* is_add */ ) < 0)
570             {
571               clib_warning ("BUG: add failed!");
572             }
573           hsm->cache_size += vec_len (ce->data);
574
575           hss_cache_unlock ();
576         }
577       hs->data_offset = 0;
578     }
579
580 done:
581
582   start_send_data (hs, sc);
583   if (!hs->data)
584     hss_session_disconnect_transport (hs);
585
586   return sc;
587 }
588
589 static int
590 hss_ts_rx_callback (session_t *ts)
591 {
592   hss_session_t *hs;
593   u8 *request = 0;
594   http_msg_t msg;
595   int rv;
596
597   hs = hss_session_get (ts->thread_index, ts->opaque);
598
599   /* Read the http message header */
600   rv = svm_fifo_dequeue (ts->rx_fifo, sizeof (msg), (u8 *) &msg);
601   ASSERT (rv == sizeof (msg));
602
603   if (msg.type != HTTP_MSG_REQUEST ||
604       (msg.method_type != HTTP_REQ_GET && msg.method_type != HTTP_REQ_POST))
605     {
606       hs->data = 0;
607       start_send_data (hs, HTTP_STATUS_METHOD_NOT_ALLOWED);
608       return 0;
609     }
610
611   /* Read request */
612   vec_validate (request, msg.data.len - 1);
613   rv = svm_fifo_dequeue (ts->rx_fifo, msg.data.len, request);
614   ASSERT (rv == msg.data.len);
615
616   /* Find and send data */
617   handle_request (hs, msg.method_type, request);
618
619   vec_free (request);
620
621   return 0;
622 }
623
624 static int
625 hss_ts_tx_callback (session_t *ts)
626 {
627   hss_session_t *hs;
628   u32 to_send;
629   int rv;
630
631   hs = hss_session_get (ts->thread_index, ts->opaque);
632   if (!hs || !hs->data)
633     return 0;
634
635   to_send = hs->data_len - hs->data_offset;
636   rv = svm_fifo_enqueue (ts->tx_fifo, to_send, hs->data + hs->data_offset);
637
638   if (rv <= 0)
639     {
640       svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
641       return 0;
642     }
643
644   if (rv < to_send)
645     {
646       hs->data_offset += rv;
647       svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
648     }
649
650   if (svm_fifo_set_event (ts->tx_fifo))
651     session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
652
653   return 0;
654 }
655
656 /** \brief Session accept callback
657  */
658 static int
659 hss_ts_accept_callback (session_t *ts)
660 {
661   hss_session_t *hs;
662   u32 thresh;
663
664   hs = hss_session_alloc (ts->thread_index);
665
666   hs->vpp_session_index = ts->session_index;
667   hs->vpp_session_handle = session_handle (ts);
668
669   /* The application sets a threshold for it's fifo to get notified when
670    * additional data can be enqueued. We want to keep the TX fifo reasonably
671    * full, however avoid entering a state where the
672    * fifo is full all the time and small chunks of data are being enqueued
673    * each time. If the fifo is small (under 16K) we set
674    * the threshold to it's size, meaning a notification will be given when the
675    * fifo empties.
676    */
677   thresh = clib_min (svm_fifo_size (ts->tx_fifo), HSS_FIFO_THRESH);
678   svm_fifo_set_deq_thresh (ts->tx_fifo, thresh);
679
680   ts->opaque = hs->session_index;
681   ts->session_state = SESSION_STATE_READY;
682   return 0;
683 }
684
685 static void
686 hss_ts_disconnect_callback (session_t *ts)
687 {
688   hss_main_t *hsm = &hss_main;
689   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
690
691   a->handle = session_handle (ts);
692   a->app_index = hsm->app_index;
693   vnet_disconnect_session (a);
694 }
695
696 static void
697 hss_ts_reset_callback (session_t *ts)
698 {
699   hss_main_t *hsm = &hss_main;
700   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
701
702   a->handle = session_handle (ts);
703   a->app_index = hsm->app_index;
704   vnet_disconnect_session (a);
705 }
706
707 static int
708 hss_ts_connected_callback (u32 app_index, u32 api_context, session_t *ts,
709                            session_error_t err)
710 {
711   clib_warning ("called...");
712   return -1;
713 }
714
715 static int
716 hss_add_segment_callback (u32 client_index, u64 segment_handle)
717 {
718   return 0;
719 }
720
721 static void
722 hss_ts_cleanup (session_t *s, session_cleanup_ntf_t ntf)
723 {
724   hss_session_t *hs;
725
726   if (ntf == SESSION_CLEANUP_TRANSPORT)
727     return;
728
729   hs = hss_session_get (s->thread_index, s->opaque);
730   if (!hs)
731     return;
732
733   hss_detach_cache_entry (hs);
734   hss_session_free (hs);
735 }
736
737 static session_cb_vft_t hss_cb_vft = {
738   .session_accept_callback = hss_ts_accept_callback,
739   .session_disconnect_callback = hss_ts_disconnect_callback,
740   .session_connected_callback = hss_ts_connected_callback,
741   .add_segment_callback = hss_add_segment_callback,
742   .builtin_app_rx_callback = hss_ts_rx_callback,
743   .builtin_app_tx_callback = hss_ts_tx_callback,
744   .session_reset_callback = hss_ts_reset_callback,
745   .session_cleanup_callback = hss_ts_cleanup,
746 };
747
748 static int
749 hss_attach ()
750 {
751   vnet_app_add_cert_key_pair_args_t _ck_pair, *ck_pair = &_ck_pair;
752   hss_main_t *hsm = &hss_main;
753   u64 options[APP_OPTIONS_N_OPTIONS];
754   vnet_app_attach_args_t _a, *a = &_a;
755   u32 segment_size = 128 << 20;
756
757   clib_memset (a, 0, sizeof (*a));
758   clib_memset (options, 0, sizeof (options));
759
760   if (hsm->private_segment_size)
761     segment_size = hsm->private_segment_size;
762
763   a->api_client_index = ~0;
764   a->name = format (0, "http_static_server");
765   a->session_cb_vft = &hss_cb_vft;
766   a->options = options;
767   a->options[APP_OPTIONS_SEGMENT_SIZE] = segment_size;
768   a->options[APP_OPTIONS_ADD_SEGMENT_SIZE] = segment_size;
769   a->options[APP_OPTIONS_RX_FIFO_SIZE] =
770     hsm->fifo_size ? hsm->fifo_size : 8 << 10;
771   a->options[APP_OPTIONS_TX_FIFO_SIZE] =
772     hsm->fifo_size ? hsm->fifo_size : 32 << 10;
773   a->options[APP_OPTIONS_FLAGS] = APP_OPTIONS_FLAGS_IS_BUILTIN;
774   a->options[APP_OPTIONS_PREALLOC_FIFO_PAIRS] = hsm->prealloc_fifos;
775   a->options[APP_OPTIONS_TLS_ENGINE] = CRYPTO_ENGINE_OPENSSL;
776
777   if (vnet_application_attach (a))
778     {
779       vec_free (a->name);
780       clib_warning ("failed to attach server");
781       return -1;
782     }
783   vec_free (a->name);
784   hsm->app_index = a->app_index;
785
786   clib_memset (ck_pair, 0, sizeof (*ck_pair));
787   ck_pair->cert = (u8 *) test_srv_crt_rsa;
788   ck_pair->key = (u8 *) test_srv_key_rsa;
789   ck_pair->cert_len = test_srv_crt_rsa_len;
790   ck_pair->key_len = test_srv_key_rsa_len;
791   vnet_app_add_cert_key_pair (ck_pair);
792   hsm->ckpair_index = ck_pair->index;
793
794   return 0;
795 }
796
797 static int
798 hss_transport_needs_crypto (transport_proto_t proto)
799 {
800   return proto == TRANSPORT_PROTO_TLS || proto == TRANSPORT_PROTO_DTLS ||
801          proto == TRANSPORT_PROTO_QUIC;
802 }
803
804 static int
805 hss_listen (void)
806 {
807   hss_main_t *hsm = &hss_main;
808   session_endpoint_cfg_t sep = SESSION_ENDPOINT_CFG_NULL;
809   vnet_listen_args_t _a, *a = &_a;
810   char *uri = "tcp://0.0.0.0/80";
811   u8 need_crypto;
812   int rv;
813
814   clib_memset (a, 0, sizeof (*a));
815   a->app_index = hsm->app_index;
816
817   if (hsm->uri)
818     uri = (char *) hsm->uri;
819
820   if (parse_uri (uri, &sep))
821     return -1;
822
823   need_crypto = hss_transport_needs_crypto (a->sep_ext.transport_proto);
824
825   sep.transport_proto = TRANSPORT_PROTO_HTTP;
826   clib_memcpy (&a->sep_ext, &sep, sizeof (sep));
827
828   if (need_crypto)
829     {
830       session_endpoint_alloc_ext_cfg (&a->sep_ext,
831                                       TRANSPORT_ENDPT_EXT_CFG_CRYPTO);
832       a->sep_ext.ext_cfg->crypto.ckpair_index = hsm->ckpair_index;
833     }
834
835   rv = vnet_listen (a);
836
837   if (need_crypto)
838     clib_mem_free (a->sep_ext.ext_cfg);
839
840   return rv;
841 }
842
843 static void
844 hss_url_handlers_init (hss_main_t *hsm)
845 {
846   if (!hsm->get_url_handlers)
847     {
848       hsm->get_url_handlers = hash_create_string (0, sizeof (uword));
849       hsm->post_url_handlers = hash_create_string (0, sizeof (uword));
850     }
851
852   hss_builtinurl_json_handlers_init ();
853 }
854
855 int
856 hss_create (vlib_main_t *vm)
857 {
858   vlib_thread_main_t *vtm = vlib_get_thread_main ();
859   hss_main_t *hsm = &hss_main;
860   u32 num_threads;
861
862   num_threads = 1 /* main thread */  + vtm->n_threads;
863   vec_validate (hsm->sessions, num_threads - 1);
864
865   clib_spinlock_init (&hsm->cache_lock);
866
867   if (hss_attach ())
868     {
869       clib_warning ("failed to attach server");
870       return -1;
871     }
872   if (hss_listen ())
873     {
874       clib_warning ("failed to start listening");
875       return -1;
876     }
877
878   /* Init path-to-cache hash table */
879   BV (clib_bihash_init) (&hsm->name_to_data, "http cache", 128, 32 << 20);
880
881   if (hsm->enable_url_handlers)
882     hss_url_handlers_init (hsm);
883
884   return 0;
885 }
886
887 static clib_error_t *
888 hss_create_command_fn (vlib_main_t *vm, unformat_input_t *input,
889                        vlib_cli_command_t *cmd)
890 {
891   unformat_input_t _line_input, *line_input = &_line_input;
892   hss_main_t *hsm = &hss_main;
893   clib_error_t *error = 0;
894   u64 seg_size;
895   int rv;
896
897   if (hsm->app_index != (u32) ~0)
898     return clib_error_return (0, "http server already running...");
899
900   hsm->prealloc_fifos = 0;
901   hsm->private_segment_size = 0;
902   hsm->fifo_size = 0;
903   /* 10mb cache limit, before LRU occurs */
904   hsm->cache_limit = 10 << 20;
905
906   /* Get a line of input. */
907   if (!unformat_user (input, unformat_line_input, line_input))
908     goto no_input;
909
910   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
911     {
912       if (unformat (line_input, "www-root %s", &hsm->www_root))
913         ;
914       else
915         if (unformat (line_input, "prealloc-fifos %d", &hsm->prealloc_fifos))
916         ;
917       else if (unformat (line_input, "private-segment-size %U",
918                          unformat_memory_size, &seg_size))
919         hsm->private_segment_size = seg_size;
920       else if (unformat (line_input, "fifo-size %d", &hsm->fifo_size))
921         hsm->fifo_size <<= 10;
922       else if (unformat (line_input, "cache-size %U", unformat_memory_size,
923                          &hsm->cache_limit))
924         ;
925       else if (unformat (line_input, "uri %s", &hsm->uri))
926         ;
927       else if (unformat (line_input, "debug %d", &hsm->debug_level))
928         ;
929       else if (unformat (line_input, "debug"))
930         hsm->debug_level = 1;
931       else if (unformat (line_input, "ptr-thresh %U", unformat_memory_size,
932                          &hsm->use_ptr_thresh))
933         ;
934       else if (unformat (line_input, "url-handlers"))
935         hsm->enable_url_handlers = 1;
936       else
937         {
938           error = clib_error_return (0, "unknown input `%U'",
939                                      format_unformat_error, line_input);
940         }
941     }
942
943   unformat_free (line_input);
944
945 no_input:
946
947   if (error)
948     goto done;
949
950   if (hsm->www_root == 0 && !hsm->enable_url_handlers)
951     {
952       error = clib_error_return (0, "Must set www-root or url-handlers");
953       goto done;
954     }
955
956   if (hsm->cache_limit < (128 << 10))
957     {
958       error = clib_error_return (0, "cache-size must be at least 128kb");
959       vec_free (hsm->www_root);
960       goto done;
961     }
962
963   vnet_session_enable_disable (vm, 1 /* turn on TCP, etc. */ );
964
965   if ((rv = hss_create (vm)))
966     {
967       error = clib_error_return (0, "server_create returned %d", rv);
968       vec_free (hsm->www_root);
969     }
970
971 done:
972
973   return error;
974 }
975
976 /*?
977  * Enable the static http server
978  *
979  * @cliexpar
980  * This command enables the static http server. Only the www-root
981  * parameter is required
982  * @clistart
983  * http static server www-root /tmp/www uri tcp://0.0.0.0/80 cache-size 2m
984  * @cliend
985  * @cliexcmd{http static server www-root <path> [prealloc-fios <nn>]
986  *   [private-segment-size <nnMG>] [fifo-size <nbytes>] [uri <uri>]}
987 ?*/
988 VLIB_CLI_COMMAND (hss_create_command, static) = {
989   .path = "http static server",
990   .short_help =
991     "http static server www-root <path> [prealloc-fifos <nn>]\n"
992     "[private-segment-size <nnMG>] [fifo-size <nbytes>] [uri <uri>]\n"
993     "[ptr-thresh <nn>] [url-handlers] [debug [nn]]\n",
994   .function = hss_create_command_fn,
995 };
996
997 /** \brief format a file cache entry
998  */
999 static u8 *
1000 format_hss_cache_entry (u8 *s, va_list *args)
1001 {
1002   hss_cache_entry_t *ep = va_arg (*args, hss_cache_entry_t *);
1003   f64 now = va_arg (*args, f64);
1004
1005   /* Header */
1006   if (ep == 0)
1007     {
1008       s = format (s, "%40s%12s%20s", "File", "Size", "Age");
1009       return s;
1010     }
1011   s = format (s, "%40s%12lld%20.2f", ep->filename, vec_len (ep->data),
1012               now - ep->last_used);
1013   return s;
1014 }
1015
1016 static u8 *
1017 format_hss_session (u8 *s, va_list *args)
1018 {
1019   hss_session_t *hs = va_arg (*args, hss_session_t *);
1020   int __clib_unused verbose = va_arg (*args, int);
1021
1022   s = format (s, "\n path %s, data length %u, data_offset %u",
1023               hs->path ? hs->path : (u8 *) "[none]", hs->data_len,
1024               hs->data_offset);
1025   return s;
1026 }
1027
1028 static clib_error_t *
1029 hss_show_command_fn (vlib_main_t *vm, unformat_input_t *input,
1030                      vlib_cli_command_t *cmd)
1031 {
1032   hss_main_t *hsm = &hss_main;
1033   hss_cache_entry_t *ep, **entries = 0;
1034   int verbose = 0;
1035   int show_cache = 0;
1036   int show_sessions = 0;
1037   u32 index;
1038   f64 now;
1039
1040   if (hsm->www_root == 0)
1041     return clib_error_return (0, "Static server disabled");
1042
1043   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
1044     {
1045       if (unformat (input, "verbose %d", &verbose))
1046         ;
1047       else if (unformat (input, "verbose"))
1048         verbose = 1;
1049       else if (unformat (input, "cache"))
1050         show_cache = 1;
1051       else if (unformat (input, "sessions"))
1052         show_sessions = 1;
1053       else
1054         break;
1055     }
1056
1057   if ((show_cache + show_sessions) == 0)
1058     return clib_error_return (0, "specify one or more of cache, sessions");
1059
1060   if (show_cache)
1061     {
1062       if (verbose == 0)
1063         {
1064           vlib_cli_output
1065             (vm, "www_root %s, cache size %lld bytes, limit %lld bytes, "
1066              "evictions %lld",
1067              hsm->www_root, hsm->cache_size, hsm->cache_limit,
1068              hsm->cache_evictions);
1069           return 0;
1070         }
1071
1072       now = vlib_time_now (vm);
1073
1074       vlib_cli_output (vm, "%U", format_hss_cache_entry, 0 /* header */, now);
1075
1076       for (index = hsm->first_index; index != ~0;)
1077         {
1078           ep = pool_elt_at_index (hsm->cache_pool, index);
1079           index = ep->next_index;
1080           vlib_cli_output (vm, "%U", format_hss_cache_entry, ep, now);
1081         }
1082
1083       vlib_cli_output (vm, "%40s%12lld", "Total Size", hsm->cache_size);
1084
1085       vec_free (entries);
1086     }
1087
1088   if (show_sessions)
1089     {
1090       u32 *session_indices = 0;
1091       hss_session_t *hs;
1092       int i, j;
1093
1094       hss_cache_lock ();
1095
1096       for (i = 0; i < vec_len (hsm->sessions); i++)
1097         {
1098           pool_foreach (hs, hsm->sessions[i])
1099            {
1100             vec_add1 (session_indices, hs - hsm->sessions[i]);
1101           }
1102
1103           for (j = 0; j < vec_len (session_indices); j++)
1104             {
1105               vlib_cli_output (
1106                 vm, "%U", format_hss_session,
1107                 pool_elt_at_index (hsm->sessions[i], session_indices[j]),
1108                 verbose);
1109             }
1110           vec_reset_length (session_indices);
1111         }
1112       hss_cache_unlock ();
1113       vec_free (session_indices);
1114     }
1115   return 0;
1116 }
1117
1118 /*?
1119  * Display static http server cache statistics
1120  *
1121  * @cliexpar
1122  * This command shows the contents of the static http server cache
1123  * @clistart
1124  * show http static server
1125  * @cliend
1126  * @cliexcmd{show http static server sessions cache [verbose [nn]]}
1127 ?*/
1128 VLIB_CLI_COMMAND (hss_show_command, static) = {
1129   .path = "show http static server",
1130   .short_help = "show http static server sessions cache [verbose [<nn>]]",
1131   .function = hss_show_command_fn,
1132 };
1133
1134 static clib_error_t *
1135 hss_clear_cache_command_fn (vlib_main_t *vm, unformat_input_t *input,
1136                             vlib_cli_command_t *cmd)
1137 {
1138   hss_main_t *hsm = &hss_main;
1139   hss_cache_entry_t *ce;
1140   u32 free_index;
1141   u32 busy_items = 0;
1142   BVT (clib_bihash_kv) kv;
1143
1144   if (hsm->www_root == 0)
1145     return clib_error_return (0, "Static server disabled");
1146
1147   hss_cache_lock ();
1148
1149   /* Walk the LRU list to find active entries */
1150   free_index = hsm->last_index;
1151   while (free_index != ~0)
1152     {
1153       ce = pool_elt_at_index (hsm->cache_pool, free_index);
1154       free_index = ce->prev_index;
1155       /* Which could be in use... */
1156       if (ce->inuse)
1157         {
1158           busy_items++;
1159           free_index = ce->next_index;
1160           continue;
1161         }
1162       kv.key = (u64) (ce->filename);
1163       kv.value = ~0ULL;
1164       if (BV (clib_bihash_add_del) (&hsm->name_to_data, &kv,
1165                                     0 /* is_add */ ) < 0)
1166         {
1167           clib_warning ("BUG: cache clear delete '%s' FAILED!", ce->filename);
1168         }
1169
1170       lru_remove (hsm, ce);
1171       hsm->cache_size -= vec_len (ce->data);
1172       hsm->cache_evictions++;
1173       vec_free (ce->filename);
1174       vec_free (ce->data);
1175       if (hsm->debug_level > 1)
1176         clib_warning ("pool put index %d", ce - hsm->cache_pool);
1177       pool_put (hsm->cache_pool, ce);
1178       free_index = hsm->last_index;
1179     }
1180   hss_cache_unlock ();
1181   if (busy_items > 0)
1182     vlib_cli_output (vm, "Note: %d busy items still in cache...", busy_items);
1183   else
1184     vlib_cli_output (vm, "Cache cleared...");
1185   return 0;
1186 }
1187
1188 /*?
1189  * Clear the static http server cache, to force the server to
1190  * reload content from backing files
1191  *
1192  * @cliexpar
1193  * This command clear the static http server cache
1194  * @clistart
1195  * clear http static cache
1196  * @cliend
1197  * @cliexcmd{clear http static cache}
1198 ?*/
1199 VLIB_CLI_COMMAND (clear_hss_cache_command, static) = {
1200   .path = "clear http static cache",
1201   .short_help = "clear http static cache",
1202   .function = hss_clear_cache_command_fn,
1203 };
1204
1205 static clib_error_t *
1206 hss_main_init (vlib_main_t *vm)
1207 {
1208   hss_main_t *hsm = &hss_main;
1209
1210   hsm->app_index = ~0;
1211   hsm->vlib_main = vm;
1212   hsm->first_index = hsm->last_index = ~0;
1213
1214   return 0;
1215 }
1216
1217 VLIB_INIT_FUNCTION (hss_main_init);
1218
1219 /*
1220  * fd.io coding-style-patch-verification: ON
1221  *
1222  * Local Variables:
1223  * eval: (c-set-style "gnu")
1224  * End:
1225  */