Skip to content

Commit

Permalink
Prototype
Browse files Browse the repository at this point in the history
  • Loading branch information
dunglas committed Oct 8, 2019
1 parent 8336eea commit b323187
Show file tree
Hide file tree
Showing 12 changed files with 301 additions and 62 deletions.
3 changes: 2 additions & 1 deletion .env
Expand Up @@ -6,4 +6,5 @@ ADDR=:3000
ALLOW_ANONYMOUS=1
CERT_FILE=fixtures/tls/server.crt
KEY_FILE=fixtures/tls/server.key
#LOG_FORMAT=JSON
#LOG_FORMAT=JSON
OPENAPI_FILE=fixtures/openapi.yaml
87 changes: 87 additions & 0 deletions fixtures/openapi.yaml
@@ -0,0 +1,87 @@
openapi: 3.0.0
info:
title: Vulcain demo
version: '1.0'
description: ''
paths:
/oa/books.json:
get:
summary: book list
tags: []
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
member:
type: array
items:
type: integer
'/oa/books/{id}':
parameters:
- schema:
type: integer
name: id
in: path
required: true
get:
summary: book item
tags: []
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/book'
links:
author:
# TODO: operationRef isn't supported yet
#operationRef: '#/paths/~1oa~1authors~1{id}/get'
operationId: getAuthor
parameters:
id: '$response.body#/author'
'/oa/authors/{id}':
parameters:
- type: string
name: id
in: path
required: true
schema:
type: integer
get:
operationId: getAuthor
summary: author item
tags: []
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/author'
components:
schemas:
author:
title: author
type: object
properties:
id:
type: integer
name:
type: string
book:
title: book
type: object
properties:
id:
type: integer
title:
type: string
description:
type: string
author:
type: integer
77 changes: 56 additions & 21 deletions gateway/gateway.go
Expand Up @@ -18,10 +18,10 @@ var jsonRe = regexp.MustCompile(`(?i)\bjson\b`)

// Gateway is the main struct
type Gateway struct {
options *options
server *http.Server
pushers *pushers
openAPIRouter *openapi3filter.Router
options *Options
server *http.Server
pushers *pushers
openAPI *openAPI
}

func addToVary(r *http.Response, header string) {
Expand Down Expand Up @@ -86,21 +86,45 @@ func (g *Gateway) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
tree.importPointers(Fields, query["fields"])
}

var route *openapi3filter.Route
newBody := traverseJSON(currentBody, tree, useFieldsHeader || useFieldsQuery, func(u *url.URL, n *node) {
if g.openAPIRouter != nil && route == nil {
route, _, err = g.openAPIRouter.FindRoute("GET", req.URL)
var openAPIroute *openapi3filter.Route
openAPIrouteTested := g.openAPI == nil
newBody := traverseJSON(currentBody, tree, useFieldsHeader || useFieldsQuery, func(n *node, v string) string {
var (
u *url.URL
uStr string
useOpenAPI bool
newValue string
)

if !openAPIrouteTested {
openAPIroute = g.openAPI.getRoute(req.URL)
openAPIrouteTested = true
}
if openAPIroute != nil {
if rel := g.openAPI.getRelation(openAPIroute, n.String(), v); rel != "" {
if u, err = parseRelation(n, rel); err != nil {
useOpenAPI = true
}
}
}

if usePreloadQuery || useFieldsQuery {
if u == nil {
if u, err = parseRelation(n, v); err != nil {
return ""
}
}

// Never rewrite values when using OpenAPI, use header instead of query parameters
if (usePreloadQuery || useFieldsQuery) && !useOpenAPI {
urlRewriter(u, n)
newValue = u.String()
}

if !usePreloadHeader && !usePreloadQuery {
return
return newValue
}

uStr := u.String()
uStr = u.String()
// TODO: allow to disable Server Push from the config
if !u.IsAbs() && pusher != nil {
pushOptions := &http.PushOptions{Header: req.Header}
Expand All @@ -127,12 +151,12 @@ func (g *Gateway) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
err := pusher.Push(uStr, pushOptions)
if err == nil {
log.WithFields(log.Fields{"relation": uStr}).Debug("Relation pushed")
return
return newValue
}
log.WithFields(log.Fields{"relation": uStr, "reason": err.Error()}).Debug("Failed to push")
if _, ok := err.(*relationAlreadyPushedError); ok {
// Don't add the preload header for something already pushed
return
return newValue
}
}

Expand All @@ -141,6 +165,8 @@ func (g *Gateway) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
// TODO: send 103 early hints responses (https://tools.ietf.org/html/rfc8297)
r.Header.Add("Link", "<"+uStr+">; rel=preload; as=fetch")
log.WithFields(log.Fields{"relation": uStr}).Debug("Link preload header added")

return newValue
})

if useFieldsHeader {
Expand Down Expand Up @@ -220,21 +246,30 @@ func NewGatewayFromEnv() (*Gateway, error) {
}

// NewGateway creates a Vulcain gateway instance
<<<<<<< HEAD
func NewGateway(options *options) *Gateway {
=======
func NewGateway(options *Options) *Gateway {
var router *openapi3filter.Router

var o *openAPI
if options.OpenAPIFile != "" {
router = openapi3filter.NewRouter().WithSwaggerFromFile(options.OpenAPIFile)
o = newOpenAPI(options.OpenAPIFile)
}

>>>>>>> wip
return &Gateway{
options,
nil,
&pushers{pusherMap: make(map[string]*waitPusher)},
router,
o,
}
}

func parseRelation(n *node, r string) (*url.URL, error) {
u, err := url.Parse(r)
if err != nil {
log.WithFields(
log.Fields{
"node": n.String(),
"relation": r,
"reason": err,
}).Debug("The URL generated using the OpenAPI specification is invalid")
}

return u, err
}
6 changes: 3 additions & 3 deletions gateway/gateway_test.go
Expand Up @@ -14,7 +14,7 @@ import (
)

func TestNewGateway(t *testing.T) {
g := NewGateway(&options{})
g := NewGateway(&Options{})
assert.NotNil(t, g)
}

Expand All @@ -34,7 +34,7 @@ func createServers() (*httptest.Server, *httptest.Server) {
upstream := httptest.NewServer(&api.JSONLDHandler{})

upstreamURL, _ := url.Parse(upstream.URL)
g := NewGateway(&options{Upstream: upstreamURL})
g := NewGateway(&Options{Upstream: upstreamURL})
gateway := httptest.NewServer(g)

return upstream, gateway
Expand Down Expand Up @@ -115,7 +115,7 @@ func TestUpstreamError(t *testing.T) {
hook := test.NewGlobal()

upstreamURL, _ := url.Parse("https://notexist")
g := NewGateway(&options{Upstream: upstreamURL})
g := NewGateway(&Options{Upstream: upstreamURL})
gateway := httptest.NewServer(g)
defer gateway.Close()

Expand Down
22 changes: 18 additions & 4 deletions gateway/json_pointer.go
Expand Up @@ -8,6 +8,7 @@ type node struct {
preload bool
fields bool
path string
parent *node
children []*node
}

Expand All @@ -24,13 +25,25 @@ const (
func (n *node) importPointers(t Type, pointers []string) {
for _, pointer := range pointers {
pointer = strings.Trim(pointer, "/")
if pointer == "" {
continue
if pointer != "" {
partsToTree(t, strings.Split(pointer, "/"), n)
}
}
}

parts := strings.Split(pointer, "/")
partsToTree(t, parts, n)
func (n *node) String() string {
if n.parent == nil {
return "/"
}

s := n.path
c := n.parent
for c != nil {
s = c.path + "/" + s
c = c.parent
}

return s
}

func partsToTree(t Type, parts []string, root *node) {
Expand All @@ -49,6 +62,7 @@ func partsToTree(t Type, parts []string, root *node) {
if child == nil {
child = &node{}
child.path = parts[0]
child.parent = root
root.children = append(root.children, child)
}

Expand Down
11 changes: 11 additions & 0 deletions gateway/json_pointer_test.go
Expand Up @@ -19,3 +19,14 @@ func TestImportPointers(t *testing.T) {
assert.Equal(t, []string{"/foo/*", "/bar/foo/*/baz"}, n.strings(Preload, ""))
assert.Equal(t, []string{"/foo/bat", "/baz/*"}, n.strings(Fields, ""))
}

func TestString(t *testing.T) {
n := &node{}
n.importPointers(Preload, []string{"/foo", "/bar/foo", "/foo/*", "/bar/foo/*/baz"})

assert.Equal(t, "/", n.String())
assert.Equal(t, "/foo", n.children[0].String())
assert.Equal(t, "/bar/foo", n.children[1].children[0].String())
assert.Equal(t, "/foo/*", n.children[0].children[0].String())
assert.Equal(t, "/bar/foo/*/baz", n.children[1].children[0].children[0].children[0].String())
}

0 comments on commit b323187

Please sign in to comment.