github.com/dbernstein1/tyk@v2.9.0-beta9-dl-apic+incompatible/gateway/mw_transform_jq.go (about) 1 // +build jq 2 3 package gateway 4 5 import ( 6 "bytes" 7 "encoding/json" 8 "errors" 9 "io" 10 "io/ioutil" 11 "net/http" 12 13 "github.com/sirupsen/logrus" 14 15 "github.com/TykTechnologies/tyk/apidef" 16 ) 17 18 type TransformJQMiddleware struct { 19 BaseMiddleware 20 } 21 22 // JQResult structure stores the result of Tyk-JQ filter. 23 // It means that the result of the filter must contains the following fields 24 type JQResult struct { 25 Body interface{} `mapstructure:"body"` 26 RewriteHeaders map[string]string `mapstructure:"rewrite_headers"` 27 TykContext map[string]interface{} `mapstructure:"tyk_context"` 28 } 29 30 func (t *TransformJQMiddleware) Name() string { 31 return "TransformJQMiddleware" 32 } 33 34 func (t *TransformJQMiddleware) EnabledForSpec() bool { 35 for _, version := range t.Spec.VersionData.Versions { 36 if len(version.ExtendedPaths.TransformJQ) > 0 { 37 return true 38 } 39 } 40 return false 41 } 42 43 // ProcessRequest will run any checks on the request on the way through the system, return an error to have the chain fail 44 func (t *TransformJQMiddleware) ProcessRequest(w http.ResponseWriter, r *http.Request, _ interface{}) (error, int) { 45 _, versionPaths, _, _ := t.Spec.Version(r) 46 found, meta := t.Spec.CheckSpecMatchesStatus(r, versionPaths, TransformedJQ) 47 if !found { 48 return nil, http.StatusOK 49 } 50 51 err := t.transformJQBody(r, meta.(*TransformJQSpec)) 52 if err != nil { 53 log.WithFields(logrus.Fields{ 54 "prefix": "inbound-transform-jq", 55 "server_name": t.Spec.Proxy.TargetURL, 56 "api_id": t.Spec.APIID, 57 "path": r.URL.Path, 58 }).Error(err) 59 return err, 415 60 } 61 return nil, http.StatusOK 62 } 63 64 func (t *TransformJQMiddleware) transformJQBody(r *http.Request, ts *TransformJQSpec) error { 65 defer r.Body.Close() 66 67 var bodyObj interface{} 68 dec := json.NewDecoder(r.Body) 69 err := dec.Decode(&bodyObj) 70 71 // Do not fail if the body is empty 72 if err != nil && err != io.EOF { 73 return err 74 } 75 76 jqObj := map[string]interface{}{ 77 "body": bodyObj, 78 "_tyk_context": ctxGetData(r), 79 } 80 81 jqResult, err := lockedJQTransform(t.Spec, ts, jqObj) 82 if err != nil { 83 return err 84 } 85 86 transformed, _ := json.Marshal(jqResult.Body) 87 bodyBuffer := bytes.NewBuffer(transformed) 88 r.Body = ioutil.NopCloser(bodyBuffer) 89 r.ContentLength = int64(bodyBuffer.Len()) 90 91 // Replace header in the request 92 for hName, hValue := range jqResult.RewriteHeaders { 93 r.Header.Set(hName, hValue) 94 } 95 96 if t.Spec.EnableContextVars { 97 // Set variables in context vars 98 contextDataObject := ctxGetData(r) 99 for k, v := range jqResult.TykContext { 100 contextDataObject[k] = v 101 } 102 ctxSetData(r, contextDataObject) 103 } 104 105 return nil 106 } 107 108 func lockedJQTransform(s *APISpec, t *TransformJQSpec, jqObj map[string]interface{}) (JQResult, error) { 109 s.Lock() 110 value, err := t.JQFilter.Handle(jqObj) 111 s.Unlock() 112 if err != nil { 113 return JQResult{}, err 114 } 115 // The filter MUST return the following JSON object 116 // { 117 // "body": THE_TRANSFORMED_BODY, 118 // "rewrite_headers": {"header1_name": "header1_value", ...}, 119 // "tyk_context": {"var1_name": "var1_value", ...} 120 // } 121 122 var jqResult JQResult 123 values, ok := value.(map[string]interface{}) 124 if !ok { 125 return JQResult{}, errors.New("Invalid JSON object returned by JQ filter. Allowed field are 'body', 'rewrite_headers' and 'tyk_context'") 126 } 127 128 jqResult.Body = values["body"] 129 130 headers, converted := values["rewrite_headers"].(map[string]interface{}) 131 if !converted { 132 log.Error("rewrite_headers field must be a JSON object of string/string pairs") 133 } else { 134 jqResult.RewriteHeaders = make(map[string]string) 135 for k, v := range headers { 136 switch x := v.(type) { 137 case string: 138 jqResult.RewriteHeaders[k] = x 139 default: 140 log.Errorf("rewrite_header field must be a JSON object of string/string pairs (%s isn't)", k) 141 } 142 } 143 } 144 145 jqResult.TykContext, _ = values["tyk_context"].(map[string]interface{}) 146 147 return jqResult, nil 148 } 149 150 type TransformJQSpec struct { 151 apidef.TransformJQMeta 152 JQFilter *JQ 153 } 154 155 func (a *APIDefinitionLoader) compileTransformJQPathSpec(paths []apidef.TransformJQMeta, stat URLStatus) []URLSpec { 156 urlSpec := []URLSpec{} 157 158 log.Debug("Checking for JQ tranform paths ...") 159 for _, stringSpec := range paths { 160 newSpec := URLSpec{} 161 a.generateRegex(stringSpec.Path, &newSpec, stat) 162 newTransformSpec := TransformJQSpec{TransformJQMeta: stringSpec} 163 164 var err error 165 newTransformSpec.JQFilter, err = NewJQ(stringSpec.Filter) 166 167 if stat == TransformedJQ { 168 newSpec.TransformJQAction = newTransformSpec 169 } else { 170 newSpec.TransformJQResponseAction = newTransformSpec 171 } 172 173 if err == nil { 174 urlSpec = append(urlSpec, newSpec) 175 } else { 176 log.Error("JQ Filter load failure! Skipping transformation: ", err) 177 } 178 } 179 180 return urlSpec 181 }