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