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