svm: merge want deq and leq threshold notifications
[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
28 /** @file static_server.c
29  *  Static http server, sufficient to
30  *  serve .html / .css / .js content.
31  */
32 /*? %%clicmd:group_label Static HTTP Server %% ?*/
33
34 #define HTTP_FIFO_THRESH (16 << 10)
35
36 http_static_server_main_t http_static_server_main;
37
38 /** \brief Format the called-from enum
39  */
40
41 static u8 *
42 format_state_machine_called_from (u8 * s, va_list * args)
43 {
44   http_state_machine_called_from_t cf =
45     va_arg (*args, http_state_machine_called_from_t);
46   char *which = "bogus!";
47
48   switch (cf)
49     {
50     case CALLED_FROM_RX:
51       which = "from rx";
52       break;
53     case CALLED_FROM_TX:
54       which = "from tx";
55       break;
56     case CALLED_FROM_TIMER:
57       which = "from timer";
58       break;
59
60     default:
61       break;
62     }
63
64   s = format (s, "%s", which);
65   return s;
66 }
67
68
69 /** \brief Acquire reader lock on the sessions pools
70  */
71 static void
72 http_static_server_sessions_reader_lock (void)
73 {
74   clib_rwlock_reader_lock (&http_static_server_main.sessions_lock);
75 }
76
77 /** \brief Drop reader lock on the sessions pools
78  */
79 static void
80 http_static_server_sessions_reader_unlock (void)
81 {
82   clib_rwlock_reader_unlock (&http_static_server_main.sessions_lock);
83 }
84
85 /** \brief Acquire writer lock on the sessions pools
86  */
87 static void
88 http_static_server_sessions_writer_lock (void)
89 {
90   clib_rwlock_writer_lock (&http_static_server_main.sessions_lock);
91 }
92
93 /** \brief Drop writer lock on the sessions pools
94  */
95 static void
96 http_static_server_sessions_writer_unlock (void)
97 {
98   clib_rwlock_writer_unlock (&http_static_server_main.sessions_lock);
99 }
100
101 /** \brief Start a session cleanup timer
102  */
103 static void
104 http_static_server_session_timer_start (http_session_t * hs)
105 {
106   http_static_server_main_t *hsm = &http_static_server_main;
107   u32 hs_handle;
108
109   /* The session layer may fire a callback at a later date... */
110   if (!pool_is_free (hsm->sessions[hs->thread_index], hs))
111     {
112       hs_handle = hs->thread_index << 24 | hs->session_index;
113       clib_spinlock_lock (&http_static_server_main.tw_lock);
114       hs->timer_handle = tw_timer_start_2t_1w_2048sl
115         (&http_static_server_main.tw, hs_handle, 0, 60);
116       clib_spinlock_unlock (&http_static_server_main.tw_lock);
117     }
118 }
119
120 /** \brief stop a session cleanup timer
121  */
122 static void
123 http_static_server_session_timer_stop (http_session_t * hs)
124 {
125   if (hs->timer_handle == ~0)
126     return;
127   clib_spinlock_lock (&http_static_server_main.tw_lock);
128   tw_timer_stop_2t_1w_2048sl (&http_static_server_main.tw, hs->timer_handle);
129   clib_spinlock_unlock (&http_static_server_main.tw_lock);
130 }
131
132 /** \brief Allocate an http session
133  */
134 static http_session_t *
135 http_static_server_session_alloc (u32 thread_index)
136 {
137   http_static_server_main_t *hsm = &http_static_server_main;
138   http_session_t *hs;
139   pool_get_aligned_zero_numa (hsm->sessions[thread_index], hs,
140                               0 /* not aligned */ ,
141                               1 /* zero */ ,
142                               os_get_numa_index ());
143   hs->session_index = hs - hsm->sessions[thread_index];
144   hs->thread_index = thread_index;
145   hs->timer_handle = ~0;
146   hs->cache_pool_index = ~0;
147   return hs;
148 }
149
150 /** \brief Get an http session by index
151  */
152 static http_session_t *
153 http_static_server_session_get (u32 thread_index, u32 hs_index)
154 {
155   http_static_server_main_t *hsm = &http_static_server_main;
156   if (pool_is_free_index (hsm->sessions[thread_index], hs_index))
157     return 0;
158   return pool_elt_at_index (hsm->sessions[thread_index], hs_index);
159 }
160
161 /** \brief Free an http session
162  */
163 static void
164 http_static_server_session_free (http_session_t * hs)
165 {
166   http_static_server_main_t *hsm = &http_static_server_main;
167
168   /* Make sure the timer is stopped... */
169   http_static_server_session_timer_stop (hs);
170   pool_put (hsm->sessions[hs->thread_index], hs);
171
172   if (CLIB_DEBUG)
173     {
174       u32 save_thread_index;
175       save_thread_index = hs->thread_index;
176       /* Poison the entry, preserve timer state and thread index */
177       memset (hs, 0xfa, sizeof (*hs));
178       hs->timer_handle = ~0;
179       hs->thread_index = save_thread_index;
180     }
181 }
182
183 /** \brief add a session to the vpp < -- > http session index map
184  */
185 static void
186 http_static_server_session_lookup_add (u32 thread_index, u32 s_index,
187                                        u32 hs_index)
188 {
189   http_static_server_main_t *hsm = &http_static_server_main;
190   vec_validate (hsm->session_to_http_session[thread_index], s_index);
191   hsm->session_to_http_session[thread_index][s_index] = hs_index;
192 }
193
194 /** \brief Remove a session from the vpp < -- > http session index map
195  */
196 static void
197 http_static_server_session_lookup_del (u32 thread_index, u32 s_index)
198 {
199   http_static_server_main_t *hsm = &http_static_server_main;
200   hsm->session_to_http_session[thread_index][s_index] = ~0;
201 }
202
203 /** \brief lookup a session in the vpp < -- > http session index map
204  */
205
206 static http_session_t *
207 http_static_server_session_lookup (u32 thread_index, u32 s_index)
208 {
209   http_static_server_main_t *hsm = &http_static_server_main;
210   u32 hs_index;
211
212   if (s_index < vec_len (hsm->session_to_http_session[thread_index]))
213     {
214       hs_index = hsm->session_to_http_session[thread_index][s_index];
215       return http_static_server_session_get (thread_index, hs_index);
216     }
217   return 0;
218 }
219
220 /** \brief Detach cache entry from session
221  */
222
223 static void
224 http_static_server_detach_cache_entry (http_session_t * hs)
225 {
226   http_static_server_main_t *hsm = &http_static_server_main;
227   file_data_cache_t *ep;
228
229   /*
230    * Decrement cache pool entry reference count
231    * Note that if e.g. a file lookup fails, the cache pool index
232    * won't be set
233    */
234   if (hs->cache_pool_index != ~0)
235     {
236       ep = pool_elt_at_index (hsm->cache_pool, hs->cache_pool_index);
237       ep->inuse--;
238       if (hsm->debug_level > 1)
239         clib_warning ("index %d refcnt now %d", hs->cache_pool_index,
240                       ep->inuse);
241     }
242   hs->cache_pool_index = ~0;
243   if (hs->free_data)
244     vec_free (hs->data);
245   hs->data = 0;
246   hs->data_offset = 0;
247   hs->free_data = 0;
248   vec_free (hs->path);
249 }
250
251 /** \brief Disconnect a session
252  */
253 static void
254 http_static_server_session_disconnect (http_session_t * hs)
255 {
256   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
257   a->handle = hs->vpp_session_handle;
258   a->app_index = http_static_server_main.app_index;
259   vnet_disconnect_session (a);
260 }
261
262 /* *INDENT-OFF* */
263 /** \brief http error boilerplate
264  */
265 static const char *http_error_template =
266     "HTTP/1.1 %s\r\n"
267     "Date: %U GMT\r\n"
268     "Content-Type: text/html\r\n"
269     "Connection: close\r\n"
270     "Pragma: no-cache\r\n"
271     "Content-Length: 0\r\n\r\n";
272
273 /** \brief http response boilerplate
274  */
275 static const char *http_response_template =
276     "Date: %U GMT\r\n"
277     "Expires: %U GMT\r\n"
278     "Server: VPP Static\r\n"
279     "Content-Type: %s\r\n"
280     "Content-Length: %d\r\n\r\n";
281
282 /* *INDENT-ON* */
283
284 /** \brief send http data
285     @param hs - http session
286     @param data - the data vector to transmit
287     @param length - length of data
288     @param offset - transmit offset for this operation
289     @return offset for next transmit operation, may be unchanged w/ full fifo
290 */
291
292 static u32
293 static_send_data (http_session_t * hs, u8 * data, u32 length, u32 offset)
294 {
295   u32 bytes_to_send;
296   http_static_server_main_t *hsm = &http_static_server_main;
297
298   bytes_to_send = length - offset;
299
300   while (bytes_to_send > 0)
301     {
302       int actual_transfer;
303
304       actual_transfer = svm_fifo_enqueue (
305         hs->tx_fifo, clib_min (bytes_to_send, 4 << 20), data + offset);
306
307       /* Made any progress? */
308       if (actual_transfer <= 0)
309         {
310           if (hsm->debug_level > 0 && bytes_to_send > 0)
311             clib_warning ("WARNING: still %d bytes to send", bytes_to_send);
312           return offset;
313         }
314       else
315         {
316           offset += actual_transfer;
317           bytes_to_send -= actual_transfer;
318
319           if (hsm->debug_level && bytes_to_send > 0)
320             clib_warning ("WARNING: still %d bytes to send", bytes_to_send);
321
322           if (svm_fifo_set_event (hs->tx_fifo))
323             session_send_io_evt_to_thread (hs->tx_fifo,
324                                            SESSION_IO_EVT_TX_FLUSH);
325           return offset;
326         }
327     }
328   /* NOTREACHED */
329   return ~0;
330 }
331
332 /** \brief Send an http error string
333     @param hs - the http session
334     @param str - the error string, e.g. "404 Not Found"
335 */
336 static void
337 send_error (http_session_t * hs, char *str)
338 {
339   http_static_server_main_t *hsm = &http_static_server_main;
340   u8 *data;
341   f64 now;
342
343   now = clib_timebase_now (&hsm->timebase);
344   data = format (0, http_error_template, str, format_clib_timebase_time, now);
345   static_send_data (hs, data, vec_len (data), 0);
346   vec_free (data);
347 }
348
349 /** \brief Retrieve data from the application layer
350  */
351 static int
352 session_rx_request (http_session_t * hs)
353 {
354   u32 max_dequeue, cursize;
355   int n_read;
356
357   cursize = vec_len (hs->rx_buf);
358   max_dequeue = svm_fifo_max_dequeue (hs->rx_fifo);
359   if (PREDICT_FALSE (max_dequeue == 0))
360     return -1;
361
362   vec_validate (hs->rx_buf, cursize + max_dequeue - 1);
363   n_read = app_recv_stream_raw (hs->rx_fifo, hs->rx_buf + cursize,
364                                 max_dequeue, 0, 0 /* peek */ );
365   ASSERT (n_read == max_dequeue);
366   if (svm_fifo_is_empty (hs->rx_fifo))
367     svm_fifo_unset_event (hs->rx_fifo);
368
369   _vec_len (hs->rx_buf) = cursize + n_read;
370   return 0;
371 }
372
373 /** \brief Sanity-check the forward and reverse LRU lists
374  */
375 static inline void
376 lru_validate (http_static_server_main_t * hsm)
377 {
378 #if CLIB_DEBUG > 0
379   f64 last_timestamp;
380   u32 index;
381   int i;
382   file_data_cache_t *ep;
383
384   last_timestamp = 1e70;
385   for (i = 1, index = hsm->first_index; index != ~0;)
386     {
387       ep = pool_elt_at_index (hsm->cache_pool, index);
388       index = ep->next_index;
389       /* Timestamps should be smaller (older) as we walk the fwd list */
390       if (ep->last_used > last_timestamp)
391         {
392           clib_warning ("%d[%d]: last used %.6f, last_timestamp %.6f",
393                         ep - hsm->cache_pool, i,
394                         ep->last_used, last_timestamp);
395         }
396       last_timestamp = ep->last_used;
397       i++;
398     }
399
400   last_timestamp = 0.0;
401   for (i = 1, index = hsm->last_index; index != ~0;)
402     {
403       ep = pool_elt_at_index (hsm->cache_pool, index);
404       index = ep->prev_index;
405       /* Timestamps should be larger (newer) as we walk the rev list */
406       if (ep->last_used < last_timestamp)
407         {
408           clib_warning ("%d[%d]: last used %.6f, last_timestamp %.6f",
409                         ep - hsm->cache_pool, i,
410                         ep->last_used, last_timestamp);
411         }
412       last_timestamp = ep->last_used;
413       i++;
414     }
415 #endif
416 }
417
418 /** \brief Remove a data cache entry from the LRU lists
419  */
420 static inline void
421 lru_remove (http_static_server_main_t * hsm, file_data_cache_t * ep)
422 {
423   file_data_cache_t *next_ep, *prev_ep;
424   u32 ep_index;
425
426   lru_validate (hsm);
427
428   ep_index = ep - hsm->cache_pool;
429
430   /* Deal with list heads */
431   if (ep_index == hsm->first_index)
432     hsm->first_index = ep->next_index;
433   if (ep_index == hsm->last_index)
434     hsm->last_index = ep->prev_index;
435
436   /* Fix next->prev */
437   if (ep->next_index != ~0)
438     {
439       next_ep = pool_elt_at_index (hsm->cache_pool, ep->next_index);
440       next_ep->prev_index = ep->prev_index;
441     }
442   /* Fix prev->next */
443   if (ep->prev_index != ~0)
444     {
445       prev_ep = pool_elt_at_index (hsm->cache_pool, ep->prev_index);
446       prev_ep->next_index = ep->next_index;
447     }
448   lru_validate (hsm);
449 }
450
451 /** \brief Add an entry to the LRU lists, tag w/ supplied timestamp
452  */
453
454 static inline void
455 lru_add (http_static_server_main_t * hsm, file_data_cache_t * ep, f64 now)
456 {
457   file_data_cache_t *next_ep;
458   u32 ep_index;
459
460   lru_validate (hsm);
461
462   ep_index = ep - hsm->cache_pool;
463
464   /*
465    * Re-add at the head of the forward LRU list,
466    * tail of the reverse LRU list
467    */
468   if (hsm->first_index != ~0)
469     {
470       next_ep = pool_elt_at_index (hsm->cache_pool, hsm->first_index);
471       next_ep->prev_index = ep_index;
472     }
473
474   ep->prev_index = ~0;
475
476   /* ep now the new head of the LRU forward list */
477   ep->next_index = hsm->first_index;
478   hsm->first_index = ep_index;
479
480   /* single session case: also the tail of the reverse LRU list */
481   if (hsm->last_index == ~0)
482     hsm->last_index = ep_index;
483   ep->last_used = now;
484
485   lru_validate (hsm);
486 }
487
488 /** \brief Remove and re-add a cache entry from/to the LRU lists
489  */
490
491 static inline void
492 lru_update (http_static_server_main_t * hsm, file_data_cache_t * ep, f64 now)
493 {
494   lru_remove (hsm, ep);
495   lru_add (hsm, ep, now);
496 }
497
498 /** \brief Session-layer (main) data rx callback.
499     Parse the http request, and reply to it.
500     Future extensions might include POST processing, active content, etc.
501 */
502
503 /* svm_fifo_add_want_deq_ntf (tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF_IF_FULL)
504 get shoulder-tap when transport dequeues something, set in
505 xmit routine. */
506
507 /** \brief closed state - should never really get here
508  */
509 static int
510 state_closed (session_t * s, http_session_t * hs,
511               http_state_machine_called_from_t cf)
512 {
513   clib_warning ("WARNING: http session %d, called from %U",
514                 hs->session_index, format_state_machine_called_from, cf);
515   return -1;
516 }
517
518 static void
519 close_session (http_session_t * hs)
520 {
521   http_static_server_session_disconnect (hs);
522 }
523
524 /** \brief Register a builtin GET or POST handler
525  */
526 __clib_export void http_static_server_register_builtin_handler
527   (void *fp, char *url, int request_type)
528 {
529   http_static_server_main_t *hsm = &http_static_server_main;
530   uword *p, *builtin_table;
531
532   builtin_table = (request_type == HTTP_BUILTIN_METHOD_GET)
533     ? hsm->get_url_handlers : hsm->post_url_handlers;
534
535   p = hash_get_mem (builtin_table, url);
536
537   if (p)
538     {
539       clib_warning ("WARNING: attempt to replace handler for %s '%s' ignored",
540                     (request_type == HTTP_BUILTIN_METHOD_GET) ?
541                     "GET" : "POST", url);
542       return;
543     }
544
545   hash_set_mem (builtin_table, url, (uword) fp);
546
547   /*
548    * Need to update the hash table pointer in http_static_server_main
549    * in case we just expanded it...
550    */
551   if (request_type == HTTP_BUILTIN_METHOD_GET)
552     hsm->get_url_handlers = builtin_table;
553   else
554     hsm->post_url_handlers = builtin_table;
555 }
556
557 static int
558 v_find_index (u8 * vec, char *str)
559 {
560   int start_index;
561   u32 slen = (u32) strnlen_s_inline (str, 8);
562   u32 vlen = vec_len (vec);
563
564   ASSERT (slen > 0);
565
566   if (vlen <= slen)
567     return -1;
568
569   for (start_index = 0; start_index < (vlen - slen); start_index++)
570     {
571       if (!memcmp (vec, str, slen))
572         return start_index;
573     }
574
575   return -1;
576 }
577
578 /** \brief established state - waiting for GET, POST, etc.
579  */
580 static int
581 state_established (session_t * s, http_session_t * hs,
582                    http_state_machine_called_from_t cf)
583 {
584   http_static_server_main_t *hsm = &http_static_server_main;
585   u8 *request = 0;
586   u8 *path;
587   int i, rv;
588   struct stat _sb, *sb = &_sb;
589   clib_error_t *error;
590   u8 request_type = HTTP_BUILTIN_METHOD_GET;
591   u8 save_byte = 0;
592   uword *p, *builtin_table;
593
594   /* Read data from the sessison layer */
595   rv = session_rx_request (hs);
596
597   /* No data? Odd, but stay in this state and await further instructions */
598   if (rv)
599     return 0;
600
601   /* Process the client request */
602   request = hs->rx_buf;
603   if (vec_len (request) < 8)
604     {
605       send_error (hs, "400 Bad Request");
606       close_session (hs);
607       return -1;
608     }
609
610   if ((i = v_find_index (request, "GET ")) >= 0)
611     goto find_end;
612   else if ((i = v_find_index (request, "POST ")) >= 0)
613     {
614       request_type = HTTP_BUILTIN_METHOD_POST;
615       goto find_end;
616     }
617
618   if (hsm->debug_level > 1)
619     clib_warning ("Unknown http method");
620
621   send_error (hs, "405 Method Not Allowed");
622   close_session (hs);
623   return -1;
624
625 find_end:
626
627   /* Lose "GET " or "POST " */
628   vec_delete (request, i + 5 + request_type, 0);
629
630   /* Temporarily drop in a NULL byte for lookup purposes */
631   for (i = 0; i < vec_len (request); i++)
632     {
633       if (request[i] == ' ' || request[i] == '?')
634         {
635           save_byte = request[i];
636           request[i] = 0;
637           break;
638         }
639     }
640
641   /*
642    * Now we can construct the file to open
643    * Browsers are capable of sporadically including a leading '/'
644    */
645   if (request[0] == '/')
646     path = format (0, "%s%s%c", hsm->www_root, request, 0);
647   else
648     path = format (0, "%s/%s%c", hsm->www_root, request, 0);
649
650   if (hsm->debug_level > 0)
651     clib_warning ("%s '%s'", (request_type) == HTTP_BUILTIN_METHOD_GET ?
652                   "GET" : "POST", path);
653
654   /* Look for built-in GET / POST handlers */
655   builtin_table = (request_type == HTTP_BUILTIN_METHOD_GET) ?
656     hsm->get_url_handlers : hsm->post_url_handlers;
657
658   p = hash_get_mem (builtin_table, request);
659
660   if (save_byte != 0)
661     request[i] = save_byte;
662
663   if (p)
664     {
665       int rv;
666       int (*fp) (http_builtin_method_type_t, u8 *, http_session_t *);
667       fp = (void *) p[0];
668       hs->path = path;
669       rv = (*fp) (request_type, request, hs);
670       if (rv)
671         {
672           clib_warning ("builtin handler %llx hit on %s '%s' but failed!",
673                         p[0], (request_type == HTTP_BUILTIN_METHOD_GET) ?
674                         "GET" : "POST", request);
675           send_error (hs, "404 Not Found");
676           close_session (hs);
677           return -1;
678         }
679       vec_reset_length (hs->rx_buf);
680       goto send_ok;
681     }
682   vec_reset_length (hs->rx_buf);
683   /* poison request, it's not valid anymore */
684   request = 0;
685   /* The static server itself doesn't do POSTs */
686   if (request_type == HTTP_BUILTIN_METHOD_POST)
687     {
688       send_error (hs, "404 Not Found");
689       close_session (hs);
690       return -1;
691     }
692
693   /* Try to find the file. 2x special cases to find index.html */
694   if (stat ((char *) path, sb) < 0      /* cant even stat the file */
695       || sb->st_size < 20       /* file too small */
696       || (sb->st_mode & S_IFMT) != S_IFREG /* not a regular file */ )
697     {
698       u32 save_length = vec_len (path) - 1;
699       /* Try appending "index.html"... */
700       _vec_len (path) -= 1;
701       path = format (path, "index.html%c", 0);
702       if (stat ((char *) path, sb) < 0  /* cant even stat the file */
703           || sb->st_size < 20   /* file too small */
704           || (sb->st_mode & S_IFMT) != S_IFREG /* not a regular file */ )
705         {
706           _vec_len (path) = save_length;
707           path = format (path, "/index.html%c", 0);
708
709           /* Send a redirect, otherwise the browser will confuse itself */
710           if (stat ((char *) path, sb) < 0      /* cant even stat the file */
711               || sb->st_size < 20       /* file too small */
712               || (sb->st_mode & S_IFMT) != S_IFREG /* not a regular file */ )
713             {
714               vec_free (path);
715               send_error (hs, "404 Not Found");
716               close_session (hs);
717               return -1;
718             }
719           else
720             {
721               transport_endpoint_t endpoint;
722               transport_proto_t proto;
723               u16 local_port;
724               int print_port = 0;
725               u8 *port_str = 0;
726
727               /*
728                * To make this bit work correctly, we need to know our local
729                * IP address, etc. and send it in the redirect...
730                */
731               u8 *redirect;
732
733               vec_delete (path, vec_len (hsm->www_root) - 1, 0);
734
735               session_get_endpoint (s, &endpoint, 1 /* is_local */ );
736
737               local_port = clib_net_to_host_u16 (endpoint.port);
738
739               proto = session_type_transport_proto (s->session_type);
740
741               if ((proto == TRANSPORT_PROTO_TCP && local_port != 80)
742                   || (proto == TRANSPORT_PROTO_TLS && local_port != 443))
743                 {
744                   print_port = 1;
745                   port_str = format (0, ":%u", (u32) local_port);
746                 }
747
748               redirect = format (0, "HTTP/1.1 301 Moved Permanently\r\n"
749                                  "Location: http%s://%U%s%s\r\n\r\n",
750                                  proto == TRANSPORT_PROTO_TLS ? "s" : "",
751                                  format_ip46_address, &endpoint.ip,
752                                  endpoint.is_ip4,
753                                  print_port ? port_str : (u8 *) "", path);
754               if (hsm->debug_level > 0)
755                 clib_warning ("redirect: %s", redirect);
756
757               vec_free (port_str);
758
759               static_send_data (hs, redirect, vec_len (redirect), 0);
760               hs->session_state = HTTP_STATE_CLOSED;
761               hs->path = 0;
762               vec_free (redirect);
763               vec_free (path);
764               close_session (hs);
765               return -1;
766             }
767         }
768     }
769
770   /* find or read the file if we haven't done so yet. */
771   if (hs->data == 0)
772     {
773       BVT (clib_bihash_kv) kv;
774       file_data_cache_t *dp;
775
776       hs->path = path;
777
778       /* First, try the cache */
779       kv.key = (u64) hs->path;
780       if (BV (clib_bihash_search) (&hsm->name_to_data, &kv, &kv) == 0)
781         {
782           if (hsm->debug_level > 1)
783             clib_warning ("lookup '%s' returned %lld", kv.key, kv.value);
784
785           /* found the data.. */
786           dp = pool_elt_at_index (hsm->cache_pool, kv.value);
787           hs->data = dp->data;
788           /* Update the cache entry, mark it in-use */
789           lru_update (hsm, dp, vlib_time_now (vlib_get_main ()));
790           hs->cache_pool_index = dp - hsm->cache_pool;
791           dp->inuse++;
792           if (hsm->debug_level > 1)
793             clib_warning ("index %d refcnt now %d", hs->cache_pool_index,
794                           dp->inuse);
795         }
796       else
797         {
798           if (hsm->debug_level > 1)
799             clib_warning ("lookup '%s' failed", kv.key, kv.value);
800           /* Need to recycle one (or more cache) entries? */
801           if (hsm->cache_size > hsm->cache_limit)
802             {
803               int free_index = hsm->last_index;
804
805               while (free_index != ~0)
806                 {
807                   /* pick the LRU */
808                   dp = pool_elt_at_index (hsm->cache_pool, free_index);
809                   free_index = dp->prev_index;
810                   /* Which could be in use... */
811                   if (dp->inuse)
812                     {
813                       if (hsm->debug_level > 1)
814                         clib_warning ("index %d in use refcnt %d",
815                                       dp - hsm->cache_pool, dp->inuse);
816
817                     }
818                   kv.key = (u64) (dp->filename);
819                   kv.value = ~0ULL;
820                   if (BV (clib_bihash_add_del) (&hsm->name_to_data, &kv,
821                                                 0 /* is_add */ ) < 0)
822                     {
823                       clib_warning ("LRU delete '%s' FAILED!", dp->filename);
824                     }
825                   else if (hsm->debug_level > 1)
826                     clib_warning ("LRU delete '%s' ok", dp->filename);
827
828                   lru_remove (hsm, dp);
829                   hsm->cache_size -= vec_len (dp->data);
830                   hsm->cache_evictions++;
831                   vec_free (dp->filename);
832                   vec_free (dp->data);
833                   if (hsm->debug_level > 1)
834                     clib_warning ("pool put index %d", dp - hsm->cache_pool);
835                   pool_put (hsm->cache_pool, dp);
836                   if (hsm->cache_size < hsm->cache_limit)
837                     break;
838                 }
839             }
840
841           /* Read the file */
842           error = clib_file_contents ((char *) (hs->path), &hs->data);
843           if (error)
844             {
845               clib_warning ("Error reading '%s'", hs->path);
846               clib_error_report (error);
847               vec_free (hs->path);
848               close_session (hs);
849               return -1;
850             }
851           /* Create a cache entry for it */
852           pool_get (hsm->cache_pool, dp);
853           memset (dp, 0, sizeof (*dp));
854           dp->filename = vec_dup (hs->path);
855           dp->data = hs->data;
856           hs->cache_pool_index = dp - hsm->cache_pool;
857           dp->inuse++;
858           if (hsm->debug_level > 1)
859             clib_warning ("index %d refcnt now %d", hs->cache_pool_index,
860                           dp->inuse);
861           lru_add (hsm, dp, vlib_time_now (vlib_get_main ()));
862           kv.key = (u64) vec_dup (hs->path);
863           kv.value = dp - hsm->cache_pool;
864           /* Add to the lookup table */
865           if (hsm->debug_level > 1)
866             clib_warning ("add '%s' value %lld", kv.key, kv.value);
867
868           if (BV (clib_bihash_add_del) (&hsm->name_to_data, &kv,
869                                         1 /* is_add */ ) < 0)
870             {
871               clib_warning ("BUG: add failed!");
872             }
873           hsm->cache_size += vec_len (dp->data);
874         }
875       hs->data_offset = 0;
876     }
877   /* send 200 OK first */
878 send_ok:
879   static_send_data (hs, (u8 *) "HTTP/1.1 200 OK\r\n", 17, 0);
880   hs->session_state = HTTP_STATE_OK_SENT;
881   return 1;
882 }
883
884 static int
885 state_send_more_data (session_t * s, http_session_t * hs,
886                       http_state_machine_called_from_t cf)
887 {
888
889   /* Start sending data */
890   hs->data_offset = static_send_data (hs, hs->data, vec_len (hs->data),
891                                       hs->data_offset);
892
893   /* Did we finish? */
894   if (hs->data_offset < vec_len (hs->data))
895     {
896       /* No: ask for a shoulder-tap when the tx fifo has space */
897       svm_fifo_add_want_deq_ntf (hs->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
898       hs->session_state = HTTP_STATE_SEND_MORE_DATA;
899       return 0;
900     }
901   /* Finished with this transaction, back to HTTP_STATE_ESTABLISHED */
902
903   /* Let go of the file cache entry */
904   http_static_server_detach_cache_entry (hs);
905   hs->session_state = HTTP_STATE_ESTABLISHED;
906   return 0;
907 }
908
909 static int
910 state_sent_ok (session_t * s, http_session_t * hs,
911                http_state_machine_called_from_t cf)
912 {
913   http_static_server_main_t *hsm = &http_static_server_main;
914   char *suffix;
915   char *http_type;
916   u8 *http_response;
917   f64 now;
918   u32 offset;
919
920   /* What kind of dog food are we serving? */
921   suffix = (char *) (hs->path + vec_len (hs->path) - 1);
922   while ((u8 *) suffix >= hs->path && *suffix != '.')
923     suffix--;
924   suffix++;
925   http_type = "text/html";
926   if (!clib_strcmp (suffix, "css"))
927     http_type = "text/css";
928   else if (!clib_strcmp (suffix, "js"))
929     http_type = "text/javascript";
930   else if (!clib_strcmp (suffix, "json"))
931     http_type = "application/json";
932
933   if (hs->data == 0)
934     {
935       clib_warning ("BUG: hs->data not set for session %d",
936                     hs->session_index);
937       close_session (hs);
938       return 0;
939     }
940
941   /*
942    * Send an http response, which needs the current time,
943    * the expiration time, and the data length
944    */
945   now = clib_timebase_now (&hsm->timebase);
946   http_response = format (0, http_response_template,
947                           /* Date */
948                           format_clib_timebase_time, now,
949                           /* Expires */
950                           format_clib_timebase_time, now + 600.0,
951                           http_type, vec_len (hs->data));
952   offset = static_send_data (hs, http_response, vec_len (http_response), 0);
953   if (offset != vec_len (http_response))
954     {
955       clib_warning ("BUG: couldn't send response header!");
956       close_session (hs);
957       return 0;
958     }
959   vec_free (http_response);
960
961   /* Send data from the beginning... */
962   hs->data_offset = 0;
963   hs->session_state = HTTP_STATE_SEND_MORE_DATA;
964   return 1;
965 }
966
967 static void *state_funcs[HTTP_STATE_N_STATES] = {
968   state_closed,
969   /* Waiting for GET, POST, etc. */
970   state_established,
971   /* Sent OK */
972   state_sent_ok,
973   /* Send more data */
974   state_send_more_data,
975 };
976
977 static inline int
978 http_static_server_rx_tx_callback (session_t * s,
979                                    http_state_machine_called_from_t cf)
980 {
981   http_session_t *hs;
982   int (*fp) (session_t *, http_session_t *, http_state_machine_called_from_t);
983   int rv;
984
985   /* Acquire a reader lock on the session table */
986   http_static_server_sessions_reader_lock ();
987   hs = http_static_server_session_lookup (s->thread_index, s->session_index);
988
989   if (!hs)
990     {
991       clib_warning ("No http session for thread %d session_index %d",
992                     s->thread_index, s->session_index);
993       http_static_server_sessions_reader_unlock ();
994       return 0;
995     }
996
997   /* Execute state machine for this session */
998   do
999     {
1000       fp = state_funcs[hs->session_state];
1001       rv = (*fp) (s, hs, cf);
1002       if (rv < 0)
1003         goto session_closed;
1004     }
1005   while (rv);
1006
1007   /* Reset the session expiration timer */
1008   http_static_server_session_timer_stop (hs);
1009   http_static_server_session_timer_start (hs);
1010
1011 session_closed:
1012   http_static_server_sessions_reader_unlock ();
1013   return 0;
1014 }
1015
1016 static int
1017 http_static_server_rx_callback (session_t * s)
1018 {
1019   return http_static_server_rx_tx_callback (s, CALLED_FROM_RX);
1020 }
1021
1022 static int
1023 http_static_server_tx_callback (session_t * s)
1024 {
1025   return http_static_server_rx_tx_callback (s, CALLED_FROM_TX);
1026 }
1027
1028
1029 /** \brief Session accept callback
1030  */
1031
1032 static int
1033 http_static_server_session_accept_callback (session_t * s)
1034 {
1035   http_static_server_main_t *hsm = &http_static_server_main;
1036   http_session_t *hs;
1037   u32 thresh;
1038
1039   hsm->vpp_queue[s->thread_index] =
1040     session_main_get_vpp_event_queue (s->thread_index);
1041
1042   http_static_server_sessions_writer_lock ();
1043
1044   hs = http_static_server_session_alloc (s->thread_index);
1045   http_static_server_session_lookup_add (s->thread_index, s->session_index,
1046                                          hs->session_index);
1047   hs->rx_fifo = s->rx_fifo;
1048   hs->tx_fifo = s->tx_fifo;
1049   hs->vpp_session_index = s->session_index;
1050   hs->vpp_session_handle = session_handle (s);
1051   hs->session_state = HTTP_STATE_ESTABLISHED;
1052   http_static_server_session_timer_start (hs);
1053
1054   http_static_server_sessions_writer_unlock ();
1055
1056   /* The application sets a threshold for it's fifo to get notified when
1057    * additional data can be enqueued. We want to keep the TX fifo reasonably
1058    * full, however avoid entering a state where the
1059    * fifo is full all the time and small chunks of data are being enqueued
1060    * each time. If the fifo is small (under 16K) we set
1061    * the threshold to it's size, meaning a notification will be given when the
1062    * fifo empties.
1063    */
1064   thresh = clib_min (svm_fifo_size (hs->tx_fifo), HTTP_FIFO_THRESH);
1065   svm_fifo_set_deq_thresh (hs->tx_fifo, thresh);
1066
1067   s->session_state = SESSION_STATE_READY;
1068   return 0;
1069 }
1070
1071 /** \brief Session disconnect callback
1072  */
1073
1074 static void
1075 http_static_server_session_disconnect_callback (session_t * s)
1076 {
1077   http_static_server_main_t *hsm = &http_static_server_main;
1078   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
1079
1080   a->handle = session_handle (s);
1081   a->app_index = hsm->app_index;
1082   vnet_disconnect_session (a);
1083 }
1084
1085 /** \brief Session reset callback
1086  */
1087
1088 static void
1089 http_static_server_session_reset_callback (session_t * s)
1090 {
1091   http_static_server_main_t *hsm = &http_static_server_main;
1092   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
1093
1094   a->handle = session_handle (s);
1095   a->app_index = hsm->app_index;
1096   vnet_disconnect_session (a);
1097 }
1098
1099 static int
1100 http_static_server_session_connected_callback (u32 app_index, u32 api_context,
1101                                                session_t * s,
1102                                                session_error_t err)
1103 {
1104   clib_warning ("called...");
1105   return -1;
1106 }
1107
1108 static int
1109 http_static_server_add_segment_callback (u32 client_index, u64 segment_handle)
1110 {
1111   return 0;
1112 }
1113
1114 static void
1115 http_static_session_cleanup (session_t * s, session_cleanup_ntf_t ntf)
1116 {
1117   http_session_t *hs;
1118
1119   if (ntf == SESSION_CLEANUP_TRANSPORT)
1120     return;
1121
1122   http_static_server_sessions_writer_lock ();
1123
1124   hs = http_static_server_session_lookup (s->thread_index, s->session_index);
1125   if (!hs)
1126     goto done;
1127
1128   http_static_server_detach_cache_entry (hs);
1129   http_static_server_session_lookup_del (hs->thread_index,
1130                                          hs->vpp_session_index);
1131   vec_free (hs->rx_buf);
1132   http_static_server_session_free (hs);
1133
1134 done:
1135   http_static_server_sessions_writer_unlock ();
1136 }
1137
1138 /** \brief Session-layer virtual function table
1139  */
1140 static session_cb_vft_t http_static_server_session_cb_vft = {
1141   .session_accept_callback = http_static_server_session_accept_callback,
1142   .session_disconnect_callback =
1143     http_static_server_session_disconnect_callback,
1144   .session_connected_callback = http_static_server_session_connected_callback,
1145   .add_segment_callback = http_static_server_add_segment_callback,
1146   .builtin_app_rx_callback = http_static_server_rx_callback,
1147   .builtin_app_tx_callback = http_static_server_tx_callback,
1148   .session_reset_callback = http_static_server_session_reset_callback,
1149   .session_cleanup_callback = http_static_session_cleanup,
1150 };
1151
1152 static int
1153 http_static_server_attach ()
1154 {
1155   vnet_app_add_cert_key_pair_args_t _ck_pair, *ck_pair = &_ck_pair;
1156   http_static_server_main_t *hsm = &http_static_server_main;
1157   u64 options[APP_OPTIONS_N_OPTIONS];
1158   vnet_app_attach_args_t _a, *a = &_a;
1159   u32 segment_size = 128 << 20;
1160
1161   clib_memset (a, 0, sizeof (*a));
1162   clib_memset (options, 0, sizeof (options));
1163
1164   if (hsm->private_segment_size)
1165     segment_size = hsm->private_segment_size;
1166
1167   a->api_client_index = ~0;
1168   a->name = format (0, "test_http_static_server");
1169   a->session_cb_vft = &http_static_server_session_cb_vft;
1170   a->options = options;
1171   a->options[APP_OPTIONS_SEGMENT_SIZE] = segment_size;
1172   a->options[APP_OPTIONS_ADD_SEGMENT_SIZE] = segment_size;
1173   a->options[APP_OPTIONS_RX_FIFO_SIZE] =
1174     hsm->fifo_size ? hsm->fifo_size : 8 << 10;
1175   a->options[APP_OPTIONS_TX_FIFO_SIZE] =
1176     hsm->fifo_size ? hsm->fifo_size : 32 << 10;
1177   a->options[APP_OPTIONS_FLAGS] = APP_OPTIONS_FLAGS_IS_BUILTIN;
1178   a->options[APP_OPTIONS_PREALLOC_FIFO_PAIRS] = hsm->prealloc_fifos;
1179   a->options[APP_OPTIONS_TLS_ENGINE] = CRYPTO_ENGINE_OPENSSL;
1180
1181   if (vnet_application_attach (a))
1182     {
1183       vec_free (a->name);
1184       clib_warning ("failed to attach server");
1185       return -1;
1186     }
1187   vec_free (a->name);
1188   hsm->app_index = a->app_index;
1189
1190   clib_memset (ck_pair, 0, sizeof (*ck_pair));
1191   ck_pair->cert = (u8 *) test_srv_crt_rsa;
1192   ck_pair->key = (u8 *) test_srv_key_rsa;
1193   ck_pair->cert_len = test_srv_crt_rsa_len;
1194   ck_pair->key_len = test_srv_key_rsa_len;
1195   vnet_app_add_cert_key_pair (ck_pair);
1196   hsm->ckpair_index = ck_pair->index;
1197
1198   return 0;
1199 }
1200
1201 static int
1202 http_static_transport_needs_crypto (transport_proto_t proto)
1203 {
1204   return proto == TRANSPORT_PROTO_TLS || proto == TRANSPORT_PROTO_DTLS ||
1205          proto == TRANSPORT_PROTO_QUIC;
1206 }
1207
1208 static int
1209 http_static_server_listen ()
1210 {
1211   http_static_server_main_t *hsm = &http_static_server_main;
1212   session_endpoint_cfg_t sep = SESSION_ENDPOINT_CFG_NULL;
1213   vnet_listen_args_t _a, *a = &_a;
1214   char *uri = "tcp://0.0.0.0/80";
1215   int rv;
1216
1217   clib_memset (a, 0, sizeof (*a));
1218   a->app_index = hsm->app_index;
1219
1220   if (hsm->uri)
1221     uri = (char *) hsm->uri;
1222
1223   if (parse_uri (uri, &sep))
1224     return -1;
1225
1226   clib_memcpy (&a->sep_ext, &sep, sizeof (sep));
1227   if (http_static_transport_needs_crypto (a->sep_ext.transport_proto))
1228     {
1229       session_endpoint_alloc_ext_cfg (&a->sep_ext,
1230                                       TRANSPORT_ENDPT_EXT_CFG_CRYPTO);
1231       a->sep_ext.ext_cfg->crypto.ckpair_index = hsm->ckpair_index;
1232     }
1233
1234   rv = vnet_listen (a);
1235   if (a->sep_ext.ext_cfg)
1236     clib_mem_free (a->sep_ext.ext_cfg);
1237   return rv;
1238 }
1239
1240 static void
1241 http_static_server_session_close_cb (void *hs_handlep)
1242 {
1243   http_static_server_main_t *hsm = &http_static_server_main;
1244   http_session_t *hs;
1245   uword hs_handle;
1246   hs_handle = pointer_to_uword (hs_handlep);
1247   hs =
1248     http_static_server_session_get (hs_handle >> 24, hs_handle & 0x00FFFFFF);
1249
1250   if (hsm->debug_level > 1)
1251     clib_warning ("terminate thread %d index %d hs %llx",
1252                   hs_handle >> 24, hs_handle & 0x00FFFFFF, hs);
1253   if (!hs)
1254     return;
1255   hs->timer_handle = ~0;
1256   http_static_server_session_disconnect (hs);
1257 }
1258
1259 /** \brief Expired session timer-wheel callback
1260  */
1261 static void
1262 http_expired_timers_dispatch (u32 * expired_timers)
1263 {
1264   u32 hs_handle;
1265   int i;
1266
1267   for (i = 0; i < vec_len (expired_timers); i++)
1268     {
1269       /* Get session handle. The first bit is the timer id */
1270       hs_handle = expired_timers[i] & 0x7FFFFFFF;
1271       session_send_rpc_evt_to_thread (hs_handle >> 24,
1272                                       http_static_server_session_close_cb,
1273                                       uword_to_pointer (hs_handle, void *));
1274     }
1275 }
1276
1277 /** \brief Timer-wheel expiration process
1278  */
1279 static uword
1280 http_static_server_process (vlib_main_t * vm, vlib_node_runtime_t * rt,
1281                             vlib_frame_t * f)
1282 {
1283   http_static_server_main_t *hsm = &http_static_server_main;
1284   f64 now, timeout = 1.0;
1285   uword *event_data = 0;
1286   uword __clib_unused event_type;
1287
1288   while (1)
1289     {
1290       vlib_process_wait_for_event_or_clock (vm, timeout);
1291       now = vlib_time_now (vm);
1292       event_type = vlib_process_get_events (vm, (uword **) & event_data);
1293
1294       /* expire timers */
1295       clib_spinlock_lock (&http_static_server_main.tw_lock);
1296       tw_timer_expire_timers_2t_1w_2048sl (&hsm->tw, now);
1297       clib_spinlock_unlock (&http_static_server_main.tw_lock);
1298
1299       vec_reset_length (event_data);
1300     }
1301   return 0;
1302 }
1303
1304 /* *INDENT-OFF* */
1305 VLIB_REGISTER_NODE (http_static_server_process_node) =
1306 {
1307   .function = http_static_server_process,
1308   .type = VLIB_NODE_TYPE_PROCESS,
1309   .name = "static-http-server-process",
1310   .state = VLIB_NODE_STATE_DISABLED,
1311 };
1312 /* *INDENT-ON* */
1313
1314 static int
1315 http_static_server_create (vlib_main_t * vm)
1316 {
1317   vlib_thread_main_t *vtm = vlib_get_thread_main ();
1318   http_static_server_main_t *hsm = &http_static_server_main;
1319   u32 num_threads;
1320   vlib_node_t *n;
1321
1322   num_threads = 1 /* main thread */  + vtm->n_threads;
1323   vec_validate (hsm->vpp_queue, num_threads - 1);
1324   vec_validate (hsm->sessions, num_threads - 1);
1325   vec_validate (hsm->session_to_http_session, num_threads - 1);
1326
1327   clib_rwlock_init (&hsm->sessions_lock);
1328   clib_spinlock_init (&hsm->tw_lock);
1329
1330   if (http_static_server_attach ())
1331     {
1332       clib_warning ("failed to attach server");
1333       return -1;
1334     }
1335   if (http_static_server_listen ())
1336     {
1337       clib_warning ("failed to start listening");
1338       return -1;
1339     }
1340
1341   /* Init path-to-cache hash table */
1342   BV (clib_bihash_init) (&hsm->name_to_data, "http cache", 128, 32 << 20);
1343
1344   hsm->get_url_handlers = hash_create_string (0, sizeof (uword));
1345   hsm->post_url_handlers = hash_create_string (0, sizeof (uword));
1346
1347   /* Init timer wheel and process */
1348   tw_timer_wheel_init_2t_1w_2048sl (&hsm->tw, http_expired_timers_dispatch,
1349                                     1.0 /* timer interval */ , ~0);
1350   vlib_node_set_state (vm, http_static_server_process_node.index,
1351                        VLIB_NODE_STATE_POLLING);
1352   n = vlib_get_node (vm, http_static_server_process_node.index);
1353   vlib_start_process (vm, n->runtime_index);
1354
1355   return 0;
1356 }
1357
1358 /** \brief API helper function for vl_api_http_static_enable_t messages
1359  */
1360 int
1361 http_static_server_enable_api (u32 fifo_size, u32 cache_limit,
1362                                u32 prealloc_fifos,
1363                                u32 private_segment_size,
1364                                u8 * www_root, u8 * uri)
1365 {
1366   http_static_server_main_t *hsm = &http_static_server_main;
1367   int rv;
1368
1369   hsm->fifo_size = fifo_size;
1370   hsm->cache_limit = cache_limit;
1371   hsm->prealloc_fifos = prealloc_fifos;
1372   hsm->private_segment_size = private_segment_size;
1373   hsm->www_root = format (0, "%s%c", www_root, 0);
1374   hsm->uri = format (0, "%s%c", uri, 0);
1375
1376   if (vec_len (hsm->www_root) < 2)
1377     return VNET_API_ERROR_INVALID_VALUE;
1378
1379   if (hsm->my_client_index != ~0)
1380     return VNET_API_ERROR_APP_ALREADY_ATTACHED;
1381
1382   vnet_session_enable_disable (hsm->vlib_main, 1 /* turn on TCP, etc. */ );
1383
1384   rv = http_static_server_create (hsm->vlib_main);
1385   switch (rv)
1386     {
1387     case 0:
1388       break;
1389     default:
1390       vec_free (hsm->www_root);
1391       vec_free (hsm->uri);
1392       return VNET_API_ERROR_INIT_FAILED;
1393     }
1394   return 0;
1395 }
1396
1397 static clib_error_t *
1398 http_static_server_create_command_fn (vlib_main_t * vm,
1399                                       unformat_input_t * input,
1400                                       vlib_cli_command_t * cmd)
1401 {
1402   http_static_server_main_t *hsm = &http_static_server_main;
1403   unformat_input_t _line_input, *line_input = &_line_input;
1404   u64 seg_size;
1405   u8 *www_root = 0;
1406   int rv;
1407
1408   hsm->prealloc_fifos = 0;
1409   hsm->private_segment_size = 0;
1410   hsm->fifo_size = 0;
1411   /* 10mb cache limit, before LRU occurs */
1412   hsm->cache_limit = 10 << 20;
1413
1414   /* Get a line of input. */
1415   if (!unformat_user (input, unformat_line_input, line_input))
1416     goto no_wwwroot;
1417
1418   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
1419     {
1420       if (unformat (line_input, "www-root %s", &www_root))
1421         ;
1422       else
1423         if (unformat (line_input, "prealloc-fifos %d", &hsm->prealloc_fifos))
1424         ;
1425       else if (unformat (line_input, "private-segment-size %U",
1426                          unformat_memory_size, &seg_size))
1427         {
1428           if (seg_size >= 0x100000000ULL)
1429             {
1430               vlib_cli_output (vm, "private segment size %llu, too large",
1431                                seg_size);
1432               return 0;
1433             }
1434           hsm->private_segment_size = seg_size;
1435         }
1436       else if (unformat (line_input, "fifo-size %d", &hsm->fifo_size))
1437         hsm->fifo_size <<= 10;
1438       else if (unformat (line_input, "cache-size %U", unformat_memory_size,
1439                          &hsm->cache_limit))
1440         {
1441           if (hsm->cache_limit < (128 << 10))
1442             {
1443               return clib_error_return (0,
1444                                         "cache-size must be at least 128kb");
1445             }
1446         }
1447
1448       else if (unformat (line_input, "uri %s", &hsm->uri))
1449         ;
1450       else if (unformat (line_input, "debug %d", &hsm->debug_level))
1451         ;
1452       else if (unformat (line_input, "debug"))
1453         hsm->debug_level = 1;
1454       else
1455         return clib_error_return (0, "unknown input `%U'",
1456                                   format_unformat_error, line_input);
1457     }
1458   unformat_free (line_input);
1459
1460   if (www_root == 0)
1461     {
1462     no_wwwroot:
1463       return clib_error_return (0, "Must specify www-root <path>");
1464     }
1465
1466   if (hsm->my_client_index != (u32) ~ 0)
1467     {
1468       vec_free (www_root);
1469       return clib_error_return (0, "http server already running...");
1470     }
1471
1472   hsm->www_root = www_root;
1473
1474   vnet_session_enable_disable (vm, 1 /* turn on TCP, etc. */ );
1475
1476   rv = http_static_server_create (vm);
1477   switch (rv)
1478     {
1479     case 0:
1480       break;
1481     default:
1482       vec_free (hsm->www_root);
1483       return clib_error_return (0, "server_create returned %d", rv);
1484     }
1485   return 0;
1486 }
1487
1488 /*?
1489  * Enable the static http server
1490  *
1491  * @cliexpar
1492  * This command enables the static http server. Only the www-root
1493  * parameter is required
1494  * @clistart
1495  * http static server www-root /tmp/www uri tcp://0.0.0.0/80 cache-size 2m
1496  * @cliend
1497  * @cliexcmd{http static server www-root <path> [prealloc-fios <nn>]
1498  *   [private-segment-size <nnMG>] [fifo-size <nbytes>] [uri <uri>]}
1499 ?*/
1500 /* *INDENT-OFF* */
1501 VLIB_CLI_COMMAND (http_static_server_create_command, static) =
1502 {
1503   .path = "http static server",
1504   .short_help = "http static server www-root <path> [prealloc-fifos <nn>]\n"
1505   "[private-segment-size <nnMG>] [fifo-size <nbytes>] [uri <uri>]\n"
1506   "[debug [nn]]\n",
1507   .function = http_static_server_create_command_fn,
1508 };
1509 /* *INDENT-ON* */
1510
1511 /** \brief format a file cache entry
1512  */
1513 u8 *
1514 format_hsm_cache_entry (u8 * s, va_list * args)
1515 {
1516   file_data_cache_t *ep = va_arg (*args, file_data_cache_t *);
1517   f64 now = va_arg (*args, f64);
1518
1519   /* Header */
1520   if (ep == 0)
1521     {
1522       s = format (s, "%40s%12s%20s", "File", "Size", "Age");
1523       return s;
1524     }
1525   s = format (s, "%40s%12lld%20.2f", ep->filename, vec_len (ep->data),
1526               now - ep->last_used);
1527   return s;
1528 }
1529
1530 u8 *
1531 format_http_session_state (u8 * s, va_list * args)
1532 {
1533   http_session_state_t state = va_arg (*args, http_session_state_t);
1534   char *state_string = "bogus!";
1535
1536   switch (state)
1537     {
1538     case HTTP_STATE_CLOSED:
1539       state_string = "closed";
1540       break;
1541     case HTTP_STATE_ESTABLISHED:
1542       state_string = "established";
1543       break;
1544     case HTTP_STATE_OK_SENT:
1545       state_string = "ok sent";
1546       break;
1547     case HTTP_STATE_SEND_MORE_DATA:
1548       state_string = "send more data";
1549       break;
1550     default:
1551       break;
1552     }
1553
1554   return format (s, "%s", state_string);
1555 }
1556
1557 u8 *
1558 format_http_session (u8 * s, va_list * args)
1559 {
1560   http_session_t *hs = va_arg (*args, http_session_t *);
1561   int verbose = va_arg (*args, int);
1562
1563   s = format (s, "[%d]: state %U", hs->session_index,
1564               format_http_session_state, hs->session_state);
1565   if (verbose > 0)
1566     {
1567       s = format (s, "\n path %s, data length %u, data_offset %u",
1568                   hs->path ? hs->path : (u8 *) "[none]",
1569                   vec_len (hs->data), hs->data_offset);
1570     }
1571   return s;
1572 }
1573
1574 static clib_error_t *
1575 http_show_static_server_command_fn (vlib_main_t * vm,
1576                                     unformat_input_t * input,
1577                                     vlib_cli_command_t * cmd)
1578 {
1579   http_static_server_main_t *hsm = &http_static_server_main;
1580   file_data_cache_t *ep, **entries = 0;
1581   int verbose = 0;
1582   int show_cache = 0;
1583   int show_sessions = 0;
1584   u32 index;
1585   f64 now;
1586
1587   if (hsm->www_root == 0)
1588     return clib_error_return (0, "Static server disabled");
1589
1590   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
1591     {
1592       if (unformat (input, "verbose %d", &verbose))
1593         ;
1594       else if (unformat (input, "verbose"))
1595         verbose = 1;
1596       else if (unformat (input, "cache"))
1597         show_cache = 1;
1598       else if (unformat (input, "sessions"))
1599         show_sessions = 1;
1600       else
1601         break;
1602     }
1603
1604   if ((show_cache + show_sessions) == 0)
1605     return clib_error_return (0, "specify one or more of cache, sessions");
1606
1607   if (show_cache)
1608     {
1609       if (verbose == 0)
1610         {
1611           vlib_cli_output
1612             (vm, "www_root %s, cache size %lld bytes, limit %lld bytes, "
1613              "evictions %lld",
1614              hsm->www_root, hsm->cache_size, hsm->cache_limit,
1615              hsm->cache_evictions);
1616           return 0;
1617         }
1618
1619       now = vlib_time_now (vm);
1620
1621       vlib_cli_output (vm, "%U", format_hsm_cache_entry, 0 /* header */ ,
1622                        now);
1623
1624       for (index = hsm->first_index; index != ~0;)
1625         {
1626           ep = pool_elt_at_index (hsm->cache_pool, index);
1627           index = ep->next_index;
1628           vlib_cli_output (vm, "%U", format_hsm_cache_entry, ep, now);
1629         }
1630
1631       vlib_cli_output (vm, "%40s%12lld", "Total Size", hsm->cache_size);
1632
1633       vec_free (entries);
1634     }
1635
1636   if (show_sessions)
1637     {
1638       u32 *session_indices = 0;
1639       http_session_t *hs;
1640       int i, j;
1641
1642       http_static_server_sessions_reader_lock ();
1643
1644       for (i = 0; i < vec_len (hsm->sessions); i++)
1645         {
1646           /* *INDENT-OFF* */
1647           pool_foreach (hs, hsm->sessions[i])
1648            {
1649             vec_add1 (session_indices, hs - hsm->sessions[i]);
1650           }
1651           /* *INDENT-ON* */
1652
1653           for (j = 0; j < vec_len (session_indices); j++)
1654             {
1655               vlib_cli_output (vm, "%U", format_http_session,
1656                                pool_elt_at_index
1657                                (hsm->sessions[i], session_indices[j]),
1658                                verbose);
1659             }
1660           vec_reset_length (session_indices);
1661         }
1662       http_static_server_sessions_reader_unlock ();
1663       vec_free (session_indices);
1664     }
1665   return 0;
1666 }
1667
1668 /*?
1669  * Display static http server cache statistics
1670  *
1671  * @cliexpar
1672  * This command shows the contents of the static http server cache
1673  * @clistart
1674  * show http static server
1675  * @cliend
1676  * @cliexcmd{show http static server sessions cache [verbose [nn]]}
1677 ?*/
1678 /* *INDENT-OFF* */
1679 VLIB_CLI_COMMAND (http_show_static_server_command, static) =
1680 {
1681   .path = "show http static server",
1682   .short_help = "show http static server sessions cache [verbose [<nn>]]",
1683   .function = http_show_static_server_command_fn,
1684 };
1685 /* *INDENT-ON* */
1686
1687 static clib_error_t *
1688 http_clear_static_cache_command_fn (vlib_main_t * vm,
1689                                     unformat_input_t * input,
1690                                     vlib_cli_command_t * cmd)
1691 {
1692   http_static_server_main_t *hsm = &http_static_server_main;
1693   file_data_cache_t *dp;
1694   u32 free_index;
1695   u32 busy_items = 0;
1696   BVT (clib_bihash_kv) kv;
1697
1698   if (hsm->www_root == 0)
1699     return clib_error_return (0, "Static server disabled");
1700
1701   http_static_server_sessions_reader_lock ();
1702
1703   /* Walk the LRU list to find active entries */
1704   free_index = hsm->last_index;
1705   while (free_index != ~0)
1706     {
1707       dp = pool_elt_at_index (hsm->cache_pool, free_index);
1708       free_index = dp->prev_index;
1709       /* Which could be in use... */
1710       if (dp->inuse)
1711         {
1712           busy_items++;
1713           free_index = dp->next_index;
1714           continue;
1715         }
1716       kv.key = (u64) (dp->filename);
1717       kv.value = ~0ULL;
1718       if (BV (clib_bihash_add_del) (&hsm->name_to_data, &kv,
1719                                     0 /* is_add */ ) < 0)
1720         {
1721           clib_warning ("BUG: cache clear delete '%s' FAILED!", dp->filename);
1722         }
1723
1724       lru_remove (hsm, dp);
1725       hsm->cache_size -= vec_len (dp->data);
1726       hsm->cache_evictions++;
1727       vec_free (dp->filename);
1728       vec_free (dp->data);
1729       if (hsm->debug_level > 1)
1730         clib_warning ("pool put index %d", dp - hsm->cache_pool);
1731       pool_put (hsm->cache_pool, dp);
1732       free_index = hsm->last_index;
1733     }
1734   http_static_server_sessions_reader_unlock ();
1735   if (busy_items > 0)
1736     vlib_cli_output (vm, "Note: %d busy items still in cache...", busy_items);
1737   else
1738     vlib_cli_output (vm, "Cache cleared...");
1739   return 0;
1740 }
1741
1742 /*?
1743  * Clear the static http server cache, to force the server to
1744  * reload content from backing files
1745  *
1746  * @cliexpar
1747  * This command clear the static http server cache
1748  * @clistart
1749  * clear http static cache
1750  * @cliend
1751  * @cliexcmd{clear http static cache}
1752 ?*/
1753 /* *INDENT-OFF* */
1754 VLIB_CLI_COMMAND (clear_http_static_cache_command, static) =
1755 {
1756   .path = "clear http static cache",
1757   .short_help = "clear http static cache",
1758   .function = http_clear_static_cache_command_fn,
1759 };
1760 /* *INDENT-ON* */
1761
1762 static clib_error_t *
1763 http_static_server_main_init (vlib_main_t * vm)
1764 {
1765   http_static_server_main_t *hsm = &http_static_server_main;
1766
1767   hsm->my_client_index = ~0;
1768   hsm->vlib_main = vm;
1769   hsm->first_index = hsm->last_index = ~0;
1770
1771   clib_timebase_init (&hsm->timebase, 0 /* GMT */ ,
1772                       CLIB_TIMEBASE_DAYLIGHT_NONE,
1773                       &vm->clib_time /* share the system clock */ );
1774
1775   return 0;
1776 }
1777
1778 VLIB_INIT_FUNCTION (http_static_server_main_init);
1779
1780 /*
1781  * fd.io coding-style-patch-verification: ON
1782  *
1783  * Local Variables:
1784  * eval: (c-set-style "gnu")
1785  * End:
1786  */