http_static: clean up http redirect generation
[vpp.git] / src / plugins / http_static / static_server.c
1 /*
2  * Copyright (c) 2017-2022 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 <http_static/http_static.h>
17
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 #include <unistd.h>
21
22 /** @file static_server.c
23  *  Static http server, sufficient to serve .html / .css / .js content.
24  */
25 /*? %%clicmd:group_label Static HTTP Server %% ?*/
26
27 #define HSS_FIFO_THRESH (16 << 10)
28
29 hss_main_t hss_main;
30
31 static hss_session_t *
32 hss_session_alloc (u32 thread_index)
33 {
34   hss_main_t *hsm = &hss_main;
35   hss_session_t *hs;
36
37   pool_get_zero (hsm->sessions[thread_index], hs);
38   hs->session_index = hs - hsm->sessions[thread_index];
39   hs->thread_index = thread_index;
40   hs->cache_pool_index = ~0;
41   return hs;
42 }
43
44 __clib_export hss_session_t *
45 hss_session_get (u32 thread_index, u32 hs_index)
46 {
47   hss_main_t *hsm = &hss_main;
48   if (pool_is_free_index (hsm->sessions[thread_index], hs_index))
49     return 0;
50   return pool_elt_at_index (hsm->sessions[thread_index], hs_index);
51 }
52
53 static void
54 hss_session_free (hss_session_t *hs)
55 {
56   hss_main_t *hsm = &hss_main;
57
58   pool_put (hsm->sessions[hs->thread_index], hs);
59
60   if (CLIB_DEBUG)
61     {
62       u32 save_thread_index;
63       save_thread_index = hs->thread_index;
64       /* Poison the entry, preserve timer state and thread index */
65       memset (hs, 0xfa, sizeof (*hs));
66       hs->thread_index = save_thread_index;
67     }
68 }
69
70 /** \brief Disconnect a session
71  */
72 static void
73 hss_session_disconnect_transport (hss_session_t *hs)
74 {
75   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
76   a->handle = hs->vpp_session_handle;
77   a->app_index = hss_main.app_index;
78   vnet_disconnect_session (a);
79 }
80
81 static void
82 start_send_data (hss_session_t *hs, http_status_code_t status)
83 {
84   http_msg_t msg;
85   session_t *ts;
86   int rv;
87
88   ts = session_get (hs->vpp_session_index, hs->thread_index);
89
90   msg.type = HTTP_MSG_REPLY;
91   msg.code = status;
92   msg.content_type = hs->content_type;
93   msg.data.len = hs->data_len;
94
95   if (hs->data_len > hss_main.use_ptr_thresh)
96     {
97       msg.data.type = HTTP_MSG_DATA_PTR;
98       rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (msg), (u8 *) &msg);
99       ASSERT (rv == sizeof (msg));
100
101       uword data = pointer_to_uword (hs->data);
102       rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (data), (u8 *) &data);
103       ASSERT (rv == sizeof (sizeof (data)));
104
105       goto done;
106     }
107
108   msg.data.type = HTTP_MSG_DATA_INLINE;
109
110   rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (msg), (u8 *) &msg);
111   ASSERT (rv == sizeof (msg));
112
113   if (!msg.data.len)
114     goto done;
115
116   rv = svm_fifo_enqueue (ts->tx_fifo, hs->data_len, hs->data);
117
118   if (rv != hs->data_len)
119     {
120       hs->data_offset = rv;
121       svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
122     }
123
124 done:
125
126   if (svm_fifo_set_event (ts->tx_fifo))
127     session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
128 }
129
130 __clib_export void
131 hss_session_send_data (hss_url_handler_args_t *args)
132 {
133   hss_session_t *hs;
134
135   hs = hss_session_get (args->sh.thread_index, args->sh.session_index);
136   if (!hs)
137     return;
138
139   if (hs->data && hs->free_data)
140     vec_free (hs->data);
141
142   hs->data = args->data;
143   hs->data_len = args->data_len;
144   hs->free_data = args->free_vec_data;
145   start_send_data (hs, args->sc);
146 }
147
148 /*
149  * path_has_known_suffix()
150  * Returns 1 if the request ends with a known suffix, like .htm or .ico
151  * Used to avoid looking for "/favicon.ico/index.html" or similar.
152  */
153
154 static int
155 path_has_known_suffix (u8 *request)
156 {
157   u8 *ext;
158   uword *p;
159
160   if (vec_len (request) == 0)
161     {
162       return 0;
163     }
164
165   ext = request + vec_len (request) - 1;
166
167   while (ext > request && ext[0] != '.')
168     ext--;
169
170   if (ext == request)
171     return 0;
172
173   p = hash_get_mem (hss_main.mime_type_indices_by_file_extensions, ext);
174   if (p)
175     return 1;
176
177   return 0;
178 }
179
180 /*
181  * content_type_from_request
182  * Returns the index of the request's suffix in the
183  * http-layer http_content_type_str[] array.
184  */
185
186 static http_content_type_t
187 content_type_from_request (u8 *request)
188 {
189   u8 *ext;
190   uword *p;
191   /* default to text/html */
192   http_content_type_t rv = HTTP_CONTENT_TEXT_HTML;
193
194   ASSERT (vec_len (request) > 0);
195
196   ext = request + vec_len (request) - 1;
197
198   while (ext > request && ext[0] != '.')
199     ext--;
200
201   if (ext == request)
202     return rv;
203
204   p = hash_get_mem (hss_main.mime_type_indices_by_file_extensions, ext);
205
206   if (p == 0)
207     return rv;
208
209   rv = p[0];
210   return rv;
211 }
212
213 static int
214 try_url_handler (hss_main_t *hsm, hss_session_t *hs, http_req_method_t rt,
215                  u8 *request)
216 {
217   http_status_code_t sc = HTTP_STATUS_OK;
218   hss_url_handler_args_t args = {};
219   uword *p, *url_table;
220   http_content_type_t type;
221   int rv;
222
223   if (!hsm->enable_url_handlers || !request)
224     return -1;
225
226   /* zero-length? try "index.html" */
227   if (vec_len (request) == 0)
228     {
229       request = format (request, "index.html");
230     }
231
232   type = content_type_from_request (request);
233
234   /* Look for built-in GET / POST handlers */
235   url_table =
236     (rt == HTTP_REQ_GET) ? hsm->get_url_handlers : hsm->post_url_handlers;
237
238   p = hash_get_mem (url_table, request);
239   if (!p)
240     return -1;
241
242   hs->path = 0;
243   hs->data_offset = 0;
244   hs->cache_pool_index = ~0;
245
246   if (hsm->debug_level > 0)
247     clib_warning ("%s '%s'", (rt == HTTP_REQ_GET) ? "GET" : "POST", request);
248
249   args.reqtype = rt;
250   args.request = request;
251   args.sh.thread_index = hs->thread_index;
252   args.sh.session_index = hs->session_index;
253
254   rv = ((hss_url_handler_fn) p[0]) (&args);
255
256   /* Wait for data from handler */
257   if (rv == HSS_URL_HANDLER_ASYNC)
258     return 0;
259
260   if (rv == HSS_URL_HANDLER_ERROR)
261     {
262       clib_warning ("builtin handler %llx hit on %s '%s' but failed!", p[0],
263                     (rt == HTTP_REQ_GET) ? "GET" : "POST", request);
264       sc = HTTP_STATUS_NOT_FOUND;
265     }
266
267   hs->data = args.data;
268   hs->data_len = args.data_len;
269   hs->free_data = args.free_vec_data;
270   hs->content_type = type;
271
272   start_send_data (hs, sc);
273
274   if (!hs->data)
275     hss_session_disconnect_transport (hs);
276
277   return 0;
278 }
279
280 static u8
281 file_path_is_valid (u8 *path)
282 {
283   struct stat _sb, *sb = &_sb;
284
285   if (stat ((char *) path, sb) < 0 /* can't stat the file */
286       || (sb->st_mode & S_IFMT) != S_IFREG /* not a regular file */)
287     return 0;
288
289   return 1;
290 }
291
292 static u32
293 try_index_file (hss_main_t *hsm, hss_session_t *hs, u8 *path)
294 {
295   u8 *port_str = 0, *redirect;
296   transport_endpoint_t endpt;
297   transport_proto_t proto;
298   int print_port = 0;
299   u16 local_port;
300   session_t *ts;
301   u32 plen;
302
303   /* Remove the trailing space */
304   vec_dec_len (path, 1);
305   plen = vec_len (path);
306
307   /* Append "index.html" */
308   if (path[plen - 1] != '/')
309     path = format (path, "/index.html%c", 0);
310   else
311     path = format (path, "index.html%c", 0);
312
313   if (hsm->debug_level > 0)
314     clib_warning ("trying to find index: %s", path);
315
316   if (!file_path_is_valid (path))
317     return HTTP_STATUS_NOT_FOUND;
318
319   /*
320    * We found an index.html file, build a redirect
321    */
322   vec_delete (path, vec_len (hsm->www_root) - 1, 0);
323
324   ts = session_get (hs->vpp_session_index, hs->thread_index);
325   session_get_endpoint (ts, &endpt, 1 /* is_local */);
326
327   local_port = clib_net_to_host_u16 (endpt.port);
328   proto = session_type_transport_proto (ts->session_type);
329
330   if ((proto == TRANSPORT_PROTO_TCP && local_port != 80) ||
331       (proto == TRANSPORT_PROTO_TLS && local_port != 443))
332     {
333       print_port = 1;
334       port_str = format (0, ":%u", (u32) local_port);
335     }
336
337   redirect =
338     format (0,
339             "Location: http%s://%U%s%s\r\n\r\n",
340             proto == TRANSPORT_PROTO_TLS ? "s" : "", format_ip46_address,
341             &endpt.ip, endpt.is_ip4, print_port ? port_str : (u8 *) "", path);
342
343   if (hsm->debug_level > 0)
344     clib_warning ("redirect: %s", redirect);
345
346   vec_free (port_str);
347
348   hs->data = redirect;
349   hs->data_len = vec_len (redirect);
350   hs->free_data = 1;
351
352   return HTTP_STATUS_MOVED;
353 }
354
355 static int
356 try_file_handler (hss_main_t *hsm, hss_session_t *hs, http_req_method_t rt,
357                   u8 *request)
358 {
359   http_status_code_t sc = HTTP_STATUS_OK;
360   u8 *path;
361   u32 ce_index;
362   http_content_type_t type;
363
364   /* Feature not enabled */
365   if (!hsm->www_root)
366     return -1;
367
368   type = content_type_from_request (request);
369
370   /*
371    * Construct the file to open
372    * Browsers are capable of sporadically including a leading '/'
373    */
374   if (!request)
375     path = format (0, "%s%c", hsm->www_root, 0);
376   else if (request[0] == '/')
377     path = format (0, "%s%s%c", hsm->www_root, request, 0);
378   else
379     path = format (0, "%s/%s%c", hsm->www_root, request, 0);
380
381   if (hsm->debug_level > 0)
382     clib_warning ("%s '%s'", (rt == HTTP_REQ_GET) ? "GET" : "POST", path);
383
384   if (hs->data && hs->free_data)
385     vec_free (hs->data);
386
387   hs->data_offset = 0;
388
389   ce_index =
390     hss_cache_lookup_and_attach (&hsm->cache, path, &hs->data, &hs->data_len);
391   if (ce_index == ~0)
392     {
393       if (!file_path_is_valid (path))
394         {
395           /*
396            * Generate error 404 right now if we can't find a path with
397            * a known file extension. It's silly to look for
398            * "favicon.ico/index.html" if you can't find
399            * "favicon.ico"; realistic example which used to happen.
400            */
401           if (path_has_known_suffix (path))
402             {
403               sc = HTTP_STATUS_NOT_FOUND;
404               goto done;
405             }
406           sc = try_index_file (hsm, hs, path);
407           goto done;
408         }
409       ce_index =
410         hss_cache_add_and_attach (&hsm->cache, path, &hs->data, &hs->data_len);
411       if (ce_index == ~0)
412         {
413           sc = HTTP_STATUS_INTERNAL_ERROR;
414           goto done;
415         }
416     }
417
418   hs->path = path;
419   hs->cache_pool_index = ce_index;
420
421 done:
422
423   hs->content_type = type;
424   start_send_data (hs, sc);
425   if (!hs->data)
426     hss_session_disconnect_transport (hs);
427
428   return 0;
429 }
430
431 static int
432 handle_request (hss_session_t *hs, http_req_method_t rt, u8 *request)
433 {
434   hss_main_t *hsm = &hss_main;
435
436   if (!try_url_handler (hsm, hs, rt, request))
437     return 0;
438
439   if (!try_file_handler (hsm, hs, rt, request))
440     return 0;
441
442   /* Handler did not find anything return 404 */
443   start_send_data (hs, HTTP_STATUS_NOT_FOUND);
444   hss_session_disconnect_transport (hs);
445
446   return 0;
447 }
448
449 static int
450 hss_ts_rx_callback (session_t *ts)
451 {
452   hss_session_t *hs;
453   u8 *request = 0;
454   http_msg_t msg;
455   int rv;
456
457   hs = hss_session_get (ts->thread_index, ts->opaque);
458
459   /* Read the http message header */
460   rv = svm_fifo_dequeue (ts->rx_fifo, sizeof (msg), (u8 *) &msg);
461   ASSERT (rv == sizeof (msg));
462
463   if (msg.type != HTTP_MSG_REQUEST ||
464       (msg.method_type != HTTP_REQ_GET && msg.method_type != HTTP_REQ_POST))
465     {
466       hs->data = 0;
467       start_send_data (hs, HTTP_STATUS_METHOD_NOT_ALLOWED);
468       return 0;
469     }
470
471   /* Read request */
472   if (msg.data.len)
473     {
474       vec_validate (request, msg.data.len - 1);
475       rv = svm_fifo_dequeue (ts->rx_fifo, msg.data.len, request);
476       ASSERT (rv == msg.data.len);
477       /* request must be a proper C-string in addition to a vector */
478       vec_add1 (request, 0);
479     }
480
481   /* Find and send data */
482   handle_request (hs, msg.method_type, request);
483
484   vec_free (request);
485
486   return 0;
487 }
488
489 static int
490 hss_ts_tx_callback (session_t *ts)
491 {
492   hss_session_t *hs;
493   u32 to_send;
494   int rv;
495
496   hs = hss_session_get (ts->thread_index, ts->opaque);
497   if (!hs || !hs->data)
498     return 0;
499
500   to_send = hs->data_len - hs->data_offset;
501   rv = svm_fifo_enqueue (ts->tx_fifo, to_send, hs->data + hs->data_offset);
502
503   if (rv <= 0)
504     {
505       svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
506       return 0;
507     }
508
509   if (rv < to_send)
510     {
511       hs->data_offset += rv;
512       svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
513     }
514
515   if (svm_fifo_set_event (ts->tx_fifo))
516     session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
517
518   return 0;
519 }
520
521 /** \brief Session accept callback
522  */
523 static int
524 hss_ts_accept_callback (session_t *ts)
525 {
526   hss_session_t *hs;
527   u32 thresh;
528
529   hs = hss_session_alloc (ts->thread_index);
530
531   hs->vpp_session_index = ts->session_index;
532   hs->vpp_session_handle = session_handle (ts);
533
534   /* The application sets a threshold for it's fifo to get notified when
535    * additional data can be enqueued. We want to keep the TX fifo reasonably
536    * full, however avoid entering a state where the
537    * fifo is full all the time and small chunks of data are being enqueued
538    * each time. If the fifo is small (under 16K) we set
539    * the threshold to it's size, meaning a notification will be given when the
540    * fifo empties.
541    */
542   thresh = clib_min (svm_fifo_size (ts->tx_fifo), HSS_FIFO_THRESH);
543   svm_fifo_set_deq_thresh (ts->tx_fifo, thresh);
544
545   ts->opaque = hs->session_index;
546   ts->session_state = SESSION_STATE_READY;
547   return 0;
548 }
549
550 static void
551 hss_ts_disconnect_callback (session_t *ts)
552 {
553   hss_main_t *hsm = &hss_main;
554   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
555
556   a->handle = session_handle (ts);
557   a->app_index = hsm->app_index;
558   vnet_disconnect_session (a);
559 }
560
561 static void
562 hss_ts_reset_callback (session_t *ts)
563 {
564   hss_main_t *hsm = &hss_main;
565   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
566
567   a->handle = session_handle (ts);
568   a->app_index = hsm->app_index;
569   vnet_disconnect_session (a);
570 }
571
572 static int
573 hss_ts_connected_callback (u32 app_index, u32 api_context, session_t *ts,
574                            session_error_t err)
575 {
576   clib_warning ("called...");
577   return -1;
578 }
579
580 static int
581 hss_add_segment_callback (u32 client_index, u64 segment_handle)
582 {
583   return 0;
584 }
585
586 static void
587 hss_ts_cleanup (session_t *s, session_cleanup_ntf_t ntf)
588 {
589   hss_main_t *hsm = &hss_main;
590   hss_session_t *hs;
591
592   if (ntf == SESSION_CLEANUP_TRANSPORT)
593     return;
594
595   hs = hss_session_get (s->thread_index, s->opaque);
596   if (!hs)
597     return;
598
599   if (hs->cache_pool_index != ~0)
600     {
601       hss_cache_detach_entry (&hsm->cache, hs->cache_pool_index);
602       hs->cache_pool_index = ~0;
603     }
604
605   if (hs->free_data)
606     vec_free (hs->data);
607   hs->data = 0;
608   hs->data_offset = 0;
609   hs->free_data = 0;
610   vec_free (hs->path);
611
612   hss_session_free (hs);
613 }
614
615 static session_cb_vft_t hss_cb_vft = {
616   .session_accept_callback = hss_ts_accept_callback,
617   .session_disconnect_callback = hss_ts_disconnect_callback,
618   .session_connected_callback = hss_ts_connected_callback,
619   .add_segment_callback = hss_add_segment_callback,
620   .builtin_app_rx_callback = hss_ts_rx_callback,
621   .builtin_app_tx_callback = hss_ts_tx_callback,
622   .session_reset_callback = hss_ts_reset_callback,
623   .session_cleanup_callback = hss_ts_cleanup,
624 };
625
626 static int
627 hss_attach ()
628 {
629   vnet_app_add_cert_key_pair_args_t _ck_pair, *ck_pair = &_ck_pair;
630   hss_main_t *hsm = &hss_main;
631   u64 options[APP_OPTIONS_N_OPTIONS];
632   vnet_app_attach_args_t _a, *a = &_a;
633   u32 segment_size = 128 << 20;
634
635   clib_memset (a, 0, sizeof (*a));
636   clib_memset (options, 0, sizeof (options));
637
638   if (hsm->private_segment_size)
639     segment_size = hsm->private_segment_size;
640
641   a->api_client_index = ~0;
642   a->name = format (0, "http_static_server");
643   a->session_cb_vft = &hss_cb_vft;
644   a->options = options;
645   a->options[APP_OPTIONS_SEGMENT_SIZE] = segment_size;
646   a->options[APP_OPTIONS_ADD_SEGMENT_SIZE] = segment_size;
647   a->options[APP_OPTIONS_RX_FIFO_SIZE] =
648     hsm->fifo_size ? hsm->fifo_size : 8 << 10;
649   a->options[APP_OPTIONS_TX_FIFO_SIZE] =
650     hsm->fifo_size ? hsm->fifo_size : 32 << 10;
651   a->options[APP_OPTIONS_FLAGS] = APP_OPTIONS_FLAGS_IS_BUILTIN;
652   a->options[APP_OPTIONS_PREALLOC_FIFO_PAIRS] = hsm->prealloc_fifos;
653   a->options[APP_OPTIONS_TLS_ENGINE] = CRYPTO_ENGINE_OPENSSL;
654
655   if (vnet_application_attach (a))
656     {
657       vec_free (a->name);
658       clib_warning ("failed to attach server");
659       return -1;
660     }
661   vec_free (a->name);
662   hsm->app_index = a->app_index;
663
664   clib_memset (ck_pair, 0, sizeof (*ck_pair));
665   ck_pair->cert = (u8 *) test_srv_crt_rsa;
666   ck_pair->key = (u8 *) test_srv_key_rsa;
667   ck_pair->cert_len = test_srv_crt_rsa_len;
668   ck_pair->key_len = test_srv_key_rsa_len;
669   vnet_app_add_cert_key_pair (ck_pair);
670   hsm->ckpair_index = ck_pair->index;
671
672   return 0;
673 }
674
675 static int
676 hss_transport_needs_crypto (transport_proto_t proto)
677 {
678   return proto == TRANSPORT_PROTO_TLS || proto == TRANSPORT_PROTO_DTLS ||
679          proto == TRANSPORT_PROTO_QUIC;
680 }
681
682 static int
683 hss_listen (void)
684 {
685   hss_main_t *hsm = &hss_main;
686   session_endpoint_cfg_t sep = SESSION_ENDPOINT_CFG_NULL;
687   vnet_listen_args_t _a, *a = &_a;
688   char *uri = "tcp://0.0.0.0/80";
689   u8 need_crypto;
690   int rv;
691
692   clib_memset (a, 0, sizeof (*a));
693   a->app_index = hsm->app_index;
694
695   if (hsm->uri)
696     uri = (char *) hsm->uri;
697
698   if (parse_uri (uri, &sep))
699     return -1;
700
701   need_crypto = hss_transport_needs_crypto (sep.transport_proto);
702
703   sep.transport_proto = TRANSPORT_PROTO_HTTP;
704   clib_memcpy (&a->sep_ext, &sep, sizeof (sep));
705
706   if (need_crypto)
707     {
708       session_endpoint_alloc_ext_cfg (&a->sep_ext,
709                                       TRANSPORT_ENDPT_EXT_CFG_CRYPTO);
710       a->sep_ext.ext_cfg->crypto.ckpair_index = hsm->ckpair_index;
711     }
712
713   rv = vnet_listen (a);
714
715   if (need_crypto)
716     clib_mem_free (a->sep_ext.ext_cfg);
717
718   return rv;
719 }
720
721 static void
722 hss_url_handlers_init (hss_main_t *hsm)
723 {
724   if (!hsm->get_url_handlers)
725     {
726       hsm->get_url_handlers = hash_create_string (0, sizeof (uword));
727       hsm->post_url_handlers = hash_create_string (0, sizeof (uword));
728     }
729
730   hss_builtinurl_json_handlers_init ();
731 }
732
733 int
734 hss_create (vlib_main_t *vm)
735 {
736   vlib_thread_main_t *vtm = vlib_get_thread_main ();
737   hss_main_t *hsm = &hss_main;
738   u32 num_threads;
739
740   num_threads = 1 /* main thread */  + vtm->n_threads;
741   vec_validate (hsm->sessions, num_threads - 1);
742
743   if (hss_attach ())
744     {
745       clib_warning ("failed to attach server");
746       return -1;
747     }
748   if (hss_listen ())
749     {
750       clib_warning ("failed to start listening");
751       return -1;
752     }
753
754   if (hsm->www_root)
755     hss_cache_init (&hsm->cache, hsm->cache_size, hsm->debug_level);
756
757   if (hsm->enable_url_handlers)
758     hss_url_handlers_init (hsm);
759
760   return 0;
761 }
762
763 static clib_error_t *
764 hss_create_command_fn (vlib_main_t *vm, unformat_input_t *input,
765                        vlib_cli_command_t *cmd)
766 {
767   unformat_input_t _line_input, *line_input = &_line_input;
768   hss_main_t *hsm = &hss_main;
769   clib_error_t *error = 0;
770   u64 seg_size;
771   int rv;
772
773   if (hsm->app_index != (u32) ~0)
774     return clib_error_return (0, "http server already running...");
775
776   hsm->prealloc_fifos = 0;
777   hsm->private_segment_size = 0;
778   hsm->fifo_size = 0;
779   hsm->cache_size = 10 << 20;
780
781   /* Get a line of input. */
782   if (!unformat_user (input, unformat_line_input, line_input))
783     goto no_input;
784
785   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
786     {
787       if (unformat (line_input, "www-root %s", &hsm->www_root))
788         ;
789       else
790         if (unformat (line_input, "prealloc-fifos %d", &hsm->prealloc_fifos))
791         ;
792       else if (unformat (line_input, "private-segment-size %U",
793                          unformat_memory_size, &seg_size))
794         hsm->private_segment_size = seg_size;
795       else if (unformat (line_input, "fifo-size %d", &hsm->fifo_size))
796         hsm->fifo_size <<= 10;
797       else if (unformat (line_input, "cache-size %U", unformat_memory_size,
798                          &hsm->cache_size))
799         ;
800       else if (unformat (line_input, "uri %s", &hsm->uri))
801         ;
802       else if (unformat (line_input, "debug %d", &hsm->debug_level))
803         ;
804       else if (unformat (line_input, "debug"))
805         hsm->debug_level = 1;
806       else if (unformat (line_input, "ptr-thresh %U", unformat_memory_size,
807                          &hsm->use_ptr_thresh))
808         ;
809       else if (unformat (line_input, "url-handlers"))
810         hsm->enable_url_handlers = 1;
811       else
812         {
813           error = clib_error_return (0, "unknown input `%U'",
814                                      format_unformat_error, line_input);
815           break;
816         }
817     }
818
819   unformat_free (line_input);
820
821 no_input:
822
823   if (error)
824     goto done;
825
826   if (hsm->www_root == 0 && !hsm->enable_url_handlers)
827     {
828       error = clib_error_return (0, "Must set www-root or url-handlers");
829       goto done;
830     }
831
832   if (hsm->cache_size < (128 << 10))
833     {
834       error = clib_error_return (0, "cache-size must be at least 128kb");
835       vec_free (hsm->www_root);
836       goto done;
837     }
838
839   vnet_session_enable_disable (vm, 1 /* turn on TCP, etc. */ );
840
841   if ((rv = hss_create (vm)))
842     {
843       error = clib_error_return (0, "server_create returned %d", rv);
844       vec_free (hsm->www_root);
845     }
846
847 done:
848
849   return error;
850 }
851
852 /*?
853  * Enable the static http server
854  *
855  * @cliexpar
856  * This command enables the static http server. Only the www-root
857  * parameter is required
858  * @clistart
859  * http static server www-root /tmp/www uri tcp://0.0.0.0/80 cache-size 2m
860  * @cliend
861  * @cliexcmd{http static server www-root <path> [prealloc-fios <nn>]
862  *   [private-segment-size <nnMG>] [fifo-size <nbytes>] [uri <uri>]}
863 ?*/
864 VLIB_CLI_COMMAND (hss_create_command, static) = {
865   .path = "http static server",
866   .short_help =
867     "http static server www-root <path> [prealloc-fifos <nn>]\n"
868     "[private-segment-size <nnMG>] [fifo-size <nbytes>] [uri <uri>]\n"
869     "[ptr-thresh <nn>] [url-handlers] [debug [nn]]\n",
870   .function = hss_create_command_fn,
871 };
872
873 static u8 *
874 format_hss_session (u8 *s, va_list *args)
875 {
876   hss_session_t *hs = va_arg (*args, hss_session_t *);
877   int __clib_unused verbose = va_arg (*args, int);
878
879   s = format (s, "\n path %s, data length %u, data_offset %u",
880               hs->path ? hs->path : (u8 *) "[none]", hs->data_len,
881               hs->data_offset);
882   return s;
883 }
884
885 static clib_error_t *
886 hss_show_command_fn (vlib_main_t *vm, unformat_input_t *input,
887                      vlib_cli_command_t *cmd)
888 {
889   int verbose = 0, show_cache = 0, show_sessions = 0;
890   hss_main_t *hsm = &hss_main;
891
892   if (hsm->www_root == 0)
893     return clib_error_return (0, "Static server disabled");
894
895   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
896     {
897       if (unformat (input, "verbose %d", &verbose))
898         ;
899       else if (unformat (input, "verbose"))
900         verbose = 1;
901       else if (unformat (input, "cache"))
902         show_cache = 1;
903       else if (unformat (input, "sessions"))
904         show_sessions = 1;
905       else
906         break;
907     }
908
909   if ((show_cache + show_sessions) == 0)
910     return clib_error_return (0, "specify one or more of cache, sessions");
911
912   if (show_cache)
913     vlib_cli_output (vm, "%U", format_hss_cache, &hsm->cache, verbose);
914
915   if (show_sessions)
916     {
917       u32 *session_indices = 0;
918       hss_session_t *hs;
919       int i, j;
920
921
922       for (i = 0; i < vec_len (hsm->sessions); i++)
923         {
924           pool_foreach (hs, hsm->sessions[i])
925             vec_add1 (session_indices, hs - hsm->sessions[i]);
926
927           for (j = 0; j < vec_len (session_indices); j++)
928             {
929               vlib_cli_output (
930                 vm, "%U", format_hss_session,
931                 pool_elt_at_index (hsm->sessions[i], session_indices[j]),
932                 verbose);
933             }
934           vec_reset_length (session_indices);
935         }
936       vec_free (session_indices);
937     }
938   return 0;
939 }
940
941 /*?
942  * Display static http server cache statistics
943  *
944  * @cliexpar
945  * This command shows the contents of the static http server cache
946  * @clistart
947  * show http static server
948  * @cliend
949  * @cliexcmd{show http static server sessions cache [verbose [nn]]}
950 ?*/
951 VLIB_CLI_COMMAND (hss_show_command, static) = {
952   .path = "show http static server",
953   .short_help = "show http static server sessions cache [verbose [<nn>]]",
954   .function = hss_show_command_fn,
955 };
956
957 static clib_error_t *
958 hss_clear_cache_command_fn (vlib_main_t *vm, unformat_input_t *input,
959                             vlib_cli_command_t *cmd)
960 {
961   hss_main_t *hsm = &hss_main;
962   u32 busy_items = 0;
963
964   if (hsm->www_root == 0)
965     return clib_error_return (0, "Static server disabled");
966
967   busy_items = hss_cache_clear (&hsm->cache);
968
969   if (busy_items > 0)
970     vlib_cli_output (vm, "Note: %d busy items still in cache...", busy_items);
971   else
972     vlib_cli_output (vm, "Cache cleared...");
973   return 0;
974 }
975
976 /*?
977  * Clear the static http server cache, to force the server to
978  * reload content from backing files
979  *
980  * @cliexpar
981  * This command clear the static http server cache
982  * @clistart
983  * clear http static cache
984  * @cliend
985  * @cliexcmd{clear http static cache}
986 ?*/
987 VLIB_CLI_COMMAND (clear_hss_cache_command, static) = {
988   .path = "clear http static cache",
989   .short_help = "clear http static cache",
990   .function = hss_clear_cache_command_fn,
991 };
992
993 static clib_error_t *
994 hss_main_init (vlib_main_t *vm)
995 {
996   hss_main_t *hsm = &hss_main;
997
998   hsm->app_index = ~0;
999   hsm->vlib_main = vm;
1000
1001   /* Set up file extension to mime type index map */
1002   hsm->mime_type_indices_by_file_extensions =
1003     hash_create_string (0, sizeof (uword));
1004
1005 #define _(def, ext, str)                                                      \
1006   hash_set_mem (hsm->mime_type_indices_by_file_extensions, ext,               \
1007                 HTTP_CONTENT_##def);
1008   foreach_http_content_type;
1009 #undef _
1010
1011   return 0;
1012 }
1013
1014 VLIB_INIT_FUNCTION (hss_main_init);
1015
1016 /*
1017  * fd.io coding-style-patch-verification: ON
1018  *
1019  * Local Variables:
1020  * eval: (c-set-style "gnu")
1021  * End:
1022  */