svm: more fifo refactor/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/tw_timer_2t_1w_2048sl.h>
21 #include <vppinfra/unix.h>
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 #include <unistd.h>
25 #include <http_static/http_static.h>
26 #include <vppinfra/bihash_vec8_8.h>
27
28 #include <vppinfra/bihash_template.c>
29
30 /** @file
31     Simple Static http server, sufficient to
32     serve .html / .css / .js content.
33 */
34 /*? %%clicmd:group_label Static HTTP Server %% ?*/
35
36 /** \brief Session States
37  */
38
39 typedef enum
40 {
41   /** Session is closed */
42   HTTP_STATE_CLOSED,
43   /** Session is established */
44   HTTP_STATE_ESTABLISHED,
45   /** Session has sent an OK response */
46   HTTP_STATE_OK_SENT,
47   /** Session has sent an HTML response */
48   HTTP_STATE_RESPONSE_SENT,
49 } http_session_state_t;
50
51 /** \brief Application session
52  */
53 typedef struct
54 {
55   CLIB_CACHE_LINE_ALIGN_MARK (cacheline0);
56   /** Base class instance variables */
57 #define _(type, name) type name;
58   foreach_app_session_field
59 #undef _
60   /** rx thread index */
61   u32 thread_index;
62   /** rx buffer */
63   u8 *rx_buf;
64   /** vpp session index, handle */
65   u32 vpp_session_index;
66   u64 vpp_session_handle;
67   /** Timeout timer handle */
68   u32 timer_handle;
69   /** Fully-resolved file path */
70   u8 *path;
71   /** File data, a vector */
72   u8 *data;
73   /** Current data send offset */
74   u32 data_offset;
75   /** File cache pool index */
76   u32 cache_pool_index;
77 } http_session_t;
78
79 /** \brief In-memory file data cache entry
80  */
81 typedef struct
82 {
83   /** Name of the file */
84   u8 *filename;
85   /** Contents of the file, as a u8 * vector */
86   u8 *data;
87   /** Last time the cache entry was used */
88   f64 last_used;
89   /** Cache LRU links */
90   u32 next_index;
91   u32 prev_index;
92   /** Reference count, so we don't recycle while referenced */
93   int inuse;
94 } file_data_cache_t;
95
96 /** \brief Main data structure
97  */
98
99 typedef struct
100 {
101   /** Per thread vector of session pools */
102   http_session_t **sessions;
103   /** Session pool reader writer lock */
104   clib_rwlock_t sessions_lock;
105   /** vpp session to http session index map */
106   u32 **session_to_http_session;
107
108   /** vpp message/event queue */
109   svm_msg_q_t **vpp_queue;
110
111   /** Unified file data cache pool */
112   file_data_cache_t *cache_pool;
113   /** Hash table which maps file name to file data */
114     BVT (clib_bihash) name_to_data;
115
116   /** Current cache size */
117   u64 cache_size;
118   /** Max cache size in bytes */
119   u64 cache_limit;
120   /** Number of cache evictions */
121   u64 cache_evictions;
122
123   /** Cache LRU listheads */
124   u32 first_index;
125   u32 last_index;
126
127   /** root path to be served */
128   u8 *www_root;
129
130   /** Server's event queue */
131   svm_queue_t *vl_input_queue;
132
133   /** API client handle */
134   u32 my_client_index;
135
136   /** Application index */
137   u32 app_index;
138
139   /** Process node index for event scheduling */
140   u32 node_index;
141
142   /** Session cleanup timer wheel */
143   tw_timer_wheel_2t_1w_2048sl_t tw;
144   clib_spinlock_t tw_lock;
145
146   /** Time base, so we can generate browser cache control http spew */
147   clib_timebase_t timebase;
148
149   /** Number of preallocated fifos, usually 0 */
150   u32 prealloc_fifos;
151   /** Private segment size, usually 0 */
152   u32 private_segment_size;
153   /** Size of the allocated rx, tx fifos, roughly 8K or so */
154   u32 fifo_size;
155   /** The bind URI, defaults to tcp://0.0.0.0/80 */
156   u8 *uri;
157   vlib_main_t *vlib_main;
158 } http_static_server_main_t;
159
160 http_static_server_main_t http_static_server_main;
161
162 /** \brief Acquire reader lock on the sessions pools
163  */
164 static void
165 http_static_server_sessions_reader_lock (void)
166 {
167   clib_rwlock_reader_lock (&http_static_server_main.sessions_lock);
168 }
169
170 /** \brief Drop reader lock on the sessions pools
171  */
172 static void
173 http_static_server_sessions_reader_unlock (void)
174 {
175   clib_rwlock_reader_unlock (&http_static_server_main.sessions_lock);
176 }
177
178 /** \brief Acquire writer lock on the sessions pools
179  */
180 static void
181 http_static_server_sessions_writer_lock (void)
182 {
183   clib_rwlock_writer_lock (&http_static_server_main.sessions_lock);
184 }
185
186 /** \brief Drop writer lock on the sessions pools
187  */
188 static void
189 http_static_server_sessions_writer_unlock (void)
190 {
191   clib_rwlock_writer_unlock (&http_static_server_main.sessions_lock);
192 }
193
194 /** \brief Allocate an http session
195  */
196 static http_session_t *
197 http_static_server_session_alloc (u32 thread_index)
198 {
199   http_static_server_main_t *hsm = &http_static_server_main;
200   http_session_t *hs;
201   pool_get (hsm->sessions[thread_index], hs);
202   memset (hs, 0, sizeof (*hs));
203   hs->session_index = hs - hsm->sessions[thread_index];
204   hs->thread_index = thread_index;
205   hs->timer_handle = ~0;
206   hs->cache_pool_index = ~0;
207   return hs;
208 }
209
210 /** \brief Get an http session by index
211  */
212 static http_session_t *
213 http_static_server_session_get (u32 thread_index, u32 hs_index)
214 {
215   http_static_server_main_t *hsm = &http_static_server_main;
216   if (pool_is_free_index (hsm->sessions[thread_index], hs_index))
217     return 0;
218   return pool_elt_at_index (hsm->sessions[thread_index], hs_index);
219 }
220
221 /** \brief Free an http session
222  */
223 static void
224 http_static_server_session_free (http_session_t * hs)
225 {
226   http_static_server_main_t *hsm = &http_static_server_main;
227   pool_put (hsm->sessions[hs->thread_index], hs);
228   if (CLIB_DEBUG)
229     memset (hs, 0xfa, sizeof (*hs));
230 }
231
232 /** \brief add a session to the vpp < -- > http session index map
233  */
234 static void
235 http_static_server_session_lookup_add (u32 thread_index, u32 s_index,
236                                        u32 hs_index)
237 {
238   http_static_server_main_t *hsm = &http_static_server_main;
239   vec_validate (hsm->session_to_http_session[thread_index], s_index);
240   hsm->session_to_http_session[thread_index][s_index] = hs_index;
241 }
242
243 /** \brief Remove a session from the vpp < -- > http session index map
244  */
245 static void
246 http_static_server_session_lookup_del (u32 thread_index, u32 s_index)
247 {
248   http_static_server_main_t *hsm = &http_static_server_main;
249   hsm->session_to_http_session[thread_index][s_index] = ~0;
250 }
251
252 /** \brief lookup a session in the vpp < -- > http session index map
253  */
254
255 static http_session_t *
256 http_static_server_session_lookup (u32 thread_index, u32 s_index)
257 {
258   http_static_server_main_t *hsm = &http_static_server_main;
259   u32 hs_index;
260
261   if (s_index < vec_len (hsm->session_to_http_session[thread_index]))
262     {
263       hs_index = hsm->session_to_http_session[thread_index][s_index];
264       return http_static_server_session_get (thread_index, hs_index);
265     }
266   return 0;
267 }
268
269 /** \brief Start a session cleanup timer
270  */
271
272 static void
273 http_static_server_session_timer_start (http_session_t * hs)
274 {
275   u32 hs_handle;
276   hs_handle = hs->thread_index << 24 | hs->session_index;
277   clib_spinlock_lock (&http_static_server_main.tw_lock);
278   hs->timer_handle = tw_timer_start_2t_1w_2048sl (&http_static_server_main.tw,
279                                                   hs_handle, 0, 60);
280   clib_spinlock_unlock (&http_static_server_main.tw_lock);
281 }
282
283 /** \brief stop a session cleanup timer
284  */
285 static void
286 http_static_server_session_timer_stop (http_session_t * hs)
287 {
288   if (hs->timer_handle == ~0)
289     return;
290   clib_spinlock_lock (&http_static_server_main.tw_lock);
291   tw_timer_stop_2t_1w_2048sl (&http_static_server_main.tw, hs->timer_handle);
292   clib_spinlock_unlock (&http_static_server_main.tw_lock);
293 }
294
295 /** \brief Clean up an http session
296  */
297
298 static void
299 http_static_server_session_cleanup (http_session_t * hs)
300 {
301   http_static_server_main_t *hsm = &http_static_server_main;
302   file_data_cache_t *ep;
303
304   if (!hs)
305     return;
306
307   /*
308    * Decrement cache pool entry reference count
309    * Note that if e.g. a file lookup fails, the cache pool index
310    * won't be set
311    */
312   if (hs->cache_pool_index != ~0)
313     {
314       ep = pool_elt_at_index (hsm->cache_pool, hs->cache_pool_index);
315       ep->inuse--;
316       if (0)
317         clib_warning ("index %d refcnt now %d", hs->cache_pool_index,
318                       ep->inuse);
319     }
320
321   http_static_server_session_lookup_del (hs->thread_index,
322                                          hs->vpp_session_index);
323   vec_free (hs->rx_buf);
324   vec_free (hs->path);
325   http_static_server_session_timer_stop (hs);
326   http_static_server_session_free (hs);
327 }
328
329 /** \brief Disconnect a session
330  */
331
332 static void
333 http_static_server_session_disconnect (http_session_t * hs)
334 {
335   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
336   a->handle = hs->vpp_session_handle;
337   a->app_index = http_static_server_main.app_index;
338   vnet_disconnect_session (a);
339 }
340
341 /* *INDENT-OFF* */
342 /** \brief http error boilerplate
343  */
344 static const char *http_error_template =
345     "HTTP/1.1 %s\r\n"
346     "Date: %U GMT\r\n"
347     "Content-Type: text/html\r\n"
348     "Connection: close\r\n"
349     "Pragma: no-cache\r\n"
350     "Content-Length: 0\r\n\r\n";
351
352 /** \brief http response boilerplate
353  */
354 static const char *http_response_template =
355     "Date: %U GMT\r\n"
356     "Expires: %U GMT\r\n"
357     "Server: VPP Static\r\n"
358     "Content-Type: text/%s\r\n"
359     "Connection: close \r\n"
360     "Content-Length: %d\r\n\r\n";
361
362 /* *INDENT-ON* */
363
364 /** \brief send http data
365     @param hs - http session
366     @param data - the data vector to transmit
367     @param offset - transmit offset for this operation
368     @return offset for next transmit operation, may be unchanged w/ full fifo
369 */
370
371 static u32
372 static_send_data (http_session_t * hs, u8 * data, u32 length, u32 offset)
373 {
374   u32 bytes_to_send;
375
376   bytes_to_send = length - offset;
377
378   while (bytes_to_send > 0)
379     {
380       int actual_transfer;
381
382       actual_transfer = svm_fifo_enqueue
383         (hs->tx_fifo, bytes_to_send, data + offset);
384
385       /* Made any progress? */
386       if (actual_transfer <= 0)
387         return offset;
388       else
389         {
390           offset += actual_transfer;
391           bytes_to_send -= actual_transfer;
392
393           if (svm_fifo_set_event (hs->tx_fifo))
394             session_send_io_evt_to_thread (hs->tx_fifo,
395                                            SESSION_IO_EVT_TX_FLUSH);
396           return offset;
397         }
398     }
399   /* NOTREACHED */
400   return ~0;
401 }
402
403 /** \brief Send an http error string
404     @param hs - the http session
405     @param str - the error string, e.g. "404 Not Found"
406 */
407 static void
408 send_error (http_session_t * hs, char *str)
409 {
410   http_static_server_main_t *hsm = &http_static_server_main;
411   u8 *data;
412   f64 now;
413
414   now = clib_timebase_now (&hsm->timebase);
415   data = format (0, http_error_template, str, format_clib_timebase_time, now);
416   static_send_data (hs, data, vec_len (data), 0);
417   vec_free (data);
418 }
419
420 /** \brief Retrieve data from the application layer
421  */
422 static int
423 session_rx_request (http_session_t * hs)
424 {
425   u32 max_dequeue, cursize;
426   int n_read;
427
428   cursize = vec_len (hs->rx_buf);
429   max_dequeue = svm_fifo_max_dequeue (hs->rx_fifo);
430   if (PREDICT_FALSE (max_dequeue == 0))
431     return -1;
432
433   vec_validate (hs->rx_buf, cursize + max_dequeue - 1);
434   n_read = app_recv_stream_raw (hs->rx_fifo, hs->rx_buf + cursize,
435                                 max_dequeue, 0, 0 /* peek */ );
436   ASSERT (n_read == max_dequeue);
437   if (svm_fifo_is_empty (hs->rx_fifo))
438     svm_fifo_unset_event (hs->rx_fifo);
439
440   _vec_len (hs->rx_buf) = cursize + n_read;
441   return 0;
442 }
443
444 /** \brief Sanity-check the forward and reverse LRU lists
445  */
446 static inline void
447 lru_validate (http_static_server_main_t * hsm)
448 {
449 #if CLIB_DEBUG > 0
450   f64 last_timestamp;
451   u32 index;
452   int i;
453   file_data_cache_t *ep;
454
455   last_timestamp = 1e70;
456   for (i = 1, index = hsm->first_index; index != ~0;)
457     {
458       ep = pool_elt_at_index (hsm->cache_pool, index);
459       index = ep->next_index;
460       /* Timestamps should be smaller (older) as we walk the fwd list */
461       if (ep->last_used > last_timestamp)
462         {
463           clib_warning ("%d[%d]: last used %.6f, last_timestamp %.6f",
464                         ep - hsm->cache_pool, i,
465                         ep->last_used, last_timestamp);
466         }
467       last_timestamp = ep->last_used;
468       i++;
469     }
470
471   last_timestamp = 0.0;
472   for (i = 1, index = hsm->last_index; index != ~0;)
473     {
474       ep = pool_elt_at_index (hsm->cache_pool, index);
475       index = ep->prev_index;
476       /* Timestamps should be larger (newer) as we walk the rev list */
477       if (ep->last_used < last_timestamp)
478         {
479           clib_warning ("%d[%d]: last used %.6f, last_timestamp %.6f",
480                         ep - hsm->cache_pool, i,
481                         ep->last_used, last_timestamp);
482         }
483       last_timestamp = ep->last_used;
484       i++;
485     }
486 #endif
487 }
488
489 /** \brief Remove a data cache entry from the LRU lists
490  */
491 static inline void
492 lru_remove (http_static_server_main_t * hsm, file_data_cache_t * ep)
493 {
494   file_data_cache_t *next_ep, *prev_ep;
495   u32 ep_index;
496
497   lru_validate (hsm);
498
499   ep_index = ep - hsm->cache_pool;
500
501   /* Deal with list heads */
502   if (ep_index == hsm->first_index)
503     hsm->first_index = ep->next_index;
504   if (ep_index == hsm->last_index)
505     hsm->last_index = ep->prev_index;
506
507   /* Fix next->prev */
508   if (ep->next_index != ~0)
509     {
510       next_ep = pool_elt_at_index (hsm->cache_pool, ep->next_index);
511       next_ep->prev_index = ep->prev_index;
512     }
513   /* Fix prev->next */
514   if (ep->prev_index != ~0)
515     {
516       prev_ep = pool_elt_at_index (hsm->cache_pool, ep->prev_index);
517       prev_ep->next_index = ep->next_index;
518     }
519   lru_validate (hsm);
520 }
521
522 /** \brief Add an entry to the LRU lists, tag w/ supplied timestamp
523  */
524
525 static inline void
526 lru_add (http_static_server_main_t * hsm, file_data_cache_t * ep, f64 now)
527 {
528   file_data_cache_t *next_ep;
529   u32 ep_index;
530
531   lru_validate (hsm);
532
533   ep_index = ep - hsm->cache_pool;
534
535   /*
536    * Re-add at the head of the forward LRU list,
537    * tail of the reverse LRU list
538    */
539   if (hsm->first_index != ~0)
540     {
541       next_ep = pool_elt_at_index (hsm->cache_pool, hsm->first_index);
542       next_ep->prev_index = ep_index;
543     }
544
545   ep->prev_index = ~0;
546
547   /* ep now the new head of the LRU forward list */
548   ep->next_index = hsm->first_index;
549   hsm->first_index = ep_index;
550
551   /* single session case: also the tail of the reverse LRU list */
552   if (hsm->last_index == ~0)
553     hsm->last_index = ep_index;
554   ep->last_used = now;
555
556   lru_validate (hsm);
557 }
558
559 /** \brief Remove and re-add a cache entry from/to the LRU lists
560  */
561
562 static inline void
563 lru_update (http_static_server_main_t * hsm, file_data_cache_t * ep, f64 now)
564 {
565   lru_remove (hsm, ep);
566   lru_add (hsm, ep, now);
567 }
568
569 /** \brief Session-layer (main) data rx callback.
570     Parse the http request, and reply to it.
571     Future extensions might include POST processing, active content, etc.
572 */
573
574 static int
575 http_static_server_rx_callback (session_t * s)
576 {
577   http_static_server_main_t *hsm = &http_static_server_main;
578   u32 request_len;
579   u8 *request = 0;
580   u8 *path;
581   http_session_t *hs;
582   int i, rv;
583   struct stat _sb, *sb = &_sb;
584   u8 *http_response;
585   f64 now;
586   clib_error_t *error;
587   char *suffix;
588   char *http_type;
589
590   /* Acquire a reader lock on the session table */
591   http_static_server_sessions_reader_lock ();
592   hs = http_static_server_session_lookup (s->thread_index, s->session_index);
593
594   /* No such session? Say goodnight, Gracie... */
595   if (!hs || hs->session_state == HTTP_STATE_CLOSED)
596     {
597       http_static_server_sessions_reader_unlock ();
598       return 0;
599     }
600
601   /* If this session has already sent "OK 200" */
602   if (hs->session_state == HTTP_STATE_OK_SENT)
603     goto static_send_response;
604
605   /* If this session has already sent a response (needs to send more data)  */
606   if (hs->session_state == HTTP_STATE_RESPONSE_SENT)
607     goto static_send_data;
608
609   /* Read data from the sesison layer */
610   rv = session_rx_request (hs);
611   if (rv)
612     goto wait_for_data;
613
614   /* Process the client request */
615   request = hs->rx_buf;
616   request_len = vec_len (request);
617   if (vec_len (request) < 7)
618     {
619       send_error (hs, "400 Bad Request");
620       goto close_session;
621     }
622
623   /* We only handle GET requests at the moment */
624   for (i = 0; i < request_len - 4; i++)
625     {
626       if (request[i] == 'G' &&
627           request[i + 1] == 'E' &&
628           request[i + 2] == 'T' && request[i + 3] == ' ')
629         goto find_end;
630     }
631   send_error (hs, "400 Bad Request");
632   goto close_session;
633
634 find_end:
635
636   /* Lose "GET " */
637   vec_delete (request, i + 5, 0);
638
639   /* Lose stuff to the right of the path */
640   for (i = 0; i < vec_len (request); i++)
641     {
642       if (request[i] == ' ' || request[i] == '?')
643         {
644           request[i] = 0;
645           break;
646         }
647     }
648
649   /*
650    * Now we can construct the file to open
651    * Browsers are capable of sporadically including a leading '/'
652    */
653   if (request[0] == '/')
654     path = format (0, "%s%s%c", hsm->www_root, request, 0);
655   else
656     path = format (0, "%s/%s%c", hsm->www_root, request, 0);
657
658   if (0)
659     clib_warning ("GET '%s'", path);
660
661   /* Try to find the file. 2x special cases to find index.html */
662   if (stat ((char *) path, sb) < 0      /* cant even stat the file */
663       || sb->st_size < 20       /* file too small */
664       || (sb->st_mode & S_IFMT) != S_IFREG /* not a regular file */ )
665     {
666       u32 save_length = vec_len (path) - 1;
667       /* Try appending "index.html"... */
668       _vec_len (path) -= 1;
669       path = format (path, "index.html%c", 0);
670       if (stat ((char *) path, sb) < 0  /* cant even stat the file */
671           || sb->st_size < 20   /* file too small */
672           || (sb->st_mode & S_IFMT) != S_IFREG /* not a regular file */ )
673         {
674           _vec_len (path) = save_length;
675           path = format (path, "/index.html%c", 0);
676
677           /* Send a redirect, otherwise the browser will confuse itself */
678           if (stat ((char *) path, sb) < 0      /* cant even stat the file */
679               || sb->st_size < 20       /* file too small */
680               || (sb->st_mode & S_IFMT) != S_IFREG /* not a regular file */ )
681             {
682               vec_free (path);
683               send_error (hs, "404 Not Found");
684               goto close_session;
685             }
686           else
687             {
688               transport_connection_t *tc;
689               /*
690                * To make this bit work correctly, we need to know our local
691                * IP address, etc. and send it in the redirect...
692                */
693               u8 *redirect;
694
695               vec_delete (path, vec_len (hsm->www_root) - 1, 0);
696
697
698               tc = session_get_transport (s);
699               redirect = format (0, "HTTP/1.1 301 Moved Permanently\r\n"
700                                  "Location: http://%U%s\r\n"
701                                  "Connection: close\r\n",
702                                  format_ip46_address, &tc->lcl_ip, tc->is_ip4,
703                                  path);
704               if (0)
705                 clib_warning ("redirect: %s", redirect);
706
707               static_send_data (hs, redirect, vec_len (redirect), 0);
708               hs->session_state = HTTP_STATE_RESPONSE_SENT;
709               hs->path = 0;
710               vec_free (redirect);
711               vec_free (path);
712               goto close_session;
713             }
714         }
715     }
716
717   /* find or read the file if we haven't done so yet. */
718   if (hs->data == 0)
719     {
720       BVT (clib_bihash_kv) kv;
721       file_data_cache_t *dp;
722
723       hs->path = path;
724
725       /* First, try the cache */
726       kv.key = (u64) hs->path;
727       if (BV (clib_bihash_search) (&hsm->name_to_data, &kv, &kv) == 0)
728         {
729           if (0)
730             clib_warning ("lookup '%s' returned %lld", kv.key, kv.value);
731
732           /* found the data.. */
733           dp = pool_elt_at_index (hsm->cache_pool, kv.value);
734           hs->data = dp->data;
735           /* Update the cache entry, mark it in-use */
736           lru_update (hsm, dp, vlib_time_now (hsm->vlib_main));
737           hs->cache_pool_index = dp - hsm->cache_pool;
738           dp->inuse++;
739           if (0)
740             clib_warning ("index %d refcnt now %d", hs->cache_pool_index,
741                           dp->inuse);
742         }
743       else
744         {
745           if (0)
746             clib_warning ("lookup '%s' failed", kv.key, kv.value);
747           /* Need to recycle one (or more cache) entries? */
748           if (hsm->cache_size > hsm->cache_limit)
749             {
750               int free_index = hsm->last_index;
751
752               while (free_index != ~0)
753                 {
754                   /* pick the LRU */
755                   dp = pool_elt_at_index (hsm->cache_pool, free_index);
756                   free_index = dp->prev_index;
757                   /* Which could be in use... */
758                   if (dp->inuse)
759                     {
760                       if (0)
761                         clib_warning ("index %d in use refcnt %d",
762                                       dp - hsm->cache_pool, dp->inuse);
763
764                     }
765                   kv.key = (u64) (dp->filename);
766                   kv.value = ~0ULL;
767                   if (BV (clib_bihash_add_del) (&hsm->name_to_data, &kv,
768                                                 0 /* is_add */ ) < 0)
769                     {
770                       clib_warning ("LRU delete '%s' FAILED!", dp->filename);
771                     }
772                   else if (0)
773                     clib_warning ("LRU delete '%s' ok", dp->filename);
774
775                   lru_remove (hsm, dp);
776                   hsm->cache_size -= vec_len (dp->data);
777                   hsm->cache_evictions++;
778                   vec_free (dp->filename);
779                   vec_free (dp->data);
780                   if (0)
781                     clib_warning ("pool put index %d", dp - hsm->cache_pool);
782                   pool_put (hsm->cache_pool, dp);
783                   if (hsm->cache_size < hsm->cache_limit)
784                     break;
785                 }
786             }
787
788           /* Read the file */
789           error = clib_file_contents ((char *) (hs->path), &hs->data);
790           if (error)
791             {
792               clib_warning ("Error reading '%s'", hs->path);
793               clib_error_report (error);
794               vec_free (hs->path);
795               goto close_session;
796             }
797           /* Create a cache entry for it */
798           pool_get (hsm->cache_pool, dp);
799           memset (dp, 0, sizeof (*dp));
800           dp->filename = vec_dup (hs->path);
801           dp->data = hs->data;
802           hs->cache_pool_index = dp - hsm->cache_pool;
803           dp->inuse++;
804           if (0)
805             clib_warning ("index %d refcnt now %d", hs->cache_pool_index,
806                           dp->inuse);
807           lru_add (hsm, dp, vlib_time_now (hsm->vlib_main));
808           kv.key = (u64) vec_dup (hs->path);
809           kv.value = dp - hsm->cache_pool;
810           /* Add to the lookup table */
811           if (0)
812             clib_warning ("add '%s' value %lld", kv.key, kv.value);
813
814           if (BV (clib_bihash_add_del) (&hsm->name_to_data, &kv,
815                                         1 /* is_add */ ) < 0)
816             {
817               clib_warning ("BUG: add failed!");
818             }
819           hsm->cache_size += vec_len (dp->data);
820         }
821       hs->data_offset = 0;
822     }
823
824   /* send 200 OK first */
825   static_send_data (hs, (u8 *) "HTTP/1.1 200 OK\r\n", 17, 0);
826   hs->session_state = HTTP_STATE_OK_SENT;
827   goto postpone;
828
829 static_send_response:
830
831   /* What kind of dog food are we serving? */
832   suffix = (char *) (hs->path + vec_len (hs->path) - 1);
833   while (*suffix != '.')
834     suffix--;
835   suffix++;
836   http_type = "html";
837   if (!clib_strcmp (suffix, "css"))
838     http_type = "css";
839   else if (!clib_strcmp (suffix, "js"))
840     http_type = "javascript";
841
842   /*
843    * Send an http response, which needs the current time,
844    * the expiration time, and the data length
845    */
846   now = clib_timebase_now (&hsm->timebase);
847   http_response = format (0, http_response_template,
848                           /* Date */
849                           format_clib_timebase_time, now,
850                           /* Expires */
851                           format_clib_timebase_time, now + 600.0,
852                           http_type, vec_len (hs->data));
853   static_send_data (hs, http_response, vec_len (http_response), 0);
854   vec_free (http_response);
855   hs->session_state = HTTP_STATE_RESPONSE_SENT;
856   /* NOTE FALLTHROUGH */
857
858 static_send_data:
859
860   /*
861    * Try to send data. Ideally, the fifos will be large
862    * enough to send the entire file in one motion.
863    */
864
865   hs->data_offset = static_send_data (hs, hs->data, vec_len (hs->data),
866                                       hs->data_offset);
867   if (hs->data_offset < vec_len (hs->data))
868     goto postpone;
869
870 close_session:
871   http_static_server_session_disconnect (hs);
872   http_static_server_session_cleanup (hs);
873   http_static_server_sessions_reader_unlock ();
874   return 0;
875
876 postpone:
877   (void) svm_fifo_set_event (hs->rx_fifo);
878   session_send_io_evt_to_thread (hs->rx_fifo, SESSION_IO_EVT_BUILTIN_RX);
879   http_static_server_sessions_reader_unlock ();
880   return 0;
881
882 wait_for_data:
883   http_static_server_sessions_reader_unlock ();
884   return 0;
885 }
886
887 /** \brief Session accept callback
888  */
889
890 static int
891 http_static_server_session_accept_callback (session_t * s)
892 {
893   http_static_server_main_t *hsm = &http_static_server_main;
894   http_session_t *hs;
895
896   hsm->vpp_queue[s->thread_index] =
897     session_main_get_vpp_event_queue (s->thread_index);
898
899   http_static_server_sessions_writer_lock ();
900
901   hs = http_static_server_session_alloc (s->thread_index);
902   http_static_server_session_lookup_add (s->thread_index, s->session_index,
903                                          hs->session_index);
904   hs->rx_fifo = s->rx_fifo;
905   hs->tx_fifo = s->tx_fifo;
906   hs->vpp_session_index = s->session_index;
907   hs->vpp_session_handle = session_handle (s);
908   hs->session_state = HTTP_STATE_ESTABLISHED;
909   http_static_server_session_timer_start (hs);
910
911   http_static_server_sessions_writer_unlock ();
912
913   s->session_state = SESSION_STATE_READY;
914   return 0;
915 }
916
917 /** \brief Session disconnect callback
918  */
919
920 static void
921 http_static_server_session_disconnect_callback (session_t * s)
922 {
923   http_static_server_main_t *hsm = &http_static_server_main;
924   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
925   http_session_t *hs;
926
927   http_static_server_sessions_writer_lock ();
928
929   hs = http_static_server_session_lookup (s->thread_index, s->session_index);
930   http_static_server_session_cleanup (hs);
931
932   http_static_server_sessions_writer_unlock ();
933
934   a->handle = session_handle (s);
935   a->app_index = hsm->app_index;
936   vnet_disconnect_session (a);
937 }
938
939 /** \brief Session reset callback
940  */
941
942 static void
943 http_static_server_session_reset_callback (session_t * s)
944 {
945   http_static_server_main_t *hsm = &http_static_server_main;
946   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
947   http_session_t *hs;
948
949   http_static_server_sessions_writer_lock ();
950
951   hs = http_static_server_session_lookup (s->thread_index, s->session_index);
952   http_static_server_session_cleanup (hs);
953
954   http_static_server_sessions_writer_unlock ();
955
956   a->handle = session_handle (s);
957   a->app_index = hsm->app_index;
958   vnet_disconnect_session (a);
959 }
960
961 static int
962 http_static_server_session_connected_callback (u32 app_index, u32 api_context,
963                                                session_t * s, u8 is_fail)
964 {
965   clib_warning ("called...");
966   return -1;
967 }
968
969 static int
970 http_static_server_add_segment_callback (u32 client_index, u64 segment_handle)
971 {
972   clib_warning ("called...");
973   return -1;
974 }
975
976 /** \brief Session-layer virtual function table
977  */
978 static session_cb_vft_t http_static_server_session_cb_vft = {
979   .session_accept_callback = http_static_server_session_accept_callback,
980   .session_disconnect_callback =
981     http_static_server_session_disconnect_callback,
982   .session_connected_callback = http_static_server_session_connected_callback,
983   .add_segment_callback = http_static_server_add_segment_callback,
984   .builtin_app_rx_callback = http_static_server_rx_callback,
985   .session_reset_callback = http_static_server_session_reset_callback
986 };
987
988 static int
989 http_static_server_attach ()
990 {
991   vnet_app_add_tls_cert_args_t _a_cert, *a_cert = &_a_cert;
992   vnet_app_add_tls_key_args_t _a_key, *a_key = &_a_key;
993   http_static_server_main_t *hsm = &http_static_server_main;
994   u64 options[APP_OPTIONS_N_OPTIONS];
995   vnet_app_attach_args_t _a, *a = &_a;
996   u32 segment_size = 128 << 20;
997
998   clib_memset (a, 0, sizeof (*a));
999   clib_memset (options, 0, sizeof (options));
1000
1001   if (hsm->private_segment_size)
1002     segment_size = hsm->private_segment_size;
1003
1004   a->api_client_index = ~0;
1005   a->name = format (0, "test_http_static_server");
1006   a->session_cb_vft = &http_static_server_session_cb_vft;
1007   a->options = options;
1008   a->options[APP_OPTIONS_SEGMENT_SIZE] = segment_size;
1009   a->options[APP_OPTIONS_RX_FIFO_SIZE] =
1010     hsm->fifo_size ? hsm->fifo_size : 8 << 10;
1011   a->options[APP_OPTIONS_TX_FIFO_SIZE] =
1012     hsm->fifo_size ? hsm->fifo_size : 32 << 10;
1013   a->options[APP_OPTIONS_FLAGS] = APP_OPTIONS_FLAGS_IS_BUILTIN;
1014   a->options[APP_OPTIONS_PREALLOC_FIFO_PAIRS] = hsm->prealloc_fifos;
1015
1016   if (vnet_application_attach (a))
1017     {
1018       vec_free (a->name);
1019       clib_warning ("failed to attach server");
1020       return -1;
1021     }
1022   vec_free (a->name);
1023   hsm->app_index = a->app_index;
1024
1025   clib_memset (a_cert, 0, sizeof (*a_cert));
1026   a_cert->app_index = a->app_index;
1027   vec_validate (a_cert->cert, test_srv_crt_rsa_len);
1028   clib_memcpy_fast (a_cert->cert, test_srv_crt_rsa, test_srv_crt_rsa_len);
1029   vnet_app_add_tls_cert (a_cert);
1030
1031   clib_memset (a_key, 0, sizeof (*a_key));
1032   a_key->app_index = a->app_index;
1033   vec_validate (a_key->key, test_srv_key_rsa_len);
1034   clib_memcpy_fast (a_key->key, test_srv_key_rsa, test_srv_key_rsa_len);
1035   vnet_app_add_tls_key (a_key);
1036
1037   return 0;
1038 }
1039
1040 static int
1041 http_static_server_listen ()
1042 {
1043   http_static_server_main_t *hsm = &http_static_server_main;
1044   vnet_listen_args_t _a, *a = &_a;
1045   clib_memset (a, 0, sizeof (*a));
1046   a->app_index = hsm->app_index;
1047   a->uri = "tcp://0.0.0.0/80";
1048   if (hsm->uri)
1049     a->uri = (char *) hsm->uri;
1050   return vnet_bind_uri (a);
1051 }
1052
1053 static void
1054 http_static_server_session_cleanup_cb (void *hs_handlep)
1055 {
1056   http_session_t *hs;
1057   uword hs_handle;
1058   hs_handle = pointer_to_uword (hs_handlep);
1059   hs =
1060     http_static_server_session_get (hs_handle >> 24, hs_handle & 0x00FFFFFF);
1061   if (!hs)
1062     return;
1063   hs->timer_handle = ~0;
1064   http_static_server_session_disconnect (hs);
1065   http_static_server_session_cleanup (hs);
1066 }
1067
1068 /** \brief Expired session timer-wheel callback
1069  */
1070 static void
1071 http_expired_timers_dispatch (u32 * expired_timers)
1072 {
1073   u32 hs_handle;
1074   int i;
1075
1076   for (i = 0; i < vec_len (expired_timers); i++)
1077     {
1078       /* Get session handle. The first bit is the timer id */
1079       hs_handle = expired_timers[i] & 0x7FFFFFFF;
1080       session_send_rpc_evt_to_thread (hs_handle >> 24,
1081                                       http_static_server_session_cleanup_cb,
1082                                       uword_to_pointer (hs_handle, void *));
1083     }
1084 }
1085
1086 /** \brief Timer-wheel expiration process
1087  */
1088 static uword
1089 http_static_server_process (vlib_main_t * vm, vlib_node_runtime_t * rt,
1090                             vlib_frame_t * f)
1091 {
1092   http_static_server_main_t *hsm = &http_static_server_main;
1093   f64 now, timeout = 1.0;
1094   uword *event_data = 0;
1095   uword __clib_unused event_type;
1096
1097   while (1)
1098     {
1099       vlib_process_wait_for_event_or_clock (vm, timeout);
1100       now = vlib_time_now (vm);
1101       event_type = vlib_process_get_events (vm, (uword **) & event_data);
1102
1103       /* expire timers */
1104       clib_spinlock_lock (&http_static_server_main.tw_lock);
1105       tw_timer_expire_timers_2t_1w_2048sl (&hsm->tw, now);
1106       clib_spinlock_unlock (&http_static_server_main.tw_lock);
1107
1108       vec_reset_length (event_data);
1109     }
1110   return 0;
1111 }
1112
1113 /* *INDENT-OFF* */
1114 VLIB_REGISTER_NODE (http_static_server_process_node) =
1115 {
1116   .function = http_static_server_process,
1117   .type = VLIB_NODE_TYPE_PROCESS,
1118   .name = "static-http-server-process",
1119   .state = VLIB_NODE_STATE_DISABLED,
1120 };
1121 /* *INDENT-ON* */
1122
1123 static int
1124 http_static_server_create (vlib_main_t * vm)
1125 {
1126   vlib_thread_main_t *vtm = vlib_get_thread_main ();
1127   http_static_server_main_t *hsm = &http_static_server_main;
1128   u32 num_threads;
1129   vlib_node_t *n;
1130
1131   num_threads = 1 /* main thread */  + vtm->n_threads;
1132   vec_validate (hsm->vpp_queue, num_threads - 1);
1133   vec_validate (hsm->sessions, num_threads - 1);
1134   vec_validate (hsm->session_to_http_session, num_threads - 1);
1135
1136   clib_rwlock_init (&hsm->sessions_lock);
1137   clib_spinlock_init (&hsm->tw_lock);
1138
1139   if (http_static_server_attach ())
1140     {
1141       clib_warning ("failed to attach server");
1142       return -1;
1143     }
1144   if (http_static_server_listen ())
1145     {
1146       clib_warning ("failed to start listening");
1147       return -1;
1148     }
1149
1150   /* Init path-to-cache hash table */
1151   BV (clib_bihash_init) (&hsm->name_to_data, "http cache", 128, 32 << 20);
1152
1153   /* Init timer wheel and process */
1154   tw_timer_wheel_init_2t_1w_2048sl (&hsm->tw, http_expired_timers_dispatch,
1155                                     1 /* timer interval */ , ~0);
1156   vlib_node_set_state (vm, http_static_server_process_node.index,
1157                        VLIB_NODE_STATE_POLLING);
1158   n = vlib_get_node (vm, http_static_server_process_node.index);
1159   vlib_start_process (vm, n->runtime_index);
1160
1161   return 0;
1162 }
1163
1164 /** \brief API helper function for vl_api_http_static_enable_t messages
1165  */
1166 int
1167 http_static_server_enable_api (u32 fifo_size, u32 cache_limit,
1168                                u32 prealloc_fifos,
1169                                u32 private_segment_size,
1170                                u8 * www_root, u8 * uri)
1171 {
1172   http_static_server_main_t *hsm = &http_static_server_main;
1173   int rv;
1174
1175   hsm->fifo_size = fifo_size;
1176   hsm->cache_limit = cache_limit;
1177   hsm->prealloc_fifos = prealloc_fifos;
1178   hsm->private_segment_size = private_segment_size;
1179   hsm->www_root = format (0, "%s%c", www_root, 0);
1180   hsm->uri = format (0, "%s%c", uri, 0);
1181
1182   if (vec_len (hsm->www_root) < 2)
1183     return VNET_API_ERROR_INVALID_VALUE;
1184
1185   if (hsm->my_client_index != ~0)
1186     return VNET_API_ERROR_APP_ALREADY_ATTACHED;
1187
1188   vnet_session_enable_disable (hsm->vlib_main, 1 /* turn on TCP, etc. */ );
1189
1190   rv = http_static_server_create (hsm->vlib_main);
1191   switch (rv)
1192     {
1193     case 0:
1194       break;
1195     default:
1196       vec_free (hsm->www_root);
1197       vec_free (hsm->uri);
1198       return VNET_API_ERROR_INIT_FAILED;
1199     }
1200   return 0;
1201 }
1202
1203 static clib_error_t *
1204 http_static_server_create_command_fn (vlib_main_t * vm,
1205                                       unformat_input_t * input,
1206                                       vlib_cli_command_t * cmd)
1207 {
1208   http_static_server_main_t *hsm = &http_static_server_main;
1209   unformat_input_t _line_input, *line_input = &_line_input;
1210   u64 seg_size;
1211   u8 *www_root = 0;
1212   int rv;
1213
1214   hsm->prealloc_fifos = 0;
1215   hsm->private_segment_size = 0;
1216   hsm->fifo_size = 0;
1217   /* 10mb cache limit, before LRU occurs */
1218   hsm->cache_limit = 10 << 20;
1219
1220   /* Get a line of input. */
1221   if (!unformat_user (input, unformat_line_input, line_input))
1222     goto no_wwwroot;
1223
1224   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
1225     {
1226       if (unformat (line_input, "www-root %s", &www_root))
1227         ;
1228       else
1229         if (unformat (line_input, "prealloc-fifos %d", &hsm->prealloc_fifos))
1230         ;
1231       else if (unformat (line_input, "private-segment-size %U",
1232                          unformat_memory_size, &seg_size))
1233         {
1234           if (seg_size >= 0x100000000ULL)
1235             {
1236               vlib_cli_output (vm, "private segment size %llu, too large",
1237                                seg_size);
1238               return 0;
1239             }
1240           hsm->private_segment_size = seg_size;
1241         }
1242       else if (unformat (line_input, "fifo-size %d", &hsm->fifo_size))
1243         hsm->fifo_size <<= 10;
1244       else if (unformat (line_input, "cache-size %U", unformat_memory_size,
1245                          &hsm->cache_limit))
1246         {
1247           if (hsm->cache_limit < (128 << 10))
1248             {
1249               return clib_error_return (0,
1250                                         "cache-size must be at least 128kb");
1251             }
1252         }
1253
1254       else if (unformat (line_input, "uri %s", &hsm->uri))
1255         ;
1256       else
1257         return clib_error_return (0, "unknown input `%U'",
1258                                   format_unformat_error, line_input);
1259     }
1260   unformat_free (line_input);
1261
1262   if (www_root == 0)
1263     {
1264     no_wwwroot:
1265       return clib_error_return (0, "Must specify www-root <path>");
1266     }
1267
1268   if (hsm->my_client_index != (u32) ~ 0)
1269     {
1270       vec_free (www_root);
1271       return clib_error_return (0, "http server already running...");
1272     }
1273
1274   hsm->www_root = www_root;
1275
1276   vnet_session_enable_disable (vm, 1 /* turn on TCP, etc. */ );
1277
1278   rv = http_static_server_create (vm);
1279   switch (rv)
1280     {
1281     case 0:
1282       break;
1283     default:
1284       vec_free (hsm->www_root);
1285       return clib_error_return (0, "server_create returned %d", rv);
1286     }
1287   return 0;
1288 }
1289
1290 /*?
1291  * Enable the static http server
1292  *
1293  * @cliexpar
1294  * This command enables the static http server. Only the www-root
1295  * parameter is required
1296  * @clistart
1297  * http static server www-root /tmp/www uri tcp://0.0.0.0/80 cache-size 2m
1298  * @cliend
1299  * @cliexcmd{http static server www-root <path> [prealloc-fios <nn>]
1300  *   [private-segment-size <nnMG>] [fifo-size <nbytes>] [uri <uri>]}
1301 ?*/
1302 /* *INDENT-OFF* */
1303 VLIB_CLI_COMMAND (http_static_server_create_command, static) =
1304 {
1305   .path = "http static server",
1306   .short_help = "http static server www-root <path> [prealloc-fios <nn>]\n"
1307   "[private-segment-size <nnMG>] [fifo-size <nbytes>] [uri <uri>]\n",
1308   .function = http_static_server_create_command_fn,
1309 };
1310 /* *INDENT-ON* */
1311
1312 /** \brief format a file cache entry
1313  */
1314 u8 *
1315 format_hsm_cache_entry (u8 * s, va_list * args)
1316 {
1317   file_data_cache_t *ep = va_arg (*args, file_data_cache_t *);
1318   f64 now = va_arg (*args, f64);
1319
1320   /* Header */
1321   if (ep == 0)
1322     {
1323       s = format (s, "%40s%12s%20s", "File", "Size", "Age");
1324       return s;
1325     }
1326   s = format (s, "%40s%12lld%20.2f", ep->filename, vec_len (ep->data),
1327               now - ep->last_used);
1328   return s;
1329 }
1330
1331 static clib_error_t *
1332 http_show_static_server_command_fn (vlib_main_t * vm,
1333                                     unformat_input_t * input,
1334                                     vlib_cli_command_t * cmd)
1335 {
1336   http_static_server_main_t *hsm = &http_static_server_main;
1337   file_data_cache_t *ep, **entries = 0;
1338   int verbose = 0;
1339   u32 index;
1340   f64 now;
1341
1342   if (hsm->www_root == 0)
1343     return clib_error_return (0, "Static server disabled");
1344
1345   if (unformat (input, "verbose %d", &verbose))
1346     ;
1347   else if (unformat (input, "verbose"))
1348     verbose = 1;
1349
1350   if (verbose == 0)
1351     {
1352       vlib_cli_output
1353         (vm, "www_root %s, cache size %lld bytes, limit %lld bytes, "
1354          "evictions %lld",
1355          hsm->www_root, hsm->cache_size, hsm->cache_limit,
1356          hsm->cache_evictions);
1357       return 0;
1358     }
1359
1360   now = vlib_time_now (vm);
1361
1362   vlib_cli_output (vm, "%U", format_hsm_cache_entry, 0 /* header */ ,
1363                    now);
1364
1365   for (index = hsm->first_index; index != ~0;)
1366     {
1367       ep = pool_elt_at_index (hsm->cache_pool, index);
1368       index = ep->next_index;
1369       vlib_cli_output (vm, "%U", format_hsm_cache_entry, ep, now);
1370     }
1371
1372   vlib_cli_output (vm, "%40s%12lld", "Total Size", hsm->cache_size);
1373
1374   vec_free (entries);
1375
1376   return 0;
1377 }
1378
1379 /*?
1380  * Display static http server cache statistics
1381  *
1382  * @cliexpar
1383  * This command shows the contents of the static http server cache
1384  * @clistart
1385  * show http static server
1386  * @cliend
1387  * @cliexcmd{show http static server [verbose]}
1388 ?*/
1389 /* *INDENT-OFF* */
1390 VLIB_CLI_COMMAND (http_show_static_server_command, static) =
1391 {
1392   .path = "show http static server",
1393   .short_help = "show http static server [verbose]",
1394   .function = http_show_static_server_command_fn,
1395 };
1396 /* *INDENT-ON* */
1397
1398 static clib_error_t *
1399 http_static_server_main_init (vlib_main_t * vm)
1400 {
1401   http_static_server_main_t *hsm = &http_static_server_main;
1402
1403   hsm->my_client_index = ~0;
1404   hsm->vlib_main = vm;
1405   hsm->first_index = hsm->last_index = ~0;
1406
1407   clib_timebase_init (&hsm->timebase, 0 /* GMT */ ,
1408                       CLIB_TIMEBASE_DAYLIGHT_NONE);
1409
1410   return 0;
1411 }
1412
1413 VLIB_INIT_FUNCTION (http_static_server_main_init);
1414
1415 /*
1416 * fd.io coding-style-patch-verification: ON
1417 *
1418 * Local Variables:
1419 * eval: (c-set-style "gnu")
1420 * End:
1421 */