Introduction to Apex Web Services and Callouts
Apex Code supports the ability to expose Apex methods as a Web service. This article will refer to this as 'Apex Web Services.' Apex also supports the ability to invoke external web services and this article will refer to this concept as 'Callouts.' The former is involved in creating a web service that a client can invoke, while the latter is invoking an external web service.
For Apex Web Services, Apex supports the ability to write Apex logic and expose the logic as a web service. Therefore an external application can invoke this Apex web service to perform custom logic. The next section of this article will go into the syntax and some examples of how to enable an
Apex Web Service.
With 'Callouts', where Apex invokes an external web service, Apex provides integration with Web services that utilize SOAP and WSDL, or HTTP services (RESTful services). Apex supports the importing of WSDLs to auto-generate the corresponding Apex classes. Additionally, Apex supports HTTP services to use HTTP Request and Response objects to invoke the external web service. These concepts are covered in the
Apex Callouts section.
Remote Site Administration and Security
Before any Apex callout can call an external site, that site must be registered in the Remote Site Settings page, or the call will fail. The platform, by default, prevents calls to unauthorized network addresses.
To access the page, click Setup | Security Controls | Remote Site Settings. This page displays a list of any remote sites already registered and provides additional information about each site, including remote site name and URL.
For security reasons, the platform restricts the outbound ports you may specify to one of the following:
- 80: This port only accepts HTTP connections.
- 443: This port only accepts HTTPS connections.
- 1024–66535 (inclusive): These ports accept HTTP or HTTPS connections.
Please make sure to add your external site domains to the Remote Site Settings prior to invoke any third party web services.
Apex Web Services
Apex Web Services allow you to write Apex logic and expose the logic as a web service that an external application can invoke. This allows you to expose your Apex scripts so that external applications can access your code and your Force.com application. In other words, you can write you own custom web services with Apex.
Let's take the example of wanting to create a custom Apex Web Service to handle the integration between a custom Account Planning application and Force.com. Therefore, the requirement is to enable an Apex Web Service to receive an Account Plan from this external application and synchronize the data in Force.com. The following sections will walk through building the necessary Apex Classes to enable the Apex Web Service for creating Account Plans.
Syntax
There are just a few simple syntactical steps to enable an Apex method as a custom web service.
1. First, create and define an Apex class as global. Add the global
access identifier to the class definition as shown below.
1 | global class AccountPlan { |
The global
access modifier declares that the class is visible to all Apex scripts everywhere. This means the class can be used by any Apex code, not just the Apex in the same application.
2. Secondly, create and define an Apex method using both the static
and webservice
modifiers.
1 | global class AccountPlan { |
3 | webservice static Plan createAccountPlan(Plan vPlan) { |
That's it. The Force.com platform will automatically provision the web service for you. To find the details of the web service, you can use the automatically created WSDL describing the web service.
To access the generated WSDL, login and go to Setup | App Setup | Develop | Apex Classes. Find the specific class where the web service is defined and click on the WSDL hyperlink. This will download the WSDL which can then be imported into the external application(s) that will be invoking the web service.
Define Web Service Member Variables or Classes
Now let's add to this Account Planning scenario. Since an external application is invoking the Apex Web Service, called createAccountPlan()
, it is ideal if the external application did not have to transform its Account Plan data structure to an object in the Force.com schema. It would be ideal if the external application could simply pass its data representation of an Account Plan to the Apex Web Service and the Apex Web Service would have the logic to translate it to the proper Force.com object model.
The following section illustrates how to expose member variables and inner Apex classes to be accessed by external applications. In more detail, by exposing the member variables and inner Apex Classes with the webservice
modifier, those attributes will be included in the custom Apex WSDL created by this Web service-enabled Apex Class. Therefore, the external application that imports the WSDL has access to operate against those members or objects as it would any other data type or object.
01 | global class AccountPlan { |
03 | webservice String area; |
04 | webservice String region; |
08 | webservice String name; |
09 | webservice Integer planNumber; |
10 | webservice Date planningPeriod; |
14 | webservice static Plan createAccountPlan(Plan vPlan) { |
18 | Account acct = new Account(); |
19 | acct.Name = vPlan.name; |
20 | acct.AccountNumber = String .valueOf(vPlan.planNumber); |
Now, the WSDL auto-generated by Apex for this class will contain 2 additional String members for Area and Region. Additionally, the WSDL will contain a new object for Plan since the inner class Plan
has members annotated with the webservice
modifier.
All together, the external application can import this WSDL and instantiate those objects and pass them into the web service as appropriate. In the example above, the external application can build a Plan object and pass it as the input argument for the createAccountPlan
web service.
Important Considerations
When using the webservice
keyword, keep the following considerations in mind:
- You can use the
webservice
modifier to define top-level, outer class methods and variables, and member variables of an inner class. It can't be used on a class itself, or an interface or interface methods or variables.
- An Apex trigger can execute a callout when the callout is invoked within a method defined as asynchronous: that is, defined with the @future keyword. The
@future
annotation signifies that the Apex method executes asynchronously. For more information on the @future
annotation, please read the documentation.
- All classes that contain methods or variables defined with the
webservice
keyword must be declared as global. If a method, variable or inner class is declared as global, the outer, top-level class must also be defined as global.
- You must define any method that uses the
webservice
keyword as static.
- Because there are no SOAP analogs for certain Apex elements, methods defined with the
webservice
keyword cannot take the following elements as parameters. While these elements can be used within the method, they cannot be used as return values.
-
- Maps
- Sets
- Pattern objects
- Matcher objects
- Exception objects
- You must use the
webservice
keyword with any member variables that you want to expose as part of a web service. You should not mark these member variables as static. Please see thissection for more information.
Apex Callouts
Whereas Apex Web Services allows an external application to invoke Apex methods through web services, Apex Callouts enable Apex to invoke external web services. This allows you to connect to 3rd party web services such as Google, Amazon, Facebook, and any other external web service.
WSDL2Apex
WSDL2Apex allows for Apex classes to be automatically generated from a WSDL document. The generating Apex classes automatically handle the construction of the SOAP XML, the data transmission, and parsing the response XML into Apex objects. So instead of developing the logic to construct and parse the XML of the web service messages, let the Apex classes generated by WSDL2Apex internally handle all of that overhead. If you are familiar with WSDL2Java or importing a WSDL as a Web Reference in .NET, this is similar functionality for Apex.
To import a WSDL file into Apex, login, go to Setup | Develop | Apex Classes, and click Generate from WSDL.
The successfully generated Apex class includes stub and type classes for calling the third-party Web service represented by the WSDL document. These classes allow you to call the external Web service from Apex.
There are some caveats:
- The WSDL document may not exceed a maximum size. If it does, try and remove any unnecessary methods or data types from the WSDL. See the online documentation for the current size limit.
- The auto-generated Apex classes may not exceed a maximum size. If the generated class does exceed this limit, WSDL2Apex will generate the Apex code, display the code in the salesforce.com user interface along with the appropriate error message. It will not be able to save it. But again, since the code is generated in the user interface, a workaround is to copy the Apex code that is generated and break it out into 2 or more Apex classes.
- If a WSDL document contains an Apex reserved word, the word is appended with _x when the Apex class is generated.
WSDL2Apex Code Samples
This section shows an example of an WSDL2Apex generated Apex method that invokes an Amazon Web Service.
01 | public S 3 .Status DeleteObject( String Bucket, String Key, String AWSAccessKeyId,DateTime Timestamp, String Signature, String Credential) { |
02 | S 3 .DeleteObject_element request_x = new S 3 .DeleteObject_element(); |
03 | S 3 .DeleteObjectResponse_element response_x; |
04 | request_x.Bucket = Bucket; |
06 | request_x.AWSAccessKeyId = AWSAccessKeyId; |
07 | request_x.Timestamp = Timestamp; |
08 | request_x.Signature = Signature; |
09 | request_x.Credential = Credential; |
10 | Map< String , S 3 .DeleteObjectResponse_element > response_map_x = new Map< String , S 3 .DeleteObjectResponse_element > (); |
11 | response_map_x.put( 'response_x' , response_x); |
12 | WebServiceCallout.invoke( |
16 | new String []{endpoint_x, |
21 | 'DeleteObjectResponse' , |
22 | 'S3.DeleteObjectResponse_element' } |
24 | response_x = response_map_x. get ( 'response_x' ); |
25 | return response_x.DeleteObjectResponse; |
By calling this Apex method, a web service callout will be invoked, call the Amazon S3 web service, and this Apex method will return the results in an Apex Class called Status
. Notice how there is no XML construction or XML parsing involved to invoke this web service. Instead, that is all handled internally within Apex. The actual web service is invoked by the line of code that executes the Apex methodWebServiceCallout.invoke
. In general, you needn't ever look at these automatically generated Apex classes.
The WSDL2Apex generated code supports HTTP Headers. For example, you can use this feature to set the value of a cookie in an authorization header. To set HTTP headers, add inputHttpHeaders_x and outputHttpHeaders_x to the stub.
Here's an example that sets input HTTP Headers:
01 | docSample.DocSamplePort stub = new docSample.DocSamplePort(); |
02 | stub.inputHttpHeaders_x = new Map< String , String > (); |
05 | stub.inputHttpHeaders_x.put( 'Authorization' , 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==' ); |
08 | stub.inputHttpHeaders_x.put( 'Cookie' , 'name=value' ); |
11 | stub.inputHttpHeaders_x.put( 'myHeader' , 'myValue' ); |
Here's one that accesses output HTTP Headers information:
01 | docSample.DocSamplePort stub = new docSample.DocSamplePort(); |
02 | stub.outputHttpHeaders_x = new Map< String , String > (); |
03 | String input = 'This is the input string' ; |
04 | String output = stub.EchoString(input); |
07 | String cookie = stub.outputHttpHeaders_x. get ( 'Set-Cookie' ); |
10 | String myHeader = stub.outputHttpHeaders_x. get ( 'My-Header' ); |
Debugging WSDL2Apex
As mentioned earlier, the key benefit of using WSDL2Apex is to let Apex handle all of the hard work of invoking a web service. But during development, it is often key to debug your Apex logic including getting the output of the Apex generated SOAP XML messages (requests and responses).
Here's a way to troubleshoot WSDL2Apex callouts. It requires familiarity with the
Force.com IDE.
The debugging leverages the Execute Anonymous view within the Force.com IDE. So to start, open up the Execute Anonymous view in the IDE, and write a test case in Apex code in the dialog window. At a minimum, the test case should prepare the test data required to invoke the Apex method that performs the callout.
Next, when the test case is ready to be executed, click the 'Execute Anonymous' button and the Results panel below will be populated with the results, including the Callout details.
So using a combination of the Force.com IDE, Execute Anonymous view, and writing test cases to invoke the Apex callout, you can output the actual SOAP XML messages to help assist with debugging and troubleshooting, as you can see in the above screenshot.
Supported WSDL Features
Apex supports only the document literal wrapped WSDL style and the following schema types:
- String
- Int
- Double
- Float
- Long
- Boolean
- Short
- Datetime
- Date
- anyURI
- integer
- language
- Name
- NCName
- NMTOKEN
- NMTOKENS
- normalizedString
- token
- unsignedLong
- unsignedShort
- nonPositiveInteger
- positiveInteger
Reference the online documentation for further clarification and the definitive list.
Apex also supports the following schema constructs:
- Element
- Sequence
- All (in Apex Code saved using API version 15.0 or later)
- Annotation (in Apex Code saved using API version 15.0 or later)
- Attributes (in Apex Code saved using API version 15.0 or later)
- Choice (in Apex Code saved using API version 15.0 or later)
- Ref
The following data types are only supported when used as call ins, that is, when an external Web service calls an Apex Web service method. These data types are not supported as callouts, that is, when an Apex Web service method calls an external Web service.
Apex does not support any other WSDL constructs, types, or services, including:
- RPC/encoded services
- WSDL files with mulitple portTypes, multiple services, or multiple bindings
- WSDL files with imports
- Any schema types not documented in the previous table
HTTP (RESTful) Services
Apex supports HTTP Services with several built in Apex classes to creating HTTP requests like GET, POST, PUT, and DELETE. There are 3 main classes:
- HTTP Class: Use this class to initiate an HTTP request and response.
- HttpRequest Class: Use this class to programmatically create HTTP requests.
- HttpResponse Class: Use this class to handle the HTTP response returned by the HTTP.Send() operation.
Together, these classes support the ability to develop HTTP request/response functionality within Apex. Using these HTTP classes supports the ability to integrate to REST-based services. It also enables the ability to integrate to SOAP-based web services as an alternate option to leveraging WSDL2Apex. By using the HTTP classes, instead of WSDL2Apex, the developer has more responsibility to handling the construction of the SOAP message both for the request and the response.
The following sections will show a few examples of how to use these classes to make a REST-based web service call. Check out projects on
Code Share for more code - for example the
Force.com Toolkit for Google Data APIs makes extensive use of these types of callouts.
HTTP Code Sample
Here is a simple example of using the HTTP
, HTTPRequest
, and HTTPResponse
classes to construct a REST-based PUT message and receive the response. The example is making a REST-based web service call to the Amazon S3 service.
01 | HttpRequest req = new HttpRequest(); |
07 | req.setHeader( 'content-type' , 'image/gif' ); |
08 | req.setHeader( 'Content-Length' , '1024' ); |
09 | req.setHeader( 'Host' , 's3.amazonaws.com' ); |
10 | req.setHeader( 'Connection' , 'keep-alive' ); |
11 | req.setEndpoint( this .serviceEndPoint + this .bucket + '/' + this .key); |
12 | req.setHeader( 'Date' ,getDateString()); |
17 | Http http = new Http(); |
22 | HTTPResponse res = http.send(req); |
25 | System .debug(res.toString()); |
26 | System .debug( 'STATUS:' +res.getStatus()); |
27 | System .debug( 'STATUS_CODE:' +res.getStatusCode()); |
29 | } catch ( System .CalloutException e) { |
This example illustrates the setting of all required HTTP Headers, setting the body of the HTTPRequest
object to the contents of the REST-based message, and checks the HTTPResponse
status to make sure it was successfully processed.
Additional Considerations
- Callout timeout is configurable in Apex Code (up to a maximum setting for a single callout):
- The following is an example of setting a custom timeout for Web services callouts generated by WSDL2Apex:
1 | docSample.DocSamplePort stub = new docSample.DocSamplePort(); |
- The following is an example of setting a custom timeout for HTTP callouts using the Apex HTTPRequest object:
1 | HttpRequest req = new HttpRequest(); |
- There is a maximum cumulative timeout for callouts by a single Apex transaction (currently 120 seconds - see the Apex Language Reference Guide for a definitive number). This time is additive across all callouts invoked by the Apex transaction.
- The Apex callout request size must be smaller than a set maximum limit.
- A governor limit is set on the number of web service calls or callouts per trigger or Apex class invocation.
No comments:
Post a Comment