Code the Web Service Call
The general approach to coding web services in Salesforce B2C Commerce involves writing a script for a RESTful web service and storing credentials or certificates in the Business Manager.
- gets
Service
objects from the registry - provides input parameters for the
createRequest
callback used to construct the URL to call the web service - provides error handling based on the
Result
of the web service call - writes output variables to the pipeline dictionary to be used by the rendering template
If you are implementing a SOAP web service or require WS-Security features, you can also write a
script that uses the dw.ws
package to implement web services. In
this case, certificates must be stored in your cartridge.
Creating a Controller for Web Services
When creating a controller for web services, you usually need to include error handling and render the results of the web service call.
Understanding Pipelines for Web Services in Legacy Implementations
If you are maintaining an older cartridge that uses pipelines, web services are implemented in a pipeline with a script node for the web service script. The script node must have an error transition for error handling and a transition to a rendering template if you need to render the results of the web service call.
Creating a Web Service Script
Your web service
script needs to import the dw.svc
package.
Use
LocalServiceRegistry.createService
to create a
Service
object. Your script can modify or extend the
Service
object. You can also include business logic to
control the parameters used to invoke the web service.
Use
Service.call
to invoke the web service.
When the
service is invoked, the service framework checks the circuit breaker and
rate limiter. If either the circuit breaker or rate limiter are triggered,
then no call is made and the appropriate errors are logged. If neither are
triggered, then the callback methods in the service instance are executed,
and the dw.svc.Result
object is stored in the
Service
.
Example 1: Simple FTP Service Call
In this example, the operation invoked for the web service is
determined by an input variable passed from the pipeline. In the
dictionary bindings for the script node, the numCalls
variable is bound to
CurrentHttpParameterMap.numCalls.stringValue
and the
testType
variable is bound to
CurrentHttpParameterMap.testType.stringValue
.
/**
* testFTPClient.ds
*
* @input testType : String
* @input numCalls : String
* @output svcConfig : dw.svc.Service
* @output ftpResult : dw.svc.Result
*
*/
importPackage(dw.util);
importPackage(dw.svc);
importPackage(dw.net);
importPackage(dw.io);
function execute(args: PipelineDictionary): Number {
var service: Service;
var result: Result;
var counter = args.numCalls;
var mockCall = false;
var pipelineError = false;
var callTestFTP = LocalServiceRegistry.createService("test.ftp", {
mockCall: function(svc: FTPService, params) {
return [{
"name": "testfile1",
"timestamp": new Date(2011, 02, 21)
},
{
"name": "testfile2",
"timestamp": new Date(2012, 02, 21)
},
{
"name": "testfile3",
"timestamp": new Date(2013, 02, 21)
}
];
},
createRequest: function(svc: FTPService, args) {
return svc;
},
parseResponse: function(svc: FTPService, result: Array) {
var ret: Array = [];
for (var i = 0; i < result.length; i++) {
ret.push(result[i].name);
}
ret.sort();
return ret;
}
});
// Execute the request on the service configuration
function makeCall(svcConfig: Service, params: Object) {
if (counter == null) {
counter = 1;
}
while (counter != 0) {
if (mockCall) {
result = service.setMock().call(params);
} else if (pipelineError) {
result = service.setThrowOnError().call(params);
} else {
result = service.call(params);
}
counter--;
}
// Set pdict out values
args.svcConfig = service;
args.ftpResult = result;
}
switch (args.testType) {
case "LIST":
service = callTestFTP;
service.setOperation("list");
break;
case "CD":
service = callTestFTP;
service.setOperation("cd", "/");
break;
case "MKDIR":
service = callTestFTP;
service.setOperation("mkdir", "test");
break;
case "DELETE":
service = callTestFTP;
service.setOperation("del", "test");
break;
}
makeCall(service);
if (result == null || service == null) {
return PIPELET_ERROR;
}
return PIPELET_NEXT;
}
Example 2: Modifying the Service Object for an HTTP Service
This example demonstrates how you can use
the methods on the Service
object to modify the
Service
object before invoking the service. The ENCODING
case in this example adds a header, URL parameters, and changes the
encoding before invoking the service.
/**
* testHTTPClient.ds
*
* @input testType : String the type of test to run.
* @input numCalls : String the number of calls to make.
* @input returnCode : String the simulated http return code.
* @output svcConfig : dw.svc.Service the service configuration object.
* @output httpResult : dw.svc.Result the http result object.
*
*/
importPackage(dw.system);
importPackage(dw.svc);
importPackage(dw.net);
importPackage(dw.io);
function execute(args: PipelineDictionary): Number {
var service: Service;
var result: Result;
var counter = args.numCalls;
var mockCall = false;
var pipelineError = false;
var returnCode = args.returnCode;
var requestBody = {
'testString': 'foo',
'testNum': 5,
'testBool': true
};
var callTestGet = LocalServiceRegistry.createService("test.http.get", {
createRequest: function(svc: HTTPService, args) {
svc.setRequestMethod("GET");
},
parseResponse: function(svc: HTTPService, client: HTTPClient) {
return client.text;
},
mockCall: function(svc: HTTPService, client: HTTPClient) {
return {
statusCode: 200,
statusMessage: "Success",
text: "MOCK RESPONSE (" + svc.URL + ")"
};
},
filterLogMessage: function(msg: String) {
return msg.replace("headers", "OFFWITHTHEHEADERS");
}
});
var callTestPost = LocalServiceRegistry.createService("test.http.post", {
createRequest: function(svc: HTTPService, args) {
// Default request method is post
// No need to setRequestMethod
if (args) {
svc.addHeader("Content-Type", "text/json");
return JSON.stringify(args);
} else {
return null;
}
},
parseResponse: function(svc: HTTPService, client: HTTPClient) {
return client.text;
}
});
var callTestPut = LocalServiceRegistry.createService("test.http.put", {
createRequest: function(svc: HTTPService, args) {
svc.setRequestMethod("PUT");
if (args) {
svc.addHeader("Content-Type", "text/json");
return JSON.stringify(args);
} else {
return null;
}
},
parseResponse: function(svc: HTTPService, client: HTTPClient) {
return client.text;
}
});
var callTestDelete = LocalServiceRegistry.createService("test.http.delete", {
createRequest: function(svc: HTTPService, args) {
svc.setRequestMethod("DELETE");
},
parseResponse: function(svc: HTTPService, client: HTTPClient) {
return client.text;
}
});
var callTestBasicAuth = LocalServiceRegistry.createService("test.http.basicauth", {
createRequest: function(svc: HTTPService, args) {
svc.setRequestMethod("GET");
svc.setAuthentication("BASIC");
},
parseResponse: function(svc: HTTPService, client: HTTPClient) {
return client.text;
}
});
var callTestTimeout = LocalServiceRegistry.createService("test.http.timeout", {
createRequest: function(svc: HTTPService, args) {
svc.setRequestMethod("GET");
},
parseResponse: function(svc: HTTPService, client: HTTPClient) {
return client.text;
}
});
var callTestStatus = LocalServiceRegistry.createService("test.http.status", {
createRequest: function(svc: HTTPService, args) {
svc.setRequestMethod("GET");
},
parseResponse: function(svc: HTTPService, client: HTTPClient) {
return client.text;
}
});
// Execute the request on the service configuration
function makeCall(svcConfig: Service, params: Object) {
if (counter == null) {
counter = 1;
}
while (counter != 0) {
if (mockCall) {
result = service.setMock().call(params);
} else if (pipelineError) {
result = service.setThrowOnError().call(params);
} else {
result = service.call(params);
}
counter--;
}
// Set pdict out values
args.svcConfig = svcConfig;
args.httpResult = result;
}
switch (args.testType) {
// make a GET request with additional headers, query param, json payload
case "GET":
service = callTestGet;
service.URL += "/get";
service.addHeader('testHeader', 'testHeaderValue')
.addParam('filter', true);
break;
// make a POST request with additional headers, query param, json payload
case "POST":
service = callTestPost;
service.URL += "/post";
service.addHeader('testHeader', 'testHeaderValue')
break;
// make a POST request with additional headers, query param, json payload, using UTF-16 encoding
case "ENCODING":
service = callTestPost;
service.setEncoding('UTF-16');
break;
// make a PUT request with additional headers, query param concatenation, json payload
case "PUT":
service = callTestPut;
service.URL += "/put";
service.addParam('filter', true)
.addParam('secondParamAmpersand', 1);
break;
// make a DELETE request with additional headers, query param, json payload
case "DELETE":
service = callTestDelete;
service.URL += "/delete";
service.addHeader('testHeader', 'testHeaderValue')
.addParam('filter', true);
break;
// make an HTTP post with BASIC auth.
case "BASICAUTH":
service = callTestBasicAuth;
service.URL += "/basic-auth/user/passwd";
break;
// make a GET request with timeout
case "TIMEOUT":
service = callTestTimeout;
service.URL += "/delay/6";
break;
// make a GET request and write result object to file
case "OUTFILE":
service = callTestGet;
service.setOutFile(File("TEMP/" + Math.random()));
break;
// make a GET request in explicit mock mode
case "MOCKED":
service = ServiceRegistry.get("test.http.get");
mockCall = true;
break;
// make a GET request and test ratelimit
case "RATELIMIT":
service = callTestGet;
service.URL += "/get";
break;
// make a GET request with status code return as specified by returnCode pipeline query param
case "STATUS":
service = callTestStatus;
if (returnCode == null) {
trace("No return code provided");
}
service.URL += "/status/" + returnCode;
break;
// make a GET request and throwOnError() to have PIPELINE handle response error.
case "THROWONERROR":
service = callTestStatus;
service.URL += "/status/" + 500;
counter = 20;
pipelineError = true;
break;
}
// Make the service call here
makeCall(service, requestBody);
if (result == null || service == null) {
return PIPELET_ERROR;
}
return PIPELET_NEXT;
}
Error Handling
-
result.error
- service-type specific error code, such as a 404 for an HTTP service. -
result.errorMessage
- error message information, such as a Java exception
You can also call .setThrowOnError when calling the service to throw a JavaScript error if the result status is not OK:
result = service.setThrowOnError().call();