How can I dynamically set the URL for a ServiceCallout ?

I have a ServiceCallout policy that looks like this:

<ServiceCallout name='SC-SendAnotherRequest'>
  <DisplayName>SC-SendAnotherRequest</DisplayName>
  <Request variable='extraRequest'>
    <Set>
      <Verb>GET</Verb>
    </Set>
  </Request>
  <Response>extraResponse</Response>
  <HTTPTargetConnection>
    <Properties>
      <Property name='success.codes'>2xx, 4xx, 5xx</Property>
    </Properties>
    <URL>{settings_targetUrl}</URL>
  </HTTPTargetConnection>
</ServiceCallout>

The context variable settings_targetUrl is set to a URL.

When I try to import a proxy that contains such a policy, I get a validation error:

==> 400
{
  "code" : "messaging.config.beans.ProtocolMissingInURL",
  "message" : "Target Step SC-SendAnotherRequest: Protocol is missing in {settings_targetUrl}",
  "contexts" : [ ]
}

I think there is a validation step that is looking for an explicit http or https in the URL element.

How can I set this dynamically?

Solved Solved
1 18 8,874
2 ACCEPTED SOLUTIONS

Not applicable

I've run into this issue before. I believe you'll need to include the protocol, hostname, a slash, path, and query string (last two can go together). It seems as if the content of the XML URL element is being validated as a literal first. I thought I had a JIRA ticket for it.

So instead of  this: <URL>{settings_targetUrl}</URL>

Try:

<URL>https://{settings_target_hostname}/{settings_target_path_and_querystr}</URL>

View solution in original post

Diego's answer works. IF, however, you are not certain that the scheme will be http or https, then you need to use a different approach. The following works. Configure your ServiceCallout policy to set a dummy value in the URL and a Path in the request. Like this:

<ServiceCallout name='SC-SendAnotherRequest'>
  <DisplayName>SC-SendAnotherRequest</DisplayName>
  <Request variable='extraRequest'>
    <Set>
      <Verb>GET</Verb>
      <Path>{sc_urlPath}</Path>
    </Set>
  </Request>
  <Response>extraResponse</Response>
  <HTTPTargetConnection>
    <Properties>
      <Property name='success.codes'>2xx, 4xx, 5xx</Property>
    </Properties>
    <URL>http://dummy.this.will.get.set.dynamically</URL>
  </HTTPTargetConnection>
</ServiceCallout>

In the flow prior to the ServiceCallout policy, you must set two variables:

servicecallout.SC-SendAnotherRequest.target.url
sc_urlPath

The first one must follow the form servicecallout.POLICYNAME.target.url , where POLICYNAME is replaced by the name of your ServiceCallout policy. You can set this variable before the SC policy runs. However, the SC policy will use only the scheme and host from the URL that you set there, and will not use the path.

The second variable can be named anything you like. Just be consistent in the name you use when setting it, and the name you use when referencing it in the Path element. I chose sc_urlPath. This variable should contain just the path, including the leading slash, of the URL to request. It will be appended to the scheme://hostname which is set into the URL.

Supposing you have the complete URL you want to use for ServiceCallout, you need to dis-assemble that URL into two pieces: the first for the scheme+hostname, the second for the full url path. I accomplished this with a JS policy, like so:

// setTargetUrlVariables.js
// ------------------------------------------------------------------
//
// Set two variables to be use in ServiceCallout.
// the base URI goes to: servicecallout.SC-SendAnotherRequest.target.url
// the path goes to:  sc_urlPath

var urlvalue = context.getVariable('settings_serviceCalloutUrl');
var re = new RegExp('^(https?://[^/]+)(/.*)$');
var match = re.exec(urlvalue);
if (match) {
  context.setVariable('servicecallout.SC-SendAnotherRequest.target.url', match[1]);
  context.setVariable('sc_urlPath', match[2]);
}

You could parameterize that JS policy to make the variable names external. (use the Properties element in the JS policy).

It's a shame that it's this convoluted to set the URL dynamically for a ServiceCallout. But it IS possible.

View solution in original post

18 REPLIES 18

Not applicable

@Dino

Yes, there is a validation and AFAIK you cannot get away without putting a protocol element there. You can put something dummy for now like "http://this.will.get.set.dynamically" to get past the validation error.

The other way I have dealt with it in the past is to reference everything but the protocol - something like <URL>http://{settings_targetUrl}</URL>

Now coming back to how would you set this dynamically - actually there is nothing different about service callout's and the actual target backend call, they are both using the same "HTTPTargetConnection".

You can dynamically set it through a javascript variable or use target servers, KVM etc..

Not applicable

I've run into this issue before. I believe you'll need to include the protocol, hostname, a slash, path, and query string (last two can go together). It seems as if the content of the XML URL element is being validated as a literal first. I thought I had a JIRA ticket for it.

So instead of  this: <URL>{settings_targetUrl}</URL>

Try:

<URL>https://{settings_target_hostname}/{settings_target_path_and_querystr}</URL>

Thanks, Diego!

You're welcome Dino!

Not sure if separating hostname and other fields is required. It's possible that @Vinit Mehta's approach may work too.

LOL, just figured this out last night, the "/" between hostname and path is required even if its already in the variables. Thanks

Diego's answer works. IF, however, you are not certain that the scheme will be http or https, then you need to use a different approach. The following works. Configure your ServiceCallout policy to set a dummy value in the URL and a Path in the request. Like this:

<ServiceCallout name='SC-SendAnotherRequest'>
  <DisplayName>SC-SendAnotherRequest</DisplayName>
  <Request variable='extraRequest'>
    <Set>
      <Verb>GET</Verb>
      <Path>{sc_urlPath}</Path>
    </Set>
  </Request>
  <Response>extraResponse</Response>
  <HTTPTargetConnection>
    <Properties>
      <Property name='success.codes'>2xx, 4xx, 5xx</Property>
    </Properties>
    <URL>http://dummy.this.will.get.set.dynamically</URL>
  </HTTPTargetConnection>
</ServiceCallout>

In the flow prior to the ServiceCallout policy, you must set two variables:

servicecallout.SC-SendAnotherRequest.target.url
sc_urlPath

The first one must follow the form servicecallout.POLICYNAME.target.url , where POLICYNAME is replaced by the name of your ServiceCallout policy. You can set this variable before the SC policy runs. However, the SC policy will use only the scheme and host from the URL that you set there, and will not use the path.

The second variable can be named anything you like. Just be consistent in the name you use when setting it, and the name you use when referencing it in the Path element. I chose sc_urlPath. This variable should contain just the path, including the leading slash, of the URL to request. It will be appended to the scheme://hostname which is set into the URL.

Supposing you have the complete URL you want to use for ServiceCallout, you need to dis-assemble that URL into two pieces: the first for the scheme+hostname, the second for the full url path. I accomplished this with a JS policy, like so:

// setTargetUrlVariables.js
// ------------------------------------------------------------------
//
// Set two variables to be use in ServiceCallout.
// the base URI goes to: servicecallout.SC-SendAnotherRequest.target.url
// the path goes to:  sc_urlPath

var urlvalue = context.getVariable('settings_serviceCalloutUrl');
var re = new RegExp('^(https?://[^/]+)(/.*)$');
var match = re.exec(urlvalue);
if (match) {
  context.setVariable('servicecallout.SC-SendAnotherRequest.target.url', match[1]);
  context.setVariable('sc_urlPath', match[2]);
}

You could parameterize that JS policy to make the variable names external. (use the Properties element in the JS policy).

It's a shame that it's this convoluted to set the URL dynamically for a ServiceCallout. But it IS possible.

Right now service callout URL doesn't allow the scheme/protocol to be specified dynamically. This seems a bit weird and unnecessary. I had logged a bug against this to get it fixed, but looks like it was declined: https://apigeesc.atlassian.net/browse/MGMT-2557

We have faced issue with this limitation of service callout policy, where we used service callout policy to make call to BaaS. We need to access different BaaS collections based on some condition and set the URL dynamically.

In dev environment BaaS supports only http, but in prod we found that it was allowing only https, and hence we have to change the code before prod deployment.

@arghya das - That ticket has been re-opened.

Should this work in Version 4.16.05.06 (Private cloud)? Because it doesn't...

yes it should work. Can you please connect with Apigee Support to diagnose the problem with your approach? Or you can post a new question here and we'll try to help you.

5403-ask-a-question.png

Can you please tell, from where "settings_serviceCalloutUrl" is coming in your javascript code ?

And also, why is it important ?

@Dino

In my case, the settings_serviceCalloutUrl is coming from a settings file. But neither the name of the variable, nor the source of the value of the variable, matters. What's important: a prior policy in the flow has set a variable to contain a URL.

The Javascript parses the URL and sets two variables that will allow a subsequent service callout policy to "do the right thing".

You are saving the value of "settings_serviceCalloutUrl" in the variable "uri"...... but where are you using it again in your code ?

Well,that value seems to be unused.

I am sorry if my question is too basic.

I tried to implement in your way, but failed. Yes, Diego's way worked out for me.
Your answer will be appreciated.

sorry, there was a bug in that code. I fixed it now.

I follow the same pattern I use for the Target Endpoint connection, that is with a Target Server and flow variables, for example:

<ServiceCallout async="false" continueOnError="false" enabled="true" name="SC-AuthenticateUser">
    <DisplayName>SC-AuthenticateUser</DisplayName>
    <Properties/>
    <Request>
        <Set>
            <Headers>
                <Header name="Content-Type">application/x-www-form-urlencoded</Header>
            </Headers>
            <FormParams>
                <FormParam name="grant_type">password</FormParam>
                <FormParam name="username">{externalUsername}</FormParam>
                <FormParam name="password">{externalPassword}</FormParam>
            </FormParams>
            <Verb>POST</Verb>
            <Path/>
        </Set>
        <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
    </Request>
    <Response>authenticate.response</Response>
    <HTTPTargetConnection>
        <LoadBalancer>
            <Server name="oauth-v1"/>
        </LoadBalancer>
        <Path>{flow.target.basepath}/{flow.target.tokenPathsuffix}</Path>
    </HTTPTargetConnection>
</ServiceCallout>

This worked for me. Thank you.