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