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