Skip to content

Commit

Permalink
Go-plugins support added
Browse files Browse the repository at this point in the history
  • Loading branch information
dencoded committed Mar 18, 2019
1 parent dede565 commit ff5805c
Show file tree
Hide file tree
Showing 10 changed files with 391 additions and 76 deletions.
4 changes: 2 additions & 2 deletions api_definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ const (
StatusURLRewrite RequestStatus = "URL Rewritten"
StatusVirtualPath RequestStatus = "Virtual Endpoint"
StatusRequestSizeControlled RequestStatus = "Request Size Limited"
StatusRequesTracked RequestStatus = "Request Tracked"
StatusRequestTracked RequestStatus = "Request Tracked"
StatusRequestNotTracked RequestStatus = "Request Not Tracked"
StatusValidateJSON RequestStatus = "Validate JSON"
StatusInternal RequestStatus = "Internal path"
Expand Down Expand Up @@ -912,7 +912,7 @@ func (a *APISpec) getURLStatus(stat URLStatus) RequestStatus {
case MethodTransformed:
return StatusMethodTransformed
case RequestTracked:
return StatusRequesTracked
return StatusRequestTracked
case RequestNotTracked:
return StatusRequestNotTracked
case ValidateJSONRequest:
Expand Down
116 changes: 94 additions & 22 deletions api_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,10 @@ func processSpec(spec *APISpec, apisByListen map[string]int,
sessionStore = rpcAuthStore
}

// Health checkers are initialised per spec so that each API handler has it's own connection and redis sotorage pool
// Health checkers are initialised per spec so that each API handler has it's own connection and redis storage pool
spec.Init(authStore, sessionStore, healthStore, orgStore)

//Set up all the JSVM middleware
// Set up all the JSVM middleware
var mwAuthCheckFunc apidef.MiddlewareDefinition
mwPreFuncs := []apidef.MiddlewareDefinition{}
mwPostFuncs := []apidef.MiddlewareDefinition{}
Expand Down Expand Up @@ -279,12 +279,23 @@ func processSpec(spec *APISpec, apisByListen map[string]int,
handleCORS(&chainArray, spec)

for _, obj := range mwPreFuncs {
if mwDriver != apidef.OttoDriver {

if mwDriver == apidef.OttoDriver {
chainArray = append(chainArray, createDynamicMiddleware(obj.Name, true, obj.RequireSession, baseMid))
} else if mwDriver == apidef.GoPluginDriver {
mwAppendEnabled(
&chainArray,
&GoPluginMiddleware{
BaseMiddleware: baseMid,
Path: obj.Path,
SymbolName: obj.Name,
Pre: true,
UseSession: obj.RequireSession,
Auth: false,
},
)
} else {
coprocessLog.Debug("Registering coprocess middleware, hook name: ", obj.Name, "hook type: Pre", ", driver: ", mwDriver)
mwAppendEnabled(&chainArray, &CoProcessMiddleware{baseMid, coprocess.HookType_Pre, obj.Name, mwDriver})
} else {
chainArray = append(chainArray, createDynamicMiddleware(obj.Name, true, obj.RequireSession, baseMid))
}
}

Expand All @@ -309,12 +320,23 @@ func processSpec(spec *APISpec, apisByListen map[string]int,
mwAppendEnabled(&chainArray, &TransformMethod{BaseMiddleware: baseMid})

for _, obj := range mwPostFuncs {
if mwDriver != apidef.OttoDriver {

if mwDriver == apidef.OttoDriver {
chainArray = append(chainArray, createDynamicMiddleware(obj.Name, false, obj.RequireSession, baseMid))
} else if mwDriver == apidef.GoPluginDriver {
mwAppendEnabled(
&chainArray,
&GoPluginMiddleware{
BaseMiddleware: baseMid,
Path: obj.Path,
SymbolName: obj.Name,
Pre: false,
UseSession: obj.RequireSession,
Auth: false,
},
)
} else {
coprocessLog.Debug("Registering coprocess middleware, hook name: ", obj.Name, "hook type: Post", ", driver: ", mwDriver)
mwAppendEnabled(&chainArray, &CoProcessMiddleware{baseMid, coprocess.HookType_Post, obj.Name, mwDriver})
} else {
chainArray = append(chainArray, createDynamicMiddleware(obj.Name, false, obj.RequireSession, baseMid))
}
}

Expand All @@ -329,12 +351,23 @@ func processSpec(spec *APISpec, apisByListen map[string]int,

// Add pre-process MW
for _, obj := range mwPreFuncs {
if mwDriver != apidef.OttoDriver {

if mwDriver == apidef.OttoDriver {
chainArray = append(chainArray, createDynamicMiddleware(obj.Name, true, obj.RequireSession, baseMid))
} else if mwDriver == apidef.GoPluginDriver {
mwAppendEnabled(
&chainArray,
&GoPluginMiddleware{
BaseMiddleware: baseMid,
Path: obj.Path,
SymbolName: obj.Name,
Pre: true,
UseSession: obj.RequireSession,
Auth: false,
},
)
} else {
coprocessLog.Debug("Registering coprocess middleware, hook name: ", obj.Name, "hook type: Pre", ", driver: ", mwDriver)
mwAppendEnabled(&chainArray, &CoProcessMiddleware{baseMid, coprocess.HookType_Pre, obj.Name, mwDriver})
} else {
chainArray = append(chainArray, createDynamicMiddleware(obj.Name, true, obj.RequireSession, baseMid))
}
}

Expand Down Expand Up @@ -371,6 +404,7 @@ func processSpec(spec *APISpec, apisByListen map[string]int,

coprocessAuth := EnableCoProcess && mwDriver != apidef.OttoDriver && spec.EnableCoProcessAuth
ottoAuth := !coprocessAuth && mwDriver == apidef.OttoDriver && spec.EnableCoProcessAuth
gopluginAuth := !coprocessAuth && !ottoAuth && mwDriver == apidef.GoPluginDriver

if coprocessAuth {
// TODO: check if mwAuthCheckFunc is available/valid
Expand All @@ -387,6 +421,20 @@ func processSpec(spec *APISpec, apisByListen map[string]int,
authArray = append(authArray, createDynamicMiddleware(mwAuthCheckFunc.Name, true, false, baseMid))
}

if gopluginAuth {
mwAppendEnabled(
&authArray,
&GoPluginMiddleware{
BaseMiddleware: baseMid,
Path: mwAuthCheckFunc.Path,
SymbolName: mwAuthCheckFunc.Name,
Pre: true,
UseSession: false,
Auth: true,
},
)
}

if spec.UseStandardAuth || len(authArray) == 0 {
logger.Info("Checking security policy: Token")
authArray = append(authArray, createMiddleware(&AuthKey{baseMid}))
Expand All @@ -395,9 +443,22 @@ func processSpec(spec *APISpec, apisByListen map[string]int,
chainArray = append(chainArray, authArray...)

for _, obj := range mwPostAuthCheckFuncs {

coprocessLog.Debug("Registering coprocess middleware, hook name: ", obj.Name, "hook type: Pre", ", driver: ", mwDriver)
mwAppendEnabled(&chainArray, &CoProcessMiddleware{baseMid, coprocess.HookType_PostKeyAuth, obj.Name, mwDriver})
if mwDriver == apidef.GoPluginDriver {
mwAppendEnabled(
&chainArray,
&GoPluginMiddleware{
BaseMiddleware: baseMid,
Path: obj.Path,
SymbolName: obj.Name,
Pre: false,
UseSession: obj.RequireSession,
Auth: false,
},
)
} else {
coprocessLog.Debug("Registering coprocess middleware, hook name: ", obj.Name, "hook type: Pre", ", driver: ", mwDriver)
mwAppendEnabled(&chainArray, &CoProcessMiddleware{baseMid, coprocess.HookType_PostKeyAuth, obj.Name, mwDriver})
}
}

mwAppendEnabled(&chainArray, &StripAuth{baseMid})
Expand All @@ -416,12 +477,23 @@ func processSpec(spec *APISpec, apisByListen map[string]int,
mwAppendEnabled(&chainArray, &VirtualEndpoint{BaseMiddleware: baseMid})

for _, obj := range mwPostFuncs {
if mwDriver != apidef.OttoDriver {

if mwDriver == apidef.OttoDriver {
chainArray = append(chainArray, createDynamicMiddleware(obj.Name, false, obj.RequireSession, baseMid))
} else if mwDriver == apidef.GoPluginDriver {
mwAppendEnabled(
&chainArray,
&GoPluginMiddleware{
BaseMiddleware: baseMid,
Path: obj.Path,
SymbolName: obj.Name,
Pre: false,
UseSession: obj.RequireSession,
Auth: false,
},
)
} else {
coprocessLog.Debug("Registering coprocess middleware, hook name: ", obj.Name, "hook type: Post", ", driver: ", mwDriver)
mwAppendEnabled(&chainArray, &CoProcessMiddleware{baseMid, coprocess.HookType_Post, obj.Name, mwDriver})
} else {
chainArray = append(chainArray, createDynamicMiddleware(obj.Name, false, obj.RequireSession, baseMid))
}
}

Expand Down Expand Up @@ -490,7 +562,7 @@ func (d *DummyProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if found, err := isLoop(r); found {
if err != nil {
handler := ErrorHandler{*d.SH.Base()}
handler.HandleError(w, r, err.Error(), http.StatusInternalServerError)
handler.HandleError(w, r, err.Error(), http.StatusInternalServerError, true)
return
}

Expand Down
9 changes: 5 additions & 4 deletions apidef/api_definitions.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,11 @@ const (
RequestXML RequestInputType = "xml"
RequestJSON RequestInputType = "json"

OttoDriver MiddlewareDriver = "otto"
PythonDriver MiddlewareDriver = "python"
LuaDriver MiddlewareDriver = "lua"
GrpcDriver MiddlewareDriver = "grpc"
OttoDriver MiddlewareDriver = "otto"
PythonDriver MiddlewareDriver = "python"
LuaDriver MiddlewareDriver = "lua"
GrpcDriver MiddlewareDriver = "grpc"
GoPluginDriver MiddlewareDriver = "goplugin"

BodySource IdExtractorSource = "body"
HeaderSource IdExtractorSource = "header"
Expand Down
49 changes: 49 additions & 0 deletions goplugin/goplugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package goplugin

import (
"net/http"

"github.com/TykTechnologies/tyk/user"
)

// Logger provides interface to output to Tyk's logging system with log levels INFO, DEBUG, WARN and ERROR
type Logger interface {
Info(args ...interface{})
Infof(format string, args ...interface{})
Infoln(args ...interface{})

Debug(args ...interface{})
Debugf(format string, args ...interface{})
Debugln(args ...interface{})

Warning(args ...interface{})
Warningf(format string, args ...interface{})
Warningln(args ...interface{})

Error(args ...interface{})
Errorf(format string, args ...interface{})
Errorln(args ...interface{})
}

type APISpec struct {
OrgID string
APIID string
ConfigData map[string]interface{}
}

// ProcessFunc type functions are called for "pre", "post", "post_key_auth" custom middleware methods
type ProcessFunc func(
http.ResponseWriter,
*http.Request,
*user.SessionState,
APISpec,
Logger,
) error

// AuthFunc type function is called for "auth_check" custom middleware method
type AuthFunc func(
http.ResponseWriter,
*http.Request,
APISpec,
Logger,
) (session *user.SessionState, token string, err error)
82 changes: 42 additions & 40 deletions handler_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,45 +30,57 @@ type ErrorHandler struct {
}

// HandleError is the actual error handler and will store the error details in analytics if analytics processing is enabled.
func (e *ErrorHandler) HandleError(w http.ResponseWriter, r *http.Request, errMsg string, errCode int) {
func (e *ErrorHandler) HandleError(w http.ResponseWriter, r *http.Request, errMsg string, errCode int, writeResponse bool) {
defer e.Base().UpdateRequestSession(r)

var templateExtension string
var contentType string
if writeResponse {
var templateExtension string
var contentType string

switch r.Header.Get("Content-Type") {
case "application/xml":
templateExtension = "xml"
contentType = "application/xml"
default:
templateExtension = "json"
contentType = "application/json"
}

switch r.Header.Get("Content-Type") {
case "application/xml":
templateExtension = "xml"
contentType = "application/xml"
default:
templateExtension = "json"
contentType = "application/json"
}
w.Header().Set("Content-Type", contentType)

w.Header().Set("Content-Type", contentType)
templateName := "error_" + strconv.Itoa(errCode) + "." + templateExtension

templateName := "error_" + strconv.Itoa(errCode) + "." + templateExtension
// Try to use an error template that matches the HTTP error code and the content type: 500.json, 400.xml, etc.
tmpl := templates.Lookup(templateName)

// Try to use an error template that matches the HTTP error code and the content type: 500.json, 400.xml, etc.
tmpl := templates.Lookup(templateName)
// Fallback to a generic error template, but match the content type: error.json, error.xml, etc.
if tmpl == nil {
templateName = defaultTemplateName + "." + templateExtension
tmpl = templates.Lookup(templateName)
}

// Fallback to a generic error template, but match the content type: error.json, error.xml, etc.
if tmpl == nil {
templateName = defaultTemplateName + "." + templateExtension
tmpl = templates.Lookup(templateName)
}
// If no template is available for this content type, fallback to "error.json".
if tmpl == nil {
templateName = defaultTemplateName + "." + defaultTemplateFormat
tmpl = templates.Lookup(templateName)
w.Header().Set("Content-Type", defaultContentType)
}

// If no template is available for this content type, fallback to "error.json".
if tmpl == nil {
templateName = defaultTemplateName + "." + defaultTemplateFormat
tmpl = templates.Lookup(templateName)
w.Header().Set("Content-Type", defaultContentType)
}
//If the config option is not set or is false, add the header
if !e.Spec.GlobalConfig.HideGeneratorHeader {
w.Header().Add("X-Generator", "tyk.io")
}

// Need to return the correct error code!
w.WriteHeader(errCode)
apiError := APIError{errMsg}
tmpl.Execute(w, &apiError)
// Close connections
if e.Spec.GlobalConfig.CloseConnections {
w.Header().Add("Connection", "close")
}

// Need to return the correct error code!
w.WriteHeader(errCode)
apiError := APIError{errMsg}
tmpl.Execute(w, &apiError)
}

if memProfFile != nil {
pprof.WriteHeapProfile(memProfFile)
Expand Down Expand Up @@ -186,16 +198,6 @@ func (e *ErrorHandler) HandleError(w http.ResponseWriter, r *http.Request, errMs
// Report in health check
reportHealthValue(e.Spec, BlockedRequestLog, "-1")

//If the config option is not set or is false, add the header
if !e.Spec.GlobalConfig.HideGeneratorHeader {
w.Header().Add("X-Generator", "tyk.io")
}

// Close connections
if e.Spec.GlobalConfig.CloseConnections {
w.Header().Add("Connection", "close")
}

if memProfFile != nil {
pprof.WriteHeapProfile(memProfFile)
}
Expand Down
6 changes: 5 additions & 1 deletion middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,12 @@ func createMiddleware(mw TykMiddleware) func(http.Handler) http.Handler {
}
err, errCode := mw.ProcessRequest(w, r, mwConf)
if err != nil {
// GoPluginMiddleware are expected to send response in case of error
// but we still want to record error
_, isGoPlugin := mw.(*GoPluginMiddleware)

handler := ErrorHandler{*mw.Base()}
handler.HandleError(w, r, err.Error(), errCode)
handler.HandleError(w, r, err.Error(), errCode, isGoPlugin)

meta["error"] = err.Error()

Expand Down
Loading

0 comments on commit ff5805c

Please sign in to comment.