Static http server
[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_nowait
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   /* Now we can construc the file to open */
650   path = format (0, "%s/%s%c", hsm->www_root, request, 0);
651
652   if (0)
653     clib_warning ("GET '%s'", path);
654
655   /* Try to find the file. 2x special cases to find index.html */
656   if (stat ((char *) path, sb) < 0      /* cant even stat the file */
657       || sb->st_size < 20       /* file too small */
658       || (sb->st_mode & S_IFMT) != S_IFREG /* not a regular file */ )
659     {
660       u32 save_length = vec_len (path) - 1;
661       /* Try appending "index.html"... */
662       _vec_len (path) -= 1;
663       path = format (path, "index.html%c", 0);
664       if (stat ((char *) path, sb) < 0  /* cant even stat the file */
665           || sb->st_size < 20   /* file too small */
666           || (sb->st_mode & S_IFMT) != S_IFREG /* not a regular file */ )
667         {
668           _vec_len (path) = save_length;
669           path = format (path, "/index.html%c", 0);
670
671           /* Send a redirect, otherwise the browser will confuse itself */
672           if (stat ((char *) path, sb) < 0      /* cant even stat the file */
673               || sb->st_size < 20       /* file too small */
674               || (sb->st_mode & S_IFMT) != S_IFREG /* not a regular file */ )
675             {
676               vec_free (path);
677               send_error (hs, "404 Not Found");
678               goto close_session;
679             }
680           else
681             {
682               transport_connection_t *tc;
683               /*
684                * To make this bit work correctly, we need to know our local
685                * IP address, etc. and send it in the redirect...
686                */
687               u8 *redirect;
688
689               vec_delete (path, vec_len (hsm->www_root) - 1, 0);
690
691               if (0)
692                 clib_warning ("redirect to '%s'", path);
693
694               tc = session_get_transport (s);
695               redirect = format (0, "HTTP/1.1 301 Moved Permanently\r\n"
696                                  "Location: http://%U/%s",
697                                  format_ip46_address, &tc->lcl_ip, tc->is_ip4,
698                                  path);
699               static_send_data (hs, redirect, vec_len (redirect), 0);
700               vec_free (path);
701               vec_free (redirect);
702               goto close_session;
703             }
704         }
705     }
706
707   hs->path = path;
708   /* send 200 OK first */
709
710   static_send_data (hs, (u8 *) "HTTP/1.1 200 OK\r\n", 17, 0);
711   hs->session_state = HTTP_STATE_OK_SENT;
712   goto postpone;
713
714 static_send_response:
715
716   ASSERT (hs->path);
717
718   /* find or read the file if we haven't done so yet. */
719   if (hs->data == 0)
720     {
721       BVT (clib_bihash_kv) kv;
722       file_data_cache_t *dp;
723
724       /* First, try the cache */
725       kv.key = (u64) hs->path;
726       if (BV (clib_bihash_search) (&hsm->name_to_data, &kv, &kv) == 0)
727         {
728           /* found the data.. */
729           dp = pool_elt_at_index (hsm->cache_pool, kv.value);
730           hs->data = dp->data;
731           /* Update the cache entry, mark it in-use */
732           lru_update (hsm, dp, vlib_time_now (hsm->vlib_main));
733           hs->cache_pool_index = dp - hsm->cache_pool;
734           dp->inuse++;
735           if (0)
736             clib_warning ("index %d refcnt now %d", hs->cache_pool_index,
737                           dp->inuse);
738         }
739       else
740         {
741           /* Need to recycle one (or more cache) entries? */
742           if (hsm->cache_size > hsm->cache_limit)
743             {
744               int free_index = hsm->last_index;
745
746               while (free_index != ~0)
747                 {
748                   /* pick the LRU */
749                   dp = pool_elt_at_index (hsm->cache_pool, free_index);
750                   free_index = dp->prev_index;
751                   /* Which could be in use... */
752                   if (dp->inuse)
753                     {
754                       if (0)
755                         clib_warning ("index %d in use refcnt %d",
756                                       dp - hsm->cache_pool, dp->inuse);
757                       continue;
758                     }
759
760                   BV (clib_bihash_add_del) (&hsm->name_to_data, &kv,
761                                             0 /* is_add */ );
762                   lru_remove (hsm, dp);
763                   hsm->cache_size -= vec_len (dp->data);
764                   hsm->cache_evictions++;
765                   vec_free (dp->filename);
766                   vec_free (dp->data);
767                   pool_put (hsm->cache_pool, dp);
768                   if (hsm->cache_size < hsm->cache_limit)
769                     break;
770                 }
771             }
772
773           /* Read the file */
774           error = clib_file_contents ((char *) (hs->path), &hs->data);
775           if (error)
776             {
777               clib_warning ("Error reading '%s'", hs->path);
778               clib_error_report (error);
779               vec_free (hs->path);
780               goto close_session;
781             }
782           /* Create a cache entry for it */
783           pool_get (hsm->cache_pool, dp);
784           memset (dp, 0, sizeof (*dp));
785           dp->filename = vec_dup (hs->path);
786           dp->data = hs->data;
787           hs->cache_pool_index = dp - hsm->cache_pool;
788           dp->inuse++;
789           if (0)
790             clib_warning ("index %d refcnt now %d", hs->cache_pool_index,
791                           dp->inuse);
792           lru_add (hsm, dp, vlib_time_now (hsm->vlib_main));
793           kv.key = (u64) vec_dup (hs->path);
794           kv.value = dp - hsm->cache_pool;
795           /* Add to the lookup table */
796           BV (clib_bihash_add_del) (&hsm->name_to_data, &kv, 1 /* is_add */ );
797           hsm->cache_size += vec_len (dp->data);
798         }
799       hs->data_offset = 0;
800     }
801
802   /* What kind of dog food are we serving? */
803   suffix = (char *) (hs->path + vec_len (hs->path) - 1);
804   while (*suffix != '.')
805     suffix--;
806   suffix++;
807   http_type = "html";
808   if (!clib_strcmp (suffix, "css"))
809     http_type = "css";
810   else if (!clib_strcmp (suffix, "js"))
811     http_type = "javascript";
812
813   /*
814    * Send an http response, which needs the current time,
815    * the expiration time, and the data length
816    */
817   now = clib_timebase_now (&hsm->timebase);
818   http_response = format (0, http_response_template,
819                           /* Date */
820                           format_clib_timebase_time, now,
821                           /* Expires */
822                           format_clib_timebase_time, now + 600.0,
823                           http_type, vec_len (hs->data));
824   static_send_data (hs, http_response, vec_len (http_response), 0);
825   vec_free (http_response);
826   hs->session_state = HTTP_STATE_RESPONSE_SENT;
827   /* NOTE FALLTHROUGH */
828
829 static_send_data:
830
831   /*
832    * Try to send data. Ideally, the fifos will be large
833    * enough to send the entire file in one motion.
834    */
835
836   hs->data_offset = static_send_data (hs, hs->data, vec_len (hs->data),
837                                       hs->data_offset);
838   if (hs->data_offset < vec_len (hs->data))
839     goto postpone;
840
841 close_session:
842   http_static_server_session_disconnect (hs);
843   http_static_server_session_cleanup (hs);
844   http_static_server_sessions_reader_unlock ();
845   return 0;
846
847 postpone:
848   (void) svm_fifo_set_event (hs->rx_fifo);
849   session_send_io_evt_to_thread (hs->rx_fifo, SESSION_IO_EVT_BUILTIN_RX);
850   http_static_server_sessions_reader_unlock ();
851   return 0;
852
853 wait_for_data:
854   http_static_server_sessions_reader_unlock ();
855   return 0;
856 }
857
858 /** \brief Session accept callback
859  */
860
861 static int
862 http_static_server_session_accept_callback (session_t * s)
863 {
864   http_static_server_main_t *hsm = &http_static_server_main;
865   http_session_t *hs;
866
867   hsm->vpp_queue[s->thread_index] =
868     session_main_get_vpp_event_queue (s->thread_index);
869
870   http_static_server_sessions_writer_lock ();
871
872   hs = http_static_server_session_alloc (s->thread_index);
873   http_static_server_session_lookup_add (s->thread_index, s->session_index,
874                                          hs->session_index);
875   hs->rx_fifo = s->rx_fifo;
876   hs->tx_fifo = s->tx_fifo;
877   hs->vpp_session_index = s->session_index;
878   hs->vpp_session_handle = session_handle (s);
879   hs->session_state = HTTP_STATE_ESTABLISHED;
880   http_static_server_session_timer_start (hs);
881
882   http_static_server_sessions_writer_unlock ();
883
884   s->session_state = SESSION_STATE_READY;
885   return 0;
886 }
887
888 /** \brief Session disconnect callback
889  */
890
891 static void
892 http_static_server_session_disconnect_callback (session_t * s)
893 {
894   http_static_server_main_t *hsm = &http_static_server_main;
895   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
896   http_session_t *hs;
897
898   http_static_server_sessions_writer_lock ();
899
900   hs = http_static_server_session_lookup (s->thread_index, s->session_index);
901   http_static_server_session_cleanup (hs);
902
903   http_static_server_sessions_writer_unlock ();
904
905   a->handle = session_handle (s);
906   a->app_index = hsm->app_index;
907   vnet_disconnect_session (a);
908 }
909
910 /** \brief Session reset callback
911  */
912
913 static void
914 http_static_server_session_reset_callback (session_t * s)
915 {
916   http_static_server_main_t *hsm = &http_static_server_main;
917   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
918   http_session_t *hs;
919
920   http_static_server_sessions_writer_lock ();
921
922   hs = http_static_server_session_lookup (s->thread_index, s->session_index);
923   http_static_server_session_cleanup (hs);
924
925   http_static_server_sessions_writer_unlock ();
926
927   a->handle = session_handle (s);
928   a->app_index = hsm->app_index;
929   vnet_disconnect_session (a);
930 }
931
932 static int
933 http_static_server_session_connected_callback (u32 app_index, u32 api_context,
934                                                session_t * s, u8 is_fail)
935 {
936   clib_warning ("called...");
937   return -1;
938 }
939
940 static int
941 http_static_server_add_segment_callback (u32 client_index, u64 segment_handle)
942 {
943   clib_warning ("called...");
944   return -1;
945 }
946
947 /** \brief Session-layer virtual function table
948  */
949 static session_cb_vft_t http_static_server_session_cb_vft = {
950   .session_accept_callback = http_static_server_session_accept_callback,
951   .session_disconnect_callback =
952     http_static_server_session_disconnect_callback,
953   .session_connected_callback = http_static_server_session_connected_callback,
954   .add_segment_callback = http_static_server_add_segment_callback,
955   .builtin_app_rx_callback = http_static_server_rx_callback,
956   .session_reset_callback = http_static_server_session_reset_callback
957 };
958
959 static int
960 http_static_server_attach ()
961 {
962   vnet_app_add_tls_cert_args_t _a_cert, *a_cert = &_a_cert;
963   vnet_app_add_tls_key_args_t _a_key, *a_key = &_a_key;
964   http_static_server_main_t *hsm = &http_static_server_main;
965   u64 options[APP_OPTIONS_N_OPTIONS];
966   vnet_app_attach_args_t _a, *a = &_a;
967   u32 segment_size = 128 << 20;
968
969   clib_memset (a, 0, sizeof (*a));
970   clib_memset (options, 0, sizeof (options));
971
972   if (hsm->private_segment_size)
973     segment_size = hsm->private_segment_size;
974
975   a->api_client_index = ~0;
976   a->name = format (0, "test_http_static_server");
977   a->session_cb_vft = &http_static_server_session_cb_vft;
978   a->options = options;
979   a->options[APP_OPTIONS_SEGMENT_SIZE] = segment_size;
980   a->options[APP_OPTIONS_RX_FIFO_SIZE] =
981     hsm->fifo_size ? hsm->fifo_size : 8 << 10;
982   a->options[APP_OPTIONS_TX_FIFO_SIZE] =
983     hsm->fifo_size ? hsm->fifo_size : 32 << 10;
984   a->options[APP_OPTIONS_FLAGS] = APP_OPTIONS_FLAGS_IS_BUILTIN;
985   a->options[APP_OPTIONS_PREALLOC_FIFO_PAIRS] = hsm->prealloc_fifos;
986
987   if (vnet_application_attach (a))
988     {
989       vec_free (a->name);
990       clib_warning ("failed to attach server");
991       return -1;
992     }
993   vec_free (a->name);
994   hsm->app_index = a->app_index;
995
996   clib_memset (a_cert, 0, sizeof (*a_cert));
997   a_cert->app_index = a->app_index;
998   vec_validate (a_cert->cert, test_srv_crt_rsa_len);
999   clib_memcpy_fast (a_cert->cert, test_srv_crt_rsa, test_srv_crt_rsa_len);
1000   vnet_app_add_tls_cert (a_cert);
1001
1002   clib_memset (a_key, 0, sizeof (*a_key));
1003   a_key->app_index = a->app_index;
1004   vec_validate (a_key->key, test_srv_key_rsa_len);
1005   clib_memcpy_fast (a_key->key, test_srv_key_rsa, test_srv_key_rsa_len);
1006   vnet_app_add_tls_key (a_key);
1007
1008   return 0;
1009 }
1010
1011 static int
1012 http_static_server_listen ()
1013 {
1014   http_static_server_main_t *hsm = &http_static_server_main;
1015   vnet_listen_args_t _a, *a = &_a;
1016   clib_memset (a, 0, sizeof (*a));
1017   a->app_index = hsm->app_index;
1018   a->uri = "tcp://0.0.0.0/80";
1019   if (hsm->uri)
1020     a->uri = (char *) hsm->uri;
1021   return vnet_bind_uri (a);
1022 }
1023
1024 static void
1025 http_static_server_session_cleanup_cb (void *hs_handlep)
1026 {
1027   http_session_t *hs;
1028   uword hs_handle;
1029   hs_handle = pointer_to_uword (hs_handlep);
1030   hs =
1031     http_static_server_session_get (hs_handle >> 24, hs_handle & 0x00FFFFFF);
1032   if (!hs)
1033     return;
1034   hs->timer_handle = ~0;
1035   http_static_server_session_disconnect (hs);
1036   http_static_server_session_cleanup (hs);
1037 }
1038
1039 /** \brief Expired session timer-wheel callback
1040  */
1041 static void
1042 http_expired_timers_dispatch (u32 * expired_timers)
1043 {
1044   u32 hs_handle;
1045   int i;
1046
1047   for (i = 0; i < vec_len (expired_timers); i++)
1048     {
1049       /* Get session handle. The first bit is the timer id */
1050       hs_handle = expired_timers[i] & 0x7FFFFFFF;
1051       session_send_rpc_evt_to_thread (hs_handle >> 24,
1052                                       http_static_server_session_cleanup_cb,
1053                                       uword_to_pointer (hs_handle, void *));
1054     }
1055 }
1056
1057 /** \brief Timer-wheel expiration process
1058  */
1059 static uword
1060 http_static_server_process (vlib_main_t * vm, vlib_node_runtime_t * rt,
1061                             vlib_frame_t * f)
1062 {
1063   http_static_server_main_t *hsm = &http_static_server_main;
1064   f64 now, timeout = 1.0;
1065   uword *event_data = 0;
1066   uword __clib_unused event_type;
1067
1068   while (1)
1069     {
1070       vlib_process_wait_for_event_or_clock (vm, timeout);
1071       now = vlib_time_now (vm);
1072       event_type = vlib_process_get_events (vm, (uword **) & event_data);
1073
1074       /* expire timers */
1075       clib_spinlock_lock (&http_static_server_main.tw_lock);
1076       tw_timer_expire_timers_2t_1w_2048sl (&hsm->tw, now);
1077       clib_spinlock_unlock (&http_static_server_main.tw_lock);
1078
1079       vec_reset_length (event_data);
1080     }
1081   return 0;
1082 }
1083
1084 /* *INDENT-OFF* */
1085 VLIB_REGISTER_NODE (http_static_server_process_node) =
1086 {
1087   .function = http_static_server_process,
1088   .type = VLIB_NODE_TYPE_PROCESS,
1089   .name = "static-http-server-process",
1090   .state = VLIB_NODE_STATE_DISABLED,
1091 };
1092 /* *INDENT-ON* */
1093
1094 static int
1095 http_static_server_create (vlib_main_t * vm)
1096 {
1097   vlib_thread_main_t *vtm = vlib_get_thread_main ();
1098   http_static_server_main_t *hsm = &http_static_server_main;
1099   u32 num_threads;
1100   vlib_node_t *n;
1101
1102   num_threads = 1 /* main thread */  + vtm->n_threads;
1103   vec_validate (hsm->vpp_queue, num_threads - 1);
1104   vec_validate (hsm->sessions, num_threads - 1);
1105   vec_validate (hsm->session_to_http_session, num_threads - 1);
1106
1107   clib_rwlock_init (&hsm->sessions_lock);
1108   clib_spinlock_init (&hsm->tw_lock);
1109
1110   if (http_static_server_attach ())
1111     {
1112       clib_warning ("failed to attach server");
1113       return -1;
1114     }
1115   if (http_static_server_listen ())
1116     {
1117       clib_warning ("failed to start listening");
1118       return -1;
1119     }
1120
1121   /* Init path-to-cache hash table */
1122   BV (clib_bihash_init) (&hsm->name_to_data, "http cache", 128, 32 << 20);
1123
1124   /* Init timer wheel and process */
1125   tw_timer_wheel_init_2t_1w_2048sl (&hsm->tw, http_expired_timers_dispatch,
1126                                     1 /* timer interval */ , ~0);
1127   vlib_node_set_state (vm, http_static_server_process_node.index,
1128                        VLIB_NODE_STATE_POLLING);
1129   n = vlib_get_node (vm, http_static_server_process_node.index);
1130   vlib_start_process (vm, n->runtime_index);
1131
1132   return 0;
1133 }
1134
1135 /** \brief API helper function for vl_api_http_static_enable_t messages
1136  */
1137 int
1138 http_static_server_enable_api (u32 fifo_size, u32 cache_limit,
1139                                u32 prealloc_fifos,
1140                                u32 private_segment_size,
1141                                u8 * www_root, u8 * uri)
1142 {
1143   http_static_server_main_t *hsm = &http_static_server_main;
1144   int rv;
1145
1146   hsm->fifo_size = fifo_size;
1147   hsm->cache_limit = cache_limit;
1148   hsm->prealloc_fifos = prealloc_fifos;
1149   hsm->private_segment_size = private_segment_size;
1150   hsm->www_root = format (0, "%s%c", www_root, 0);
1151   hsm->uri = format (0, "%s%c", uri, 0);
1152
1153   if (vec_len (hsm->www_root) < 2)
1154     return VNET_API_ERROR_INVALID_VALUE;
1155
1156   if (hsm->my_client_index != ~0)
1157     return VNET_API_ERROR_APP_ALREADY_ATTACHED;
1158
1159   vnet_session_enable_disable (hsm->vlib_main, 1 /* turn on TCP, etc. */ );
1160
1161   rv = http_static_server_create (hsm->vlib_main);
1162   switch (rv)
1163     {
1164     case 0:
1165       break;
1166     default:
1167       vec_free (hsm->www_root);
1168       vec_free (hsm->uri);
1169       return VNET_API_ERROR_INIT_FAILED;
1170     }
1171   return 0;
1172 }
1173
1174 static clib_error_t *
1175 http_static_server_create_command_fn (vlib_main_t * vm,
1176                                       unformat_input_t * input,
1177                                       vlib_cli_command_t * cmd)
1178 {
1179   http_static_server_main_t *hsm = &http_static_server_main;
1180   unformat_input_t _line_input, *line_input = &_line_input;
1181   u64 seg_size;
1182   u8 *www_root = 0;
1183   int rv;
1184
1185   hsm->prealloc_fifos = 0;
1186   hsm->private_segment_size = 0;
1187   hsm->fifo_size = 0;
1188   /* 10mb cache limit, before LRU occurs */
1189   hsm->cache_limit = 10 << 20;
1190
1191   /* Get a line of input. */
1192   if (!unformat_user (input, unformat_line_input, line_input))
1193     goto no_wwwroot;
1194
1195   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
1196     {
1197       if (unformat (line_input, "www-root %s", &www_root))
1198         ;
1199       else
1200         if (unformat (line_input, "prealloc-fifos %d", &hsm->prealloc_fifos))
1201         ;
1202       else if (unformat (line_input, "private-segment-size %U",
1203                          unformat_memory_size, &seg_size))
1204         {
1205           if (seg_size >= 0x100000000ULL)
1206             {
1207               vlib_cli_output (vm, "private segment size %llu, too large",
1208                                seg_size);
1209               return 0;
1210             }
1211           hsm->private_segment_size = seg_size;
1212         }
1213       else if (unformat (line_input, "fifo-size %d", &hsm->fifo_size))
1214         hsm->fifo_size <<= 10;
1215       else if (unformat (line_input, "cache-size %U", unformat_memory_size,
1216                          &hsm->cache_limit))
1217         {
1218           if (hsm->cache_limit < (128 << 10))
1219             {
1220               return clib_error_return (0,
1221                                         "cache-size must be at least 128kb");
1222             }
1223         }
1224
1225       else if (unformat (line_input, "uri %s", &hsm->uri))
1226         ;
1227       else
1228         return clib_error_return (0, "unknown input `%U'",
1229                                   format_unformat_error, line_input);
1230     }
1231   unformat_free (line_input);
1232
1233   if (www_root == 0)
1234     {
1235     no_wwwroot:
1236       return clib_error_return (0, "Must specify www-root <path>");
1237     }
1238
1239   if (hsm->my_client_index != (u32) ~ 0)
1240     {
1241       vec_free (www_root);
1242       return clib_error_return (0, "http server already running...");
1243     }
1244
1245   hsm->www_root = www_root;
1246
1247   vnet_session_enable_disable (vm, 1 /* turn on TCP, etc. */ );
1248
1249   rv = http_static_server_create (vm);
1250   switch (rv)
1251     {
1252     case 0:
1253       break;
1254     default:
1255       vec_free (hsm->www_root);
1256       return clib_error_return (0, "server_create returned %d", rv);
1257     }
1258   return 0;
1259 }
1260
1261 /*?
1262  * Enable the static http server
1263  *
1264  * @cliexpar
1265  * This command enables the static http server. Only the www-root
1266  * parameter is required
1267  * @clistart
1268  * http static server www-root /tmp/www uri tcp://0.0.0.0/80 cache-size 2m
1269  * @cliend
1270  * @cliexcmd{http static server www-root <path> [prealloc-fios <nn>]
1271  *   [private-segment-size <nnMG>] [fifo-size <nbytes>] [uri <uri>]}
1272 ?*/
1273 /* *INDENT-OFF* */
1274 VLIB_CLI_COMMAND (http_static_server_create_command, static) =
1275 {
1276   .path = "http static server",
1277   .short_help = "http static server www-root <path> [prealloc-fios <nn>]\n"
1278   "[private-segment-size <nnMG>] [fifo-size <nbytes>] [uri <uri>]\n",
1279   .function = http_static_server_create_command_fn,
1280 };
1281 /* *INDENT-ON* */
1282
1283 /** \brief format a file cache entry
1284  */
1285 u8 *
1286 format_hsm_cache_entry (u8 * s, va_list * args)
1287 {
1288   file_data_cache_t *ep = va_arg (*args, file_data_cache_t *);
1289   f64 now = va_arg (*args, f64);
1290
1291   /* Header */
1292   if (ep == 0)
1293     {
1294       s = format (s, "%40s%12s%20s", "File", "Size", "Age");
1295       return s;
1296     }
1297   s = format (s, "%40s%12lld%20.2f", ep->filename, vec_len (ep->data),
1298               now - ep->last_used);
1299   return s;
1300 }
1301
1302 static clib_error_t *
1303 http_show_static_server_command_fn (vlib_main_t * vm,
1304                                     unformat_input_t * input,
1305                                     vlib_cli_command_t * cmd)
1306 {
1307   http_static_server_main_t *hsm = &http_static_server_main;
1308   file_data_cache_t *ep, **entries = 0;
1309   int verbose = 0;
1310   u32 index;
1311   f64 now;
1312
1313   if (hsm->www_root == 0)
1314     return clib_error_return (0, "Static server disabled");
1315
1316   if (unformat (input, "verbose %d", &verbose))
1317     ;
1318   else if (unformat (input, "verbose"))
1319     verbose = 1;
1320
1321   if (verbose == 0)
1322     {
1323       vlib_cli_output
1324         (vm, "www_root %s, cache size %lld bytes, limit %lld bytes, "
1325          "evictions %lld",
1326          hsm->www_root, hsm->cache_size, hsm->cache_limit,
1327          hsm->cache_evictions);
1328       return 0;
1329     }
1330
1331   now = vlib_time_now (vm);
1332
1333   vlib_cli_output (vm, "%U", format_hsm_cache_entry, 0 /* header */ ,
1334                    now);
1335
1336   for (index = hsm->first_index; index != ~0;)
1337     {
1338       ep = pool_elt_at_index (hsm->cache_pool, index);
1339       index = ep->next_index;
1340       vlib_cli_output (vm, "%U", format_hsm_cache_entry, ep, now);
1341     }
1342
1343   vlib_cli_output (vm, "%40s%12lld", "Total Size", hsm->cache_size);
1344
1345   vec_free (entries);
1346
1347   return 0;
1348 }
1349
1350 /*?
1351  * Display static http server cache statistics
1352  *
1353  * @cliexpar
1354  * This command shows the contents of the static http server cache
1355  * @clistart
1356  * show http static server
1357  * @cliend
1358  * @cliexcmd{show http static server [verbose]}
1359 ?*/
1360 /* *INDENT-OFF* */
1361 VLIB_CLI_COMMAND (http_show_static_server_command, static) =
1362 {
1363   .path = "show http static server",
1364   .short_help = "show http static server [verbose]",
1365   .function = http_show_static_server_command_fn,
1366 };
1367 /* *INDENT-ON* */
1368
1369 static clib_error_t *
1370 http_static_server_main_init (vlib_main_t * vm)
1371 {
1372   http_static_server_main_t *hsm = &http_static_server_main;
1373
1374   hsm->my_client_index = ~0;
1375   hsm->vlib_main = vm;
1376   hsm->first_index = hsm->last_index = ~0;
1377
1378   clib_timebase_init (&hsm->timebase, 0 /* GMT */ ,
1379                       CLIB_TIMEBASE_DAYLIGHT_NONE);
1380
1381   return 0;
1382 }
1383
1384 VLIB_INIT_FUNCTION (http_static_server_main_init);
1385
1386 /*
1387 * fd.io coding-style-patch-verification: ON
1388 *
1389 * Local Variables:
1390 * eval: (c-set-style "gnu")
1391 * End:
1392 */