Testing OData $batch Processing with Content ID in SAP Gateway
This page is for explaining how to test the OData $batch services with Content ID, which is implemented in the H2G.
In this H2G, we’re going through the steps of testing OData $batch services with Content ID by REST Client.
In the test steps, we’ll create a $batch request that contains one parent entity with Content ID $100, and two children.
Two testing tools are explained:
SAP Gateway Client
In the SAP tx SEGW, find your Gateway project and select “Service Maintenance” > “Your GW System” > “SAP Gateway Client”.
In a SAP Gateway Client tool, enter following request information:
Note: SAP Gateway Client handles all the authentication info such as X-CSRF-Token or Basic Auth behind the scenes.
- HTTP Method = POST
- Request URI = the URL has $batch suffix
- Content-Type = multipart/mixed;boundary=batch_mybatch
- Payload body = enter the batch script
And execute it – confirm the successful response.
POSTMAN REST client
Start a POSTMAN REST client. Type the URL of your OData service document URL (the URL that ends with “_SRV/”). Set following params:
- HTTP GET
- Content-Type = multipart/mixed;boundary=batch_mybatch
- X-CSRF-Token = Fetch
- Authentication – (basic auth credentials)
Execute the request by pressing “Send”. You should be able to obtain the returned x-csrf-token value. Copy it – this is required to execute HTTP POST later.
Paste the token value for X-CSRF-Token param in the header. Set following params:
- HTTP POST – the URL has $batch suffix
- Content-Type = multipart/mixed;boundary=batch_mybatch
- X-CSRF-Token = <token>
- Authentication – (basic auth credentials)
Now your POSTMAN is fully ready for sending the $batch payload. Click on “Body” and select raw.
Here you’ll enter the payload. (Explanation follows)
01 --batch_mybatch 02 Content-Type: multipart/mixed; boundary=changeset_mychangeset1 03 04 --changeset_mychangeset1 05 Content-Type: application/http 06 Content-Transfer-Encoding: binary 07 08 POST HeaderSet HTTP/1.1 09 Content-Type: application/xml 10 Content-Length: 1021 11 Content-ID: 100 12 13 <?xml version="1.0" encoding="utf-8"?> 14 <entry xml:base="http://..._SRV/" 15 xmlns="http://www.w3.org/2005/Atom" 16 xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" 17 xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"> 18 <content type="application/xml"> 19 <m:properties> 20 <d:Id>1</d:Id> 21 <d:Text>New parent entity</d:Text> 22 </m:properties> 23 </content> 24 </entry> 25 26 --changeset_mychangeset1 27 Content-Type: application/http 28 Content-Transfer-Encoding: binary 29 30 POST $100/ToItems HTTP/1.1 31 Content-Type: application/xml 32 Content-Length: 1021 33 34 <?xml version="1.0" encoding="utf-8"?> 35 <entry xml:base="http://..._SRV/" 36 xmlns="http://www.w3.org/2005/Atom" 37 xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" 38 xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"> 39 <content type="application/xml"> 40 <m:properties> 41 <d:ParentId></d:ParentId> 42 <d:Id>000010</d:Id> 43 <d:Text>First child entity</d:Text> 44 </m:properties> 45 </content> 46 </entry> 47 48 --changeset_mychangeset1 49 Content-Type: application/http 50 Content-Transfer-Encoding: binary 51 52 POST $100/ToItems HTTP/1.1 53 Content-Type: application/xml 54 Content-Length: 1021 55 56 <?xml version="1.0" encoding="utf-8"?> 57 <entry xml:base="http://..._SRV/" 58 xmlns="http://www.w3.org/2005/Atom" 59 xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" 60 xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"> 61 <content type="application/xml"> 62 <m:properties> 63 <d:ParentId></d:ParentId> 64 <d:Id>000020</d:Id> 65 <d:Text>Second child entity</d:Text> 66 </m:properties> 67 </content> 68 </entry> 69 70 --changeset_mychangeset1-- 71 72 --batch_mybatch--
#01 – The beginning of the $batch request. It has to start with “–” with the batch name, which is the same name you defined in the header of Content-Type = multipart/mixed;boundary=batch_mybatch.
#02 – Content type definition of the changeset.
#03 – This line space is very important. At least one “carriage return line feed” is required.
#04 – The beginning of the changeset. It has to start with “–” with the changeset name, which is the same name you defined in the line #02.
#05/06 – Conventional value for Content-Type and Content-Transfer-Encoding.
#07 – At least one “carriage return line feed” is required.
#08/09 – POST request for HeaderSet entity.
#10 – Content-Length value must be equal to or greater than the real length of the data below.
#11 – A line of the Content ID value.
#12 – At least one “carriage return line feed” is required.
#13 to 24 – The HeaderSet payload. You can obtain this payload format via Read operation. If you obtain the payload via Read, that should contain the tags like <id/>, <title/>, <updated/>, <category/>, and <link/>. You can keep them in this $batch payload or just discard them – the OData services simply ignore them.
#25 – At least one “carriage return line feed” is required.
#26 – As the next entity is a part of the same changeset, it has the same value with the line #04.
#27 to 29 – Same rule follows in the ItemSet.
#30 – POST request for ItemSet entity. As you see, the Content ID is being used.
#31 to 33 – Same rule follows in the ItemSet.
#34 to 46 – The ItemSet payload. Just like you did for HeaderSet entity, you can obtain this payload format via Read operation. Please note this payload has the empty value for <parentID> tag. During OData $batch processing, this value will be handled correctly via the Content ID value.
#47 to 69 – The second HeaderItem POST request.
#70 – The closing of the changeset. It has to start with “–” with the changeset name AND closing “–” characters.
#71 – At least one “carriage return line feed” is required.
#72 – The closing of the batch request. It has to start with “–” with the batch name AND closing “–” characters.
Let’s execute the $batch request by pressing Send button. If the $batch processing is done correctly, you should be able to see “201 Created” responses for each entity in the returned payload body.
Note: As written, the line spacing is critical (it is a part of the standard OData specification).
Time to experiment further… try set the remote breakpoint in the ABAP editor – and see how the changeset_begin, changeset_process, and changeset_end are called sequentially.
Hi Kenichi Unnai,
I exactly followed you steps for batch post but i am getting following error in the method.
Thanks
Siva
Hi Siva,
All I can tell you right here is to debug the gc_method_get. You'll be able to tell why you get the malformed syntax error..
Hello Former Member .
Firstly start your body code with the code above (replacing the existing code from line 1 to line 5)
--batch_123
Content-Type: multipart/mixed; boundary=changeset_123
--changeset_123
Content-Type: application/http
Content-Transfer-Encoding: binary
At the end the changeset and the batch must be closed
--changeset_123--
--batch_123--
Hope this would help 😉
Hi,
I tried like u told. But its giving me 500: Internal Server error
Hi .
If it were a problem of the BATCH script formatting it would had returned the code 400 bad request,
Code 500 does not have to do with the formatting of the BATCH ,
check your header parameters please if are alimented correctly , if yes then put a break point in the method you are calling via the batch (POST GET PUT) .
Thnx .
Toni
Hi,
The Changeset_process method code was right. The only change I did now is, I given "--" instead of "-" in payload:
--batch_mybatch
Content-Type: multipart/mixed; boundary=changeset_mychangeset
--changeset_mychangeset
Content-Type: application/http
Content-Transfer-Encoding: binary
It's now working fine. Thanks for your help. Excellent post by Kenichi Unnai
You're welcome 😉
Hello Kenichi Unnai .
Actually I am trying to use the batch to improve the performance of my custom ODATA service that creates SalesOrders using a Bapi .
The payload we are using is that of the type deep entity (each XML payload contains all the data of the header and item ).
I was wondering if there is a way to tell that I am using the $batch processing in order to set a different processing logic when we are in $batch.
Is there any chance i can have inside an internal table all the Payloads of the change sets so i can populate all the tables of the BAPI in one shot and if it ever fails for any kind of error in any of the item , the posting (update/create) should result in an error and not be executed.
Actually the processing of the payloads is done sequentially , which means if I am going to create a document with 4 positions and supose position 3 should block the document creation , we fristly create the document then for the second line we go on modify of that document adding an item and so on , but this is kind of wrong .
Because if one of the items should go wrong the document must not be created at alla, so thats why I need to have all the items in one Internal Table so i can do the create or update calling the BAPI only once per document .
Any idea or solution on this would be appreciated.
Thanks
Toni
Hi Toni,
Just caught up with your question.
Are you familiar with the LUW of ABAP? As all the $batch execution is in the same LUW scope, if you execute BAPIs inside, it will be rolled back in case any BAPI execution fails.
Best regards,
Ken
Hi Ken.
Yes I am familiar with the Logical Unit of Work 🙂 .
In fact my main concern was how to handle all the payloads infos in one internal table , but actually i solved my issue using 3 FMs of the same Function Group .
1- Z_APPEND_BATCH "-> append payloads inside global ITAB of the FG
2- Z_RETRIVE_ITAB_BATCH "->retrive the ITAB with all payloads once we are at the last changeset .
3- Z_REFRESH_ITAB_BATCH "-> clear the ITAB for the next uses .
Inside the Loop of the table IT_CHANGESET_PROCESS used to call my 1-st FM in order to collect all the data in the GLOBAL Internal Table of the FG .
In order to find the last request we set a logic with a client so we decided to add a field to the structures of the payload that showed the last row of that particular SO .
So after looping all the changesets I had all the payloads inside my Itab and run the control on the last record in order to set the logic to populate the the BAPI's structures and tables right after the check.
Now I have only one question if you have experienced it before ,
Since I am collecting all the payloads before calling the BAPI at the last call , the method needs a response in the IT_CHANGESET_RESPONSE parameter ,
but unless I execute the BAPI cant know the fate of the SO if it will be created or will have problems, and still need to send a response back .
Can you please address any method how to send my own custom responses , of type S, I or W ,
Already tried some methods using message containers but the custom message wont display unless i throw an exception , which despite the type of message I used to create , turns on display an error message .
Thanks again.
Toni.
Hello Former Member,
I am trying to pass Header and Line Item in the payload.
I could find the header in the changeset_begin method but I am not able to find the line item. It show only one entity type ie the header.
This is the payload I am sending , Request you to help me on this
--batch
Content-Type: multipart/mixed; boundary=changeset_01869434-0005-0001
--changeset_01869434-0005-0001
Content-Type: application/http
Content-Transfer-Encoding: binary
POST SalesOrders HTTP/1.1
Content-Type: application/atom+xml
Content-Length: 1021
<?xml version="1.0" encoding="UTF-8"?>
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices">
<content type="application/xml">
<m:properties>
<d:Singleshipmentindicator>X</d:Singleshipmentindicator>
<d:Salesordersimulation>false</d:Salesordersimulation>
<d:Salesordernumber>0</d:Salesordernumber>
<d:Po/><d:Requesteddate>20170523</d:Requesteddate>
<d:Customerid>3272</d:Customerid>
<d:Salesorganization>3020</d:Salesorganization>
<d:Distributionchannel>30</d:Distributionchannel>
<d:Division>00</d:Division>
<d:Shipmentinstruction/>
<d:Notestoreceiver/>
<d:Shiptopartnerid>0000003272</d:Shiptopartnerid>
</m:properties>
</content>
</entry>
--changeset_01869434-0005-0001
Content-Type: application/http
Content-Transfer-Encoding: binary
POST OrderItems HTTP/1.1
Content-Type: application/atom+xml
Content-Length: 1021
<?xml version="1.0" encoding="UTF-8"?>
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices">
<content type="application/xml">
<m:properties>
<d:Quantity>1</d:Quantity>
<d:UnitofMeasure>PC</d:UnitofMeasure>
<d:RequestedDeliveryDate>20170523</d:RequestedDeliveryDate>
<d:Product>358</d:Product>
<d:SalesOrderNumber/>
<d:ItemNumber>000010</d:ItemNumber>
<d:Currency>USD</d:Currency>
</m:properties>
</content>
</entry>
--changeset_01869434-0005-0001--
--batch--
Thank you for your helpful information!!
We succeeded testing odata batch processing in our backend environment.
Hello Kenichi,
I am getting 500 error. My payload is below. Could you please help me fixing the issue.
SPAN {
font-family: “Courier New”;
font-size: 10pt;
color: #000000;
background: #FFFFFF;
}
.L1S32 {
color: #7D9EC0;
}
.L1S33 {
color: #009300;
}
-batch_mybatch
Content-Type: multipart/mixed; boundary=changeset_mychangeset1
-changeset_mychangeset1
Content-Type: application/http
Content-Transfer-Encoding: binary
POST HeaderSet HTTP/1.1
Content-Type: application/xml
Content-Length: 1021
Content-ID: 100
<?xml version=“1.0” encoding=“utf-8”?>
<entry xml:base=“http://…_SRV/”
xmlns=“http://www.w3.org/2005/Atom”
xmlns:m=“http://schemas.microsoft.com/ado/2007/08/dataservices/metadata”
xmlns:d=“http://schemas.microsoft.com/ado/2007/08/dataservices”>
<content type=“application/xml”>
<m:properties>
<d:Id>1</d:Id>
<d:Text>New parent entity</d:Text>
</m:properties>
</content>
</entry>
-changeset_mychangeset1
Content-Type: application/http
Content-Transfer-Encoding: binary
POST $100/HeaderItemNav HTTP/1.1
Content-Type: application/xml
Content-Length: 1021
<?xml version=“1.0” encoding=“utf-8”?>
<entry xml:base=“http://…_SRV/”
xmlns=“http://www.w3.org/2005/Atom”
xmlns:m=“http://schemas.microsoft.com/ado/2007/08/dataservices/metadata”
xmlns:d=“http://schemas.microsoft.com/ado/2007/08/dataservices”>
<content type=“application/xml”>
<m:properties>
<d:ZParentId></d:ZParentId>
<d:Id>000010</d:Id>
<d:Text>First child entity</d:Text>
</m:properties>
</content>
</entry>
-changeset_mychangeset1
Content-Type: application/http
Content-Transfer-Encoding: binary
POST $100/HeaderItemNav HTTP/1.1
Content-Type: application/xml
Content-Length: 1021
<?xml version=“1.0” encoding=“utf-8”?>
<entry xml:base=“http://…_SRV/”
xmlns=“http://www.w3.org/2005/Atom”
xmlns:m=“http://schemas.microsoft.com/ado/2007/08/dataservices/metadata”
xmlns:d=“http://schemas.microsoft.com/ado/2007/08/dataservices”>
<content type=“application/xml”>
<m:properties>
<d:ZParentId></d:ZParentId>
<d:Id>000020</d:Id>
<d:Text>Second child entity</d:Text>
</m:properties>
</content>
</entry>
-changeset_mychangeset1-
-batch_mybatch-
below is the response:
Error in log: Dereferencing of the NULL reference
I debugged the code and found that it is failing here. This appears to be an issue with format of dataload only: