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