Nathan Skrzypczak | 9ad39c0 | 2021-08-19 11:38:06 +0200 | [diff] [blame] | 1 | Web applications with VPP |
| 2 | ========================= |
| 3 | |
| 4 | Vpp includes a versatile http/https “static” server plugin. We quote the |
| 5 | word static in the previous sentence because the server is easily |
| 6 | extended. This note describes how to build a Hugo site which includes |
| 7 | both monitoring and control functions. |
| 8 | |
| 9 | Let’s assume that we have a vpp data-plane plugin which needs a |
| 10 | monitoring and control web application. Here’s how to build one. |
| 11 | |
| 12 | Step 1: Add URL handlers |
| 13 | ------------------------ |
| 14 | |
| 15 | Individual URL handlers are pretty straightforward. You can return just |
| 16 | about anything you like, but as we work through the example you’ll see |
| 17 | why returning data in .json format tends to work out pretty well. |
| 18 | |
| 19 | :: |
| 20 | |
| 21 | static int |
Matus Fabian | a67da25 | 2024-08-01 16:36:44 +0200 | [diff] [blame] | 22 | handle_get_status (hss_url_handler_args_t *args) |
Nathan Skrzypczak | 9ad39c0 | 2021-08-19 11:38:06 +0200 | [diff] [blame] | 23 | { |
| 24 | my_main_t *mm = &my_main; |
| 25 | u8 *s = 0; |
| 26 | |
| 27 | /* Construct a .json reply */ |
| 28 | s = format (s, "{\"status\": {"); |
| 29 | s = format (s, " \"thing1\": \"%s\",", mm->thing1_value_string); |
| 30 | s = format (s, " \"thing2\": \"%s\",", mm->thing2_value_string); |
| 31 | /* ... etc ... */ |
| 32 | s = format (s, " \"lastthing\": \"%s\"", mm->last_value_string); |
| 33 | s = format (s, "}}"); |
| 34 | |
| 35 | /* And tell the static server plugin how to send the results */ |
Matus Fabian | a67da25 | 2024-08-01 16:36:44 +0200 | [diff] [blame] | 36 | args->data = s; |
| 37 | args->data_len = vec_len (s); |
| 38 | args->ct = HTTP_CONTENT_APP_JSON; |
| 39 | args->free_vec_data = 1; /* free s when done with it, in the framework */ |
| 40 | return HSS_URL_HANDLER_OK; |
Nathan Skrzypczak | 9ad39c0 | 2021-08-19 11:38:06 +0200 | [diff] [blame] | 41 | } |
| 42 | |
| 43 | Words to the Wise: Chrome has a very nice set of debugging tools. Select |
| 44 | “More Tools -> Developer Tools”. Right-hand sidebar appears with html |
| 45 | source code, a javascript debugger, network results including .json |
| 46 | objects, and so on. |
| 47 | |
| 48 | Note: .json object format is **intolerant** of both missing and extra |
| 49 | commas, missing and extra curly-braces. It’s easy to waste a |
| 50 | considerable amount of time debugging .json bugs. |
| 51 | |
| 52 | Step 2: Register URL handlers with the server |
| 53 | --------------------------------------------- |
| 54 | |
Matus Fabian | a67da25 | 2024-08-01 16:36:44 +0200 | [diff] [blame] | 55 | Call ``hss_register_url_handler`` as shown. It’s likely |
Nathan Skrzypczak | 9ad39c0 | 2021-08-19 11:38:06 +0200 | [diff] [blame] | 56 | but not guaranteed that the static server plugin will be available. |
| 57 | |
| 58 | :: |
| 59 | |
| 60 | int |
| 61 | plugin_url_init (vlib_main_t * vm) |
| 62 | { |
| 63 | void (*fp) (void *, char *, int); |
| 64 | |
| 65 | /* Look up the builtin URL registration handler */ |
| 66 | fp = vlib_get_plugin_symbol ("http_static_plugin.so", |
Matus Fabian | a67da25 | 2024-08-01 16:36:44 +0200 | [diff] [blame] | 67 | "hss_register_url_handler"); |
Nathan Skrzypczak | 9ad39c0 | 2021-08-19 11:38:06 +0200 | [diff] [blame] | 68 | |
| 69 | if (fp == 0) |
| 70 | { |
| 71 | clib_warning ("http_static_plugin.so not loaded..."); |
| 72 | return -1; |
| 73 | } |
| 74 | |
| 75 | (*fp) (handle_get_status, "status.json", HTTP_BUILTIN_METHOD_GET); |
| 76 | (*fp) (handle_get_run, "run.json", HTTP_BUILTIN_METHOD_GET); |
| 77 | (*fp) (handle_get_reset, "reset.json", HTTP_BUILTIN_METHOD_GET); |
| 78 | (*fp) (handle_get_stop, "stop.json", HTTP_BUILTIN_METHOD_GET); |
| 79 | return 0; |
| 80 | } |
| 81 | |
| 82 | Make sure to start the http static server **before** calling |
| 83 | plugin_url_init(…), or the registrations will disappear. |
| 84 | |
| 85 | Step 3: Install Hugo, pick a theme, and create a site |
| 86 | ----------------------------------------------------- |
| 87 | |
| 88 | Please refer to the Hugo documentation. |
| 89 | |
| 90 | See `the Hugo Quick Start |
| 91 | Page <https://gohugo.io/getting-started/quick-start>`__. Prebuilt binary |
| 92 | artifacts for many different environments are available on `the Hugo |
| 93 | release page <https://github.com/gohugoio/hugo/releases>`__. |
| 94 | |
| 95 | To pick a theme, visit `the Hugo Theme |
| 96 | site <https://themes.gohugo.io>`__. Decide what you need your site to |
| 97 | look like. Stay away from complex themes unless you’re prepared to spend |
| 98 | considerable time tweaking and tuning. |
| 99 | |
| 100 | The “Introduction” theme is a good choice for a simple site, YMMV. |
| 101 | |
| 102 | Step 4: Create a “rawhtml” shortcode |
| 103 | ------------------------------------ |
| 104 | |
| 105 | Once you’ve initialized your new site, create the directory |
| 106 | /layouts/shortcodes. Create the file “rawhtml.html” in that directory, |
| 107 | with the following contents: |
| 108 | |
| 109 | :: |
| 110 | |
| 111 | <!-- raw html --> |
| 112 | {{.Inner}} |
| 113 | |
| 114 | This is a key trick which allows a static Hugo site to include |
| 115 | javascript code. |
| 116 | |
| 117 | Step 5: create Hugo content which interacts with vpp |
| 118 | ---------------------------------------------------- |
| 119 | |
| 120 | Now it’s time to do some web front-end coding in javascript. Of course, |
| 121 | you can create static text, images, etc. as described in the Hugo |
| 122 | documentation. Nothing changes in that respect. |
| 123 | |
| 124 | To include dynamically-generated data in your Hugo pages, splat down |
| 125 | some |
| 126 | |
| 127 | .. raw:: html |
| 128 | |
| 129 | <div> |
| 130 | |
| 131 | HTML tags, and define a few buttons: |
| 132 | |
| 133 | :: |
| 134 | |
| 135 | {{< rawhtml >}} |
| 136 | <div id="Thing1"></div> |
| 137 | <div id="Thing2"></div> |
| 138 | <div id="Lastthing"></div> |
| 139 | <input type="button" value="Run" onclick="runButtonClick()"> |
| 140 | <input type="button" value="Reset" onclick="resetButtonClick()"> |
| 141 | <input type="button" value="Stop" onclick="stopButtonClick()"> |
| 142 | <div id="Message"></div> |
| 143 | {{< /rawhtml >}} |
| 144 | |
| 145 | Time for some javascript code to interact with vpp: |
| 146 | |
| 147 | :: |
| 148 | |
| 149 | {{< rawhtml >}} |
| 150 | <script> |
| 151 | async function getStatusJson() { |
| 152 | pump_url = location.href + "status.json"; |
| 153 | const json = await fetch(pump_url, { |
| 154 | method: 'GET', |
| 155 | mode: 'no-cors', |
| 156 | cache: 'no-cache', |
| 157 | headers: { |
| 158 | 'Content-Type': 'application/json', |
| 159 | }, |
| 160 | }) |
| 161 | .then((response) => response.json()) |
| 162 | .catch(function(error) { |
| 163 | console.log(error); |
| 164 | }); |
| 165 | |
| 166 | return json.status; |
| 167 | }; |
| 168 | |
| 169 | async function sendButton(which) { |
| 170 | my_url = location.href + which + ".json"; |
| 171 | const json = await fetch(my_url, { |
| 172 | method: 'GET', |
| 173 | mode: 'no-cors', |
| 174 | cache: 'no-cache', |
| 175 | headers: { |
| 176 | 'Content-Type': 'application/json', |
| 177 | }, |
| 178 | }) |
| 179 | .then((response) => response.json()) |
| 180 | .catch(function(error) { |
| 181 | console.log(error); |
| 182 | }); |
| 183 | return json.message; |
| 184 | }; |
| 185 | |
| 186 | async function getStatus() { |
| 187 | const status = await getStatusJson(); |
| 188 | |
| 189 | document.getElementById("Thing1").innerHTML = status.thing1; |
| 190 | document.getElementById("Thing2").innerHTML = status.thing2; |
| 191 | document.getElementById("Lastthing").innerHTML = status.lastthing; |
| 192 | }; |
| 193 | |
| 194 | async function runButtonClick() { |
| 195 | const json = await sendButton("run"); |
| 196 | document.getElementById("Message").innerHTML = json.Message; |
| 197 | } |
| 198 | |
| 199 | async function resetButtonClick() { |
| 200 | const json = await sendButton("reset"); |
| 201 | document.getElementById("Message").innerHTML = json.Message; |
| 202 | } |
| 203 | async function stopButtonClick() { |
| 204 | const json = await sendButton("stop"); |
| 205 | document.getElementById("Message").innerHTML = json.Message; |
| 206 | } |
| 207 | |
| 208 | getStatus(); |
| 209 | |
| 210 | </script> |
| 211 | {{< /rawhtml >}} |
| 212 | |
| 213 | At this level, javascript coding is pretty simple. Unless you know |
| 214 | exactly what you’re doing, please follow the async function / await |
| 215 | pattern shown above. |
| 216 | |
| 217 | Step 6: compile the website |
| 218 | --------------------------- |
| 219 | |
| 220 | At the top of the website workspace, simply type “hugo”. The compiled |
| 221 | website lands in the “public” subdirectory. |
| 222 | |
| 223 | You can use the Hugo static server - with suitable stub javascript code |
| 224 | - to see what your site will eventually look like. To start the hugo |
| 225 | static server, type “hugo server”. Browse to “http://localhost:1313”. |
| 226 | |
| 227 | Step 7: configure vpp |
| 228 | --------------------- |
| 229 | |
| 230 | In terms of command-line args: you may wish to use poll-sleep-usec 100 |
| 231 | to keep the load average low. Totally appropriate if vpp won’t be |
| 232 | processing a lot of packets or handling high-rate http/https traffic. |
| 233 | |
| 234 | :: |
| 235 | |
| 236 | unix { |
| 237 | ... |
| 238 | poll-sleep-usec 100 |
| 239 | startup-config ... see below ... |
| 240 | ... |
| 241 | } |
| 242 | |
| 243 | If you wish to provide an https site, configure tls. The simplest tls |
| 244 | configuration uses a built-in test certificate - which will annoy Chrome |
| 245 | / Firefox - but it’s sufficient for testing: |
| 246 | |
| 247 | :: |
| 248 | |
| 249 | tls { |
| 250 | use-test-cert-in-ca |
| 251 | } |
| 252 | |
| 253 | vpp startup configuration |
| 254 | ~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 255 | |
| 256 | Enable the vpp static server by way of the startup config mentioned |
| 257 | above: |
| 258 | |
| 259 | :: |
| 260 | |
Matus Fabian | a67da25 | 2024-08-01 16:36:44 +0200 | [diff] [blame] | 261 | http static server url-handlers www-root /myhugosite/public uri tcp://0.0.0.0/2345 cache-size 5m fifo-size 8192 |
Nathan Skrzypczak | 9ad39c0 | 2021-08-19 11:38:06 +0200 | [diff] [blame] | 262 | |
| 263 | The www-root must be specified, and must correctly name the compiled |
| 264 | hugo site root. If your Hugo site is located at /myhugosite, specify |
| 265 | “www-root /myhugosite/public” in the “http static server” stanza. The |
| 266 | uri shown above binds to TCP port 2345. |
| 267 | |
| 268 | If you’re using https, use a uri like “tls://0.0.0.0/443” instead of the |
| 269 | uri shown above. |
| 270 | |
| 271 | You may want to add a Linux host interface to view the full-up site |
| 272 | locally: |
| 273 | |
| 274 | :: |
| 275 | |
| 276 | create tap host-if-name lstack host-ip4-addr 192.168.10.2/24 |
| 277 | set int ip address tap0 192.168.10.1/24 |
| 278 | set int state tap0 up |