Documentation ¶
Overview ¶
Package vulcain helps implementing the Vulcain protocol (https://vulcain.rocks) in Go projects. It provides helper functions to parse HTTP requests containing "preload" and "fields" directives, to extract and push the relations of a JSON document matched by the "preload" directive, and to modify the JSON document according to both directives.
This package can be used in any HTTP handler as well as with httputil.ReverseProxy.
Example ¶
package main import ( "bytes" "fmt" "io" "log" "net/http" "net/http/httptest" "net/http/httputil" "net/url" "github.com/dunglas/vulcain" ) func main() { handler := http.NewServeMux() handler.Handle("/books.json", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") fmt.Fprintln(w, `{ "title": "1984", "genre": "dystopia", "author": "/authors/orwell.json" }`) })) handler.Handle("/authors/orwell.json", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") fmt.Fprintln(w, `{ "name": "George Orwell", "birthDate": "1903-06-25" }`) })) backendServer := httptest.NewServer(handler) defer backendServer.Close() rpURL, err := url.Parse(backendServer.URL) if err != nil { log.Fatal(err) } vulcain := vulcain.New() rpHandler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { r := req.WithContext(vulcain.CreateRequestContext(rw, req)) var wait bool defer func() { vulcain.Finish(r, wait) }() rp := httputil.NewSingleHostReverseProxy(rpURL) rp.ModifyResponse = func(resp *http.Response) error { if !vulcain.IsValidRequest(r) || !vulcain.IsValidResponse(r, resp.StatusCode, resp.Header) { return nil } newBody, err := vulcain.Apply(r, rw, resp.Body, resp.Header) if newBody == nil { return err } wait = true newBodyBuffer := bytes.NewBuffer(newBody) resp.Body = io.NopCloser(newBodyBuffer) return nil } rp.ErrorHandler = func(rw http.ResponseWriter, req *http.Request, err error) { wait = false } rp.ServeHTTP(rw, req) }) frontendProxy := httptest.NewServer(rpHandler) defer frontendProxy.Close() resp, err := http.Get(frontendProxy.URL + `/books.json?preload="/author"&fields="/title","/author"`) if err != nil { log.Fatal(err) } b, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } // Go's HTTP client doesn't support HTTP/2 Server Push yet, so a Link rel=preload is added as fallback // Browsers and other clients supporting Server Push will receive a push instead fmt.Printf("%v\n\n", resp.Header.Values("Link")) fmt.Printf("%s", b) }
Output: [</authors/orwell.json>; rel=preload; as=fetch] {"author":"/authors/orwell.json","title":"1984"}
Index ¶
- func NewServer(options *ServerOptions) *serverdeprecated
- func NewServerFromEnv() (*server, error)deprecated
- type Option
- type ServerOptionsdeprecated
- type Vulcain
- func (v *Vulcain) Apply(req *http.Request, rw http.ResponseWriter, responseBody io.Reader, ...) ([]byte, error)
- func (v *Vulcain) CreateRequestContext(rw http.ResponseWriter, req *http.Request) context.Context
- func (v *Vulcain) Finish(req *http.Request, wait bool)
- func (v *Vulcain) IsValidRequest(req *http.Request) bool
- func (v *Vulcain) IsValidResponse(req *http.Request, responseStatus int, responseHeaders http.Header) bool
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func NewServer
deprecated
func NewServer(options *ServerOptions) *server
NewServer creates a Vulcain server
Deprecated: use the Caddy server module or the standalone library instead
func NewServerFromEnv
deprecated
func NewServerFromEnv() (*server, error)
NewServerFromEnv creates a server using the configuration set in env vars
Deprecated: use the Caddy server module or the standalone library instead
Types ¶
type Option ¶
type Option func(o *opt)
Option instances allow to configure the library
func WithApiUrl ¶
func WithEarlyHints ¶
func WithEarlyHints() Option
WithEarlyHints instructs the gateway server to send Preload hints in 103 Early Hints response. Enabling this setting is usually useless because the gateway server doesn't supports JSON streaming yet, consequently the server will have to wait for the full JSON response to be received from upstream before being able to compute the Link headers to send. When the full response is available, we can send the final response directly. Better send Early Hints responses as soon as possible, directly from the upstream application. The proxy will forward them even if this option is not enabled.
func WithMaxPushes ¶
WithMaxPushes sets the maximum number of resources to push There is no limit by default
func WithOpenAPIFile ¶
WithOpenAPIFile sets the path to an OpenAPI definition (in YAML or JSON) documenting the relations between resources This option is only useful for non-hypermedia APIs
type ServerOptions
deprecated
type ServerOptions struct { Debug bool Addr string Upstream *url.URL EarlyHints bool MaxPushes int AcmeHosts []string AcmeCertDir string CertFile string KeyFile string ReadTimeout time.Duration WriteTimeout time.Duration Compress bool OpenAPIFile string }
ServerOptions stores the server's options
Deprecated: use the Caddy server module or the standalone library instead
func NewOptionsFromEnv
deprecated
func NewOptionsFromEnv() (*ServerOptions, error)
NewOptionsFromEnv creates a new option instance from environment It returns an error if mandatory env env vars are missing
Deprecated: use the Caddy server module or the standalone library instead
type Vulcain ¶
type Vulcain struct {
// contains filtered or unexported fields
}
Vulcain is the entrypoint of the library Use New() to create an instance
func (*Vulcain) Apply ¶
func (v *Vulcain) Apply(req *http.Request, rw http.ResponseWriter, responseBody io.Reader, responseHeaders http.Header) ([]byte, error)
Apply pushes the requested relations, modifies the response headers and returns a modified response to send to the client. It's the responsibility of the caller to use the updated response body. Apply must not be called if IsValidRequest or IsValidResponse return false.
func (*Vulcain) CreateRequestContext ¶
CreateRequestContext assign the waitPusher used by other functions to the request context. CreateRequestContext must always be called first.
func (*Vulcain) Finish ¶
Finish cleanups the waitPusher and, if it's the explicit response, waits for all PUSH_PROMISEs to be sent before returning. Finish must always be called, even if IsValidRequest or IsValidResponse returns false. If the current response is the explicit one and wait is false, then the body is sent instantly, even if all PUSH_PROMISEs haven't been sent yet.
func (*Vulcain) IsValidRequest ¶
IsValidRequest tells if this request contains at least one Vulcain directive. IsValidRequest must always be called before Apply.