Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement header_upstream directive for #4, docs #8

Merged
merged 3 commits into from Sep 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Expand Up @@ -51,6 +51,7 @@ awslambda <path-prefix> {
name_append string to append to function name
single name of a single lambda function to invoke
strip_path_prefix If true, path and function name are stripped from the path
header_upstream header-name header-value
}
```

Expand All @@ -64,6 +65,13 @@ awslambda <path-prefix> {
* **name_append** is an optional string to append to the function name parsed from the URL before invoking the Lambda.
* **single** is an optional function name. If set, function name is not parsed from the URI path.
* **strip_path_prefix** If 'true', path and function name is stripped from the path sent as request metadata to the Lambda function. (default=false)
* **header_upstream** Inject "header" key-value pairs into the upstream request json. Supports usage of [caddyfile placeholders](https://caddyserver.com/docs/placeholders). Can be used multiple times. Comes handy with frameworks like express. Example:
```
header_upstream X-API-Secret super1337secretapikey
header_upstream X-Forwarded-For {remote}
header_upstream X-Forwarded-Host {hostonly}
header_upstream X-Forwarded-Proto {scheme}
```

Function names are parsed from the portion of request path following the path-prefix in the
directive based on this convention: `[path-prefix]/[function-name]/[extra-path-info]` unless `single` attribute is set.
Expand Down Expand Up @@ -277,6 +285,7 @@ recompile caddy with the plugin installed:

```bash
go get github.com/mholt/caddy/caddy
go get github.com/caddyserver/builds
cd $GOPATH/src/github.com/mholt/caddy/caddy
```

Expand Down
22 changes: 22 additions & 0 deletions config.go
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/lambda"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
)

// Config specifies configuration for a single awslambda block
Expand Down Expand Up @@ -55,6 +56,9 @@ type Config struct {
// the RequestMeta.Path would be /foo
StripPathPrefix bool

// headers to set in the upstream "headers" array - caddy placeholders work here
UpstreamHeaders map[string][]string

invoker Invoker
}

Expand Down Expand Up @@ -151,6 +155,18 @@ func (c *Config) MaybeToInvokeInput(r *http.Request) (*lambda.InvokeInput, error
req.Meta.Path = c.stripPathPrefix(req.Meta.Path, funcName)
}

// inject upstream headers defined with the header_upstream directive into req.Meta.Headers
// uses caddy's integrated replacer for placeholder replacement (https://caddyserver.com/docs/placeholders)
replInt := r.Context().Value(httpserver.ReplacerCtxKey)
replacer := replInt.(httpserver.Replacer)
for k, v := range c.UpstreamHeaders {
newValue := make([]string, len(v))
for i, v := range v {
newValue[i] = replacer.Replace(v)
}
req.Meta.Headers[strings.ToLower(k)] = newValue
}

payload, err := json.Marshal(req)
if err != nil {
return nil, err
Expand Down Expand Up @@ -240,6 +256,12 @@ func ParseConfigs(c *caddy.Controller) ([]*Config, error) {
case "exclude":
conf.Exclude = append(conf.Exclude, val)
conf.Exclude = append(conf.Exclude, c.RemainingArgs()...)
case "header_upstream":
if conf.UpstreamHeaders == nil {
conf.UpstreamHeaders = make(map[string][]string)
}
value := strings.Join(c.RemainingArgs(), " ")
conf.UpstreamHeaders[val] = []string{value}
default:
last = val
}
Expand Down
15 changes: 15 additions & 0 deletions config_test.go
Expand Up @@ -2,6 +2,7 @@ package awslambda

import (
"bytes"
"context"
"io"
"net/http"
"reflect"
Expand All @@ -11,6 +12,7 @@ import (
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/service/lambda"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
)

func TestAcceptsFunction(t *testing.T) {
Expand Down Expand Up @@ -120,6 +122,7 @@ func TestParseConfigs(t *testing.T) {
name_append _suffix_here
single my-single-func
strip_path_prefix on
header_upstream x-real-ip {remote}
}`,
[]*Config{
&Config{
Expand All @@ -134,6 +137,9 @@ func TestParseConfigs(t *testing.T) {
NameAppend: "_suffix_here",
Single: "my-single-func",
StripPathPrefix: true,
UpstreamHeaders: map[string][]string{
"x-real-ip": []string{"{remote}"},
},
},
},
},
Expand Down Expand Up @@ -207,6 +213,10 @@ func TestMaybeToInvokeInput(t *testing.T) {
NamePrepend: "before-",
NameAppend: "-after",
Qualifier: "prod",
UpstreamHeaders: map[string][]string{
"x-real-proto": []string{"{proto}"},
"x-real-method": []string{"{method}"},
},
}
input, err := c.MaybeToInvokeInput(r1)
if err != nil {
Expand All @@ -220,6 +230,8 @@ func TestMaybeToInvokeInput(t *testing.T) {
if err != nil {
t.Fatalf("NewRequest returned err: %v", err)
}
req.Meta.Headers["x-real-proto"] = []string{"HTTP/1.1"}
req.Meta.Headers["x-real-method"] = []string{"PUT"}
expected := lambda.InvokeInput{
FunctionName: &funcName,
Qualifier: &c.Qualifier,
Expand Down Expand Up @@ -290,5 +302,8 @@ func mustNewRequest(method, path string, body io.Reader) *http.Request {
if err != nil {
panic(err)
}
replacer := httpserver.NewReplacer(req, nil, "")
newContext := context.WithValue(req.Context(), httpserver.ReplacerCtxKey, replacer)
req = req.WithContext(newContext)
return req
}
5 changes: 1 addition & 4 deletions lambda_test.go
Expand Up @@ -19,10 +19,7 @@ func TestInvokeOK(t *testing.T) {
},
}
h := initHandler(invoker)
r, err := http.NewRequest("POST", "/lambda-test/foo", bytes.NewBufferString("hi"))
if err != nil {
t.Fatalf("Failed to create request: %v", err)
}
r := mustNewRequest("POST", "/lambda-test/foo", bytes.NewBufferString("hi"))
w := httptest.NewRecorder()

status, err := h.ServeHTTP(w, r)
Expand Down