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