Files
fn-serverless/docs/function-format.md
Reed Allman 20089c4e83 make headers quasi-consistent (#660)
possible breakages:

* `FN_HEADER` on cold are no longer `s/-/_/` -- this is so that cold functions
can rebuild the headers as they were when they came in on the request (fdks,
specifically), there's no guarantee that a reversal `s/_/-/` is the original
header on the request.
* app and route config no longer `s/-/_/` -- it seemed really weird to rewrite
the users config vars on these. should just pass them exactly as is to env.
* headers no longer contain the environment vars (previously, base config; app
config, route config, `FN_PATH`, etc.), these are still available in the
environment.

this gets rid of a lot of the code around headers, specifically the stuff that
shoved everything into headers when constructing a call to begin with. now we
just store the headers separately and add a few things, like FN_CALL_ID to
them, and build a separate 'config' now to store on the call. I thought
'config' was more aptly named, 'env' was confusing, though now 'config' is
exactly what 'base_vars' was, which is only the things being put into the env.
we weren't storing this field in the db, this doesn't break unless there are
messages in a queue from another version, anyway, don't think we're there and
don't expect any breakage for anybody with field name changes.

this makes the configuration stuff pretty straight forward, there's just two
separate buckets of things, and cold just needs to mash them together into the
env, and otherwise hot containers just need to put 'config' in the env, and then
hot format can shove 'headers' in however they'd like. this seems better than
my last idea about making this easier but worse (RIP).

this means:

* headers no longer contain all vars, the set of base vars can only be found
in the environment.
* headers is only the headers from request + call_id, deadline, method, url
* for cold, we simply add the headers to the environment, prepending
`FN_HEADER_` to them, BUT NOT upper casing or `s/-/_/`
* fixes issue where async hot functions would end up with `Fn_header_`
prefixed headers
* removes idea of 'base' vars and 'env'. this was a strange concept. now we just have
'config' which was base vars, and headers, which was base_env+headers; i.e.
they are disjoint now.
* casing for all headers will lean to be `My-Header` style, which should help
with consistency. notable exceptions for cold only are FN_CALL_ID, FN_METHOD,
and FN_REQUEST_URL -- this is simply to avoid breakage, in either hot format
they appear as `Fn_call_id` still.
* removes FN_PARAM stuff
* updated doc with behavior

weird things left:

`Fn_call_id` e.g. isn't a correctly formatted http header, it should likely be
`Fn-Call-Id` but I wanted to live to fight another day on this one, it would
add some breakage.

examples to be posted of each format below

closes #329
2018-01-09 10:08:30 -08:00

5.1 KiB

Open Function Format

This document will describe the details of how a function works, inputs/outputs, etc.

I/O Formats

STDIN and Environment Variables

While wanting to keep things simple, flexible and expandable, we decided to go back to the basics, using Unix input and output. Standard in is easy to use in any language and doesn't require anything extra. It also allows streaming input so we can do things like keeping a container running some time and stream requests into the container.

Configuration values, environment information and other things will be passed in through environment variables.

The goals of the I/O format are the following:

  • Very easy to use and parse
  • Supports hot for increasing performance (more than one call per container execution)
  • Ability to build higher level abstractions on top (ie: Lambda syntax compatible)

The format is still up for discussion and in order to move forward and remain flexible, it's likely we will just allow different I/O formats and the function creator can decide what they want, on a per function basis. Default being the simplest format to use.

TODO: Put common env vars here, that show up in all formats.

Default I/O Format

The default format is simply the request body itself plus some environment variables. For instance, if someone were to post a JSON body, the unmodified body would be sent in via STDIN. The result comes via STDOUT. When task is done, pipes are closed and the container running the function is terminated.

Pros/Cons

Pros:

  • Very simple to use

Cons:

  • Not very efficient resource utilization - one function for one event.

HTTP I/O Format

format: http

HTTP format could be a good option as it is in very common use obviously, most languages have some semi-easy way to parse it, and it supports hot format. The response will look like a HTTP response. The communication is still done via stdin/stdout, but these pipes are never closed unless the container is explicitly terminated. The basic format is:

Request

GET / HTTP/1.1
Content-Length: 5

world

Response

HTTP/1.1 200 OK
Content-Length: 11

hello world

The header keys and values will be populated with information about the function call such as the request URL and query parameters, in addition to any headers sent in the request to invoke the function itself. The additional headers are:

  • Fn_deadline - RFC3339 time stamp of the expiration (deadline) date of function execution.
  • Fn_request_url - the full URL for the request (parsing example)
  • Fn_call_id - a unique ID for each function execution.
  • Fn_method - the HTTP method used to invoke
  • $X - the HTTP headers that were set for this request, exactly as they were sent in the request.

HTTP Headers will not be populated with app config, route config or any of the following, that may be found in the environment instead:

  • FN_APP_NAME
  • FN_PATH
  • FN_METHOD
  • FN_FORMAT
  • FN_MEMORY
  • FN_TYPE

Pros/Cons

Pros:

  • Supports streaming
  • Common format

Cons:

  • Requires a parsing library or fair amount of code to parse headers properly
  • Double parsing - headers + body (if body is to be parsed, such as json)

JSON I/O Format

format: json

The JSON format is a nice hot format as it is easy to parse in most languages.

If a request comes in like this:

{
  "some": "input"
}

Input

Internally functions receive data in the example format below:

{
  "call_id": "123",
  "content_type": "application/json",
  "body": "{\"some\":\"input\"}",
  "protocol": {
    "type": "http",
    "request_url": "http://localhost:8080/r/myapp/myfunc?q=hi",
    "headers": {
      "Content-Type": ["application/json"],
      "Other-Header": ["something"]
    }
  }
}
BLANK LINE
{ 
  NEXT INPUT OBJECT
}
  • call_id - the unique ID for the call.
  • content_type - format of the body parameter.
  • protocol - arbitrary map of protocol specific data. The above example shows what the HTTP protocol handler passes in. Subject to change and reduces reusability of your functions. USE AT YOUR OWN RISK.

Under protocol, headers contain Default format environment variables listed in Inputs

Each request will be separated by a blank line.

Output

Function's output format should have the following format:

{
  "body": "{\"some\":\"output\"}",
  "content_type": "application/json",
  "protocol": {
    "status_code": 200,
    "headers": {
      "Other-Header": ["something"]
    }
  }
}
BLANK LINE
{
  NEXT OUTPUT OBJECT
}
  • body - required - the response body.
  • content_type - optional - format of body. Default is application/json.
  • protocol - optional - protocol specific response options. Entirely optional. Contents defined by each protocol.

Pros/Cons

Pros:

  • Supports hot format
  • Easy to parse

Cons:

  • Not streamable

Logging

Standard error is reserved for logging, like it was meant to be. Anything you output to STDERR will show up in the logs. And if you use a log collector like logspout, you can collect those logs in a central location. See logging.