Skip to content

Commit

Permalink
JSVM Based middleware working for both PRE and POST hooks. TODO: JS E…
Browse files Browse the repository at this point in the history
…vent handler
  • Loading branch information
Martin Buhr committed Jan 14, 2015
1 parent 7f7c34c commit c2a85b9
Show file tree
Hide file tree
Showing 11 changed files with 321 additions and 82 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ apps/test.json
logs
lint_results.txt
build/
test_apps/
tyk.conf.bak
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# DEV

- Added expiry TTL to `tykcommon`, data expiry headers will be added to all analytics records, set `expire_analytics_after` to `0` to have data live indefinetely (currently 100 years), set to anything above zero for data in MongoDB to be removed after x seconds. **requirement**: You must create an expiry TTL index on the tyk_analytics collection manually (http://docs.mongodb.org/manual/tutorial/expire-data/). If you do not wish mongo to manage data warehousing at all, simply do not create the index.
- Added a JS Virtual Machine so dynamic JS middleware can be run PRE and POST middleware chain


# v1.3:
Expand Down
78 changes: 2 additions & 76 deletions api_definition_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package main

import (
"encoding/json"
// "github.com/RangelReale/osin"
"github.com/robertkrimen/otto"
"github.com/lonelycode/tykcommon"
"io/ioutil"
"labix.org/v2/mgo"
Expand All @@ -15,87 +15,12 @@ import (
"time"
)

//type AuthProviderCode string
//type SessionProviderCode string
//type StorageEngineCode string
//
const (
DefaultAuthProvider tykcommon.AuthProviderCode = "default"
DefaultSessionProvider tykcommon.SessionProviderCode = "default"
DefaultStorageEngine tykcommon.StorageEngineCode = "redis"
)

//
//type AuthProviderMeta struct {
// Name AuthProviderCode `bson:"name" json:"name"`
// StorageEngine StorageEngineCode `bson:"storage_engine" json:"storage_engine"`
// Meta interface{} `bson:"meta" json:"meta"`
//}
//
//type SessionProviderMeta struct {
// Name SessionProviderCode `bson:"name" json:"name"`
// StorageEngine StorageEngineCode `bson:"storage_engine" json:"storage_engine"`
// Meta interface{} `bson:"meta" json:"meta"`
//}
//
//type EventHandlerTriggerConfig struct {
// Handler TykEventHandlerName `bson:"handler_name" json:"handler_name"`
// HandlerMeta interface{} `bson:"handler_meta" json:"handler_meta"`
//}
//
//type EventHandlerMetaConfig struct {
// Events map[TykEvent][]EventHandlerTriggerConfig `bson:"events" json:"events"`
//}
//
//// APIDefinition represents the configuration for a single proxied API and it's versions.
//type APIDefinition struct {
// ID bson.ObjectId `bson:"_id,omitempty" json:"id"`
// Name string `bson:"name" json:"name"`
// APIID string `bson:"api_id" json:"api_id"`
// OrgID string `bson:"org_id" json:"org_id"`
// UseKeylessAccess bool `bson:"use_keyless" json:"use_keyless"`
// UseOauth2 bool `bson:"use_oauth2" json:"use_oauth2"`
// Oauth2Meta struct {
// AllowedAccessTypes []osin.AccessRequestType `bson:"allowed_access_types" json:"allowed_access_types"`
// AllowedAuthorizeTypes []osin.AuthorizeRequestType `bson:"allowed_authorize_types" json:"allowed_authorize_types"`
// AuthorizeLoginRedirect string `bson:"auth_login_redirect" json:"auth_login_redirect"`
// } `bson:"oauth_meta" json:"oauth_meta"`
// UseBasicAuth bool `bson:"use_basic_auth" json:"use_basic_auth"`
// NotificationsDetails NotificationsManager `bson:"notifications" json:"notifications"`
// EnableSignatureChecking bool `bson:"enable_signature_checking" json:"enable_signature_checking"`
// VersionDefinition struct {
// Location string `bson:"location" json:"location"`
// Key string `bson:"key" json:"key"`
// } `bson:"definition" json:"definition"`
// VersionData struct {
// NotVersioned bool `bson:"not_versioned" json:"not_versioned"`
// Versions map[string]VersionInfo `bson:"versions" json:"versions"`
// } `bson:"version_data" json:"version_data"`
// Proxy struct {
// ListenPath string `bson:"listen_path" json:"listen_path"`
// TargetURL string `bson:"target_url" json:"target_url"`
// StripListenPath bool `bson:"strip_listen_path" json:"strip_listen_path"`
// } `bson:"proxy" json:"proxy"`
// SessionLifetime int64 `bson:"session_lifetime" json:"session_lifetime"`
// Active bool `bson:"active" json:"active"`
// AuthProvider AuthProviderMeta `bson:"auth_provider" json:"auth_provider"`
// SessionProvider SessionProviderMeta `bson:"session_provider" json:"session_provider"`
// EventHandlers EventHandlerMetaConfig `bson:"event_handlers" json:"event_handlers"`
// EnableBatchRequestSupport bool `bson:"enable_batch_request_support" json:"enable_batch_request_support"`
// RawData map[string]interface{} `bson:"raw_data,omitempty" json:"raw_data,omitempty"` // Not used in actual configuration, loaded by config for plugable arc
//}
//
//// VersionInfo encapsulates all the data for a specific api_version, elements in the
//// Paths array are checked as part of the proxy routing.
//type VersionInfo struct {
// Name string `bson:"name" json:"name"`
// Expires string `bson:"expires" json:"expires"`
// Paths struct {
// Ignored []string `bson:"ignored" json:"ignored"`
// WhiteList []string `bson:"white_list" json:"white_list"`
// BlackList []string `bson:"black_list" json:"black_list"`
// } `bson:"paths" json:"paths"`
//}

// URLStatus is a custom enum type to avoid collisions
type URLStatus int
Expand Down Expand Up @@ -148,6 +73,7 @@ type APISpec struct {
OrgSessionManager SessionHandler
EventPaths map[tykcommon.TykEvent][]TykEventHandler
Health HealthChecker
JSVM *otto.Otto
}

// APIDefinitionLoader will load an Api definition from a storage system. It has two methods LoadDefinitionsFromMongo()
Expand Down
2 changes: 1 addition & 1 deletion auth_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ func (b DefaultSessionManager) GetSessionDetail(keyName string) (SessionState, b
jsonKeyVal, err := b.Store.GetKey(keyName)
var thisSession SessionState
if err != nil {
log.Warning("Key does not exist")
log.Debug("Key does not exist")
return thisSession, false
}

Expand Down
48 changes: 48 additions & 0 deletions js/tyk.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// ----- Tyk Middleware JS definition: this should be in the global context -----

var TykJS = {
TykMiddleware: {
MiddlewareComponentMeta: function(configuration) {
this.configuration = configuration;
}
}
};

TykJS.TykMiddleware.MiddlewareComponentMeta.prototype.ProcessRequest = function(request, session) {
log("Process Request Not Implemented");
return request;
};

TykJS.TykMiddleware.MiddlewareComponentMeta.prototype.DoProcessRequest = function(request, session) {
var processed_request = this.ProcessRequest(request, session);

if (!processed_request) {
log("Middleware didn't return request object!");
return;
}

// Reset the headers object
processed_request.Request.Headers = {}

return JSON.stringify(processed_request)
};

// The user-level middleware component
TykJS.TykMiddleware.NewMiddleware = function(configuration) {
TykJS.TykMiddleware.MiddlewareComponentMeta.call(this, configuration);
};

// Set up object inheritance
TykJS.TykMiddleware.NewMiddleware.prototype = Object.create(TykJS.TykMiddleware.MiddlewareComponentMeta.prototype);
TykJS.TykMiddleware.NewMiddleware.prototype.constructor = TykJS.TykMiddleware.NewMiddleware;

TykJS.TykMiddleware.NewMiddleware.prototype.NewProcessRequest = function(callback) {
this.ProcessRequest = callback;
};

TykJS.TykMiddleware.NewMiddleware.prototype.ReturnData = function(request, session) {
return {Request: request, SessionMeta: session}
};

// ---- End middleware implementation for global context ----

43 changes: 39 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,23 @@ func loadApps(APISpecs []APISpec, Muxer *http.ServeMux) {
// Health checkers are initialised per spec so that each API handler has it's own connection and redis sotorage pool
healthStore := &RedisStorageManager{KeyPrefix: "apihealth."}
referenceSpec.Init(authStore, sessionStore, healthStore, orgStore)

//TODO: Add a VM AND LOAD MIDDLEWARE CLASSES here (TESTING)
mwPaths := []string{}
mwPreNames := []string{}
mwPostNames := []string{}
for _, mwObj := range(referenceSpec.APIDefinition.CustomMiddleware.Pre) {
mwPaths = append(mwPaths, mwObj.Path)
mwPreNames = append(mwPreNames, mwObj.Name)
log.Info("Loading custom PRE-PROCESSOR middleware: ", mwObj.Name)
}
for _, mwObj := range(referenceSpec.APIDefinition.CustomMiddleware.Post) {
mwPaths = append(mwPaths, mwObj.Path)
mwPostNames = append(mwPostNames, mwObj.Name)
log.Info("Loading custom POST-PROCESSOR middleware: ", mwObj.Name)
}

referenceSpec.JSVM = CreateJSVM(mwPaths)

if referenceSpec.EnableBatchRequestSupport {
addBatchEndpoint(&referenceSpec, Muxer)
Expand Down Expand Up @@ -240,16 +257,34 @@ func loadApps(APISpecs []APISpec, Muxer *http.ServeMux) {
// Auth key
keyCheck = CreateMiddleware(&AuthKey{tykMiddleware}, tykMiddleware)
}

var chainArray = []alice.Constructor{}

// Use CreateMiddleware(&ModifiedMiddleware{tykMiddleware}, tykMiddleware) to run custom middleware
chain := alice.New(
CreateMiddleware(&IPWhiteListMiddleware{tykMiddleware}, tykMiddleware),
var baseChainArray = []alice.Constructor{
CreateMiddleware(&IPWhiteListMiddleware{tykMiddleware}, tykMiddleware),
CreateMiddleware(&OrganizationMonitor{tykMiddleware}, tykMiddleware),
CreateMiddleware(&VersionCheck{tykMiddleware}, tykMiddleware),
keyCheck,
CreateMiddleware(&KeyExpired{tykMiddleware}, tykMiddleware),
CreateMiddleware(&AccessRightsCheck{tykMiddleware}, tykMiddleware),
CreateMiddleware(&RateLimitAndQuotaCheck{tykMiddleware}, tykMiddleware)).Then(proxyHandler)
CreateMiddleware(&RateLimitAndQuotaCheck{tykMiddleware}, tykMiddleware),
}

// Add pre-process MW
for _, name := range(mwPreNames) {
chainArray = append(chainArray, CreateDynamicMiddleware(name, true, tykMiddleware))
}

for _, baseMw := range(baseChainArray) {
chainArray = append(chainArray, baseMw)
}

for _, name := range(mwPostNames) {
chainArray = append(chainArray, CreateDynamicMiddleware(name, false, tykMiddleware))
}

// Use CreateMiddleware(&ModifiedMiddleware{tykMiddleware}, tykMiddleware) to run custom middleware
chain := alice.New(chainArray...).Then(proxyHandler)

userCheckHandler := http.HandlerFunc(UserRatesCheck())
simpleChain := alice.New(
Expand Down
6 changes: 6 additions & 0 deletions middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ type TykMiddlewareImplementation interface {
ProcessRequest(w http.ResponseWriter, r *http.Request, configuration interface{}) (error, int) // Handles request
}

func CreateDynamicMiddleware(MiddlewareName string, IsPre bool, tykMwSuper TykMiddleware) func(http.Handler) http.Handler {
dMiddleware := &DynamicMiddleware{tykMwSuper, MiddlewareName, IsPre}

return CreateMiddleware(dMiddleware, tykMwSuper)
}

// Generic middleware caller to make extension easier
func CreateMiddleware(mw TykMiddlewareImplementation, tykMwSuper TykMiddleware) func(http.Handler) http.Handler {
aliceHandler := func(h http.Handler) http.Handler {
Expand Down
30 changes: 30 additions & 0 deletions middleware/sample.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// ---- Sample middleware creation by end-user -----
var sampleMiddleware = new TykJS.TykMiddleware.NewMiddleware({});

sampleMiddleware.NewProcessRequest(function(request, session) {
// You can log to Tyk console output by calloing the built-in log() function:
log("Running sample JSVM middleware")

// Set and Delete headers in an outbound request
request.SetHeaders["User-Agent"] = "Tyk-Custom-JSVM-Middleware";
//request.DeleteHeaders.push("Authorization");

// Change the outbound URL Path (only fragment, domain is fixed)
// request.URL = "/get";

// Add or delete request parmeters, these are encoded for the request as needed.
request.AddParams["test_param"] = "My Teapot";
request.DeleteParams.push("delete_me");

// Override the body:
request.Body = "New Request body"

// If you have multiple middlewares that need to communicate, set or read keys in the session object.
// This will only work in a postprocessing MW
if (session.meta_data) {
session.meta_data["MiddlewareDataString"] = "SomeValue";
}

// You MUST return both the request and session metadata
return sampleMiddleware.ReturnData(request, session.meta_data);
});
Loading

0 comments on commit c2a85b9

Please sign in to comment.