Internet of Functions: HTTP Proxy

Sep 28, 2020 • 4 minutes to read

One of the limitations of the WebAssembly VM is that it provides no built-in support for network applications. In the web browser or in Node.js, it is possible to call the host JavaScript to perform networking tasks. But of course, a JavaScript bridge is slow, highly dependent on the host platform (i.e., not portable), and requires the developer to use JavaScript. In the SSVM, we have a new way to do this.

The http_proxy is a command you can invoke from your Rust function to access network resources from within the SSVM WebAssembly program. The SSVM executes the http_proxy as a native program, and exchange data with the calling SSVM program via STDIN and STDOUT. To show how this works, let’s revisit our simple email function service.

A generic email function service

The generic email function service takes a JSON object as input and then invokes the sendgrid API to send the email. The input JSON contains the email’s from and to addresses, the subject text, the body’s MIME type and content, and the sendgrid API token to use for this transaction. The source code of the example application is available on GitHub. The Rust function is as follows. The Msg struct models the input JSON object. The function first parses the input JSON object. It then creates a payload JSON object that conforms to the sendgrid API specification. The Command::new("http_proxy") statement calls the http_proxy to make the sendgrid web service call, which we will cover the next.

#[derive(Serialize, Deserialize, Debug)]
struct Msg {
  from: String,
  token: String,
  to: String,
  subject: String,
  mime: String,
  body: String
}

#[wasm_bindgen]
pub fn send_email(s: &str) -> String {
  let msg: Msg = serde_json::from_str(s).unwrap();
  let payload = json!(
    {
        "personalizations": [{
            "to": [{
                "email": &msg.to
            }]
        }],
        "from": {
            "email": &msg.from
        },
        "subject":&msg.subject,
        "content": [{
            "type": &msg.mime,
            "value": &msg.body
        }]
    });
  let auth_header: String = "{\"Content-Type\": \"application/json\",\"authorization\": \"Bearer ".to_owned() + &msg.token + "\"}";
  
  // Execute the command for the HTTP web service
  let mut cmd = Command::new("http_proxy");
  cmd.arg("post")
      .arg("https://api.sendgrid.com/v3/mail/send")
      .arg(auth_header);
  cmd.stdin_u8vec(payload.to_string().as_bytes());
  
  // Receives the HTTP response
  let out = cmd.output();
  if out.status != 0 {
    println!("{}", str::from_utf8(&out.stderr).unwrap());
  }

  return str::from_utf8(&out.stdout).unwrap().to_string();
}

The http_proxy command

The http_proxy command in send_email() takes three arguments via the chained arg() function calls.

  • The first argument is the HTTP method. It could be get or post.
  • The second argument is the URL to access.
  • The third argument is a JSON string for the HTTP headers. It is a JSON dictionary for header key and value pairs.

The HTTP request body is passed to the command via the cmd.stdin_u8vec() function call. You can put an arbitrary byte array in the HTTP request body. The cmd.output() function call executes the web request, and encapsulates the HTTP response in the return value out.

  • The out.stdout is the byte array of the HTTP response.
  • The out.stderr is the byte array of any error message http_proxy emits.

The send_email() function returns the HTTP response from the sendgrid API.

Try it out

First, build the function via the ssvmup tool.

$ ssvmup build

Upload the WebAssembly file to the FaaS and get a wasm_id to call later.

$ curl --location --request POST 'https://rpc.ssvm.secondstate.io:8081/api/executables' \
--header 'Content-Type: application/octet-stream' \
--header 'SSVM-Description: send email' \
--data-binary '@pkg/send_email_lib_bg.wasm'
{"wasm_id":151,"wasm_sha256":"0xec9e4c7d01920f...644bed9bf7922","SSVM_Usage_Key":"00000000-0000-0000-0000-000000000000","SSVM_Admin_Key":"b425089...8bfa58e6"}

Now you can call the function to send an email!

$ curl --location --request POST 'https://rpc.ssvm.secondstate.io:8081/api/run/151/send_email' \
--header 'Content-Type: text/plain' \
--data '{"from":"michael@secondstate.io", "token":"SG.xxx", "to":"juntao_yuan@yahoo.com", "subject":"This is a HTTP Proxy FaaS test", "mime":"text/plain", "body":"Hello Second State FaaS!"}'

The SG.xxx token is a sendgrid authorization token that is associated with the from email address. You can get a limited sendgrid plan for free.

Web UI

On a static web page, you can use JavaScript to make an AJAX call to this FaaS function. The JavaScript assembles a JSON object, posts it to the FaaS function, and displays the result.

Source code | Live demo

  jsonObj = {};
  jsonObj['from'] = $('#from').val();
  jsonObj['token'] = $('#token').val();
  jsonObj['to'] = $('#to').val();
  jsonObj['subject'] = $('#subject').val();
  jsonObj['mime'] = $('#mime').val();
  jsonObj['body'] = $('#body').val();

  $.ajax({
      url: "https://rpc.ssvm.secondstate.io:8081/api/run/151/send_email",
      type: "post",
      data : JSON.stringify(jsonObj),
      contentType: "text/plain",
      processData: false,
      success: function (data) {
        if (data) {
            $('#result').html(data);
        } else {
            $('#result').html("The email is sent to " + jsonObj['to'] + ". Please check your INBOX.");
        }
      }
  });

What’s next

The use of commands is a unique feature that makes the Second State VM extensible. It is very important for server-side applications. The Second State FaaS supports high performance AI services through the command API.

RustJavaScriptWebAssemblyNode.jsFaaSRust FaaSServerlesscloud computing
A high-performance, extensible, and hardware optimized WebAssembly Virtual Machine for automotive, cloud, AI, and blockchain applications