Skip to content

Commit

Permalink
Refactored Subscription model (#429)
Browse files Browse the repository at this point in the history
  • Loading branch information
mthenw authored May 25, 2018
1 parent d710407 commit e4360c6
Show file tree
Hide file tree
Showing 17 changed files with 722 additions and 809 deletions.
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,9 @@ curl --request POST \
--url http://localhost:4001/v1/spaces/default/subscriptions \
--header 'content-type: application/json' \
--data '{
"type": "async",
"eventType": "user.created",
"functionId": "sendEmail",
"event": "user.created",
"path": "/myteam"
}'
```
Expand All @@ -187,7 +188,8 @@ curl --request POST \
```javascript
const eventGateway = new EventGateway({ url: 'http://localhost' })
eventGateway.subscribe({
event: 'user.created',
type: 'async',
eventType: 'user.created',
functionId: 'sendEmail',
path: '/myteam'
})
Expand Down Expand Up @@ -238,8 +240,9 @@ curl --request POST \
--url http://localhost:4001/v1/spaces/default/subscriptions \
--header 'content-type: application/json' \
--data '{
"type": "sync",
"eventType": "http.request",
"functionId": "listUsers",
"event": "http",
"method": "GET",
"path": "/users"
}'
Expand All @@ -252,8 +255,9 @@ curl --request POST \
```javascript
const eventGateway = new EventGateway({ url: 'http://localhost' })
eventGateway.subscribe({
type: 'sync',
eventType: 'http.request',
functionId: 'listUsers',
event: 'http',
method: 'GET',
path: '/users'
})
Expand Down
1 change: 0 additions & 1 deletion cmd/event-gateway/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ func main() {
service := &eventgateway.Service{
FunctionStore: intstore.NewPrefixed("/serverless-event-gateway/functions", kvstore),
SubscriptionStore: intstore.NewPrefixed("/serverless-event-gateway/subscriptions", kvstore),
EndpointStore: intstore.NewPrefixed("/serverless-event-gateway/endpoints", kvstore),
Log: log,
}

Expand Down
71 changes: 38 additions & 33 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ The MIME type of the data block can be specified using the `Content-Type` header
to. In case of `application/json` type the event gateway passes JSON payload to the target functions. In any other case
the data block is base64 encoded.

#### HTTP Event
#### HTTP Request Event

`http` event is a built-in type of event occurring for HTTP requests on paths defined in HTTP subscriptions. The
`data` field of an `http` event has the following structure:
`http.request` event is a built-in type of event occurring for HTTP requests on paths defined in HTTP subscriptions. The
`data` field of an `http.request` event has the following structure:

* `path` - `string` - request path
* `method` - `string` - request method
Expand Down Expand Up @@ -100,7 +100,7 @@ HTTP subscription response depends on [response object](#respond-to-an-http-even

##### CORS

By default cross-origin resource sharing (CORS) is disabled for `http` subscriptions. It can be enabled and configured
By default cross-origin resource sharing (CORS) is disabled for `sync` subscriptions. It can be enabled and configured
per-subscription basis.

Event Gateway handles preflight `OPTIONS` requests for you. You don't need to setup subscription for `OPTIONS` method
Expand All @@ -124,7 +124,7 @@ Special type of path parameter is wildcard parameter. It's a path segment prefix
be specified at the end of the path and will match every character till the end of the path. For examples
parameter `/users/*userpath` for request path `/users/group1/user1` will match `group1/user1` as a `userpath` parameter.

#### Respond to an HTTP Event
#### Respond to an HTTP Request Event

To respond to an HTTP event a function needs to return object with following fields:

Expand All @@ -136,7 +136,7 @@ Currently, the event gateway supports only string responses.

### CORS

Events API supports CORS requests which means that any origin can emit a custom event. In case of `http` events CORS is
Events API supports CORS requests which means that any origin can emit a custom event. In case of `sync` subscriptions CORS is
configured per-subscription basis.

## Configuration API
Expand Down Expand Up @@ -304,11 +304,12 @@ JSON object:

**Request**

* `event` - `string` - event name
* `type` - `string` - subscription type, `sync` or `async`
* `eventType` - `string` - event type
* `functionId` - `string` - ID of function to receive events
* `path` - `string` - optional, URL path under which events (HTTP requests) are accepted, default: `/`
* `method` - `string` - required for `http` event, HTTP method that accepts requests
* `cors` - `object` - optional, in case of `http` event, By default CORS is disabled. When set to empty object CORS configuration will use default values for all fields below. Available fields:
* `method` - `string` - optional, HTTP method that accepts requests, default: `POST`
* `cors` - `object` - optional, by default CORS is disabled for `sync` subscriptions. When set to empty object CORS configuration will use default values for all fields below. Available fields:
* `origins` - `array` of `string` - list of allowed origins. An origin may contain a wildcard (\*) to replace 0 or more characters (i.e.: http://\*.domain.com), default: `*`
* `methods` - `array` of `string` - list of allowed methods, default: `HEAD`, `GET`, `POST`
* `headers` - `array` of `string` - list of allowed headers, default: `Origin`, `Accept`, `Content-Type`
Expand All @@ -325,11 +326,12 @@ JSON object:

* `space` - `string` - space name
* `subscriptionId` - `string` - subscription ID
* `event` - `string` - event name
* `type` - `string` - subscription type
* `eventType` - `string` - event type
* `functionId` - function ID
* `method` - `string` - optional, in case of `http` event, HTTP method that accepts requests
* `path` - `string` - optional, in case of `http` event, path that accepts requests, starts with `/`
* `cors` - `object` - optional, in case of `http` event, CORS configuration
* `method` - `string` - HTTP method that accepts requests
* `path` - `string` - path that accepts requests, starts with `/`
* `cors` - `object` - optional, CORS configuration

---

Expand All @@ -341,13 +343,14 @@ JSON object:

**Request**

_Note that `event`, `functionId`, `path`, and `method` may not be updated in an UpdateSubscription call._
_Note that `type`, `eventType`, `functionId`, `path`, and `method` may not be updated in an UpdateSubscription call._

* `event` - `string` - event name
* `type` - `string` - subscription type, `sync` or `async`
* `eventType` - `string` - event type
* `functionId` - `string` - ID of function to receive events
* `path` - `string` - optional, URL path under which events (HTTP requests) are accepted, default: `/`
* `method` - `string` - required for `http` event, HTTP method that accepts requests
* `cors` - `object` - optional, in case of `http` event, By default CORS is disabled. When set to empty object CORS configuration will use default values for all fields below. Available fields:
* `method` - `string` - optional, HTTP method that accepts requests, default: `POST`
* `cors` - `object` - optional, by default CORS is disabled for `sync` subscriptions. When set to empty object CORS configuration will use default values for all fields below. Available fields:
* `origins` - `array` of `string` - list of allowed origins. An origin may contain a wildcard (\*) to replace 0 or more characters (i.e.: http://\*.domain.com), default: `*`
* `methods` - `array` of `string` - list of allowed methods, default: `HEAD`, `GET`, `POST`
* `headers` - `array` of `string` - list of allowed headers, default: `Origin`, `Accept`, `Content-Type`
Expand All @@ -365,11 +368,12 @@ JSON object:

* `space` - `string` - space name
* `subscriptionId` - `string` - subscription ID
* `event` - `string` - event name
* `type` - `string` - subscription type
* `eventType` - `string` - event type
* `functionId` - function ID
* `method` - `string` - optional, in case of `http` event, HTTP method that accepts requests
* `path` - `string` - optional, in case of `http` event, path that accepts requests, starts with `/`
* `cors` - `object` - optional, in case of `http` event, CORS configuration
* `method` - `string` - HTTP method that accepts requests
* `path` - `string` - path that accepts requests, starts with `/`
* `cors` - `object` - optional, CORS configuration

---

Expand Down Expand Up @@ -403,13 +407,14 @@ Status code:
JSON object:

* `subscriptions` - `array` of `object` - subscriptions
* `space` - `string` - space name
* `space` - `string` - space name
* `subscriptionId` - `string` - subscription ID
* `event` - `string` - event name
* `type` - `string` - subscription type
* `eventType` - `string` - event type
* `functionId` - function ID
* `method` - `string` - optional, in case of `http` event, HTTP method that accepts requests
* `path` - `string` - optional, in case of `http` event, path that accepts requests
* `cors` - `object` - optional, in case of `http` event, CORS configuration
* `method` - `string` - HTTP method that accepts requests
* `path` - `string` - path that accepts requests, starts with `/`
* `cors` - `object` - optional, CORS configuration

#### Get Subscription

Expand All @@ -426,14 +431,14 @@ Status code:

JSON object:

* `subscriptions` - `array` of `object` - subscriptions
* `space` - `string` - space name
* `subscriptionId` - `string` - subscription ID
* `event` - `string` - event name
* `functionId` - function ID
* `method` - `string` - optional, in case of `http` event, HTTP method that accepts requests
* `path` - `string` - optional, in case of `http` event, path that accepts requests
* `cors` - `object` - optional, in case of `http` event, CORS configuration
* `subscriptionId` - `string` - subscription ID
* `type` - `string` - subscription type
* `eventType` - `string` - event type
* `functionId` - function ID
* `method` - `string` - HTTP method that accepts requests
* `path` - `string` - path that accepts requests, starts with `/`
* `cors` - `object` - optional, CORS configuration

### Prometheus Metrics

Expand Down
25 changes: 18 additions & 7 deletions docs/openapi/openapi-config-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,11 @@ components:
type: string
SubscriptionID:
type: string
SubscriptionType:
type: string
enum:
- async
- sync
ProviderType:
type: string
description: "function provider"
Expand Down Expand Up @@ -275,10 +280,12 @@ components:
$ref: '#/components/schemas/SpaceName'
subscriptionId:
$ref: '#/components/schemas/SubscriptionID'
type:
$ref: '#/components/schemas/SubscriptionType'
eventType:
$ref: '#/components/schemas/EventType'
functionId:
$ref: '#/components/schemas/FunctionID'
event:
$ref: '#/components/schemas/Event'
path:
$ref: '#/components/schemas/Path'
method:
Expand Down Expand Up @@ -377,7 +384,7 @@ components:
type: string
format: url
description: "HTTP endpoint URL"
Event:
EventType:
type: string
description: "event type"
Method:
Expand Down Expand Up @@ -481,10 +488,12 @@ components:
schema:
type: object
properties:
type:
$ref: '#/components/schemas/SubscriptionType'
eventType:
$ref: '#/components/schemas/EventType'
functionId:
$ref: '#/components/schemas/FunctionID'
event:
$ref: '#/components/schemas/Event'
path:
$ref: '#/components/schemas/Path'
method:
Expand All @@ -498,10 +507,12 @@ components:
schema:
type: object
properties:
type:
$ref: '#/components/schemas/SubscriptionType'
eventType:
$ref: '#/components/schemas/EventType'
functionId:
$ref: '#/components/schemas/FunctionID'
event:
$ref: '#/components/schemas/Event'
path:
$ref: '#/components/schemas/Path'
method:
Expand Down
6 changes: 3 additions & 3 deletions event/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ import (
type Type string

const (
// TypeHTTP is a special type of event for sync http subscriptions.
TypeHTTP = Type("http")
// TypeHTTPRequest is a special type of event for sync http subscriptions.
TypeHTTPRequest = Type("http.request")

// TransformationVersion is indicative of the revision of how Event Gateway transforms a request into CloudEvents format.
TransformationVersion = "0.1"
Expand Down Expand Up @@ -107,7 +107,7 @@ func FromRequest(r *http.Request) (*Event, error) {
return New(Type(r.Header.Get("event")), mimeType, body), nil
}

return New(TypeHTTP, mimeCloudEventsJSON, NewHTTPRequestData(r, body)), nil
return New(TypeHTTPRequest, mimeCloudEventsJSON, NewHTTPRequestData(r, body)), nil
}

// Validate Event struct
Expand Down
2 changes: 1 addition & 1 deletion event/event_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ var fromRequestTests = []struct {
},
requestBody: []byte("hey there"),
expectedEvent: &eventpkg.Event{
EventType: eventpkg.TypeHTTP,
EventType: eventpkg.TypeHTTPRequest,
CloudEventsVersion: "0.1",
Source: "https://serverless.com/event-gateway/#transformationVersion=0.1",
ContentType: "application/cloudevents+json",
Expand Down
21 changes: 7 additions & 14 deletions httpapi/httpapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package httpapi
import (
"encoding/json"
"net/http"
"strings"

"github.com/julienschmidt/httprouter"
"github.com/prometheus/client_golang/prometheus/promhttp"
Expand Down Expand Up @@ -39,10 +38,10 @@ func (h HTTPAPI) RegisterRoutes(router *httprouter.Router) {
router.DELETE("/v1/spaces/:space/functions/:id", h.deleteFunction)

router.GET("/v1/spaces/:space/subscriptions", h.listSubscriptions)
router.GET("/v1/spaces/:space/subscriptions/*id", h.getSubscription)
router.GET("/v1/spaces/:space/subscriptions/:id", h.getSubscription)
router.POST("/v1/spaces/:space/subscriptions", h.createSubscription)
router.PUT("/v1/spaces/:space/subscriptions/*id", h.updateSubscription)
router.DELETE("/v1/spaces/:space/subscriptions/*id", h.deleteSubscription)
router.PUT("/v1/spaces/:space/subscriptions/:id", h.updateSubscription)
router.DELETE("/v1/spaces/:space/subscriptions/:id", h.deleteSubscription)
}

func (h HTTPAPI) getFunction(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
Expand Down Expand Up @@ -198,7 +197,7 @@ func (h HTTPAPI) getSubscription(w http.ResponseWriter, r *http.Request, params
encoder := json.NewEncoder(w)

space := params.ByName("space")
fn, err := h.Subscriptions.GetSubscription(space, extractSubscriptionID(r.URL.RawPath))
fn, err := h.Subscriptions.GetSubscription(space, subscription.ID(params.ByName("id")))
if err != nil {
if _, ok := err.(*subscription.ErrSubscriptionNotFound); ok {
w.WriteHeader(http.StatusNotFound)
Expand Down Expand Up @@ -269,7 +268,8 @@ func (h HTTPAPI) updateSubscription(w http.ResponseWriter, r *http.Request, para
}

s.Space = params.ByName("space")
s.ID = extractSubscriptionID(r.URL.RawPath)
s.ID = subscription.ID(params.ByName("id"))

output, err := h.Subscriptions.UpdateSubscription(s.ID, s)
if err != nil {
if _, ok := err.(*subscription.ErrInvalidSubscriptionUpdate); ok {
Expand Down Expand Up @@ -298,7 +298,7 @@ func (h HTTPAPI) deleteSubscription(w http.ResponseWriter, r *http.Request, para
encoder := json.NewEncoder(w)

space := params.ByName("space")
err := h.Subscriptions.DeleteSubscription(space, extractSubscriptionID(r.URL.RawPath))
err := h.Subscriptions.DeleteSubscription(space, subscription.ID(params.ByName("id")))
if err != nil {
if _, ok := err.(*subscription.ErrSubscriptionNotFound); ok {
w.WriteHeader(http.StatusNotFound)
Expand All @@ -314,10 +314,3 @@ func (h HTTPAPI) deleteSubscription(w http.ResponseWriter, r *http.Request, para

metricConfigRequests.WithLabelValues(space, "subscription", "delete").Inc()
}

// httprouter weirdness: params are based on Request.URL.Path, not Request.URL.RawPath
func extractSubscriptionID(rawPath string) subscription.ID {
segments := strings.Split(rawPath, "/")
sid := segments[len(segments)-1]
return subscription.ID(sid)
}
Loading

0 comments on commit e4360c6

Please sign in to comment.