3

I'm new to rest services in Salesforce. I've defined the following rest service in Apex:

@RestResource(urlMapping='/mediaPlanDetail')
global with sharing class MediaPlanDetailService {

    @HttpGet
    global static String doGet() {

        RestResponse response = RestContext.response;    
        response.addHeader('Access-Control-Allow-Origin', '*');
        response.addHeader('content-Type', 'application/json');

        String URL = 'http://services.somecompany.com/services/loadByMediaPlanId';

        String payloadStr = '';
        System.debug(RestContext.request.requestBody);

        String rows = RestContext.request.params.get('rows');  System.debug('rows = ' + rows);
        String page = RestContext.request.params.get('page');  System.debug('page = ' + page);
        String sidx = RestContext.request.params.get('sidx');  System.debug('sidx = ' + sidx);
        String sord = RestContext.request.params.get('sord');  System.debug('sord = ' + sord);
        String mediaPlanId = RestContext.request.params.get('mediaPlanId');
        String pageSize = RestContext.request.params.get('pageSize');

        String finalUrl = URL + 
                '?rows=' + rows +
                '&page=' + page + 
                '&sidx=' + sidx + 
                '&sord=' + sord + 
                '&mediaPlanId=' + mediaPlanId +
                '&pageSize=' + pageSize +
                ''
                ;

        Http objhttp = new Http();
        HttpRequest req = new HttpRequest();
        HttpResponse res = new HttpResponse();
        req.setMethod('GET');

        req.setEndpoint(finalUrl);

        req.setHeader('Accept','application/json');
        req.setHeader('Content-type','application/json; charset=UTF-8');
        req.setHeader('Accept-Language', 'en-US');
        req.setTimeout(120000);

        try {
            res = objhttp.send(req);

            if(res.getStatus().equalsIgnoreCase('OK') && res.getStatusCode() == 200) {
                // Logic to parse the response
                String body = res.getBody();
                system.debug('-------- HttpResponse received: ' + body);

                final String PAYLOAD_ELEMENT = '\"payload\":';
                final String EXCEPTION_ELEMENT = '\"exception\":';

                integer payloadPos = body.indexOf(PAYLOAD_ELEMENT); System.debug('payloadPos = ' + payloadPos);
                integer exceptionPos = body.indexOf(EXCEPTION_ELEMENT); System.debug('exceptionPos = ' + exceptionPos);

                payloadStr = body.substring(payloadPos + PAYLOAD_ELEMENT.length(), exceptionPos - 1);

            }

        }
        catch(System.CalloutException e) {
            System.debug('Callout error: '+ e);
        }

        return payloadStr;
    }

}

I'm calling this service using a jQuery plugin call jqGrid. Here's a snippet of the code:

$(document).ready(function () {

    var mpId = $.urlParam('mediaPlanId');

    $("#jqGrid").jqGrid({
    url: '/services/apexrest/mediaPlanDetail',  // this fails with 302 Found
    //url: 'https://somecompany--cpq.cs7.my.salesforce.com/services/apexrest/mediaPlanDetail',  // this fails with 401 not authorized
    postData: {
        mediaPlanId: mpId,
        pageSize: 10
    },
    loadBeforeSend: function(jqXHR) {
        //jqXHR.setRequestHeader("Authorization", 'OAuth ' + __sfdcSessionId);
        jqXHR.setRequestHeader("Authorization", 'Bearer {!$Api.Session_ID}');
        //jqXHR.setRequestHeader("Authorization", 'OAuth {!$Api.Session_ID}');
    },

The VF page where I'm calling the service has this location:

https://somecompany--cpq--c.cs7.visual.force.com/apex/jqgrid_test_page?mediaPlanId=2271

When I call the service using

/services/apexrest/mediaPlanDetail

the service returns a 302 with an alternative Location in the header:

https://somecompany--cpq.cs7.my.salesforce.com/services/apexrest/mediaPlanDetail?mediaPlanId=2271&pageSize
=10&_search=false&nd=1437743215834&rows=10&page=1&sidx=&sord=asc

When I use the redirected location as the URL, I get a 401 Unauthorized response with Cross-Origin Request Blocked messages in my browser.

In either case, my Apex rest service is never called. I see nothing in the Debug Logs indicating that my browser is actually calling it.

This looks like a classic cross domain scripting issue between the Salesforce VP page domain and the domain used for Apex rest services. I spent a lot of time yesterday reading various posts on both calling SF rest services and cross domain issues, but wasn't able to find a solution. Can anyone help me with this?

1

A quick google search return pretty helpful results:

Code:

function CallRestAPI()
{
var weblink = "http://URL";

$.ajax(
{
url : weblink,
type : 'GET',
dataType: 'json',
beforeSend: function (request)
{
request.setRequestHeader("HeaderName", "HeaderValue");
},
crossDomain: true,
success : function(result)
{
    //process the result
},
error : function(jqXHR, textStatus, errorThrown) {
alert('Error: '+jqXHR.status);
alert('ErrorThrown: '+errorThrown)
} 
}); 
}

LINK

https://developer.salesforce.com/forums/?id=906F00000009AtOIAU

I would think the JS removing would be a MUCH better case, call the remote method and have that method call the web service directly.

| improve this answer | |
  • I tried adding j$.ajaxSetup({crossDomain:true}); and j$.ajax({crossDomain:true}); to my page. I still get the same errors. – Michael Sobczak Jul 24 '15 at 14:37
0

There is a known behavior of Apex REST services that calls from the VF domain are not considered safe in the same origin policy.

To work around this the forcetk library from the force.com Javascript REST toolkit will allow you to make such calls via a proxy.

Although, I'm not sure that is necessary in this instance. You could, instead, use the @RemoteAction Apex annotation instead. Unless you're finding the request size is too big or something.

| improve this answer | |
  • I'm not having any luck getting forcetk to work. I'll post a new question on that. Appreciate your help! – Michael Sobczak Jul 24 '15 at 16:31
0

<html>
    <head>
    <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
    <script>
    // https://salesforce.stackexchange.com/questions/84766/cannot-call-apex-rest-service-from-jquery-on-visualforce-page
    $(document).ready(function () {
        var sessionId = '00AAA000000BBBB!YYYY_ZZZZ_.VVV_WWW.PPP_QQQ_RRR.SSS';
        $.ajax({
            method: "POST",
            url: 'https://csMM.salesforce.com/services/apexrest/NNN/updateLLL',
            data: `{
                "request": [
                    {
                        "action": "FFF",
                        "startDate": "2018-01-01T00:00:00.000Z",
                        "moreParameters": "..."
                    }
                ]
            }`,
            processData: false,
            // "For cross-domain requests, setting the content type to anything
            // other than application/x-www-form-urlencoded, multipart/form-data, or
            // text/plain will trigger the browser to send a preflight OPTIONS request
            // to the server"
            // On the other hand, sending Content-Type: text/plain... meets HTTP 415 Unsupported Media Type
            // The best match for JSON worked with the OPTIONS preflight
            // request (but required sending a text in data with processData set
            // to false).
            contentType: "application/json; charset=UTF-8",

            // The response comes with "Content-Type: application/octetstream" as a JSON text.
            dataType: "json",
            jsonp: false,

            // The "withCredentials" flag causes Firefox refuse sending the
            // POST after OPTIONS because Salesforce declares "do not send cookies
            // in your data requests" with the following response header,
            //  Access-Control-Allow-Credentials: false.
            //
            // On the other hand Salesforce's OPTION response also has
            //  Access-Control-Allow-Headers: X-SFDC-Request-Id,authorization,content-type
            // which seems sufficient for Firefox to proceed with POST after commenting out
            // withCredentials.
            // xhrFields: {
            //     withCredentials: true
            // },
            headers: {
                "Authorization": "Bearer " + sessionId
            }
        }).done(function( data, textStatus, jqXHR ) {
            // To let the browser access the returned data, add the
            // URL of the site hosting this HTML page in
            //   https://csMM.salesforce.com
            // under Setup / Quick Find / CORS / New.
            $('#output').text(data.field1.subfield2[0]);
        }).fail(function( jqXHR, textStatus, errorThrown ) {
            $('#error').text(textStatus);
        })
    })
    </script>
    </head>
    <body>
        Hello, world.
        <p>
        Output:
        <span id="output"></span>
        <p>
        Error:
        <span id="error"></span>
    </body>
</html>
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.