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