Deploy a Nanobus Web Server

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 nanobus/nanobus 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:
  https:
    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 +  (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 a 405 Method Not Allowed. This also makes sense because we haven’t defined GET 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:
  https:
    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 http.StripPrefix()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 https://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.