diff options
author | Dave Barach <dave@barachs.net> | 2020-02-10 17:12:06 -0500 |
---|---|---|
committer | Dave Barach <openvpp@barachs.net> | 2020-02-11 17:10:07 +0000 |
commit | dee0a3bb04f4e8d4fb588a1d5220042220765b83 (patch) | |
tree | 1ceb9f9f92a5e074692ed7bee04f63daf4fcced6 /docs | |
parent | 30e7671c85087b5781a34bde45ef3846f5de8c17 (diff) |
http_static: VPP web application HowTo
This sketch should save a lot of time working out uninteresting but
important details.
Type: docs
Signed-off-by: Dave Barach <dave@barachs.net>
Change-Id: Icd5705230adfda6539360ef3c46ff3a68b3bed74
Diffstat (limited to 'docs')
-rw-r--r-- | docs/usecases/index.rst | 2 | ||||
-rw-r--r-- | docs/usecases/webapp.md | 274 |
2 files changed, 275 insertions, 1 deletions
diff --git a/docs/usecases/index.rst b/docs/usecases/index.rst index 3bcc129f6ae..878902f7db5 100644 --- a/docs/usecases/index.rst +++ b/docs/usecases/index.rst @@ -18,4 +18,4 @@ extensive list, but should give a sampling of the many features contained in FD. homegateway contiv/index.rst networksim - + webapp diff --git a/docs/usecases/webapp.md b/docs/usecases/webapp.md new file mode 100644 index 00000000000..3b1a3b7b5b7 --- /dev/null +++ b/docs/usecases/webapp.md @@ -0,0 +1,274 @@ +Building VPP web applications +============================= + +Vpp includes a versatile http/https "static" server plugin. We quote +the word static in the previous sentence because the server is easily +extended. This note describes how to build a Hugo site which includes +both monitoring and control functions. + +Let's assume that we have a vpp data-plane plugin which needs a +monitoring and control web application. Here's how to build one. + +Step 1: Add URL handlers +------------------------ + +Individual URL handlers are pretty straightforward. You can +return just about anything you like, but as we work through +the example you'll see why returning data in .json format +tends to work out pretty well. + +``` + static int + handle_get_status (http_builtin_method_type_t reqtype, + u8 * request, http_session_t * hs) + { + my_main_t *mm = &my_main; + u8 *s = 0; + + /* Construct a .json reply */ + s = format (s, "{\"status\": {"); + s = format (s, " \"thing1\": \"%s\",", mm->thing1_value_string); + s = format (s, " \"thing2\": \"%s\",", mm->thing2_value_string); + /* ... etc ... */ + s = format (s, " \"lastthing\": \"%s\"", mm->last_value_string); + s = format (s, "}}"); + + /* And tell the static server plugin how to send the results */ + hs->data = s; + hs->data_offset = 0; + hs->cache_pool_index = ~0; + hs->free_data = 1; /* free s when done with it, in the framework */ + return 0; + } +``` + +Words to the Wise: Chrome has a very nice set of debugging +tools. Select "More Tools -> Developer Tools". Right-hand sidebar +appears with html source code, a javascript debugger, network results +including .json objects, and so on. + +Note: .json object format is **intolerant** of both missing and extra +commas, missing and extra curly-braces. It's easy to waste a +considerable amount of time debugging .json bugs. + +Step 2: Register URL handlers with the server +--------------------------------------------- + +Call http_static_server_register_builtin_handler() as shown. It's +likely but not guaranteed that the static server plugin will be +available. + + +``` + int + plugin_url_init (vlib_main_t * vm) + { + void (*fp) (void *, char *, int); + + /* Look up the builtin URL registration handler */ + fp = vlib_get_plugin_symbol ("http_static_plugin.so", + "http_static_server_register_builtin_handler"); + + if (fp == 0) + { + clib_warning ("http_static_plugin.so not loaded..."); + return -1; + } + + (*fp) (handle_get_status, "status.json", HTTP_BUILTIN_METHOD_GET); + (*fp) (handle_get_run, "run.json", HTTP_BUILTIN_METHOD_GET); + (*fp) (handle_get_reset, "reset.json", HTTP_BUILTIN_METHOD_GET); + (*fp) (handle_get_stop, "stop.json", HTTP_BUILTIN_METHOD_GET); + return 0; + } +``` + +Make sure to start the http static server **before** calling +plugin_url_init(...), or the registrations will disappear. + +Step 3: Install Hugo, pick a theme, and create a site +----------------------------------------------------- + +Please refer to the Hugo documentation. + +See [the Hugo Quick Start +Page](https://gohugo.io/getting-started/quick-start). Prebuilt binary +artifacts for many different environments are available on +[the Hugo release page](https://github.com/gohugoio/hugo/releases). + +To pick a theme, visit [the Hugo Theme +site](https://themes.gohugo.io). Decide what you need your site to +look like. Stay away from complex themes unless you're prepared to +spend considerable time tweaking and tuning. + +The "Introduction" theme is a good choice for a simple site, YMMV. + +Step 4: Create a "rawhtml" shortcode +------------------------------------ + +Once you've initialized your new site, create the directory +<site-root>/layouts/shortcodes. Create the file "rawhtml.html" in that +directory, with the following contents: + +``` + <!-- raw html --> + {{.Inner}} +``` +This is a key trick which allows a static Hugo site to include +javascript code. + +Step 5: create Hugo content which interacts with vpp +---------------------------------------------------- + +Now it's time to do some web front-end coding in javascript. Of +course, you can create static text, images, etc. as described in the +Hugo documentation. Nothing changes in that respect. + +To include dynamically-generated data in your Hugo pages, splat down +some <div> HTML tags, and define a few buttons: + +``` + {{< rawhtml >}} + <div id="Thing1"></div> + <div id="Thing2"></div> + <div id="Lastthing"></div> + <input type="button" value="Run" onclick="runButtonClick()"> + <input type="button" value="Reset" onclick="resetButtonClick()"> + <input type="button" value="Stop" onclick="stopButtonClick()"> + <div id="Message"></div> + {{< /rawhtml >}} +``` + +Time for some javascript code to interact with vpp: + + {{< rawhtml >}} + <script> + async function getStatusJson() { + pump_url = location.href + "status.json"; + const json = await fetch(pump_url, { + method: 'GET', + mode: 'no-cors', + cache: 'no-cache', + headers: { + 'Content-Type': 'application/json', + }, + }) + .then((response) => response.json()) + .catch(function(error) { + console.log(error); + }); + + return json.status; + }; + + async function sendButton(which) { + my_url = location.href + which + ".json"; + const json = await fetch(my_url, { + method: 'GET', + mode: 'no-cors', + cache: 'no-cache', + headers: { + 'Content-Type': 'application/json', + }, + }) + .then((response) => response.json()) + .catch(function(error) { + console.log(error); + }); + return json.message; + }; + + async function getStatus() { + const status = await getStatusJson(); + + document.getElementById("Thing1").innerHTML = status.thing1; + document.getElementById("Thing2").innerHTML = status.thing2; + document.getElementById("Lastthing").innerHTML = status.lastthing; + }; + + async function runButtonClick() { + const json = await sendButton("run"); + document.getElementById("Message").innerHTML = json.Message; + } + + async function resetButtonClick() { + const json = await sendButton("reset"); + document.getElementById("Message").innerHTML = json.Message; + } + async function stopButtonClick() { + const json = await sendButton("stop"); + document.getElementById("Message").innerHTML = json.Message; + } + + getStatus(); + + </script> + {{< /rawhtml >}} + +At this level, javascript coding is pretty simple. Unless you know +exactly what you're doing, please follow the async function / await +pattern shown above. + +Step 6: compile the website +--------------------------- + +At the top of the website workspace, simply type "hugo". The compiled +website lands in the "public" subdirectory. + +You can use the Hugo static server - with suitable stub javascript +code - to see what your site will eventually look like. To start the +hugo static server, type "hugo server". Browse to +"http://localhost:1313". + +Step 7: configure vpp +--------------------- + +In terms of command-line args: you may wish to use poll-sleep-usec 100 +to keep the load average low. Totally appropriate if vpp won't be +processing a lot of packets or handling high-rate http/https traffic. + +``` + unix { + ... + poll-sleep-usec 100 + startup-config ... see below ... + ... + } +``` + +If you wish to provide an https site, configure tls. The simplest tls +configuration uses a built-in test certificate - which will annoy +Chrome / Firefox - but it's sufficient for testing: + +``` + tls { + use-test-cert-in-ca + } +``` + + + + +### vpp startup configuration + +Enable the vpp static server by way of the startup config mentioned above: + +``` + http static server www-root /myhugosite/public uri tcp://0.0.0.0/2345 cache-size 5m fifo-size 8192 +``` + +The www-root must be specified, and must correctly name the compiled +hugo site root. If your Hugo site is located at /myhugosite, specify +"www-root /myhugosite/public" in the "http static server" stanza. The +uri shown above binds to TCP port 2345. + +If you're using https, use a uri like "tls://0.0.0.0/443" instead of +the uri shown above. + +You may want to add a Linux host interface to view the full-up site locally: + +``` + create tap host-if-name lstack host-ip4-addr 192.168.10.2/24 + set int ip address tap0 192.168.10.1/24 + set int state tap0 up +``` |