Documentation
¶
Overview ¶
Package outbound calls outside services. Client returns an *http.Client whose requests go to the runner, which sends each one only to a host that egress in the permission list names:
egress:
- api.weather.example
- {host: api.partner.example, secret: partner_api_key, header: Authorization, prefix: "Bearer "}
A host bound to a secret gets the secret's value, after the prefix, in the header of every request; the app never reads the key, and secrets.Get of it is E-RUN-015.
Rules:
- Build each request with http.NewRequestWithContext and the context of the request or job it serves; a request without the app's context is E-RUN-004.
- Only https to port 443 (E-OUT-002) of a host that egress lists (E-OUT-001). The platform's own hosts and private networks are never reachable (E-OUT-003).
- A request body and a response body are at most 10 MiB each (E-OUT-004, E-OUT-005). The runner ends a request after 30 s with E-OUT-006; the client itself gives up 5 s later, only when the runner does not answer. An instance runs at most 16 requests at once (E-OUT-007).
- Redirects are not followed: a 3xx response comes back with its Location.
- Any other HTTP client, such as http.Get or an http.Client of the app's own, is E-LINT-009.
A refusal comes back as a coded error, which the response's error wraps. A new outside service is a change of the permission list, and a new key binding always needs approval.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Client ¶
Client returns a client whose requests go to the runner, which sends them to the outside service. Each request must carry the context of the request or job it serves, as http.NewRequestWithContext gives it. The client follows no redirect: a 3xx response comes back with its Location. The runner ends a request after 30 s with E-OUT-006; the client gives up 5 s later, on a runner that does not answer.
Example ¶
Call an outside service that egress in the permission list names, from a method of the app's Deps, with the context of the request or job the call serves. The example only compiles: it needs the runner of a running app.
package main
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"aicoded.dev/framework/outbound"
)
// Deps is what an app's pages share, as deps.Deps is in an app. It keeps the client in an
// unexported field: aicoded check refuses a value that holds a client in an exported field when
// it becomes an interface value, as each page's data provider does in the generated code
// (E-LINT-009).
type Deps struct {
weather *http.Client
}
// New returns Deps with the client whose requests go through the runner.
func New() *Deps {
return &Deps{weather: outbound.Client()}
}
// Today returns the weather service's summary of today in city. ctx is the context of the
// request or job it serves. The service's host, api.weather.example, is in egress in the
// permission list, and the runner adds the key that egress binds to it.
func (d *Deps) Today(ctx context.Context, city string) (string, error) {
u := "https://api.weather.example/v1/today?city=" + url.QueryEscape(city)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, http.NoBody)
if err != nil {
return "", err
}
resp, err := d.weather.Do(req)
if err != nil {
return "", err
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("the weather service answered %s", resp.Status)
}
var today struct{ Summary string }
if err := json.NewDecoder(resp.Body).Decode(&today); err != nil {
return "", err
}
return today.Summary, nil
}
// Call an outside service that egress in the permission list names, from a method of the app's
// Deps, with the context of the request or job the call serves. The example only compiles: it
// needs the runner of a running app.
func main() {
_ = New().Today
}
Output:
Types ¶
This section is empty.