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