SAP Gateway Part 4: Paging, Filtering, CSRF and Authorization
Implement Gateway services that behave well in production: server-side paging ($top/$skip), filtering and sorting via the request context, $inlinecount, CSRF handling, and authorization checks per operation.
Explanation
📌 KEY POINTS
✅ GET_ENTITYSET must apply $filter, $orderby, $top and $skip in the database SELECT — not after reading everything.
✅ Use io_tech_request_context: get_osql_where_clause_convert, get_top, get_skip, get_orderby.
✅ Fill es_response_context-inlinecount when $inlinecount=allpages is requested.
⚠️ Never disable CSRF protection to "fix" 403 errors; fix the client instead.
━━━━━━━━━━━━━━━━━━━━
▶ WHERE WE ARE
Parts 1–3 built and registered a service. The most common production problem of SEGW services: GET_ENTITYSET selects everything and ignores query options. This part fixes that.
━━━━━━━━━━━━━━━━━━━━
▶ 1. THE ANTI-PATTERN
SELECT * FROM zpr_head INTO TABLE et_entityset. — then the framework applies $top/$skip in memory ("generic paging"). With 2 million rows, every list request reads 2 million rows.
━━━━━━━━━━━━━━━━━━━━
▶ 2. READ QUERY OPTIONS FROM THE REQUEST CONTEXT
- WHERE: io_tech_request_context->get_osql_where_clause_convert( ) returns an ABAP SQL WHERE clause built from $filter (property names mapped to ABAP field names). Use it in a dynamic WHERE — it is generated safely by the framework from the model.
- Paging: io_tech_request_context->get_top( ) and get_skip( ).
- Sorting: io_tech_request_context->get_orderby( ) — map to an ORDER BY clause.
- Count: io_tech_request_context->has_inlinecount( ) → run a COUNT with the same WHERE and set es_response_context-inlinecount.
- $select: get_select_with_mandtry_fields( ) if you want to read only requested columns.
━━━━━━━━━━━━━━━━━━━━
▶ 3. SORT BEFORE PAGING
$skip/$top without a stable order return random pages. Always add a default ORDER BY (e.g. by key) when the client sends none.\n\n━━━━━━━━━━━━━━━━━━━━\n▶ 4. CSRF\n• Gateway requires an X-CSRF-Token for modifying requests: clients fetch it with GET + "X-CSRF-Token: Fetch".
- UI5 models do this automatically; custom clients must implement it.
- Disabling CSRF protection for a service (possible via ICF/service settings) opens it to cross-site request forgery — only for tightly controlled technical scenarios with other protection.
━━━━━━━━━━━━━━━━━━━━
▶ 5. AUTHORIZATION
- Service level: S_SERVICE via roles (frontend).
- Business level: AUTHORITY-CHECK in each DPC_EXT method (read → activity 03, create → 01, change → 02, delete → 06), or filter data by authorized organizational units.
- Return a business exception with a clear message on failure.
━━━━━━━━━━━━━━━━━━━━
▶ STEP BY STEP
- Replace SELECT * in GET_ENTITYSET with the pattern in the code example.
- Test $filter, $orderby, $top/$skip and $inlinecount in GW client.
- Compare runtime with ST05 before and after.
- Add AUTHORITY-CHECK per operation.
✅ CHECKPOINT: your Gateway services page, filter, count and authorize on the server. RAP module: all topics covered.
Code example
METHOD purchaserequestse_get_entityset. " ---- 1. business authorization (display) ---- AUTHORITY-CHECK OBJECT 'ZPR_AUTH' ID 'ACTVT' FIELD '03'. IF sy-subrc <> 0. RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception EXPORTING textid = /iwbep/cx_mgw_busi_exception=>business_error message = 'No authorization to display purchase requests'. ENDIF. " ---- 2. query options from the request context ---- DATA(where) = io_tech_request_context->get_osql_where_clause_convert( ). " from $filter DATA(top) = io_tech_request_context->get_top( ). DATA(skip) = io_tech_request_context->get_skip( ). DATA(orderby) = io_tech_request_context->get_orderby( ). " ---- 3. stable order (default by key) ---- DATA(order_clause) = `REQUEST_ID`. " default: by key IF orderby IS NOT INITIAL. " technical property names -> DB columns (map explicitly if they differ) order_clause = concat_lines_of( table = VALUE string_table( FOR o IN orderby ( |{ o-property } { COND string( WHEN o-order = 'desc' THEN `DESCENDING` ELSE `ASCENDING` ) }| ) ) sep = `, ` ). ENDIF. " ---- 4. paged, filtered, sorted SELECT in the database ---- DATA(max_rows) = COND i( WHEN top > 0 THEN top + skip ELSE 100 ). SELECT request_id AS requestid, description, status, total_amount AS totalamount, currency FROM zpr_head WHERE (where) ORDER BY (order_clause) INTO CORRESPONDING FIELDS OF TABLE @et_entityset UP TO @max_rows ROWS OFFSET @skip. " ---- 5. $inlinecount=allpages ---- IF io_tech_request_context->has_inlinecount( ) = abap_true. SELECT COUNT(*) FROM zpr_head WHERE (where) INTO @DATA(total). es_response_context-inlinecount = total. ENDIF.ENDMETHOD. " ===== Tests in /IWFND/GW_CLIENT =====" .../PurchaseRequestSet?$filter=Status eq 'S'&$orderby=TotalAmount desc" &$top=20&$skip=40&$inlinecount=allpages" ST05: statement must contain the WHERE + LIMIT/OFFSET, not a full table read " ===== CSRF: client side (never disable it on the server) =====" GET service root X-CSRF-Token: Fetch -> token + cookies" POST/PUT/MERGE/DELETE with X-CSRF-Token: <token> + same cookiesReal project scenario
A purchase request list app became slower every month. ST05 showed that each request read the entire ZPR_HEAD table (1.8 million rows) because GET_ENTITYSET used SELECT * and relied on generic paging.
The team implemented filtering, sorting, paging and inline count from the request context, with a default sort by key. Response time dropped from 12 seconds to under 300 ms, and memory dumps on the application server during peak hours disappeared.
Common mistakes
- SELECT * in GET_ENTITYSET with in-memory paging.
- Paging without a stable ORDER BY.
- Ignoring $inlinecount, so UIs show wrong totals.
- Building WHERE clauses by string concatenation of user input.
- Disabling CSRF protection to fix client errors.
- No business authorization checks in DPC_EXT.
Best practices
- Push $filter, $orderby, $top, $skip into the SELECT.
- Use the request context helper methods for WHERE and paging.
- Always define a default sort order.
- Implement $inlinecount with the same WHERE.
- Keep CSRF on; check authorization per operation.
Interview angle
Questions: "How do you implement paging and filtering in a SEGW service?", "What is generic paging and why is it a problem?", "How is CSRF handled in Gateway?"
Strong answer: in GET_ENTITYSET read $filter via get_osql_where_clause_convert, $top/$skip via get_top/get_skip, sorting via get_orderby, and apply them in the database SELECT (UP TO / OFFSET, ORDER BY) with a default key order; set es_response_context-inlinecount when requested. Generic paging reads all rows first. CSRF: clients fetch a token and send it with writes; don't disable protection. Check business authorization per operation.
Practice this lesson
Quick check
1. Which method converts an OData $filter into an ABAP SQL WHERE clause in SEGW?
2. A UI shows "57 of 57" but the backend has 12,000 matching rows. What is wrong?
3. A mobile service returned 100,000 rows because the backend ignored $top. Which change fixes the root cause?
Interview questions
1. Trace an OData request through Gateway and say where each type of error originates.
Answer it in your own words first, then compare.
2. A SEGW list service slows down as data grows. What do you check in GET_ENTITYSET?
Answer it in your own words first, then compare.
Reference-verified: the steps are checked against official SAP documentation and tutorials, and object and field names in the sample code are checked for consistency. The code has not been compiled or run on an SAP system by ERPClimb. Syntax marked release-dependent varies by SAP release, so check it in your own system.