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