+ Start a Discussion
Helen He 7Helen He 7 

How to get the list of current Apex Web Services

Hello,

New to Salesforce, and am learning Apex web services.
Assume there is no source-driven development in our team, and developers write their own web services through console, how can I get the whole list of existing Apex web services, any utilities or I have to open each Apex class to figure out?

Thanks.
Best Answer chosen by Helen He 7
Alain CabonAlain Cabon
There is probably other techniques and that could be interesting to publish it here.

The code above is "minimalist" (+ zero installation) and not "handy" when you cannot get the result easily from the log with a lot of web services.

I know other techniques but they are even more technical and I am rarely a success witth them.

You have opened end points but that is not sufficient to get all the web service names.

The most appreciated solution would be a simple appexchange free application (  https://appexchange.salesforce.com/  (https://appexchange.salesforce.com/) )

It is easy to add a small form with a caller button and to get the result on screen in a basic textarea field ( + copy/paste) with the code above.
 

All Answers

Alain CabonAlain Cabon
Hi,

If you use SOAP, your Apex web services use WebServiceCallout and its method invoke.
There is also the simplest Rest call for the web services.

SOAP : invoke(stub, request, response, infoArray)) )
Invokes an external SOAP web service operation based on an Apex class that is auto-generated from a WSDL.

infoArray:  Type: String[]
An array of strings that contains information about the callout—web service endpoint, SOAP action, request, and response. The order of the elements in the array matters.
Element at index 0 ([0]): One of the following options for identifying the URL of the external web service.
Endpoint URL. For example: 'http://YourServer/YourService'

Named credential URL, which contains the scheme callout, the name of the named credential, and optionally, an appended path. For example: 'callout:MyNamedCredential/some/path'
Element at index 1 ([1]): The SOAP action. For example: 'urn:dotnet.callouttest.soap.sforce.com/EchoString'
Element at index 2 ([2]): The request namespace. For example: 'http://doc.sample.com/docSample'
Element at index 3 ([3]): The request name. For example: 'EchoString'

From the anonymous window ( Developer console ), you can execute this short piece of code:
List<ApexClass> lapc = [select id,namespaceprefix,name,isvalid,body from apexclass];
pattern myPattern = pattern.compile('(?msi)WebServiceCallout.invoke\\(.+?\\)');
for (ApexClass apx:lapc) {
    String st = apx.body;
    // system.debug(apx.name);
    matcher myMatcher = myPattern.matcher(st);
    if (myMatcher.find()) {
        system.debug('found: class: (' + apx.namespaceprefix + ') [' + apx.name + '] = ' + myMatcher.group().replace('\n',''));    
    }    
}

User-added image


 
Alain CabonAlain Cabon
The following pattern is better because you can have: WebServiceCallout.invoke   (    // some spaces between invoke and the parenthesis)

pattern myPattern = pattern.compile('(?msi)WebServiceCallout.invoke.*?\\(.+?\\)');
 
Helen He 7Helen He 7
So we need to get all Apexclass, and parse the body with the pattern to find whatever I am looking for.
Tried to run the code via anonymous window, but it prompt me with 'No response from the server', I can run the soql and get some data from Query Editor, but not from anonymous windows, the first line of code gave me the prompt, tried different soql, same thing, need to dig into the code.

Thanks Alian, I think I got the idea.
 
Alain CabonAlain Cabon
Hi Helen,

Exactly you have understood the whole picture. 

'No response from the server',  ; perhaps you have too many classes and you overcome a governor limit.

I tested the following code with a lot of code (680 real classes) on our acceptance org and that works but that could be different on your org.

if (!apx.name.tolowercase().endswith('test')) {     // because it is useless to scan the test classes

pattern myPattern2 = pattern.compile('(?msi)new.+?String.*?\\[.*?\\].*?\\{(.+?)\\}');   // scan the infoArray but that could be different on you org.
 
pattern myPattern2 = pattern.compile('(?msi)new.+?String.*?\\[.*?\\].*?\\{(.+?)\\}');
List<ApexClass> lapc = [select id,namespaceprefix,name,isvalid,body from apexclass];
pattern myPattern = pattern.compile('(?msi)WebServiceCallout.invoke\\(.+?\\)');
integer nbclasses = 0 , nbfound = 0;
string infoArray = '';
for (ApexClass apx:lapc) {
    nbclasses++;
    if (!apx.name.tolowercase().endswith('test')) {
        String st = apx.body;    
		matcher myMatcher = myPattern.matcher(st);
		if (myMatcher.find()) {
            nbfound++;
            matcher myMatcher2 = myPattern2.matcher(myMatcher.group().replace('\n',''));
            if (myMatcher2.find()) {
                infoArray = myMatcher2.group(1).split(',')[3];
            } else {
                infoArray = '<not resolved>';
            }           
			system.debug(nbfound + ') found: class: prefix(' + apx.namespaceprefix + ') name [' + apx.name + '] service = [' + infoarray + '] = ' + myMatcher.group().replace('\n',''));    
		}    
	}    
}
system.debug('nb classes:' + nbclasses + ' found :' + nbfound);


List<ApexClass> lapc = [select id,namespaceprefix,name,isvalid,body from apexclass LIMIT 100];  //  for a try with just the first 100 classes

You could then retrieve the next 100 classes, 101 through 201, using the following query:

List<ApexClass> lapc = [select id,namespaceprefix,name,isvalid,body from apexclass LIMIT 100 OFFSET 100]; 
Helen He 7Helen He 7
Hi Alain,

Thanks for the quick response, I figured out the cause is our windows security updates happened on the weekend.
I don’t know what blocks the server response, just tried to run the same code from outside of company’s network, it works, but got ‘No response from the server’ when I switch back to company’s network.
 
Alain CabonAlain Cabon
There is probably other techniques and that could be interesting to publish it here.

The code above is "minimalist" (+ zero installation) and not "handy" when you cannot get the result easily from the log with a lot of web services.

I know other techniques but they are even more technical and I am rarely a success witth them.

You have opened end points but that is not sufficient to get all the web service names.

The most appreciated solution would be a simple appexchange free application (  https://appexchange.salesforce.com/  (https://appexchange.salesforce.com/) )

It is easy to add a small form with a caller button and to get the result on screen in a basic textarea field ( + copy/paste) with the code above.
 
This was selected as the best answer
Alain CabonAlain Cabon
Dear Helen,

I will create a simple page and we will see if that works better for your org. I will make some tests before publishing it here.
Your question is interesting and I have the same need.

Alain
Helen He 7Helen He 7
That would be great!
Alain CabonAlain Cabon

If you are allowed to create Visualforce pages and Apex classes, you can try this code.

Apex Class:
public class FindWebservices {
    public static String myResult{get;set;}
    public static String myOffset{get;set;}
    public static String myLimit {get;set;}
    private void reset() {
        myOffset = '0';
        myLimit ='300';
        myResult = '';
    }
    public FindWebservices (){
        reset();
    } 
    public PageReference cancel() {
        reset();
        return null;
    }
    public PageReference searchWebservices() {
        integer off = Integer.valueOf(myOffset);
        integer lim = Integer.valueOf(myLimit);
        pattern myPattern2 = pattern.compile('(?msi)new.+?String.*?\\[.*?\\].*?\\{(.+?)\\}');
        List<ApexClass> lapc = null;
        if (off > 0) {
            lapc = [SELECT id,namespaceprefix,name,isvalid,body FROM apexclass LIMIT :lim OFFSET :off];
        } else {
            lapc = [SELECT id,namespaceprefix,name,isvalid,body FROM apexclass LIMIT :lim ];
        }      
        pattern myPattern = pattern.compile('(?msi)WebServiceCallout.invoke\\(.+?\\)');
        integer nbclasses = 0 , nbfound = 0;
        string infoArray = '';
        String res = '';
        for (ApexClass apx:lapc) {
            nbclasses++;
            if (!apx.name.tolowercase().endswith('test')) {
                String st = apx.body;    
                matcher myMatcher = myPattern.matcher(st);
                if (myMatcher.find()) {
                    nbfound++;
                    matcher myMatcher2 = myPattern2.matcher(myMatcher.group().replace('\n',''));
                    if (myMatcher2.find()) {
                        infoArray = myMatcher2.group(1).split(',')[3];
                    } else {
                        infoArray = '<not resolved>';
                    }   
                    res = nbfound + ') class: prefix(' + apx.namespaceprefix + ') name [' + apx.name + '] service = [' + infoarray.trim() + '] = ' + myMatcher.group().replace('\n','').replace(' ','');
                    myResult += res + '\n--------------------\n';
                    system.debug(res);    
                }    
            }    
        }
        myResult += 'offset:' + myOffset +'\nlimit:' + myLimit + '\n nb classes:' + nbclasses + '\n found :' + nbfound;      
        return null;
    }    
}

Visualforce Page:
<apex:page controller="FindWebservices"  lightningStylesheets="true" >   
    <apex:form >
        <apex:pageBlock id="myBlock" title="Search Web Services" mode="edit">
            <apex:pageMessages />
            <apex:pageBlockButtons location="top">
                <apex:commandButton value="Search Web Services" action="{!searchWebservices}" status="myStatus" reRender="myBlock" />
                <apex:commandButton value="Cancel" action="{!cancel}"/>
            </apex:pageBlockButtons>
             <apex:actionStatus id="myStatus" startText="Requesting ... Please wait ..."  />
            <apex:pageBlockSection title="Limits">
               <apex:outputLabel value="Offset:">
                     <apex:inputText title="Offset" value="{!myOffset}" />
                </apex:outputLabel>       
                <apex:outputLabel value="Limit:">
                      <apex:inputText title="Limit" value="{!myLimit}" />
                </apex:outputLabel>             
            </apex:pageBlockSection>
            <apex:pageBlockSection title="Result" columns="1">
              <apex:pageBlockSectionItem  >
                   <apex:inputTextArea value="{!myResult}" cols = "150" rows="10"  />
                </apex:pageBlockSectionItem>   
           </apex:pageBlockSection>
        </apex:pageBlock>
    </apex:form>   
</apex:page>