vppinfra: refactor clib_timebase_t
[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, u8 is_fail)
1122 {
1123   clib_warning ("called...");
1124   return -1;
1125 }
1126
1127 static int
1128 http_static_server_add_segment_callback (u32 client_index, u64 segment_handle)
1129 {
1130   clib_warning ("called...");
1131   return -1;
1132 }
1133
1134 /** \brief Session-layer virtual function table
1135  */
1136 static session_cb_vft_t http_static_server_session_cb_vft = {
1137   .session_accept_callback = http_static_server_session_accept_callback,
1138   .session_disconnect_callback =
1139     http_static_server_session_disconnect_callback,
1140   .session_connected_callback = http_static_server_session_connected_callback,
1141   .add_segment_callback = http_static_server_add_segment_callback,
1142   .builtin_app_rx_callback = http_static_server_rx_callback,
1143   .builtin_app_tx_callback = http_static_server_tx_callback,
1144   .session_reset_callback = http_static_server_session_reset_callback
1145 };
1146
1147 static int
1148 http_static_server_attach ()
1149 {
1150   vnet_app_add_tls_cert_args_t _a_cert, *a_cert = &_a_cert;
1151   vnet_app_add_tls_key_args_t _a_key, *a_key = &_a_key;
1152   http_static_server_main_t *hsm = &http_static_server_main;
1153   u64 options[APP_OPTIONS_N_OPTIONS];
1154   vnet_app_attach_args_t _a, *a = &_a;
1155   u32 segment_size = 128 << 20;
1156
1157   clib_memset (a, 0, sizeof (*a));
1158   clib_memset (options, 0, sizeof (options));
1159
1160   if (hsm->private_segment_size)
1161     segment_size = hsm->private_segment_size;
1162
1163   a->api_client_index = ~0;
1164   a->name = format (0, "test_http_static_server");
1165   a->session_cb_vft = &http_static_server_session_cb_vft;
1166   a->options = options;
1167   a->options[APP_OPTIONS_SEGMENT_SIZE] = segment_size;
1168   a->options[APP_OPTIONS_RX_FIFO_SIZE] =
1169     hsm->fifo_size ? hsm->fifo_size : 8 << 10;
1170   a->options[APP_OPTIONS_TX_FIFO_SIZE] =
1171     hsm->fifo_size ? hsm->fifo_size : 32 << 10;
1172   a->options[APP_OPTIONS_FLAGS] = APP_OPTIONS_FLAGS_IS_BUILTIN;
1173   a->options[APP_OPTIONS_PREALLOC_FIFO_PAIRS] = hsm->prealloc_fifos;
1174   a->options[APP_OPTIONS_TLS_ENGINE] = CRYPTO_ENGINE_OPENSSL;
1175
1176   if (vnet_application_attach (a))
1177     {
1178       vec_free (a->name);
1179       clib_warning ("failed to attach server");
1180       return -1;
1181     }
1182   vec_free (a->name);
1183   hsm->app_index = a->app_index;
1184
1185   clib_memset (a_cert, 0, sizeof (*a_cert));
1186   a_cert->app_index = a->app_index;
1187   vec_validate (a_cert->cert, test_srv_crt_rsa_len);
1188   clib_memcpy_fast (a_cert->cert, test_srv_crt_rsa, test_srv_crt_rsa_len);
1189   vnet_app_add_tls_cert (a_cert);
1190
1191   clib_memset (a_key, 0, sizeof (*a_key));
1192   a_key->app_index = a->app_index;
1193   vec_validate (a_key->key, test_srv_key_rsa_len);
1194   clib_memcpy_fast (a_key->key, test_srv_key_rsa, test_srv_key_rsa_len);
1195   vnet_app_add_tls_key (a_key);
1196
1197   return 0;
1198 }
1199
1200 static int
1201 http_static_server_listen ()
1202 {
1203   http_static_server_main_t *hsm = &http_static_server_main;
1204   vnet_listen_args_t _a, *a = &_a;
1205   clib_memset (a, 0, sizeof (*a));
1206   a->app_index = hsm->app_index;
1207   a->uri = "tcp://0.0.0.0/80";
1208   if (hsm->uri)
1209     a->uri = (char *) hsm->uri;
1210   return vnet_bind_uri (a);
1211 }
1212
1213 static void
1214 http_static_server_session_cleanup_cb (void *hs_handlep)
1215 {
1216   http_static_server_main_t *hsm = &http_static_server_main;
1217   http_session_t *hs;
1218   uword hs_handle;
1219   hs_handle = pointer_to_uword (hs_handlep);
1220   hs =
1221     http_static_server_session_get (hs_handle >> 24, hs_handle & 0x00FFFFFF);
1222
1223   if (hsm->debug_level > 1)
1224     clib_warning ("terminate thread %d index %d hs %llx",
1225                   hs_handle >> 24, hs_handle & 0x00FFFFFF, hs);
1226   if (!hs)
1227     return;
1228   hs->timer_handle = ~0;
1229   http_static_server_session_disconnect (hs);
1230   http_static_server_session_cleanup (hs);
1231 }
1232
1233 /** \brief Expired session timer-wheel callback
1234  */
1235 static void
1236 http_expired_timers_dispatch (u32 * expired_timers)
1237 {
1238   u32 hs_handle;
1239   int i;
1240
1241   for (i = 0; i < vec_len (expired_timers); i++)
1242     {
1243       /* Get session handle. The first bit is the timer id */
1244       hs_handle = expired_timers[i] & 0x7FFFFFFF;
1245       session_send_rpc_evt_to_thread (hs_handle >> 24,
1246                                       http_static_server_session_cleanup_cb,
1247                                       uword_to_pointer (hs_handle, void *));
1248     }
1249 }
1250
1251 /** \brief Timer-wheel expiration process
1252  */
1253 static uword
1254 http_static_server_process (vlib_main_t * vm, vlib_node_runtime_t * rt,
1255                             vlib_frame_t * f)
1256 {
1257   http_static_server_main_t *hsm = &http_static_server_main;
1258   f64 now, timeout = 1.0;
1259   uword *event_data = 0;
1260   uword __clib_unused event_type;
1261
1262   while (1)
1263     {
1264       vlib_process_wait_for_event_or_clock (vm, timeout);
1265       now = vlib_time_now (vm);
1266       event_type = vlib_process_get_events (vm, (uword **) & event_data);
1267
1268       /* expire timers */
1269       clib_spinlock_lock (&http_static_server_main.tw_lock);
1270       tw_timer_expire_timers_2t_1w_2048sl (&hsm->tw, now);
1271       clib_spinlock_unlock (&http_static_server_main.tw_lock);
1272
1273       vec_reset_length (event_data);
1274     }
1275   return 0;
1276 }
1277
1278 /* *INDENT-OFF* */
1279 VLIB_REGISTER_NODE (http_static_server_process_node) =
1280 {
1281   .function = http_static_server_process,
1282   .type = VLIB_NODE_TYPE_PROCESS,
1283   .name = "static-http-server-process",
1284   .state = VLIB_NODE_STATE_DISABLED,
1285 };
1286 /* *INDENT-ON* */
1287
1288 static int
1289 http_static_server_create (vlib_main_t * vm)
1290 {
1291   vlib_thread_main_t *vtm = vlib_get_thread_main ();
1292   http_static_server_main_t *hsm = &http_static_server_main;
1293   u32 num_threads;
1294   vlib_node_t *n;
1295
1296   num_threads = 1 /* main thread */  + vtm->n_threads;
1297   vec_validate (hsm->vpp_queue, num_threads - 1);
1298   vec_validate (hsm->sessions, num_threads - 1);
1299   vec_validate (hsm->session_to_http_session, num_threads - 1);
1300
1301   clib_rwlock_init (&hsm->sessions_lock);
1302   clib_spinlock_init (&hsm->tw_lock);
1303
1304   if (http_static_server_attach ())
1305     {
1306       clib_warning ("failed to attach server");
1307       return -1;
1308     }
1309   if (http_static_server_listen ())
1310     {
1311       clib_warning ("failed to start listening");
1312       return -1;
1313     }
1314
1315   /* Init path-to-cache hash table */
1316   BV (clib_bihash_init) (&hsm->name_to_data, "http cache", 128, 32 << 20);
1317
1318   hsm->get_url_handlers = hash_create_string (0, sizeof (uword));
1319   hsm->post_url_handlers = hash_create_string (0, sizeof (uword));
1320
1321   /* Init timer wheel and process */
1322   tw_timer_wheel_init_2t_1w_2048sl (&hsm->tw, http_expired_timers_dispatch,
1323                                     1.0 /* timer interval */ , ~0);
1324   vlib_node_set_state (vm, http_static_server_process_node.index,
1325                        VLIB_NODE_STATE_POLLING);
1326   n = vlib_get_node (vm, http_static_server_process_node.index);
1327   vlib_start_process (vm, n->runtime_index);
1328
1329   return 0;
1330 }
1331
1332 /** \brief API helper function for vl_api_http_static_enable_t messages
1333  */
1334 int
1335 http_static_server_enable_api (u32 fifo_size, u32 cache_limit,
1336                                u32 prealloc_fifos,
1337                                u32 private_segment_size,
1338                                u8 * www_root, u8 * uri)
1339 {
1340   http_static_server_main_t *hsm = &http_static_server_main;
1341   int rv;
1342
1343   hsm->fifo_size = fifo_size;
1344   hsm->cache_limit = cache_limit;
1345   hsm->prealloc_fifos = prealloc_fifos;
1346   hsm->private_segment_size = private_segment_size;
1347   hsm->www_root = format (0, "%s%c", www_root, 0);
1348   hsm->uri = format (0, "%s%c", uri, 0);
1349
1350   if (vec_len (hsm->www_root) < 2)
1351     return VNET_API_ERROR_INVALID_VALUE;
1352
1353   if (hsm->my_client_index != ~0)
1354     return VNET_API_ERROR_APP_ALREADY_ATTACHED;
1355
1356   vnet_session_enable_disable (hsm->vlib_main, 1 /* turn on TCP, etc. */ );
1357
1358   rv = http_static_server_create (hsm->vlib_main);
1359   switch (rv)
1360     {
1361     case 0:
1362       break;
1363     default:
1364       vec_free (hsm->www_root);
1365       vec_free (hsm->uri);
1366       return VNET_API_ERROR_INIT_FAILED;
1367     }
1368   return 0;
1369 }
1370
1371 static clib_error_t *
1372 http_static_server_create_command_fn (vlib_main_t * vm,
1373                                       unformat_input_t * input,
1374                                       vlib_cli_command_t * cmd)
1375 {
1376   http_static_server_main_t *hsm = &http_static_server_main;
1377   unformat_input_t _line_input, *line_input = &_line_input;
1378   u64 seg_size;
1379   u8 *www_root = 0;
1380   int rv;
1381
1382   hsm->prealloc_fifos = 0;
1383   hsm->private_segment_size = 0;
1384   hsm->fifo_size = 0;
1385   /* 10mb cache limit, before LRU occurs */
1386   hsm->cache_limit = 10 << 20;
1387
1388   /* Get a line of input. */
1389   if (!unformat_user (input, unformat_line_input, line_input))
1390     goto no_wwwroot;
1391
1392   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
1393     {
1394       if (unformat (line_input, "www-root %s", &www_root))
1395         ;
1396       else
1397         if (unformat (line_input, "prealloc-fifos %d", &hsm->prealloc_fifos))
1398         ;
1399       else if (unformat (line_input, "private-segment-size %U",
1400                          unformat_memory_size, &seg_size))
1401         {
1402           if (seg_size >= 0x100000000ULL)
1403             {
1404               vlib_cli_output (vm, "private segment size %llu, too large",
1405                                seg_size);
1406               return 0;
1407             }
1408           hsm->private_segment_size = seg_size;
1409         }
1410       else if (unformat (line_input, "fifo-size %d", &hsm->fifo_size))
1411         hsm->fifo_size <<= 10;
1412       else if (unformat (line_input, "cache-size %U", unformat_memory_size,
1413                          &hsm->cache_limit))
1414         {
1415           if (hsm->cache_limit < (128 << 10))
1416             {
1417               return clib_error_return (0,
1418                                         "cache-size must be at least 128kb");
1419             }
1420         }
1421
1422       else if (unformat (line_input, "uri %s", &hsm->uri))
1423         ;
1424       else if (unformat (line_input, "debug %d", &hsm->debug_level))
1425         ;
1426       else if (unformat (line_input, "debug"))
1427         hsm->debug_level = 1;
1428       else
1429         return clib_error_return (0, "unknown input `%U'",
1430                                   format_unformat_error, line_input);
1431     }
1432   unformat_free (line_input);
1433
1434   if (www_root == 0)
1435     {
1436     no_wwwroot:
1437       return clib_error_return (0, "Must specify www-root <path>");
1438     }
1439
1440   if (hsm->my_client_index != (u32) ~ 0)
1441     {
1442       vec_free (www_root);
1443       return clib_error_return (0, "http server already running...");
1444     }
1445
1446   hsm->www_root = www_root;
1447
1448   vnet_session_enable_disable (vm, 1 /* turn on TCP, etc. */ );
1449
1450   rv = http_static_server_create (vm);
1451   switch (rv)
1452     {
1453     case 0:
1454       break;
1455     default:
1456       vec_free (hsm->www_root);
1457       return clib_error_return (0, "server_create returned %d", rv);
1458     }
1459   return 0;
1460 }
1461
1462 /*?
1463  * Enable the static http server
1464  *
1465  * @cliexpar
1466  * This command enables the static http server. Only the www-root
1467  * parameter is required
1468  * @clistart
1469  * http static server www-root /tmp/www uri tcp://0.0.0.0/80 cache-size 2m
1470  * @cliend
1471  * @cliexcmd{http static server www-root <path> [prealloc-fios <nn>]
1472  *   [private-segment-size <nnMG>] [fifo-size <nbytes>] [uri <uri>]}
1473 ?*/
1474 /* *INDENT-OFF* */
1475 VLIB_CLI_COMMAND (http_static_server_create_command, static) =
1476 {
1477   .path = "http static server",
1478   .short_help = "http static server www-root <path> [prealloc-fifos <nn>]\n"
1479   "[private-segment-size <nnMG>] [fifo-size <nbytes>] [uri <uri>]\n"
1480   "[debug [nn]]\n",
1481   .function = http_static_server_create_command_fn,
1482 };
1483 /* *INDENT-ON* */
1484
1485 /** \brief format a file cache entry
1486  */
1487 u8 *
1488 format_hsm_cache_entry (u8 * s, va_list * args)
1489 {
1490   file_data_cache_t *ep = va_arg (*args, file_data_cache_t *);
1491   f64 now = va_arg (*args, f64);
1492
1493   /* Header */
1494   if (ep == 0)
1495     {
1496       s = format (s, "%40s%12s%20s", "File", "Size", "Age");
1497       return s;
1498     }
1499   s = format (s, "%40s%12lld%20.2f", ep->filename, vec_len (ep->data),
1500               now - ep->last_used);
1501   return s;
1502 }
1503
1504 u8 *
1505 format_http_session_state (u8 * s, va_list * args)
1506 {
1507   http_session_state_t state = va_arg (*args, http_session_state_t);
1508   char *state_string = "bogus!";
1509
1510   switch (state)
1511     {
1512     case HTTP_STATE_CLOSED:
1513       state_string = "closed";
1514       break;
1515     case HTTP_STATE_ESTABLISHED:
1516       state_string = "established";
1517       break;
1518     case HTTP_STATE_OK_SENT:
1519       state_string = "ok sent";
1520       break;
1521     case HTTP_STATE_SEND_MORE_DATA:
1522       state_string = "send more data";
1523       break;
1524     default:
1525       break;
1526     }
1527
1528   return format (s, "%s", state_string);
1529 }
1530
1531 u8 *
1532 format_http_session (u8 * s, va_list * args)
1533 {
1534   http_session_t *hs = va_arg (*args, http_session_t *);
1535   int verbose = va_arg (*args, int);
1536
1537   s = format (s, "[%d]: state %U", hs->session_index,
1538               format_http_session_state, hs->session_state);
1539   if (verbose > 0)
1540     {
1541       s = format (s, "\n path %s, data length %u, data_offset %u",
1542                   hs->path ? hs->path : (u8 *) "[none]",
1543                   vec_len (hs->data), hs->data_offset);
1544     }
1545   return s;
1546 }
1547
1548 static clib_error_t *
1549 http_show_static_server_command_fn (vlib_main_t * vm,
1550                                     unformat_input_t * input,
1551                                     vlib_cli_command_t * cmd)
1552 {
1553   http_static_server_main_t *hsm = &http_static_server_main;
1554   file_data_cache_t *ep, **entries = 0;
1555   int verbose = 0;
1556   int show_cache = 0;
1557   int show_sessions = 0;
1558   u32 index;
1559   f64 now;
1560
1561   if (hsm->www_root == 0)
1562     return clib_error_return (0, "Static server disabled");
1563
1564   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
1565     {
1566       if (unformat (input, "verbose %d", &verbose))
1567         ;
1568       else if (unformat (input, "verbose"))
1569         verbose = 1;
1570       else if (unformat (input, "cache"))
1571         show_cache = 1;
1572       else if (unformat (input, "sessions"))
1573         show_sessions = 1;
1574       else
1575         break;
1576     }
1577
1578   if ((show_cache + show_sessions) == 0)
1579     return clib_error_return (0, "specify one or more of cache, sessions");
1580
1581   if (show_cache)
1582     {
1583       if (verbose == 0)
1584         {
1585           vlib_cli_output
1586             (vm, "www_root %s, cache size %lld bytes, limit %lld bytes, "
1587              "evictions %lld",
1588              hsm->www_root, hsm->cache_size, hsm->cache_limit,
1589              hsm->cache_evictions);
1590           return 0;
1591         }
1592
1593       now = vlib_time_now (vm);
1594
1595       vlib_cli_output (vm, "%U", format_hsm_cache_entry, 0 /* header */ ,
1596                        now);
1597
1598       for (index = hsm->first_index; index != ~0;)
1599         {
1600           ep = pool_elt_at_index (hsm->cache_pool, index);
1601           index = ep->next_index;
1602           vlib_cli_output (vm, "%U", format_hsm_cache_entry, ep, now);
1603         }
1604
1605       vlib_cli_output (vm, "%40s%12lld", "Total Size", hsm->cache_size);
1606
1607       vec_free (entries);
1608     }
1609
1610   if (show_sessions)
1611     {
1612       u32 *session_indices = 0;
1613       http_session_t *hs;
1614       int i, j;
1615
1616       http_static_server_sessions_reader_lock ();
1617
1618       for (i = 0; i < vec_len (hsm->sessions); i++)
1619         {
1620           /* *INDENT-OFF* */
1621           pool_foreach (hs, hsm->sessions[i],
1622           ({
1623             vec_add1 (session_indices, hs - hsm->sessions[i]);
1624           }));
1625           /* *INDENT-ON* */
1626
1627           for (j = 0; j < vec_len (session_indices); j++)
1628             {
1629               vlib_cli_output (vm, "%U", format_http_session,
1630                                pool_elt_at_index
1631                                (hsm->sessions[i], session_indices[j]),
1632                                verbose);
1633             }
1634           vec_reset_length (session_indices);
1635         }
1636       http_static_server_sessions_reader_unlock ();
1637       vec_free (session_indices);
1638     }
1639   return 0;
1640 }
1641
1642 /*?
1643  * Display static http server cache statistics
1644  *
1645  * @cliexpar
1646  * This command shows the contents of the static http server cache
1647  * @clistart
1648  * show http static server
1649  * @cliend
1650  * @cliexcmd{show http static server sessions cache [verbose [nn]]}
1651 ?*/
1652 /* *INDENT-OFF* */
1653 VLIB_CLI_COMMAND (http_show_static_server_command, static) =
1654 {
1655   .path = "show http static server",
1656   .short_help = "show http static server sessions cache [verbose [<nn>]]",
1657   .function = http_show_static_server_command_fn,
1658 };
1659 /* *INDENT-ON* */
1660
1661 static clib_error_t *
1662 http_clear_static_cache_command_fn (vlib_main_t * vm,
1663                                     unformat_input_t * input,
1664                                     vlib_cli_command_t * cmd)
1665 {
1666   http_static_server_main_t *hsm = &http_static_server_main;
1667   file_data_cache_t *dp;
1668   u32 free_index;
1669   u32 busy_items = 0;
1670   BVT (clib_bihash_kv) kv;
1671
1672   if (hsm->www_root == 0)
1673     return clib_error_return (0, "Static server disabled");
1674
1675   http_static_server_sessions_reader_lock ();
1676
1677   /* Walk the LRU list to find active entries */
1678   free_index = hsm->last_index;
1679   while (free_index != ~0)
1680     {
1681       dp = pool_elt_at_index (hsm->cache_pool, free_index);
1682       free_index = dp->prev_index;
1683       /* Which could be in use... */
1684       if (dp->inuse)
1685         {
1686           busy_items++;
1687           free_index = dp->next_index;
1688           continue;
1689         }
1690       kv.key = (u64) (dp->filename);
1691       kv.value = ~0ULL;
1692       if (BV (clib_bihash_add_del) (&hsm->name_to_data, &kv,
1693                                     0 /* is_add */ ) < 0)
1694         {
1695           clib_warning ("BUG: cache clear delete '%s' FAILED!", dp->filename);
1696         }
1697
1698       lru_remove (hsm, dp);
1699       hsm->cache_size -= vec_len (dp->data);
1700       hsm->cache_evictions++;
1701       vec_free (dp->filename);
1702       vec_free (dp->data);
1703       if (hsm->debug_level > 1)
1704         clib_warning ("pool put index %d", dp - hsm->cache_pool);
1705       pool_put (hsm->cache_pool, dp);
1706       free_index = hsm->last_index;
1707     }
1708   http_static_server_sessions_reader_unlock ();
1709   if (busy_items > 0)
1710     vlib_cli_output (vm, "Note: %d busy items still in cache...", busy_items);
1711   else
1712     vlib_cli_output (vm, "Cache cleared...");
1713   return 0;
1714 }
1715
1716 /*?
1717  * Clear the static http server cache, to force the server to
1718  * reload content from backing files
1719  *
1720  * @cliexpar
1721  * This command clear the static http server cache
1722  * @clistart
1723  * clear http static cache
1724  * @cliend
1725  * @cliexcmd{clear http static cache}
1726 ?*/
1727 /* *INDENT-OFF* */
1728 VLIB_CLI_COMMAND (clear_http_static_cache_command, static) =
1729 {
1730   .path = "clear http static cache",
1731   .short_help = "clear http static cache",
1732   .function = http_clear_static_cache_command_fn,
1733 };
1734 /* *INDENT-ON* */
1735
1736 static clib_error_t *
1737 http_static_server_main_init (vlib_main_t * vm)
1738 {
1739   http_static_server_main_t *hsm = &http_static_server_main;
1740
1741   hsm->my_client_index = ~0;
1742   hsm->vlib_main = vm;
1743   hsm->first_index = hsm->last_index = ~0;
1744
1745   clib_timebase_init (&hsm->timebase, 0 /* GMT */ ,
1746                       CLIB_TIMEBASE_DAYLIGHT_NONE,
1747                       &vm->clib_time /* share the system clock */ );
1748
1749   return 0;
1750 }
1751
1752 VLIB_INIT_FUNCTION (http_static_server_main_init);
1753
1754 /*
1755  * fd.io coding-style-patch-verification: ON
1756  *
1757  * Local Variables:
1758  * eval: (c-set-style "gnu")
1759  * End:
1760  */