Adding condition in the Proxy Endpoint Flow

Hi !

I have  created a proxy where the 404, 405, 406 and 415 have a custom message as part of the business requirements.

For example, I have 3 endpoints:

GET /cars
PUT /item/order
DELETE /item

note: I use the StartsWith on my condition so that if the request matches the first string of the pathSuffix, it will run the policy that matches it. (hope this makes sense or understandable)

My sample Proxy Endpoint Flow:

 

<ProxyEndpoint name="default">
    <FaultRules>
        <FaultRule/>
        <FaultRule name="HandlingErrors">
            <Step>
                <Name>EV-ExtractRequest</Name>
                <Condition>(proxy.pathsuffix =| "/cars") or (proxy.pathsuffix =| "/item/")</Condition>
            </Step>
            <Step>
                <Name>JS-ErrorHandlingForItems</Name>
                <Condition>(proxy.pathsuffix =| "/item/")</Condition>
            </Step>
            <Step>
                <Name>JS-ErrorHandlingForCars</Name>
                <Condition>(proxy.pathsuffix =| "/cars")</Condition>
            </Step>
            <Step>
                <Name>AM-415UnsupportedMediaType</Name>
                <Condition>(request.header.content-type != "application/xml; charset=utf-8") and 
                           (request.header.content-type != "text/xml; charset=utf-8") and
                           (request.verb != "GET")
                </Condition>
            </Step>
            <Step>
                <Name>AM-406NotAcceptableException</Name>
                <Condition>(request.header.accept != "application/xml; charset=utf-8") and (request.header.accept != "*/*")</Condition>
            </Step>
            <Condition>((request.header.content-type != "application/xml; charset=utf-8") and (request.header.content-type != "text/xml; charset=utf-8")) or 
                       ((request.header.accept != "application/xml; charset=utf-8") and (request.header.accept != "*/*"))
            </Condition>
        </FaultRule>
    </FaultRules>
    <PreFlow name="PreFlow">
        <Request>
            <Step>
                <Name>RF-406NotAcceptableException</Name>
                <Condition>(request.header.accept != "application/xml; charset=utf-8") and (request.header.accept != "*/*")</Condition>
            </Step>
            <Step>
                <Name>RF-415UnsupportedMediaType</Name>
                <Condition>(request.header.content-type != "application/xml; charset=utf-8") and (request.header.content-type != "text/xml; charset=utf-8") and (request.verb != "GET")</Condition>
            </Step>
        </Request>
        <Response/>
    </PreFlow>
    <Flows>	
        <Flow name="Item">
            <Description/>
            <Request/>
            <Response/>
            <Condition>(proxy.pathsuffix MatchesPath "/item") and (request.verb = "DELETE")</Condition>
        </Flow>
        <Flow name="Order Item">
            <Description/>
            <Request>
                <Step>
                    <Name>EV-ExtractRequest</Name>
                </Step>
            </Request>
            <Response/>
            <Condition>(proxy.pathsuffix MatchesPath "/item/order/*") and (request.verb = "PUT")</Condition>
        </Flow>
        <Flow name="Cars Lists">
            <Description/>
            <Request/>
            <Response/>
            <Condition>(proxy.pathsuffix MatchesPath "/cars") and (request.verb = "GET")</Condition>
        </Flow>
        <Flow name="NotFound">
            <Request>
				<Step>
					<Name>EV-ExtractRequest</Name>
					<Condition>(proxy.pathsuffix =| "/cars") or (proxy.pathsuffix =| "/item/")</Condition>
				</Step>
				<Step>
					<Name>JS-ErrorHandlingForItems</Name>
					<Condition>(proxy.pathsuffix =| "/item/")</Condition>
				</Step>
				<Step>
					<Name>JS-ErrorHandlingForCars</Name>
					<Condition>(proxy.pathsuffix =| "/cars")</Condition>
				</Step>
                <Step>
                    <Name>RF-405MethodNotAllowed</Name>
                    <Condition>(proxy.pathsuffix MatchesPath "/cars" and request.verb != "GET") or
                               (proxy.pathsuffix MatchesPath "/item/order/*" and request.verb != "PUT") or
                               (proxy.pathsuffix MatchesPath "/item" and request.verb != "DELETE")
                    </Condition>
                </Step>
                <Step>
                    <Name>RF-404NotFound</Name>
                </Step>
            </Request>
            <Response/>
            <Condition>(proxy.pathsuffix MatchesPath "/**")</Condition>
        </Flow>
    </Flows>

 

 

For my JavaScript Policy:

JS-ErrorHandlingForCars

(Note: This is almost the same with JS-ErrorHandlingForItems, the difference would be the structure of the finalResponse, and the handling of other error statusCode)

 

var statusCode = context.getVariable("response.status.code");
var errorStatusCode = context.getVariable("error.status.code");
var faultName = context.getVariable("fault.name");
var httpMethod = context.getVariable("request.verb");
var formattedDate = new Date(context.getVariable("system.timestamp")).toISOString();

function errResponse(summary, description) {
    var finalResponse = "<?xml version='1.0' encoding='UTF-8'?>" +
                            "<response>" +
                            "<status>error</status>" +
                            "<description>" + description + "</description>" +
                            "<summary>" + summary + "</summary>" +
                            "<time>" + formattedDate + "</time>" +
                            "</response>";
    return finalResponse;
}


} if (pathSuffix != "/car") {
    var description = "Resource Not Found";
    var summary = "Not Found";
	var errorResponse = errResponse(summary, description);
	print(errorResponse);
	context.setVariable("finalResponse", errorResponse);
	context.setVariable("response.header.Content-Type", "application/xml");
} else if (pathSuffix == "/car/" && httpMethod != "GET") {
    var description = "Method used is not allowed";
    var summary = "HTTP Method Not Allowed";               
	var errorResponse = errResponse(summary, description);
	print(errorResponse);
	context.setVariable("finalResponse", errorResponse);
	context.setVariable("response.header.Content-Type", "application/xml");
} else if (faultName == "RaiseFault") {
	if (errorStatusCode == 406) {
		var description = "Server understands but can't fulfill the request";
		var summary = "Not Acceptable";    	
		var errorResponse = errResponse(summary, description);
		print(errorResponse);
		context.setVariable("finalResponse", errorResponse);
		context.setVariable("response.header.Content-Type", "application/xml");
	} else if (errorStatusCode == 415) {
		var description = "Server can't process request";
		var summary = "Unsupported Media Type";  	
		var errorResponse = errResponse(summary, description);
		print(errorResponse);
		context.setVariable("finalResponse", errorResponse);
		context.setVariable("response.header.Content-Type", "application/xml");
	}
}

 

The problem here is, when I try to send a request that has no content-type, in my case I use postman. I got the apigee default error message instead of the custom message that I have created:

{
    "fault": {
        "faultstring": "Raising fault. Fault name : RF-415UnsupportedMediaType",
        "detail": {
            "errorcode": "steps.raisefault.RaiseFault"
        }
    }
}

It's also not working for errors: 404, 405, and 406.

Any help on this is greatly appreciated. Thank you!

Solved Solved
1 2 321
1 ACCEPTED SOLUTION

I can try to make some suggestions. This is just my opinion. Thinking of making it maintainable.

First, This configuration:

 

           <Request>
				<Step>
					<Name>EV-ExtractRequest</Name>
					<Condition>(proxy.pathsuffix =| "/cars") or (proxy.pathsuffix =| "/item/")</Condition>
				</Step>
				<Step>
					<Name>JS-ErrorHandlingForItems</Name>
					<Condition>(proxy.pathsuffix =| "/item/")</Condition>
				</Step>
				<Step>
					<Name>JS-ErrorHandlingForCars</Name>
					<Condition>(proxy.pathsuffix =| "/cars")</Condition>
				</Step>
                <Step>
                    <Name>RF-405MethodNotAllowed</Name>
                    <Condition>(proxy.pathsuffix MatchesPath "/cars" and request.verb != "GET") or
                               (proxy.pathsuffix MatchesPath "/item/order/*" and request.verb != "PUT") or
                               (proxy.pathsuffix MatchesPath "/item" and request.verb != "DELETE")
                    </Condition>
                </Step>
                <Step>
                    <Name>RF-404NotFound</Name>
                </Step>
            </Request>
            <Response/>
            <Condition>(proxy.pathsuffix MatchesPath "/**")</Condition>
        </Flow>

 

..feels a little odd. If you want to return a 405 Not Allowed response for some combinations of verb + path, then include flows for them. Don't wrap a condition in a flow with another condition, and mush 5 other things in there.

maybe like this:

 

<Flows>
  <Flow name='405-non-get-on-cars'>
    <Request>
      <Name>RF-405MethodNotAllowed</Name>
      <Condition>proxy.pathsuffix MatchesPath "/cars" and request.verb != "GET"</Condition>
    </Request>
  </Flow>
  <Flow name='405-not-put-on-item-order'>
    <Request>
      <Name>RF-405MethodNotAllowed</Name>
      <Condition>proxy.pathsuffix MatchesPath "/item/order/*" and request.verb != "PUT"</Condition>
    </Request>
  </Flow>
  <Flow name='405-not-put-on-item-order'>
    <Request>
      <Name>RF-405MethodNotAllowed</Name>
      <Condition>proxy.pathsuffix MatchesPath "/item" and request.verb != "DELETE"</Condition>
    </Request>
  </Flow>

  ...
 

 

Second, regarding "it's not working" for 415, 405, 406, etc. I am not sure, but I think the conditions within your FaultRule may be defeating you. Again, they seem to be somewhat complicated and unnecessarily nested. But maybe there is a good reason for that, which I do not understand. But let me explain.

The logic for Apigee says that FaultRules are evaluated from bottom to top (last to first). In the case of a fault, Apigee considers the Condition on the FaultRule, and executes that FaultRule if the Condition evaluates to true. Be careful about what you infer from "executes the FaultRule". The FaultRule has Steps within it, and each Step can have a Condition. Suppose there is a FaultRule with an outer condition that evaluates to true. And you have 3 steps within the FaultRule, each of which has a Condition that evaluates to false. None of those Steps will execute, but the FaultRule WILL have executed! It's not the case that "if no steps within a faultrule execute, then Apigee will treat the FaultRule as if it did not execute." This is important because, remember, if the outer Condition on a FaultRule evaluates true, none of the other FaultRules will execute - The conditions won't even be evaluated. It means it is possible that a FaultRule will execute, but no Steps within the FaultRule execute. This is a FEATURE. It's up to you to use it properly.

In your case you have exactly one fault rule. (Well you have a null/empty fault rule at the top, with no Condition. This is as if there is no FaultRule at all. So it is equivalent to one faultrule. But you should remove the empty FaultRule element (<FaultRule/>) anyway). You have one faultrule, with a Condition. This is shown on line 28 of your first source excerpt. The Condition is like this:

 

      <Condition>((request.header.content-type != "application/xml; charset=utf-8") and
                  (request.header.content-type != "text/xml; charset=utf-8")) or 
                 ((request.header.accept != "application/xml; charset=utf-8") and
                  (request.header.accept != "*/*")) </Condition>

 

Apigee will evaluate the condition. If True, it will execute the FaultRule,; If False, then it will skip the faultrule. Within your faultrule, you have a number of steps, each of which has a Condition attached. When you say "it's not working" and "I got the apigee default error message instead of the custom message that I have created" I think maybe you mean that the policy named AM-415UnsupportedMediaType has not executed. There are two ways that would happen: either the Condition for the FaultRule did not evaluate to True, or the Condition on the Step did not evaluate to True. You need to check that.

I Want to offer an example, something that can help illustrate how conditions and fault rules and RaiseFault all work. I'll attach the full proxy, but let's have a look at my example proxyendpoint.

 

<ProxyEndpoint name="endpoint1">
  <Description>Illustrates fault handling</Description>
  <HTTPProxyConnection>
    <BasePath>/fault-handling-20231102</BasePath>
  </HTTPProxyConnection>

  <FaultRules>

    <FaultRule>
      <!-- No Condition: executes when none of the other FaultRules have executed -->
      <Step>
        <!-- But this step might be skipped! -->
        <Condition>NOT(request.header.skip-fault-msg-override = "true")</Condition>
        <Name>AM-Set-Generic-Fault-Message</Name>
      </Step>
    </FaultRule>

    <FaultRule>
      <!-- executes when RF-406-Not-Acceptable has raised the fault -->
      <Condition>raisefault.RF-406-Not-Acceptable.failed</Condition>
      <Step>
        <!-- But this step might be skipped! -->
        <Condition>NOT(request.header.skip-fault-msg-override = "true")</Condition>
        <Name>AM-Override-406-Fault-Message-From-RaiseFault-Policy</Name>
      </Step>
    </FaultRule>

    <FaultRule>
      <!-- executes when RF-415-Unsupported-Media-Type has raised the fault -->
      <Condition>raisefault.RF-415-Unsupported-Media-Type.failed</Condition>
      <Step>
        <!-- But this step might be skipped! -->
        <Condition>NOT(request.header.skip-fault-msg-override = "true")</Condition>
        <Name>AM-Override-415-Fault-Message-From-RaiseFault-Policy</Name>
      </Step>
    </FaultRule>

  </FaultRules>

  <Flows>

    <Flow name="flow1">
      <Response>
        <Step>
          <Name>AM-Response</Name>
        </Step>
      </Response>
      <Condition>proxy.pathsuffix MatchesPath "/no-fault" and (request.verb = "GET")</Condition>
    </Flow>

    <Flow name="flow-406">
      <Request>
        <Step>
          <Name>RF-406-Not-Acceptable</Name>
        </Step>
      </Request>
      <Condition>proxy.pathsuffix MatchesPath "/fault-406" and (request.verb = "GET")</Condition>
    </Flow>

    <Flow name="flow-415">
      <Request>
        <Step>
          <Name>RF-415-Unsupported-Media-Type</Name>
        </Step>
      </Request>
      <Condition>proxy.pathsuffix MatchesPath "/fault-415" and (request.verb = "GET")</Condition>
    </Flow>

    <Flow name="unknown request">
      <!-- this flow executes when no other flow Condition has evaluated to true -->
      <Request>
        <Step>
          <Name>RF-Unknown-Request</Name>
        </Step>
      </Request>
    </Flow>

  </Flows>

  <RouteRule name="NoRouteRule"/>

</ProxyEndpoint>

 

It's contrived but it will help illustrate. The RF-* policies all set a status code and a payload, which is in UPPERCASE. The AM-* policies set a message body that is in lowercase. (don't worry about the actual words in the body, just that it's uppercase if it is set by RaiseFault, and lowercase if set my AssignMessage). You can see that there are FaultRules that MIGHT override the message set in the RaiseFault. The Conditions use a test on a context variable that depends on the RaiseFault policy name that was used to raise the fault.

If I deploy that proxy, these are the results I see

GET :endpoint/fault-handling-20231102/no-fault

 

HTTP/1.1 200 OK

{
  "status": "ok"
}

 

GET :endpoint/fault-handling-20231102/fault-415

 

HTTP/1.1 415 Unsupported Media Type

{
  "message": "unsupported media type"
}

 

GET :endpoint/fault-handling-20231102/fault-415
SKIP-fault-msg-override: true

 

HTTP/1.1 415 Unsupported Media Type

{
  "ERROR": {
    "CODE": 415.01,
    "MESSAGE": "THAT REQUEST CANNOT BE HANDLED; USE A DIFFERENT CONTENT-TYPE HEADER."
  }
}

 

Try it yourself, experiment a little.

You may gain some insight from playing with a simpler case.

View solution in original post

2 REPLIES 2

I can try to make some suggestions. This is just my opinion. Thinking of making it maintainable.

First, This configuration:

 

           <Request>
				<Step>
					<Name>EV-ExtractRequest</Name>
					<Condition>(proxy.pathsuffix =| "/cars") or (proxy.pathsuffix =| "/item/")</Condition>
				</Step>
				<Step>
					<Name>JS-ErrorHandlingForItems</Name>
					<Condition>(proxy.pathsuffix =| "/item/")</Condition>
				</Step>
				<Step>
					<Name>JS-ErrorHandlingForCars</Name>
					<Condition>(proxy.pathsuffix =| "/cars")</Condition>
				</Step>
                <Step>
                    <Name>RF-405MethodNotAllowed</Name>
                    <Condition>(proxy.pathsuffix MatchesPath "/cars" and request.verb != "GET") or
                               (proxy.pathsuffix MatchesPath "/item/order/*" and request.verb != "PUT") or
                               (proxy.pathsuffix MatchesPath "/item" and request.verb != "DELETE")
                    </Condition>
                </Step>
                <Step>
                    <Name>RF-404NotFound</Name>
                </Step>
            </Request>
            <Response/>
            <Condition>(proxy.pathsuffix MatchesPath "/**")</Condition>
        </Flow>

 

..feels a little odd. If you want to return a 405 Not Allowed response for some combinations of verb + path, then include flows for them. Don't wrap a condition in a flow with another condition, and mush 5 other things in there.

maybe like this:

 

<Flows>
  <Flow name='405-non-get-on-cars'>
    <Request>
      <Name>RF-405MethodNotAllowed</Name>
      <Condition>proxy.pathsuffix MatchesPath "/cars" and request.verb != "GET"</Condition>
    </Request>
  </Flow>
  <Flow name='405-not-put-on-item-order'>
    <Request>
      <Name>RF-405MethodNotAllowed</Name>
      <Condition>proxy.pathsuffix MatchesPath "/item/order/*" and request.verb != "PUT"</Condition>
    </Request>
  </Flow>
  <Flow name='405-not-put-on-item-order'>
    <Request>
      <Name>RF-405MethodNotAllowed</Name>
      <Condition>proxy.pathsuffix MatchesPath "/item" and request.verb != "DELETE"</Condition>
    </Request>
  </Flow>

  ...
 

 

Second, regarding "it's not working" for 415, 405, 406, etc. I am not sure, but I think the conditions within your FaultRule may be defeating you. Again, they seem to be somewhat complicated and unnecessarily nested. But maybe there is a good reason for that, which I do not understand. But let me explain.

The logic for Apigee says that FaultRules are evaluated from bottom to top (last to first). In the case of a fault, Apigee considers the Condition on the FaultRule, and executes that FaultRule if the Condition evaluates to true. Be careful about what you infer from "executes the FaultRule". The FaultRule has Steps within it, and each Step can have a Condition. Suppose there is a FaultRule with an outer condition that evaluates to true. And you have 3 steps within the FaultRule, each of which has a Condition that evaluates to false. None of those Steps will execute, but the FaultRule WILL have executed! It's not the case that "if no steps within a faultrule execute, then Apigee will treat the FaultRule as if it did not execute." This is important because, remember, if the outer Condition on a FaultRule evaluates true, none of the other FaultRules will execute - The conditions won't even be evaluated. It means it is possible that a FaultRule will execute, but no Steps within the FaultRule execute. This is a FEATURE. It's up to you to use it properly.

In your case you have exactly one fault rule. (Well you have a null/empty fault rule at the top, with no Condition. This is as if there is no FaultRule at all. So it is equivalent to one faultrule. But you should remove the empty FaultRule element (<FaultRule/>) anyway). You have one faultrule, with a Condition. This is shown on line 28 of your first source excerpt. The Condition is like this:

 

      <Condition>((request.header.content-type != "application/xml; charset=utf-8") and
                  (request.header.content-type != "text/xml; charset=utf-8")) or 
                 ((request.header.accept != "application/xml; charset=utf-8") and
                  (request.header.accept != "*/*")) </Condition>

 

Apigee will evaluate the condition. If True, it will execute the FaultRule,; If False, then it will skip the faultrule. Within your faultrule, you have a number of steps, each of which has a Condition attached. When you say "it's not working" and "I got the apigee default error message instead of the custom message that I have created" I think maybe you mean that the policy named AM-415UnsupportedMediaType has not executed. There are two ways that would happen: either the Condition for the FaultRule did not evaluate to True, or the Condition on the Step did not evaluate to True. You need to check that.

I Want to offer an example, something that can help illustrate how conditions and fault rules and RaiseFault all work. I'll attach the full proxy, but let's have a look at my example proxyendpoint.

 

<ProxyEndpoint name="endpoint1">
  <Description>Illustrates fault handling</Description>
  <HTTPProxyConnection>
    <BasePath>/fault-handling-20231102</BasePath>
  </HTTPProxyConnection>

  <FaultRules>

    <FaultRule>
      <!-- No Condition: executes when none of the other FaultRules have executed -->
      <Step>
        <!-- But this step might be skipped! -->
        <Condition>NOT(request.header.skip-fault-msg-override = "true")</Condition>
        <Name>AM-Set-Generic-Fault-Message</Name>
      </Step>
    </FaultRule>

    <FaultRule>
      <!-- executes when RF-406-Not-Acceptable has raised the fault -->
      <Condition>raisefault.RF-406-Not-Acceptable.failed</Condition>
      <Step>
        <!-- But this step might be skipped! -->
        <Condition>NOT(request.header.skip-fault-msg-override = "true")</Condition>
        <Name>AM-Override-406-Fault-Message-From-RaiseFault-Policy</Name>
      </Step>
    </FaultRule>

    <FaultRule>
      <!-- executes when RF-415-Unsupported-Media-Type has raised the fault -->
      <Condition>raisefault.RF-415-Unsupported-Media-Type.failed</Condition>
      <Step>
        <!-- But this step might be skipped! -->
        <Condition>NOT(request.header.skip-fault-msg-override = "true")</Condition>
        <Name>AM-Override-415-Fault-Message-From-RaiseFault-Policy</Name>
      </Step>
    </FaultRule>

  </FaultRules>

  <Flows>

    <Flow name="flow1">
      <Response>
        <Step>
          <Name>AM-Response</Name>
        </Step>
      </Response>
      <Condition>proxy.pathsuffix MatchesPath "/no-fault" and (request.verb = "GET")</Condition>
    </Flow>

    <Flow name="flow-406">
      <Request>
        <Step>
          <Name>RF-406-Not-Acceptable</Name>
        </Step>
      </Request>
      <Condition>proxy.pathsuffix MatchesPath "/fault-406" and (request.verb = "GET")</Condition>
    </Flow>

    <Flow name="flow-415">
      <Request>
        <Step>
          <Name>RF-415-Unsupported-Media-Type</Name>
        </Step>
      </Request>
      <Condition>proxy.pathsuffix MatchesPath "/fault-415" and (request.verb = "GET")</Condition>
    </Flow>

    <Flow name="unknown request">
      <!-- this flow executes when no other flow Condition has evaluated to true -->
      <Request>
        <Step>
          <Name>RF-Unknown-Request</Name>
        </Step>
      </Request>
    </Flow>

  </Flows>

  <RouteRule name="NoRouteRule"/>

</ProxyEndpoint>

 

It's contrived but it will help illustrate. The RF-* policies all set a status code and a payload, which is in UPPERCASE. The AM-* policies set a message body that is in lowercase. (don't worry about the actual words in the body, just that it's uppercase if it is set by RaiseFault, and lowercase if set my AssignMessage). You can see that there are FaultRules that MIGHT override the message set in the RaiseFault. The Conditions use a test on a context variable that depends on the RaiseFault policy name that was used to raise the fault.

If I deploy that proxy, these are the results I see

GET :endpoint/fault-handling-20231102/no-fault

 

HTTP/1.1 200 OK

{
  "status": "ok"
}

 

GET :endpoint/fault-handling-20231102/fault-415

 

HTTP/1.1 415 Unsupported Media Type

{
  "message": "unsupported media type"
}

 

GET :endpoint/fault-handling-20231102/fault-415
SKIP-fault-msg-override: true

 

HTTP/1.1 415 Unsupported Media Type

{
  "ERROR": {
    "CODE": 415.01,
    "MESSAGE": "THAT REQUEST CANNOT BE HANDLED; USE A DIFFERENT CONTENT-TYPE HEADER."
  }
}

 

Try it yourself, experiment a little.

You may gain some insight from playing with a simpler case.

Thank you for the detailed suggestions,@dchiesa1! I'll implement your recommendations to enhance the maintainability of the proxy. I'll also use the zip file you provided for some experimentation. Your ideas are precisely what I was looking for. I truly appreciate your help and will accept your comment as the solution since it fulfills my needs.

Stay awesome!