After Trying Out Nanobus, I figured it was time to go a little deeper and try to deploy a Nanobus web server. As noted in my previous post, I hadn’t setup any transports for my hello world instance so I couldn’t interact with it when deployed as a container. I also realized that a <a href="https://hub.docker.com/r/nanobus/nanobus" rel="noreferrer noopener" target="_blank">nanobus/nanobus</a>
docker image exists so there’s no need for me to roll my own either.
Looking At An Example
I’m going to start by building on the example provided in their Github. Just like before, I’ll start by creating a bus.yaml file:
id: your-app
version: 0.0.1
transports:
http:
uses: nanobus.transport.http.server/v1
with:
address: ':8080'
routers:
- uses: nanobus.transport.http.router/v1
with:
routes:
- method: POST
uri: /hello
handler: greeter::say-hello
authorization:
greeter:
say-hello:
unauthenticated: true
interfaces:
greeter:
say-hello:
steps:
- name: Say Hello!
uses: assign
with:
value: '"Hello, " + input.name'
For practical purposes, we’ll say that the greeter interface defined here is just like the Hello World
interface I created in the previous article. There’s an authorization
configuration listed here but we’re going to ignore this for now. The main thing to note regarding authorization
is that we won’t require authentication.
The main difference I want to focus on is that this YAML as a transports
definition! This reads similar to Kubernetes YAML files so we can see we’re loading a server and route. For the server, we’re setting the address
to be :8080
meaning this will listen on all interfaces on port 8080. For the router, we’re setting up a method, uri, and handler. This means that for any POST requests to /hello
, the greeter::say-hello
should be executed.
Deploying The Example With Docker
With the above bus.yaml
ready to go, let’s try executing in docker similar to our previous post.
% docker run -v /home/nanobus/basic-web-server:/opt/app -p 8080:8080 nanobus/nanobus run /opt/app/bus.yaml
2023-02-01T21:57:04.240Z INFO Initializing codec {"name": "bytes", "type": "bytes"}
2023-02-01T21:57:04.242Z INFO Initializing codec {"name": "json", "type": "json"}
2023-02-01T21:57:04.242Z INFO Initializing codec {"name": "msgpack", "type": "msgpack"}
2023-02-01T21:57:04.242Z INFO Initializing codec {"name": "cloudevents+avro", "type": "cloudevents+avro"}
2023-02-01T21:57:04.243Z INFO Initializing codec {"name": "cloudevents+json", "type": "cloudevents+json"}
2023-02-01T21:57:04.243Z INFO Initializing codec {"name": "text/plain", "type": "text/plain"}
2023-02-01T21:57:04.243Z INFO Initializing codec {"name": "text/html", "type": "text/html"}
2023-02-01T21:57:04.243Z INFO Initializing transport {"name": "http"}
2023-02-01T21:57:04.244Z INFO Serving route {"uri": "/hello", "methods": "POST", "handler": "greeter::say-hello"}
2023-02-01T21:57:04.246Z INFO HTTP server listening {"address": ":8080"}
This looks so much better! I see HTTP server listening
in the output this time! It only makes sense that we should use curl to test this thing out with a bunch of different things.
% curl localhost:8080
404 page not found
% curl localhost:8080/hello
% curl localhost:8080/hello -d ''
unregistered content type: application/x-www-form-urlencoded%
% curl localhost:8080/hello -d '{"test":"test"}'
unregistered content type: application/x-www-form-urlencoded%
% curl localhost:8080/hello -d '{"test":"test"}' -H 'Content-Type: application/json'
{"type":"internal","code":"internal","status":500,"message":"invalid operation: string + \u003cnil\u003e (1:11)\n | \"Hello, \" + input.name\n | ..........^","path":"/hello","timestamp":"2023-02-01T22:00:21.4213274Z"}%
% curl localhost:8080/hello -d '{"name":"test"}' -H 'Content-Type: application/json'
"Hello, test"%
- Line 1 : This makes sense because we haven’t defined this path.
- Line 4 : The lack of feedback was a little alarming but after I added the
-v
to the curl command, I see that I’m getting a405 Method Not Allowed
. This also makes sense because we haven’t definedGET
requests in our code. - Line 6 / Line 9 : These also make sense since we know this is expecting JSON input.
- Line 12 : While the error is a little verbose, this would also be expected because we are supplying a JSON that does not input the expected
name
element. - Line 15 : Success! We did exactly what the app expected here.
When I looked at my Docker command, I noticed that I didn’t have anything showing up to tell me about any of these interactions. I did not have any logs showing me any of the successful or failed requests. I did see mention of a log
action but I wasn’t sure how to add details of the connection. I did try adding that into my bus.yaml
id: your-app
version: 0.0.1
transports:
http:
uses: nanobus.transport.http.server/v1
with:
address: ':8080'
routers:
- uses: nanobus.transport.http.router/v1
with:
routes:
- method: POST
uri: /hello
handler: greeter::say-hello
authorization:
greeter:
say-hello:
unauthenticated: true
interfaces:
greeter:
say-hello:
steps:
- name: Say Hello!
uses: assign
with:
value: '"Hello, " + input.name'
- name: Testing
uses: log
with:
format: 'testing {{host}}'
While I have {{host}}
as an example here, I tried a number of different variables without success. I decided to open an Issue with Nanobus on this for more details.
Serving Up Static Content
The whole point of this article was to deploy nanobus as a web server so I need to serve up some content. I added a new router to my bus.yaml
- uses: nanobus.transport.http.static/v1
with:
paths:
- file: /tmp/index.html
path: /
I then created a simple HTML file
<html>
<body>
testing
</body>
</html>
I copied this file into the docker as /tmp/index.html
and then tried my curl
% curl localhost:8080
<html>
<body>
test
</body>
</html>
With static content being served, let’s up our game a little and serve an entire site with images and more. I decided to not reinvent the wheel and cloned Cloud Academy’s static website repo into a web_root directory. I also updated my bus.yaml to include the following paths:
- uses: nanobus.transport.http.static/v1
with:
paths:
- file: /web_root/index.html
path: /
- dir: /web_root/assets
path: /assets
strip: /assets
- dir: /web_root/images
path: /images
strip: /images
- dir: /web_root/errors
path: /errors
strip: /errors
The strip
statements are slightly confusing but you can read about golang’s <a href="https://pkg.go.dev/net/http#StripPrefix" rel="noreferrer noopener" target="_blank">http.StripPrefix()</a>
or read a super good explanation of what happens with it here. Finally, we’ll start up the server again and also mount the web_root directory into /web_root.
% docker run -v /home/nanobus/basic-web-server:/opt/app -v /home/nanobus/basic-web-server/web_root:/web_root -p 8080:8080 nanobus/nanobus run /opt/app/bus.yaml
2023-02-07T21:07:51.614Z INFO Initializing codec {"name": "bytes", "type": "bytes"}
2023-02-07T21:07:51.614Z INFO Initializing codec {"name": "json", "type": "json"}
2023-02-07T21:07:51.614Z INFO Initializing codec {"name": "msgpack", "type": "msgpack"}
2023-02-07T21:07:51.614Z INFO Initializing codec {"name": "cloudevents+avro", "type": "cloudevents+avro"}
2023-02-07T21:07:51.615Z INFO Initializing codec {"name": "cloudevents+json", "type": "cloudevents+json"}
2023-02-07T21:07:51.615Z INFO Initializing codec {"name": "text/plain", "type": "text/plain"}
2023-02-07T21:07:51.615Z INFO Initializing codec {"name": "text/html", "type": "text/html"}
2023-02-07T21:07:51.616Z INFO Initializing transport {"name": "http"}
2023-02-07T21:07:51.617Z INFO Serving route {"uri": "/hello", "methods": "POST", "handler": "greeter::say-hello"}
2023-02-07T21:07:51.617Z INFO Serving static files {"path": "/assets", "dir": "/web_root/assets", "file": null, "strip": "/assets"}
2023-02-07T21:07:51.617Z INFO Serving static files {"path": "/images", "dir": "/web_root/images", "file": null, "strip": "/images"}
2023-02-07T21:07:51.617Z INFO Serving static files {"path": "/errors", "dir": "/web_root/errors", "file": null, "strip": "/errors"}
2023-02-07T21:07:51.617Z INFO Serving static files {"path": "/", "dir": null, "file": "/web_root/index.html", "strip": null}
2023-02-07T21:07:51.620Z INFO HTTP server listening {"address": ":8080"}
Browsing to http://localhost:8080 looks like it worked!
Conclusion
This looks pretty interesting! I can see where this would be neat to replace some of my static Nginx servers where I’m serving up static web sites from a private Github repo. I’d claim this to be a successful attempt to deploy a Nanobus web server. I’ve updated my Github repo to now include these examples as well.