├── .gitattributes ├── .gitignore ├── LICENSE.md ├── PostmanCollection.json ├── README.md └── examples ├── Mandelbrot_240x240.png ├── different-data-types.json ├── different-data-types.png ├── error-handling..json ├── error-handling.png ├── file-upload.json ├── file-upload.png ├── file-upload.txt ├── form-processing.json ├── form-processing.png ├── header-handling.json ├── header-handling.png ├── http-status-codes.json ├── http-status-codes.png ├── multiple-endpoints.json ├── multiple-endpoints.png ├── query-handling.json ├── query-handling.png ├── routing-with-placeholders.json ├── routing-with-placeholders.png ├── show-request.json ├── show-request.png ├── trivial-http-server.json ├── trivial-http-server.png ├── virtual-hosts.json └── virtual-hosts.png /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # parcel-bundler cache (https://parceljs.org/) 61 | .cache 62 | 63 | # next.js build output 64 | .next 65 | 66 | # nuxt.js build output 67 | .nuxt 68 | 69 | # vuepress build output 70 | .vuepress/dist 71 | 72 | # Serverless directories 73 | .serverless 74 | 75 | # FuseBox cache 76 | .fusebox/ 77 | 78 | .DS_Store 79 | package-lock.json 80 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021-present Andreas Rozek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /PostmanCollection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "e1b31cb0-8720-4149-a1b9-bb8d1dbf744b", 4 | "name": "node-red-http-examples", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "trivial HTTP Server", 10 | "item": [ 11 | { 12 | "name": "send Request", 13 | "request": { 14 | "method": "GET", 15 | "header": [], 16 | "url": { 17 | "raw": "{{BaseURL}}/trivial-http-server", 18 | "host": [ 19 | "{{BaseURL}}" 20 | ], 21 | "path": [ 22 | "trivial-http-server" 23 | ] 24 | } 25 | }, 26 | "response": [] 27 | } 28 | ] 29 | }, 30 | { 31 | "name": "show Request on Console", 32 | "item": [ 33 | { 34 | "name": "get /show-request", 35 | "request": { 36 | "method": "GET", 37 | "header": [], 38 | "url": { 39 | "raw": "{{BaseURL}}/show-request", 40 | "host": [ 41 | "{{BaseURL}}" 42 | ], 43 | "path": [ 44 | "show-request" 45 | ] 46 | } 47 | }, 48 | "response": [] 49 | }, 50 | { 51 | "name": "post /show-request", 52 | "request": { 53 | "method": "POST", 54 | "header": [], 55 | "body": { 56 | "mode": "formdata", 57 | "formdata": [ 58 | { 59 | "key": "text", 60 | "value": "text-field", 61 | "type": "text" 62 | }, 63 | { 64 | "key": "file", 65 | "type": "file", 66 | "src": "./dummy-for-file-upload.txt" 67 | } 68 | ] 69 | }, 70 | "url": { 71 | "raw": "{{BaseURL}}/show-request", 72 | "host": [ 73 | "{{BaseURL}}" 74 | ], 75 | "path": [ 76 | "show-request" 77 | ] 78 | } 79 | }, 80 | "response": [] 81 | }, 82 | { 83 | "name": "put /show-request", 84 | "request": { 85 | "method": "PUT", 86 | "header": [], 87 | "body": { 88 | "mode": "raw", 89 | "raw": "Hello, World!\n(a plain-text body)", 90 | "options": { 91 | "raw": { 92 | "language": "text" 93 | } 94 | } 95 | }, 96 | "url": { 97 | "raw": "{{BaseURL}}/show-request", 98 | "host": [ 99 | "{{BaseURL}}" 100 | ], 101 | "path": [ 102 | "show-request" 103 | ] 104 | } 105 | }, 106 | "response": [] 107 | }, 108 | { 109 | "name": "delete /show-request", 110 | "request": { 111 | "method": "DELETE", 112 | "header": [], 113 | "url": { 114 | "raw": "{{BaseURL}}/show-request", 115 | "host": [ 116 | "{{BaseURL}}" 117 | ], 118 | "path": [ 119 | "show-request" 120 | ] 121 | } 122 | }, 123 | "response": [] 124 | }, 125 | { 126 | "name": "patch /show-request", 127 | "request": { 128 | "method": "PATCH", 129 | "header": [], 130 | "body": { 131 | "mode": "raw", 132 | "raw": "{\n \"boolean\":true,\n \"number\":3.14159265,\n \"string\":\"test\",\n \"array\":[true,3.14159265,\"test\",[],{}],\n \"object\":{\n \"boolean\":true,\n \"number\":3.14159265,\n \"string\":\"test\",\n \"inner-array\":[],\n \"inner-object\":{}\n },\n \"null\":null\n}", 133 | "options": { 134 | "raw": { 135 | "language": "json" 136 | } 137 | } 138 | }, 139 | "url": { 140 | "raw": "{{BaseURL}}/show-request", 141 | "host": [ 142 | "{{BaseURL}}" 143 | ], 144 | "path": [ 145 | "show-request" 146 | ] 147 | } 148 | }, 149 | "response": [] 150 | } 151 | ] 152 | }, 153 | { 154 | "name": "Routing with Placeholders", 155 | "item": [ 156 | { 157 | "name": "simple \"any\"", 158 | "request": { 159 | "method": "GET", 160 | "header": [], 161 | "url": { 162 | "raw": "{{BaseURL}}/routing/any", 163 | "host": [ 164 | "{{BaseURL}}" 165 | ], 166 | "path": [ 167 | "routing", 168 | "any" 169 | ] 170 | } 171 | }, 172 | "response": [] 173 | }, 174 | { 175 | "name": "\"any\" with a simple value", 176 | "request": { 177 | "method": "GET", 178 | "header": [], 179 | "url": { 180 | "raw": "{{BaseURL}}/routing/any/simple-value", 181 | "host": [ 182 | "{{BaseURL}}" 183 | ], 184 | "path": [ 185 | "routing", 186 | "any", 187 | "simple-value" 188 | ] 189 | } 190 | }, 191 | "response": [] 192 | }, 193 | { 194 | "name": "a path for \"any\"", 195 | "request": { 196 | "method": "GET", 197 | "header": [], 198 | "url": { 199 | "raw": "{{BaseURL}}/routing/any/with/a/complete/path", 200 | "host": [ 201 | "{{BaseURL}}" 202 | ], 203 | "path": [ 204 | "routing", 205 | "any", 206 | "with", 207 | "a", 208 | "complete", 209 | "path" 210 | ] 211 | } 212 | }, 213 | "response": [] 214 | }, 215 | { 216 | "name": "\"inner\"", 217 | "request": { 218 | "method": "GET", 219 | "header": [], 220 | "url": { 221 | "raw": "{{BaseURL}}/routing/inner/suffix", 222 | "host": [ 223 | "{{BaseURL}}" 224 | ], 225 | "path": [ 226 | "routing", 227 | "inner", 228 | "suffix" 229 | ] 230 | } 231 | }, 232 | "response": [] 233 | }, 234 | { 235 | "name": "\"first\" and \"second\"", 236 | "request": { 237 | "method": "GET", 238 | "header": [], 239 | "url": { 240 | "raw": "{{BaseURL}}/routing/first/and/second", 241 | "host": [ 242 | "{{BaseURL}}" 243 | ], 244 | "path": [ 245 | "routing", 246 | "first", 247 | "and", 248 | "second" 249 | ] 250 | } 251 | }, 252 | "response": [] 253 | } 254 | ] 255 | }, 256 | { 257 | "name": "Query Handling", 258 | "item": [ 259 | { 260 | "name": "query", 261 | "protocolProfileBehavior": { 262 | "disableBodyPruning": true 263 | }, 264 | "request": { 265 | "method": "GET", 266 | "header": [], 267 | "body": { 268 | "mode": "formdata", 269 | "formdata": [] 270 | }, 271 | "url": { 272 | "raw": "{{BaseURL}}/query?single=single value with inner spaces&multiple=first value of a series&multiple=second value of a series&empty", 273 | "host": [ 274 | "{{BaseURL}}" 275 | ], 276 | "path": [ 277 | "query" 278 | ], 279 | "query": [ 280 | { 281 | "key": "single", 282 | "value": "single value with inner spaces" 283 | }, 284 | { 285 | "key": "multiple", 286 | "value": "first value of a series" 287 | }, 288 | { 289 | "key": "multiple", 290 | "value": "second value of a series" 291 | }, 292 | { 293 | "key": "empty", 294 | "value": null 295 | } 296 | ] 297 | } 298 | }, 299 | "response": [] 300 | } 301 | ] 302 | }, 303 | { 304 | "name": "Form Processing", 305 | "item": [ 306 | { 307 | "name": "submit form", 308 | "request": { 309 | "method": "POST", 310 | "header": [], 311 | "body": { 312 | "mode": "urlencoded", 313 | "urlencoded": [ 314 | { 315 | "key": "boolean", 316 | "value": "true", 317 | "type": "text" 318 | }, 319 | { 320 | "key": "number", 321 | "value": "3.14159265", 322 | "type": "text" 323 | }, 324 | { 325 | "key": "string", 326 | "value": "Test", 327 | "type": "text" 328 | }, 329 | { 330 | "key": "hidden", 331 | "value": "hidden value", 332 | "type": "text" 333 | } 334 | ] 335 | }, 336 | "url": { 337 | "raw": "{{BaseURL}}/form-processing", 338 | "host": [ 339 | "{{BaseURL}}" 340 | ], 341 | "path": [ 342 | "form-processing" 343 | ] 344 | } 345 | }, 346 | "response": [] 347 | } 348 | ] 349 | }, 350 | { 351 | "name": "File Uploads", 352 | "item": [ 353 | { 354 | "name": "upload file", 355 | "request": { 356 | "method": "POST", 357 | "header": [], 358 | "body": { 359 | "mode": "formdata", 360 | "formdata": [ 361 | { 362 | "key": "file", 363 | "contentType": "text/plain", 364 | "type": "file", 365 | "src": "./examples/file-upload.txt" 366 | } 367 | ] 368 | }, 369 | "url": { 370 | "raw": "{{BaseURL}}/file-upload", 371 | "host": [ 372 | "{{BaseURL}}" 373 | ], 374 | "path": [ 375 | "file-upload" 376 | ] 377 | } 378 | }, 379 | "response": [] 380 | } 381 | ] 382 | }, 383 | { 384 | "name": "HTTP Status Codes", 385 | "item": [ 386 | { 387 | "name": "I'm a teapot", 388 | "request": { 389 | "method": "GET", 390 | "header": [], 391 | "url": { 392 | "raw": "{{BaseURL}}/status-code/418", 393 | "host": [ 394 | "{{BaseURL}}" 395 | ], 396 | "path": [ 397 | "status-code", 398 | "418" 399 | ] 400 | } 401 | }, 402 | "response": [] 403 | }, 404 | { 405 | "name": "unknown status code", 406 | "request": { 407 | "method": "GET", 408 | "header": [], 409 | "url": { 410 | "raw": "{{BaseURL}}/status-code/499", 411 | "host": [ 412 | "{{BaseURL}}" 413 | ], 414 | "path": [ 415 | "status-code", 416 | "499" 417 | ] 418 | } 419 | }, 420 | "response": [] 421 | } 422 | ] 423 | }, 424 | { 425 | "name": "Header Handling", 426 | "item": [ 427 | { 428 | "name": "send and receive with extra header", 429 | "request": { 430 | "method": "GET", 431 | "header": [ 432 | { 433 | "key": "custom-header", 434 | "value": "custom request header value", 435 | "type": "text" 436 | } 437 | ], 438 | "url": { 439 | "raw": "{{BaseURL}}/header-handling", 440 | "host": [ 441 | "{{BaseURL}}" 442 | ], 443 | "path": [ 444 | "header-handling" 445 | ] 446 | } 447 | }, 448 | "response": [] 449 | } 450 | ] 451 | }, 452 | { 453 | "name": "different Data Types", 454 | "item": [ 455 | { 456 | "name": "plain text", 457 | "request": { 458 | "method": "GET", 459 | "header": [], 460 | "url": { 461 | "raw": "{{BaseURL}}/data-of-type-text-plain", 462 | "host": [ 463 | "{{BaseURL}}" 464 | ], 465 | "path": [ 466 | "data-of-type-text-plain" 467 | ] 468 | } 469 | }, 470 | "response": [] 471 | }, 472 | { 473 | "name": "JSON", 474 | "request": { 475 | "method": "GET", 476 | "header": [], 477 | "url": { 478 | "raw": "{{BaseURL}}/data-of-type-application-json", 479 | "host": [ 480 | "{{BaseURL}}" 481 | ], 482 | "path": [ 483 | "data-of-type-application-json" 484 | ] 485 | } 486 | }, 487 | "response": [] 488 | }, 489 | { 490 | "name": "PNG image", 491 | "request": { 492 | "method": "GET", 493 | "header": [], 494 | "url": { 495 | "raw": "{{BaseURL}}/data-of-type-image-png", 496 | "host": [ 497 | "{{BaseURL}}" 498 | ], 499 | "path": [ 500 | "data-of-type-image-png" 501 | ] 502 | } 503 | }, 504 | "response": [] 505 | } 506 | ] 507 | }, 508 | { 509 | "name": "Error Handling", 510 | "item": [ 511 | { 512 | "name": "foreseen Error", 513 | "request": { 514 | "method": "GET", 515 | "header": [], 516 | "url": { 517 | "raw": "{{BaseURL}}/foreseen-error", 518 | "host": [ 519 | "{{BaseURL}}" 520 | ], 521 | "path": [ 522 | "foreseen-error" 523 | ] 524 | } 525 | }, 526 | "response": [] 527 | }, 528 | { 529 | "name": "unforeseen Error", 530 | "request": { 531 | "method": "GET", 532 | "header": [], 533 | "url": { 534 | "raw": "{{BaseURL}}/unforeseen-error", 535 | "host": [ 536 | "{{BaseURL}}" 537 | ], 538 | "path": [ 539 | "unforeseen-error" 540 | ] 541 | } 542 | }, 543 | "response": [] 544 | } 545 | ] 546 | }, 547 | { 548 | "name": "Virtual Hosts", 549 | "item": [ 550 | { 551 | "name": "retrieve virtual host", 552 | "request": { 553 | "method": "GET", 554 | "header": [], 555 | "url": { 556 | "raw": "{{BaseURL}}/virtual-host", 557 | "host": [ 558 | "{{BaseURL}}" 559 | ], 560 | "path": [ 561 | "virtual-host" 562 | ] 563 | } 564 | }, 565 | "response": [] 566 | } 567 | ] 568 | }, 569 | { 570 | "name": "multiple HTTP Endpoints", 571 | "item": [ 572 | { 573 | "name": "get specific-file", 574 | "request": { 575 | "method": "GET", 576 | "header": [], 577 | "url": { 578 | "raw": "{{BaseURL}}/Clash/specific-file", 579 | "host": [ 580 | "{{BaseURL}}" 581 | ], 582 | "path": [ 583 | "Clash", 584 | "specific-file" 585 | ] 586 | } 587 | }, 588 | "response": [] 589 | } 590 | ] 591 | } 592 | ], 593 | "auth": { 594 | "type": "basic", 595 | "basic": [ 596 | { 597 | "key": "password", 598 | "value": "{{Password}}", 599 | "type": "string" 600 | }, 601 | { 602 | "key": "username", 603 | "value": "{{Username}}", 604 | "type": "string" 605 | } 606 | ] 607 | }, 608 | "event": [ 609 | { 610 | "listen": "prerequest", 611 | "script": { 612 | "type": "text/javascript", 613 | "exec": [ 614 | "" 615 | ] 616 | } 617 | }, 618 | { 619 | "listen": "test", 620 | "script": { 621 | "type": "text/javascript", 622 | "exec": [ 623 | "" 624 | ] 625 | } 626 | } 627 | ], 628 | "variable": [ 629 | { 630 | "key": "BaseURL", 631 | "value": "127.0.0.1:1880" 632 | }, 633 | { 634 | "key": "Username", 635 | "value": "" 636 | }, 637 | { 638 | "key": "Password", 639 | "value": "" 640 | } 641 | ] 642 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-red-http-endpoint-examples # 2 | 3 | This collection of HTTP(S)-related [Node-RED](https://nodered.org/) examples (with a matching [Postman](https://www.postman.com/) collection for testing) is mainly intended for my students, but parts of it may also be of more general interest. 4 | 5 | It demonstrates fundamental aspects of HTTP(S) endpoints in Node-RED and is continued in two additional contributions which demonstrate how to implement *real* (file-based) [web servers](https://github.com/rozek/node-red-web-server-examples) or other [REST services](https://github.com/rozek/node-red-rest-service-examples). 6 | 7 | For this series, it is assumed that the reader already installed Node-RED (as described in [Getting Started](https://nodered.org/docs/getting-started/)), optionally secured the editor (as shown in [Securing Node-RED](https://nodered.org/docs/user-guide/runtime/securing-node-red)) and started using it (as explained in [Creating your first flow](https://nodered.org/docs/tutorials/first-flow)) 8 | 9 | > Just a small note: if you like this work and plan to use it, consider "starring" this repository (you will find the "Star" button on the top right of this page), so that I know which of my repositories to take most care of. 10 | 11 | ## Examples ## 12 | 13 | All example specifications are stored in JSON format and may easily be imported into a Node-RED workspace. Preferrably, you should open a separate tab and insert them there. 14 | 15 | To test the examples, a [Postman collection](https://raw.githubusercontent.com/rozek/node-red-http-examples/main/PostmanCollection.json) is included, which may easily be imported into a running [Postman](https://www.postman.com/) instance. After the import, you should open the collection's "Variables" section and set the `BaseURL` to the base URL of your NodeRED instance (by default, it is set to `127.0.0.1:1880`, which should work out-of-the-box for most Node-RED installations). If your Node-RED instance has been configured to require basic authentication, you should also set the variables `Username` and `Password`) 16 | 17 | Alternatively, other tools like [cURL](https://curl.se/) may be used as well. 18 | 19 | ### Trivial HTTP(S) Server ### 20 | 21 | The [first example](examples/trivial-http-server.json) is just to illustrate how easy it can be to implement a web service with Node-RED. 22 | 23 | ![](examples/trivial-http-server.png) 24 | 25 | Import the source, "deploy" and send a `GET` request to `{{BaseURL}}/trivial-http-server` (where you replace {{BaseURL}} by the base URL of your Node-RED instance) - the enclosed Postman collection already includes such a request. The response includes a complete (albeit simple) HTML page - but any other result could be sent as well. 26 | 27 | ### Show Request on Debug Console ### 28 | 29 | For own developments, it is often useful to display the complete `msg` of an incoming HTTP request on the debug console. 30 | 31 | ![](examples/show-request.png) 32 | 33 | Import the [example](examples/show-request.json), "deploy", send requests to `{{BaseURL}}/show-request` trying all HTTP methods Node-RED supports and inspect their output. 34 | 35 | ### Routing (with Placeholders) ### 36 | 37 | HTTP servers usually offer more than just a single endpoint - and often, parts of the requested URL control how the response will look like (in the simplest case, the URL contains the path of a file to be sent back). 38 | 39 | ![](examples/routing-with-placeholders.png) 40 | 41 | Node-RED supports the creation of multiple HTTP endpoints and allows their configured URLs to contain "placeholders" (with or without a pattern the requested URL must match in order for the endpoint to be triggered). When matched, these placeholders will be set to the matching parts of the requested URL and may thus be used in response construction. 42 | 43 | By default, a placeholder matches all characters up to (and without) the first "/" - if a complete subpath is needed, the placeholder should be followed by the pattern `(*)` 44 | 45 | Import the [routing example](examples/routing-with-placeholders.json), "deploy" and use the included Postman collection to send various requests to `{{BaseURL}}/routing`. The examples have been programmed to respond with the values of their placeholders - provided that the corresponding endpoints get triggered... 46 | 47 | ### Query Handling ### 48 | 49 | URLs may contain a "query" with "parameters" and "values". Node-RED automatically parses incoming queries and makes their contents available under `msg.req.query`. Usually, the values found their are strings, but if the same parameter appears multiple times, its values are stored in an array 50 | 51 | ![](examples/query-handling.png) 52 | 53 | Import the [query handling example](examples/query-handling.json), "deploy" and use the included Postman collection to send a request with an empty, a single and a multiple query parameter to `{{BaseURL}}/query`. The response will contain the actual contents of `msg.req.query` 54 | 55 | ### Form Processing ### 56 | 57 | An often needed feature of web applications (especially of application "frontends") is the submission of "forms". Node-RED is prepared to receive form contents and automatically parses them in order to simplify form processing. 58 | 59 | ![](examples/form-processing.png) 60 | 61 | Import the [form processing example](examples/form-processing.json), "deploy" and use the included Postman collection to send a request to `{{BaseURL}}/form-processing`. The response sent back will contain several form variables and their values. 62 | 63 | Please keep in mind that any form input will always be transmitted as text - boolean und numeric input may therefore first have to be converted into `boolean` and `number` values before being processed further 64 | 65 | ### File Uploads ### 66 | 67 | A common operation many servers provide is the upload of files. Node-RED supports file uploads out-of-the-box as part of their handling of POST requests. 68 | 69 | ![](examples/file-upload.png) 70 | 71 | Import the [file upload example](examples/file-upload.json), "deploy" and use the included Postman collection to send a request to `{{BaseURL}}/file-upload`. As a "proof" of a successful file upload the response will contain the uploaded file's name and its MIME type. 72 | 73 | ### Setting HTTP Status Codes ### 74 | 75 | An important part of an HTTP response is its [status code](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) which informs the client about success or failure and - if the request failed - the reason for failure. 76 | 77 | Node-RED allows the status code of a response to be explicitly set. This example combines this feature with URL placeholders and answers an incoming request with the status code it contains. 78 | 79 | ![](examples/http-status-codes.png) 80 | 81 | Import the [status code example](examples/http-status-codes.json), "deploy" and use the included Postman collection to send requests for different status codes to `{{BaseURL}}/status-code`. Responses should contain the requested status code (if known) and a short explanation of their meaning 82 | 83 | ### Request and Response Header Handling ### 84 | 85 | Besides the actual content (the "body") HTTP requests and responses contain several "headers" with additional information about the request (or response). Node-RED allows incoming headers to be inspected and outgoing headers to be defined - both standard as well as non-standard headers are supported. 86 | 87 | ![](examples/header-handling.png) 88 | 89 | Import the [header handling example](examples/header-handling.json), "deploy" and use the included Postman collection to test: it contains a request with a "custom-header" whose value will be returned when sent to `{{BaseURL}}/header-handling`. Don't forget to inspect the respopnse headers for the custom one. 90 | 91 | ### Delivering different Types of Data ### 92 | 93 | HTTP servers may deliver content of different types (as text or in binary form) and with different encodings. HTTP clients (such as browsers) determine the actual type of a response body with the aid of a header called "Content-Type". 94 | 95 | Node-RED can send data in both text and binary form. While it may be able to "guess" the data type in some common situations, it is usually a good idea to set this header explicitly. 96 | 97 | ![](examples/different-data-types.png) 98 | 99 | > for this example to work, please copy file `Mandelbrot_240x240.png` into the working directory of your Node-RED instance 100 | 101 | Import the [data type example](examples/different-data-types.json), "deploy" and use the included Postman collection to send requests to `{{BaseURL}}/data-of-type-text-plain`, `{{BaseURL}}/data-of-type-application-json` and `{{BaseURL}}/data-of-type-image-png`. Because of the "Content-Type" header, Postman will be able to interpret the returned data properly and, e.g., present a returned image by displaying it. 102 | 103 | ### Error Handling ### 104 | 105 | An important aspect of request processing is the handling of errors - both "foreseen" and "unforeseen" ones. 106 | 107 | "Foreseen" errors are simple: just check incoming requests and their contents for mistakes and react by responding with an appropriate HTTP status code. Common I/O and processing errors may be handled similarly using specific "catch" nodes. 108 | 109 | Risky are all those errors the programmer does not think of - for those, a generic "catch" node dealing with all exceptions not otherwise caught should always be included and send an "Internal Server Error" (HTTP status code 500) back. 110 | 111 | ![](examples/error-handling.png) 112 | 113 | Import the [error handling example](examples/error-handling.json), "deploy" and use the included Postman collection to send two requests: one to `{{BaseURL}}/foreseen-error` (which responds with 400 "Bad Request") and one to `{{BaseURL}}/unforeseen-error` (which responds with 500 "Internal Server Error") 114 | 115 | ### Virtual Hosts ### 116 | 117 | For web site hosters, it is quite common to handle multiple domains within the same server - a feature called "virtual hosts". Technically, these hosts are only distinguished by a special HTTP header (and with a lot of help from the requesting client). 118 | 119 | And - since Node-RED supports headers, it also supports the handling of "virtual hosts". 120 | 121 | ![](examples/virtual-hosts.png) 122 | 123 | Import the [virtual host example](examples/virtual-hosts.json), "deploy" and use the included Postman collection to send a request to `{{BaseURL}}/virtual-hosts`. The example will respond with the contents of the HTTP "Host" header - which is quite boring when used locally, but may principally be used to serve different domains from a single Node-RED instance 124 | 125 | ### Multiple Endpoints matching the same URL ### 126 | 127 | So far, we have seen several examples - all of them playing well together because of different HTTP endpoints. The question arises what happens if there is more than a single endpoint for the same URL (a situation which may easily occur if endpoints with placeholders are used) 128 | 129 | ![](examples/multiple-endpoints.png) 130 | 131 | Our [final example](examples/multiple-endpoints.json) addresses this problem: two different endpoints may handle the same URL. When tested (e.g., with the included Postman collection), only one of both endpoints will be triggered. Which one, depends on the order their creation - in practice, it is therefore a good idea to assume "unpredictable behaviour". 132 | 133 | The important lesson to learn: Node-RED does not produce an error, not even a warning - it will be up to you to keep track of your endpoints and the range of URLs they cover. 134 | 135 | ## License ## 136 | 137 | [MIT License](LICENSE.md) 138 | -------------------------------------------------------------------------------- /examples/Mandelbrot_240x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rozek/node-red-http-endpoint-examples/e6e267e62f95c08304e3cd16c1aca6d5602a0d5a/examples/Mandelbrot_240x240.png -------------------------------------------------------------------------------- /examples/different-data-types.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "2f32071ef7bb110d", 4 | "type": "comment", 5 | "z": "4730a3973976294e", 6 | "name": "delivering different Types of Data (at /data-of-type-XXX)", 7 | "info": "", 8 | "x": 1540, 9 | "y": 320, 10 | "wires": [] 11 | }, 12 | { 13 | "id": "238793311dabfdd6", 14 | "type": "http in", 15 | "z": "4730a3973976294e", 16 | "name": "", 17 | "url": "data-of-type-text-plain", 18 | "method": "get", 19 | "upload": true, 20 | "swaggerDoc": "", 21 | "x": 1460, 22 | "y": 380, 23 | "wires": [ 24 | [ 25 | "cffded47ea1c03d0" 26 | ] 27 | ] 28 | }, 29 | { 30 | "id": "b51281afcd501289", 31 | "type": "http in", 32 | "z": "4730a3973976294e", 33 | "name": "", 34 | "url": "data-of-type-application-json", 35 | "method": "get", 36 | "upload": true, 37 | "swaggerDoc": "", 38 | "x": 1480, 39 | "y": 500, 40 | "wires": [ 41 | [ 42 | "68a3c31298d82cc0" 43 | ] 44 | ] 45 | }, 46 | { 47 | "id": "3e924f0ff93cd624", 48 | "type": "http in", 49 | "z": "4730a3973976294e", 50 | "name": "", 51 | "url": "data-of-type-image-png", 52 | "method": "get", 53 | "upload": true, 54 | "swaggerDoc": "", 55 | "x": 1460, 56 | "y": 620, 57 | "wires": [ 58 | [ 59 | "8fa440683ca55861" 60 | ] 61 | ] 62 | }, 63 | { 64 | "id": "cffded47ea1c03d0", 65 | "type": "change", 66 | "z": "4730a3973976294e", 67 | "name": "", 68 | "rules": [ 69 | { 70 | "t": "set", 71 | "p": "payload", 72 | "pt": "msg", 73 | "to": "this is some plain text", 74 | "tot": "str" 75 | }, 76 | { 77 | "t": "set", 78 | "p": "headers", 79 | "pt": "msg", 80 | "to": "{\"content-type\":\"text/plain\"}", 81 | "tot": "json" 82 | } 83 | ], 84 | "action": "", 85 | "property": "", 86 | "from": "", 87 | "to": "", 88 | "reg": false, 89 | "x": 1640, 90 | "y": 440, 91 | "wires": [ 92 | [ 93 | "c0f311bbfc8d346a" 94 | ] 95 | ] 96 | }, 97 | { 98 | "id": "c0f311bbfc8d346a", 99 | "type": "http response", 100 | "z": "4730a3973976294e", 101 | "name": "", 102 | "statusCode": "200", 103 | "headers": {}, 104 | "x": 1820, 105 | "y": 440, 106 | "wires": [] 107 | }, 108 | { 109 | "id": "68a3c31298d82cc0", 110 | "type": "change", 111 | "z": "4730a3973976294e", 112 | "name": "", 113 | "rules": [ 114 | { 115 | "t": "set", 116 | "p": "payload", 117 | "pt": "msg", 118 | "to": "{ \"type\":\"json\" }", 119 | "tot": "str" 120 | }, 121 | { 122 | "t": "set", 123 | "p": "headers", 124 | "pt": "msg", 125 | "to": "{\"content-type\":\"application/json\"}", 126 | "tot": "json" 127 | } 128 | ], 129 | "action": "", 130 | "property": "", 131 | "from": "", 132 | "to": "", 133 | "reg": false, 134 | "x": 1640, 135 | "y": 560, 136 | "wires": [ 137 | [ 138 | "19311e74fb2a3167" 139 | ] 140 | ] 141 | }, 142 | { 143 | "id": "19311e74fb2a3167", 144 | "type": "http response", 145 | "z": "4730a3973976294e", 146 | "name": "", 147 | "statusCode": "200", 148 | "headers": {}, 149 | "x": 1820, 150 | "y": 560, 151 | "wires": [] 152 | }, 153 | { 154 | "id": "8fa440683ca55861", 155 | "type": "file in", 156 | "z": "4730a3973976294e", 157 | "name": "", 158 | "filename": "Mandelbrot_240x240.png", 159 | "format": "", 160 | "chunk": false, 161 | "sendError": false, 162 | "encoding": "none", 163 | "allProps": false, 164 | "x": 1730, 165 | "y": 620, 166 | "wires": [ 167 | [ 168 | "7fb230d107b1b06f" 169 | ] 170 | ] 171 | }, 172 | { 173 | "id": "7fb230d107b1b06f", 174 | "type": "change", 175 | "z": "4730a3973976294e", 176 | "name": "", 177 | "rules": [ 178 | { 179 | "t": "set", 180 | "p": "headers", 181 | "pt": "msg", 182 | "to": "{\"content-type\":\"image/png\"}", 183 | "tot": "json" 184 | } 185 | ], 186 | "action": "", 187 | "property": "", 188 | "from": "", 189 | "to": "", 190 | "reg": false, 191 | "x": 1630, 192 | "y": 680, 193 | "wires": [ 194 | [ 195 | "39689ce2448f0aba" 196 | ] 197 | ] 198 | }, 199 | { 200 | "id": "39689ce2448f0aba", 201 | "type": "http response", 202 | "z": "4730a3973976294e", 203 | "name": "", 204 | "statusCode": "200", 205 | "headers": {}, 206 | "x": 1820, 207 | "y": 680, 208 | "wires": [] 209 | } 210 | ] -------------------------------------------------------------------------------- /examples/different-data-types.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rozek/node-red-http-endpoint-examples/e6e267e62f95c08304e3cd16c1aca6d5602a0d5a/examples/different-data-types.png -------------------------------------------------------------------------------- /examples/error-handling..json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "38c4a5c6d9caf636", 4 | "type": "comment", 5 | "z": "4730a3973976294e", 6 | "name": "Error Handling (at /foreseen-error and /unforeseen-error)", 7 | "info": "", 8 | "x": 890, 9 | "y": 340, 10 | "wires": [] 11 | }, 12 | { 13 | "id": "753fcfbc2be035dc", 14 | "type": "http in", 15 | "z": "4730a3973976294e", 16 | "name": "", 17 | "url": "foreseen-error", 18 | "method": "get", 19 | "upload": false, 20 | "swaggerDoc": "", 21 | "x": 770, 22 | "y": 400, 23 | "wires": [ 24 | [ 25 | "dcd8727e7c8b3abf" 26 | ] 27 | ] 28 | }, 29 | { 30 | "id": "a7c0f3928f01a639", 31 | "type": "http in", 32 | "z": "4730a3973976294e", 33 | "name": "", 34 | "url": "unforeseen-error", 35 | "method": "get", 36 | "upload": false, 37 | "swaggerDoc": "", 38 | "x": 780, 39 | "y": 540, 40 | "wires": [ 41 | [ 42 | "5ecbb1f15a401d7a" 43 | ] 44 | ] 45 | }, 46 | { 47 | "id": "5ecbb1f15a401d7a", 48 | "type": "function", 49 | "z": "4730a3973976294e", 50 | "name": "throw exception", 51 | "func": "throw 'unforeseen error'", 52 | "outputs": 1, 53 | "noerr": 0, 54 | "initialize": "", 55 | "finalize": "", 56 | "libs": [], 57 | "x": 1000, 58 | "y": 540, 59 | "wires": [ 60 | [] 61 | ] 62 | }, 63 | { 64 | "id": "467391b272bd32ca", 65 | "type": "catch", 66 | "z": "4730a3973976294e", 67 | "name": "", 68 | "scope": [ 69 | "5ecbb1f15a401d7a" 70 | ], 71 | "uncaught": false, 72 | "x": 770, 73 | "y": 580, 74 | "wires": [ 75 | [ 76 | "b48773a032616240" 77 | ] 78 | ] 79 | }, 80 | { 81 | "id": "e67c327dce742cc2", 82 | "type": "http response", 83 | "z": "4730a3973976294e", 84 | "name": "send Response", 85 | "statusCode": "", 86 | "headers": {}, 87 | "x": 1160, 88 | "y": 460, 89 | "wires": [] 90 | }, 91 | { 92 | "id": "dcd8727e7c8b3abf", 93 | "type": "function", 94 | "z": "4730a3973976294e", 95 | "name": "validate Request", 96 | "func": "let RequestIsBad = true\n\n// add your request validation here\n\nif (RequestIsBad) {\n msg.payload = 'Bad Request'\n msg.statusCode = 400\n return [msg,null]\n} else {\n return [null,msg]\n}\n", 97 | "outputs": 2, 98 | "noerr": 0, 99 | "initialize": "", 100 | "finalize": "", 101 | "libs": [], 102 | "x": 990, 103 | "y": 400, 104 | "wires": [ 105 | [ 106 | "e67c327dce742cc2" 107 | ], 108 | [ 109 | "448c92f7eb4171ae" 110 | ] 111 | ] 112 | }, 113 | { 114 | "id": "448c92f7eb4171ae", 115 | "type": "function", 116 | "z": "4730a3973976294e", 117 | "name": "additional Processing", 118 | "func": "msg.payload = 'Success'\nmsg.statusCode = 200\n\nreturn msg", 119 | "outputs": 1, 120 | "noerr": 0, 121 | "initialize": "", 122 | "finalize": "", 123 | "libs": [], 124 | "x": 820, 125 | "y": 460, 126 | "wires": [ 127 | [ 128 | "e67c327dce742cc2" 129 | ] 130 | ] 131 | }, 132 | { 133 | "id": "b48773a032616240", 134 | "type": "change", 135 | "z": "4730a3973976294e", 136 | "name": "internal Error", 137 | "rules": [ 138 | { 139 | "t": "set", 140 | "p": "payload", 141 | "pt": "msg", 142 | "to": "Internal Server Error", 143 | "tot": "str" 144 | }, 145 | { 146 | "t": "set", 147 | "p": "statusCode", 148 | "pt": "msg", 149 | "to": "500", 150 | "tot": "str" 151 | } 152 | ], 153 | "action": "", 154 | "property": "", 155 | "from": "", 156 | "to": "", 157 | "reg": false, 158 | "x": 930, 159 | "y": 580, 160 | "wires": [ 161 | [ 162 | "0596dc711f94dd27" 163 | ] 164 | ] 165 | }, 166 | { 167 | "id": "0596dc711f94dd27", 168 | "type": "http response", 169 | "z": "4730a3973976294e", 170 | "name": "send Response", 171 | "statusCode": "", 172 | "headers": {}, 173 | "x": 1160, 174 | "y": 580, 175 | "wires": [] 176 | } 177 | ] -------------------------------------------------------------------------------- /examples/error-handling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rozek/node-red-http-endpoint-examples/e6e267e62f95c08304e3cd16c1aca6d5602a0d5a/examples/error-handling.png -------------------------------------------------------------------------------- /examples/file-upload.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "a0cb99c78c0317de", 4 | "type": "comment", 5 | "z": "4730a3973976294e", 6 | "name": "File Upload (at /file-upload)", 7 | "info": "", 8 | "x": 790, 9 | "y": 880, 10 | "wires": [] 11 | }, 12 | { 13 | "id": "5c23c162fc24aea2", 14 | "type": "http response", 15 | "z": "4730a3973976294e", 16 | "name": "", 17 | "statusCode": "200", 18 | "headers": {}, 19 | "x": 1160, 20 | "y": 940, 21 | "wires": [] 22 | }, 23 | { 24 | "id": "c3d4c30f8b61ba9e", 25 | "type": "http in", 26 | "z": "4730a3973976294e", 27 | "name": "", 28 | "url": "file-upload", 29 | "method": "post", 30 | "upload": true, 31 | "swaggerDoc": "", 32 | "x": 760, 33 | "y": 940, 34 | "wires": [ 35 | [ 36 | "8bc1342748eefbfe" 37 | ] 38 | ] 39 | }, 40 | { 41 | "id": "8bc1342748eefbfe", 42 | "type": "function", 43 | "z": "4730a3973976294e", 44 | "name": "create Response", 45 | "func": "if (msg.req.files == null) { // no files uploaded\n msg.payload = \"Bad Request (missing upload file)\"\n msg.statusCode = 400\n} else {\n msg.payload = (\n 'File: \"' + msg.req.files[0].originalname + '\"\\n' +\n 'MIME-Type: \"' + msg.req.files[0].mimetype + '\"'\n )\n msg.statusCode = 200\n}\n\nreturn msg", 46 | "outputs": 1, 47 | "noerr": 0, 48 | "initialize": "", 49 | "finalize": "", 50 | "libs": [], 51 | "x": 970, 52 | "y": 940, 53 | "wires": [ 54 | [ 55 | "5c23c162fc24aea2" 56 | ] 57 | ] 58 | } 59 | ] -------------------------------------------------------------------------------- /examples/file-upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rozek/node-red-http-endpoint-examples/e6e267e62f95c08304e3cd16c1aca6d5602a0d5a/examples/file-upload.png -------------------------------------------------------------------------------- /examples/file-upload.txt: -------------------------------------------------------------------------------- 1 | This is just a dummy which may be used for HTTP file uploads 2 | -------------------------------------------------------------------------------- /examples/form-processing.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "a0cb99c78c0317de", 4 | "type": "comment", 5 | "z": "4730a3973976294e", 6 | "name": "Form Processing (at /form-processing)", 7 | "info": "", 8 | "x": 830, 9 | "y": 880, 10 | "wires": [] 11 | }, 12 | { 13 | "id": "5c23c162fc24aea2", 14 | "type": "http response", 15 | "z": "4730a3973976294e", 16 | "name": "", 17 | "statusCode": "200", 18 | "headers": {}, 19 | "x": 1160, 20 | "y": 940, 21 | "wires": [] 22 | }, 23 | { 24 | "id": "c3d4c30f8b61ba9e", 25 | "type": "http in", 26 | "z": "4730a3973976294e", 27 | "name": "", 28 | "url": "form-processing", 29 | "method": "post", 30 | "upload": false, 31 | "swaggerDoc": "", 32 | "x": 780, 33 | "y": 940, 34 | "wires": [ 35 | [ 36 | "8bc1342748eefbfe" 37 | ] 38 | ] 39 | }, 40 | { 41 | "id": "8bc1342748eefbfe", 42 | "type": "function", 43 | "z": "4730a3973976294e", 44 | "name": "create Response", 45 | "func": "msg.headers = msg.headers || {}\nmsg.headers['Content-Type'] = 'application/json'\n\nmsg.statusCode = 200 // form contents are already in msg.payload\nreturn msg\n", 46 | "outputs": 1, 47 | "noerr": 0, 48 | "initialize": "", 49 | "finalize": "", 50 | "libs": [], 51 | "x": 990, 52 | "y": 940, 53 | "wires": [ 54 | [ 55 | "5c23c162fc24aea2" 56 | ] 57 | ] 58 | } 59 | ] -------------------------------------------------------------------------------- /examples/form-processing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rozek/node-red-http-endpoint-examples/e6e267e62f95c08304e3cd16c1aca6d5602a0d5a/examples/form-processing.png -------------------------------------------------------------------------------- /examples/header-handling.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "35d1dbcf6632dc2b", 4 | "type": "comment", 5 | "z": "4730a3973976294e", 6 | "name": "Request and Response Header Handling (at /header-handling)", 7 | "info": "", 8 | "x": 900, 9 | "y": 660, 10 | "wires": [] 11 | }, 12 | { 13 | "id": "b891dfa7b32037b1", 14 | "type": "http in", 15 | "z": "4730a3973976294e", 16 | "name": "", 17 | "url": "header-handling", 18 | "method": "get", 19 | "upload": false, 20 | "swaggerDoc": "", 21 | "x": 780, 22 | "y": 720, 23 | "wires": [ 24 | [ 25 | "9bb6a4570c21e22f" 26 | ] 27 | ] 28 | }, 29 | { 30 | "id": "6fa55f30a47f3254", 31 | "type": "http response", 32 | "z": "4730a3973976294e", 33 | "name": "send Response", 34 | "statusCode": "", 35 | "headers": {}, 36 | "x": 1160, 37 | "y": 780, 38 | "wires": [] 39 | }, 40 | { 41 | "id": "9bb6a4570c21e22f", 42 | "type": "function", 43 | "z": "4730a3973976294e", 44 | "name": "process Request", 45 | "func": "let customRequestHeader = msg.req.headers['custom-header']\n\nlet Output = (\n customRequestHeader == null\n ? 'custom header is missing'\n : 'custom header found: \"' + customRequestHeader + '\"'\n)\n\nmsg.headers = msg.headers || {}\nmsg.headers['custom-header'] = 'custom response header value'\nmsg.payload = 'inspect headers, there should be a \"custom-header\"'\n\nreturn [{ payload:Output }, msg ]", 46 | "outputs": 2, 47 | "noerr": 0, 48 | "initialize": "", 49 | "finalize": "", 50 | "libs": [], 51 | "x": 1010, 52 | "y": 720, 53 | "wires": [ 54 | [ 55 | "10cd0135c91dbc8a" 56 | ], 57 | [ 58 | "6fa55f30a47f3254" 59 | ] 60 | ] 61 | }, 62 | { 63 | "id": "10cd0135c91dbc8a", 64 | "type": "debug", 65 | "z": "4730a3973976294e", 66 | "name": "", 67 | "active": true, 68 | "tosidebar": true, 69 | "console": false, 70 | "tostatus": false, 71 | "complete": "false", 72 | "statusVal": "", 73 | "statusType": "auto", 74 | "x": 1150, 75 | "y": 820, 76 | "wires": [] 77 | } 78 | ] -------------------------------------------------------------------------------- /examples/header-handling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rozek/node-red-http-endpoint-examples/e6e267e62f95c08304e3cd16c1aca6d5602a0d5a/examples/header-handling.png -------------------------------------------------------------------------------- /examples/http-status-codes.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "faa3e0fcbda2698a", 4 | "type": "comment", 5 | "z": "4730a3973976294e", 6 | "name": "set HTTP Status Code (at /status-code)", 7 | "info": "", 8 | "x": 830, 9 | "y": 100, 10 | "wires": [] 11 | }, 12 | { 13 | "id": "cfe9b57f4d2873d3", 14 | "type": "http in", 15 | "z": "4730a3973976294e", 16 | "name": "", 17 | "url": "status-code/:code", 18 | "method": "get", 19 | "upload": false, 20 | "swaggerDoc": "", 21 | "x": 790, 22 | "y": 160, 23 | "wires": [ 24 | [ 25 | "dd7a4bdc1d7b1608" 26 | ] 27 | ] 28 | }, 29 | { 30 | "id": "4cb13d15753ac34d", 31 | "type": "http response", 32 | "z": "4730a3973976294e", 33 | "name": "send Response", 34 | "statusCode": "", 35 | "headers": {}, 36 | "x": 1160, 37 | "y": 220, 38 | "wires": [] 39 | }, 40 | { 41 | "id": "dd7a4bdc1d7b1608", 42 | "type": "function", 43 | "z": "4730a3973976294e", 44 | "name": "process Request", 45 | "func": "let StatusCode = parseInt(msg.req.params.code,10)\nif (isNaN(StatusCode)) { StatusCode = 'NaN'}\n\nlet StatusText = ''\n switch (StatusCode) {\n case 'NaN': StatusText = 'invalid status code given'; StatusCode = 400; break\n\n case 100: StatusText = 'Continue'; break\n case 101: StatusText = 'Switching Protocols'; break\n case 102: StatusText = 'Processing'; break\n case 103: StatusText = 'Early Hints'; break\n case 110: StatusText = 'Response is Stale'; break\n case 111: StatusText = 'Revalidation Failed'; break\n case 112: StatusText = 'Disconnected Operation'; break\n case 113: StatusText = 'Heuristic Expiration'; break\n \n case 200: StatusText = 'OK'; break\n case 201: StatusText = 'Created'; break\n case 202: StatusText = 'Accepted'; break\n case 203: StatusText = 'Non-Authoritative Information'; break\n case 204: StatusText = 'No Content'; break\n case 205: StatusText = 'Reset Content'; break\n case 206: StatusText = 'Partial Content'; break\n case 207: StatusText = 'Multi-Status'; break\n case 208: StatusText = 'Already Reported'; break\n case 214: StatusText = 'Transformation Applied'; break\n case 226: StatusText = 'IM Used'; break\n case 299: StatusText = 'Miscellaneous Persistent Warning'; break\n \n case 300: StatusText = 'Multiple Choices'; break\n case 301: StatusText = 'Moved Permanently'; break\n case 302: StatusText = 'Found'; break\n case 303: StatusText = 'See Other'; break\n case 304: StatusText = 'Not Modified'; break\n case 305: StatusText = 'Use Proxy'; break\n case 306: StatusText = 'Switch Proxy'; break\n case 307: StatusText = 'Temporary Redirect'; break\n case 308: StatusText = 'Permanent Redirect'; break\n \n case 400: StatusText = 'Bad Request'; break\n case 401: StatusText = 'Unauthorized'; break\n case 402: StatusText = 'Payment Required'; break\n case 403: StatusText = 'Forbidden'; break\n case 404: StatusText = 'Not Found'; break\n case 405: StatusText = 'Method Not Allowed'; break\n case 406: StatusText = 'Not Acceptable'; break\n case 407: StatusText = 'Proxy Authentication Required'; break\n case 408: StatusText = 'Request Timeout'; break\n case 409: StatusText = 'Conflict'; break\n case 410: StatusText = 'Gone'; break\n case 411: StatusText = 'Length Required'; break\n case 412: StatusText = 'Precondition Failed'; break\n case 413: StatusText = 'Payload Too Large'; break\n case 414: StatusText = 'URI Too Long'; break\n case 415: StatusText = 'Unsupported Media Type'; break\n case 416: StatusText = 'Range Not Satisfiable'; break\n case 417: StatusText = 'Expectation Failed'; break\n case 418: StatusText = 'I\\'m a teapot'; break\n case 421: StatusText = 'Misdirected Request'; break\n case 422: StatusText = 'Unprocessable Entity'; break\n case 423: StatusText = 'Locked'; break\n case 424: StatusText = 'Failed Dependency'; break\n case 425: StatusText = 'Too Early'; break\n case 426: StatusText = 'Upgrade Required'; break\n case 428: StatusText = 'Precondition Required'; break\n case 429: StatusText = 'Too Many Requests'; break\n case 431: StatusText = 'Request Header Fields Too Large'; break\n case 451: StatusText = 'Unavailable For Legal Reasons'; break\n \n case 500: StatusText = 'Internal Server Error'; break\n case 501: StatusText = 'Not Implemented'; break\n case 502: StatusText = 'Bad Gateway'; break\n case 503: StatusText = 'Service Unavailable'; break\n case 504: StatusText = 'Gateway Timeout'; break\n case 505: StatusText = 'HTTP Version Not Supported'; break\n case 506: StatusText = 'Variant Also Negotiates'; break\n case 507: StatusText = 'Insufficient Storage'; break\n case 508: StatusText = 'Loop Detected'; break\n case 510: StatusText = 'Not Extended'; break\n case 511: StatusText = 'Network Authentication Required'; break\n }\nif (StatusText === '') {\n msg.payload = 'unknown HTTP status code \"' + StatusCode + '\"'\n msg.statusCode = StatusCode\n} else {\n msg.payload = StatusText\n msg.statusCode = StatusCode\n}\n\nreturn msg", 46 | "outputs": 1, 47 | "noerr": 0, 48 | "initialize": "", 49 | "finalize": "", 50 | "libs": [], 51 | "x": 1030, 52 | "y": 160, 53 | "wires": [ 54 | [ 55 | "4cb13d15753ac34d", 56 | "0401e5a9c2df2cde" 57 | ] 58 | ] 59 | }, 60 | { 61 | "id": "fbc7dbd160c441e3", 62 | "type": "debug", 63 | "z": "4730a3973976294e", 64 | "name": "", 65 | "active": true, 66 | "tosidebar": true, 67 | "console": false, 68 | "tostatus": false, 69 | "complete": "false", 70 | "statusVal": "", 71 | "statusType": "auto", 72 | "x": 1150, 73 | "y": 260, 74 | "wires": [] 75 | }, 76 | { 77 | "id": "0401e5a9c2df2cde", 78 | "type": "template", 79 | "z": "4730a3973976294e", 80 | "name": "combine StatusCode and StatusText", 81 | "field": "payload", 82 | "fieldType": "msg", 83 | "format": "handlebars", 84 | "syntax": "mustache", 85 | "template": "{{statusCode}} {{{payload}}}", 86 | "output": "str", 87 | "x": 890, 88 | "y": 260, 89 | "wires": [ 90 | [ 91 | "fbc7dbd160c441e3" 92 | ] 93 | ] 94 | } 95 | ] -------------------------------------------------------------------------------- /examples/http-status-codes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rozek/node-red-http-endpoint-examples/e6e267e62f95c08304e3cd16c1aca6d5602a0d5a/examples/http-status-codes.png -------------------------------------------------------------------------------- /examples/multiple-endpoints.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "10dca77eabe7e005", 4 | "type": "comment", 5 | "z": "4730a3973976294e", 6 | "name": "multiple Endpoints matching the same URL (at /clash)", 7 | "info": "", 8 | "x": 1540, 9 | "y": 800, 10 | "wires": [] 11 | }, 12 | { 13 | "id": "f2b4f5498759ad69", 14 | "type": "http in", 15 | "z": "4730a3973976294e", 16 | "name": "", 17 | "url": "clash/specific-file", 18 | "method": "get", 19 | "upload": false, 20 | "swaggerDoc": "", 21 | "x": 1440, 22 | "y": 920, 23 | "wires": [ 24 | [ 25 | "2aa1dc66c472d9cb" 26 | ] 27 | ] 28 | }, 29 | { 30 | "id": "2aa1dc66c472d9cb", 31 | "type": "change", 32 | "z": "4730a3973976294e", 33 | "name": "create Response", 34 | "rules": [ 35 | { 36 | "t": "set", 37 | "p": "payload", 38 | "pt": "msg", 39 | "to": "specific response", 40 | "tot": "str" 41 | } 42 | ], 43 | "action": "", 44 | "property": "", 45 | "from": "", 46 | "to": "", 47 | "reg": false, 48 | "x": 1670, 49 | "y": 920, 50 | "wires": [ 51 | [ 52 | "7a4ea35b003c7300" 53 | ] 54 | ] 55 | }, 56 | { 57 | "id": "b226fd64fdf3fadb", 58 | "type": "change", 59 | "z": "4730a3973976294e", 60 | "name": "create Response", 61 | "rules": [ 62 | { 63 | "t": "set", 64 | "p": "payload", 65 | "pt": "msg", 66 | "to": "generic response", 67 | "tot": "str" 68 | } 69 | ], 70 | "action": "", 71 | "property": "", 72 | "from": "", 73 | "to": "", 74 | "reg": false, 75 | "x": 1670, 76 | "y": 860, 77 | "wires": [ 78 | [ 79 | "c4d7c5cf7ba93079" 80 | ] 81 | ] 82 | }, 83 | { 84 | "id": "c4d7c5cf7ba93079", 85 | "type": "http response", 86 | "z": "4730a3973976294e", 87 | "name": "", 88 | "statusCode": "200", 89 | "headers": {}, 90 | "x": 1860, 91 | "y": 860, 92 | "wires": [] 93 | }, 94 | { 95 | "id": "7a4ea35b003c7300", 96 | "type": "http response", 97 | "z": "4730a3973976294e", 98 | "name": "", 99 | "statusCode": "200", 100 | "headers": {}, 101 | "x": 1860, 102 | "y": 920, 103 | "wires": [] 104 | }, 105 | { 106 | "id": "f9fbb2275dfd43a6", 107 | "type": "http in", 108 | "z": "4730a3973976294e", 109 | "name": "", 110 | "url": "clash/:any(*)", 111 | "method": "get", 112 | "upload": false, 113 | "swaggerDoc": "", 114 | "x": 1430, 115 | "y": 860, 116 | "wires": [ 117 | [ 118 | "b226fd64fdf3fadb" 119 | ] 120 | ] 121 | }, 122 | { 123 | "id": "a5a8e36543590405", 124 | "type": "comment", 125 | "z": "4730a3973976294e", 126 | "name": "but only one will be triggered", 127 | "info": "", 128 | "x": 1780, 129 | "y": 980, 130 | "wires": [] 131 | } 132 | ] -------------------------------------------------------------------------------- /examples/multiple-endpoints.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rozek/node-red-http-endpoint-examples/e6e267e62f95c08304e3cd16c1aca6d5602a0d5a/examples/multiple-endpoints.png -------------------------------------------------------------------------------- /examples/query-handling.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "75a15f0a5b21b676", 4 | "type": "comment", 5 | "z": "4730a3973976294e", 6 | "name": "Query Handling (at /query)", 7 | "info": "", 8 | "x": 130, 9 | "y": 880, 10 | "wires": [] 11 | }, 12 | { 13 | "id": "4fd79775fd3ae9a9", 14 | "type": "http in", 15 | "z": "4730a3973976294e", 16 | "name": "", 17 | "url": "query", 18 | "method": "get", 19 | "upload": false, 20 | "swaggerDoc": "", 21 | "x": 90, 22 | "y": 940, 23 | "wires": [ 24 | [ 25 | "caccc7229a5c7b7d" 26 | ] 27 | ] 28 | }, 29 | { 30 | "id": "caccc7229a5c7b7d", 31 | "type": "template", 32 | "z": "4730a3973976294e", 33 | "name": "process Request", 34 | "field": "payload", 35 | "fieldType": "msg", 36 | "format": "handlebars", 37 | "syntax": "mustache", 38 | "template": "\"single\":\"{{{req.query.single}}}\",\n\"multiple\":\"{{{req.query.multiple}}}\",\n\"empty\":\"{{{req.query.empty}}}\"", 39 | "output": "str", 40 | "x": 290, 41 | "y": 940, 42 | "wires": [ 43 | [ 44 | "487be741aedff65c", 45 | "be5a056012092bdb" 46 | ] 47 | ] 48 | }, 49 | { 50 | "id": "487be741aedff65c", 51 | "type": "http response", 52 | "z": "4730a3973976294e", 53 | "name": "", 54 | "statusCode": "200", 55 | "headers": {}, 56 | "x": 520, 57 | "y": 940, 58 | "wires": [] 59 | }, 60 | { 61 | "id": "be5a056012092bdb", 62 | "type": "debug", 63 | "z": "4730a3973976294e", 64 | "name": "", 65 | "active": true, 66 | "tosidebar": true, 67 | "console": false, 68 | "tostatus": false, 69 | "complete": "false", 70 | "statusVal": "", 71 | "statusType": "auto", 72 | "x": 490, 73 | "y": 980, 74 | "wires": [] 75 | } 76 | ] -------------------------------------------------------------------------------- /examples/query-handling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rozek/node-red-http-endpoint-examples/e6e267e62f95c08304e3cd16c1aca6d5602a0d5a/examples/query-handling.png -------------------------------------------------------------------------------- /examples/routing-with-placeholders.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "444e78ca04b111e4", 4 | "type": "comment", 5 | "z": "4730a3973976294e", 6 | "name": "Routing with Placeholders (at /routing)", 7 | "info": "", 8 | "x": 170, 9 | "y": 580, 10 | "wires": [] 11 | }, 12 | { 13 | "id": "fec2c089002c78e6", 14 | "type": "http in", 15 | "z": "4730a3973976294e", 16 | "name": "", 17 | "url": "routing/any/:subpath(*)", 18 | "method": "get", 19 | "upload": false, 20 | "swaggerDoc": "", 21 | "x": 140, 22 | "y": 640, 23 | "wires": [ 24 | [ 25 | "ba1bada606a7dc3a" 26 | ] 27 | ] 28 | }, 29 | { 30 | "id": "c049a672b28c23fc", 31 | "type": "http in", 32 | "z": "4730a3973976294e", 33 | "name": "", 34 | "url": "routing/:inner/suffix", 35 | "method": "get", 36 | "upload": false, 37 | "swaggerDoc": "", 38 | "x": 130, 39 | "y": 680, 40 | "wires": [ 41 | [ 42 | "e465a6328cf6a2ce" 43 | ] 44 | ] 45 | }, 46 | { 47 | "id": "e24c27d91e5dcb83", 48 | "type": "http in", 49 | "z": "4730a3973976294e", 50 | "name": "", 51 | "url": "routing/:first/and/:second", 52 | "method": "get", 53 | "upload": false, 54 | "swaggerDoc": "", 55 | "x": 150, 56 | "y": 720, 57 | "wires": [ 58 | [ 59 | "6a0c543d2ab4a9f3" 60 | ] 61 | ] 62 | }, 63 | { 64 | "id": "ba1bada606a7dc3a", 65 | "type": "template", 66 | "z": "4730a3973976294e", 67 | "name": "process Request", 68 | "field": "payload", 69 | "fieldType": "msg", 70 | "format": "handlebars", 71 | "syntax": "mustache", 72 | "template": "\"subpath\":\"{{{req.params.subpath}}}\"", 73 | "output": "str", 74 | "x": 410, 75 | "y": 640, 76 | "wires": [ 77 | [ 78 | "dcd1df35a0db50ae", 79 | "9967bb236a053ce9" 80 | ] 81 | ] 82 | }, 83 | { 84 | "id": "9967bb236a053ce9", 85 | "type": "http response", 86 | "z": "4730a3973976294e", 87 | "name": "", 88 | "statusCode": "200", 89 | "headers": {}, 90 | "x": 520, 91 | "y": 780, 92 | "wires": [] 93 | }, 94 | { 95 | "id": "dcd1df35a0db50ae", 96 | "type": "debug", 97 | "z": "4730a3973976294e", 98 | "name": "", 99 | "active": true, 100 | "tosidebar": true, 101 | "console": false, 102 | "tostatus": false, 103 | "complete": "false", 104 | "statusVal": "", 105 | "statusType": "auto", 106 | "x": 490, 107 | "y": 820, 108 | "wires": [] 109 | }, 110 | { 111 | "id": "e465a6328cf6a2ce", 112 | "type": "template", 113 | "z": "4730a3973976294e", 114 | "name": "process Request", 115 | "field": "payload", 116 | "fieldType": "msg", 117 | "format": "handlebars", 118 | "syntax": "mustache", 119 | "template": "\"inner\":\"{{{req.params.inner}}}\"", 120 | "output": "str", 121 | "x": 410, 122 | "y": 680, 123 | "wires": [ 124 | [ 125 | "9967bb236a053ce9", 126 | "dcd1df35a0db50ae" 127 | ] 128 | ] 129 | }, 130 | { 131 | "id": "6a0c543d2ab4a9f3", 132 | "type": "template", 133 | "z": "4730a3973976294e", 134 | "name": "process Request", 135 | "field": "payload", 136 | "fieldType": "msg", 137 | "format": "handlebars", 138 | "syntax": "mustache", 139 | "template": "\"first\":\"{{{req.params.first}}}\",\n\"second\":\"{{{req.params.second}}}\"", 140 | "output": "str", 141 | "x": 410, 142 | "y": 720, 143 | "wires": [ 144 | [ 145 | "9967bb236a053ce9", 146 | "dcd1df35a0db50ae" 147 | ] 148 | ] 149 | } 150 | ] -------------------------------------------------------------------------------- /examples/routing-with-placeholders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rozek/node-red-http-endpoint-examples/e6e267e62f95c08304e3cd16c1aca6d5602a0d5a/examples/routing-with-placeholders.png -------------------------------------------------------------------------------- /examples/show-request.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "3b19c11ce044e751", 4 | "type": "comment", 5 | "z": "4730a3973976294e", 6 | "name": "show Request on Console (at /show-request)", 7 | "info": "", 8 | "x": 190, 9 | "y": 240, 10 | "wires": [] 11 | }, 12 | { 13 | "id": "6ef2072a8470ed71", 14 | "type": "http in", 15 | "z": "4730a3973976294e", 16 | "name": "", 17 | "url": "show-request", 18 | "method": "get", 19 | "upload": false, 20 | "swaggerDoc": "", 21 | "x": 110, 22 | "y": 300, 23 | "wires": [ 24 | [ 25 | "7860b328926aa62d", 26 | "1081fc61bdcdf351" 27 | ] 28 | ] 29 | }, 30 | { 31 | "id": "a1cc7b1b1831f5cd", 32 | "type": "http response", 33 | "z": "4730a3973976294e", 34 | "name": "", 35 | "statusCode": "204", 36 | "headers": {}, 37 | "x": 520, 38 | "y": 460, 39 | "wires": [] 40 | }, 41 | { 42 | "id": "1e995a639261be26", 43 | "type": "http in", 44 | "z": "4730a3973976294e", 45 | "name": "", 46 | "url": "show-request", 47 | "method": "post", 48 | "upload": true, 49 | "swaggerDoc": "", 50 | "x": 110, 51 | "y": 340, 52 | "wires": [ 53 | [ 54 | "7860b328926aa62d", 55 | "1081fc61bdcdf351" 56 | ] 57 | ] 58 | }, 59 | { 60 | "id": "bedb272dbd0bab27", 61 | "type": "http in", 62 | "z": "4730a3973976294e", 63 | "name": "", 64 | "url": "show-request", 65 | "method": "put", 66 | "upload": false, 67 | "swaggerDoc": "", 68 | "x": 110, 69 | "y": 380, 70 | "wires": [ 71 | [ 72 | "7860b328926aa62d", 73 | "1081fc61bdcdf351" 74 | ] 75 | ] 76 | }, 77 | { 78 | "id": "cc3dd3e662f6769f", 79 | "type": "http in", 80 | "z": "4730a3973976294e", 81 | "name": "", 82 | "url": "show-request", 83 | "method": "delete", 84 | "upload": false, 85 | "swaggerDoc": "", 86 | "x": 120, 87 | "y": 420, 88 | "wires": [ 89 | [ 90 | "7860b328926aa62d", 91 | "1081fc61bdcdf351" 92 | ] 93 | ] 94 | }, 95 | { 96 | "id": "b01e9d1db5485fc7", 97 | "type": "http in", 98 | "z": "4730a3973976294e", 99 | "name": "", 100 | "url": "show-request", 101 | "method": "patch", 102 | "upload": false, 103 | "swaggerDoc": "", 104 | "x": 120, 105 | "y": 460, 106 | "wires": [ 107 | [ 108 | "7860b328926aa62d", 109 | "1081fc61bdcdf351" 110 | ] 111 | ] 112 | }, 113 | { 114 | "id": "7860b328926aa62d", 115 | "type": "debug", 116 | "z": "4730a3973976294e", 117 | "name": "", 118 | "active": true, 119 | "tosidebar": true, 120 | "console": false, 121 | "tostatus": false, 122 | "complete": "true", 123 | "targetType": "full", 124 | "statusVal": "", 125 | "statusType": "auto", 126 | "x": 350, 127 | "y": 300, 128 | "wires": [] 129 | }, 130 | { 131 | "id": "1081fc61bdcdf351", 132 | "type": "change", 133 | "z": "4730a3973976294e", 134 | "name": "create Response", 135 | "rules": [ 136 | { 137 | "t": "set", 138 | "p": "payload", 139 | "pt": "msg", 140 | "to": "", 141 | "tot": "str" 142 | } 143 | ], 144 | "action": "", 145 | "property": "", 146 | "from": "", 147 | "to": "", 148 | "reg": false, 149 | "x": 390, 150 | "y": 400, 151 | "wires": [ 152 | [ 153 | "a1cc7b1b1831f5cd" 154 | ] 155 | ] 156 | }, 157 | { 158 | "id": "02625fa57b810b92", 159 | "type": "comment", 160 | "z": "4730a3973976294e", 161 | "name": "Node-RED does not like \"null\" responses", 162 | "info": "", 163 | "x": 320, 164 | "y": 500, 165 | "wires": [] 166 | } 167 | ] -------------------------------------------------------------------------------- /examples/show-request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rozek/node-red-http-endpoint-examples/e6e267e62f95c08304e3cd16c1aca6d5602a0d5a/examples/show-request.png -------------------------------------------------------------------------------- /examples/trivial-http-server.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "457a1ee62bf5aa27", 4 | "type": "http in", 5 | "z": "4730a3973976294e", 6 | "name": "Hello, World!", 7 | "url": "trivial-http-server", 8 | "method": "get", 9 | "upload": false, 10 | "swaggerDoc": "", 11 | "x": 90, 12 | "y": 160, 13 | "wires": [ 14 | [ 15 | "bed67d33379eef9a" 16 | ] 17 | ] 18 | }, 19 | { 20 | "id": "bed67d33379eef9a", 21 | "type": "template", 22 | "z": "4730a3973976294e", 23 | "name": "create Response", 24 | "field": "payload", 25 | "fieldType": "msg", 26 | "format": "handlebars", 27 | "syntax": "mustache", 28 | "template": "\n\n \n \n \n \n

Hello, World!

\n \n", 29 | "output": "str", 30 | "x": 290, 31 | "y": 160, 32 | "wires": [ 33 | [ 34 | "ec2fd6df0b4618ba" 35 | ] 36 | ] 37 | }, 38 | { 39 | "id": "ec2fd6df0b4618ba", 40 | "type": "http response", 41 | "z": "4730a3973976294e", 42 | "name": "send Response", 43 | "statusCode": "", 44 | "headers": {}, 45 | "x": 500, 46 | "y": 160, 47 | "wires": [] 48 | }, 49 | { 50 | "id": "e5b4fa2e7622d8fa", 51 | "type": "comment", 52 | "z": "4730a3973976294e", 53 | "name": "trivial HTTP(S) Server (at /trivial-http-server)", 54 | "info": "depends on Node-RED's current settings.js", 55 | "x": 190, 56 | "y": 100, 57 | "wires": [] 58 | } 59 | ] -------------------------------------------------------------------------------- /examples/trivial-http-server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rozek/node-red-http-endpoint-examples/e6e267e62f95c08304e3cd16c1aca6d5602a0d5a/examples/trivial-http-server.png -------------------------------------------------------------------------------- /examples/virtual-hosts.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "e6ecaeaf7330ec25", 4 | "type": "comment", 5 | "z": "4730a3973976294e", 6 | "name": "virtual Hosts (at /virtual-host)", 7 | "info": "", 8 | "x": 1460, 9 | "y": 100, 10 | "wires": [] 11 | }, 12 | { 13 | "id": "73ade7c0b5fc7f80", 14 | "type": "http in", 15 | "z": "4730a3973976294e", 16 | "name": "", 17 | "url": "virtual-host", 18 | "method": "get", 19 | "upload": true, 20 | "swaggerDoc": "", 21 | "x": 1420, 22 | "y": 160, 23 | "wires": [ 24 | [ 25 | "d979be08dbbaefc9" 26 | ] 27 | ] 28 | }, 29 | { 30 | "id": "d979be08dbbaefc9", 31 | "type": "function", 32 | "z": "4730a3973976294e", 33 | "name": "process Request", 34 | "func": "let HostHeader = msg.req.headers['host']\nmsg.payload = HostHeader\n\nreturn msg", 35 | "outputs": 1, 36 | "noerr": 0, 37 | "initialize": "", 38 | "finalize": "", 39 | "libs": [], 40 | "x": 1630, 41 | "y": 160, 42 | "wires": [ 43 | [ 44 | "701b0378d7fa6771" 45 | ] 46 | ] 47 | }, 48 | { 49 | "id": "701b0378d7fa6771", 50 | "type": "http response", 51 | "z": "4730a3973976294e", 52 | "name": "", 53 | "statusCode": "200", 54 | "headers": {}, 55 | "x": 1820, 56 | "y": 160, 57 | "wires": [] 58 | } 59 | ] -------------------------------------------------------------------------------- /examples/virtual-hosts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rozek/node-red-http-endpoint-examples/e6e267e62f95c08304e3cd16c1aca6d5602a0d5a/examples/virtual-hosts.png --------------------------------------------------------------------------------