Blog Post View


This tutorial demonstrates how to retrieve client location information using Oracle Database 23ai PL/SQL by capturing the client's IP address and querying a geolocation API.

Step 1: Capture Client IP Address

When working with web applications, the client IP is often passed through proxies or load balancers. Use the X-FORWARDED-FOR header to get the original client IP:

SELECT
SUBSTR(owa_util.get_cgi_env('X-FORWARDED-FOR'), 1, 
   INSTR(owa_util.get_cgi_env('X-FORWARDED-FOR') || ',', ',') - 1) AS client_ip
FROM
dual;

This query extracts the IP address up to the first comma, which is important because X-FORWARDED-FOR can contain multiple IPs in a comma-separated list (the first one being the original client).

Alternative methods for getting the IP address:

  • SYS_CONTEXT('USERENV', 'IP_ADDRESS') — Direct database connection IP
  • owa_util.get_cgi_env('REMOTE_ADDR') — Remote address from CGI environment

Step 2: Configure Network Access Control

Before making external HTTP requests, you need to grant your database user permission to connect to external hosts:

BEGIN
DBMS_NETWORK_ACL_ADMIN.APPEND_HOST_ACE(
host => '*',
ace  => xs$ace_type(
          privilege_list => xs$name_list('connect'),
          principal_name => 'YOUR_USERNAME',
          principal_type => xs_acl.ptype_db
       )
);
END;
/

Important notes:

  • Replace 'YOUR_USERNAME' with your actual database username.
  • Using '*' as the host grants access to all external hosts; consider restricting this to specific domains like ipapi.co for better security.
  • You need appropriate privileges to execute DBMS_NETWORK_ACL_ADMIN procedures.

Step 3: Query Geolocation API

Use UTL_HTTP to call a geolocation service and retrieve location data:

DECLARE
l_http_request   UTL_HTTP.req;
l_http_response  UTL_HTTP.resp;
l_response_text  CLOB;
l_line           VARCHAR2(32767);
l_ip             VARCHAR2(100);
BEGIN
-- Get client IP address
l_ip := SYS_CONTEXT('USERENV', 'IP_ADDRESS');

-- Alternative: Get from X-FORWARDED-FOR
-- l_ip := SUBSTR(owa_util.get_cgi_env('X-FORWARDED-FOR'), 1, 
--                INSTR(owa_util.get_cgi_env('X-FORWARDED-FOR') || ',', ',') - 1);

-- Call GeoIP API
l_http_request := UTL_HTTP.begin_request(
'https://ipapi.co/' || l_ip || '/json/'
);

l_http_response := UTL_HTTP.get_response(l_http_request);

-- Read response
LOOP
UTL_HTTP.read_line(l_http_response, l_line, TRUE);
l_response_text := l_response_text || l_line;
END LOOP;

UTL_HTTP.end_response(l_http_response);

-- Display the result
DBMS_OUTPUT.PUT_LINE('Location Data: ' || l_response_text);

EXCEPTION
WHEN UTL_HTTP.end_of_body THEN
UTL_HTTP.end_response(l_http_response);
DBMS_OUTPUT.PUT_LINE('Location Data: ' || l_response_text);
WHEN OTHERS THEN
IF l_http_response.status_code IS NOT NULL THEN
    UTL_HTTP.end_response(l_http_response);
END IF;
RAISE;
END;
/

Step 4: Parse JSON Response

The API returns a JSON response that you can parse using Oracle's JSON functions:

Example API Response:

{
"ip": "102.23.54.10",
"city": "Dubai",
"region": "Dubai",
"country_name": "United Arab Emirates",
"latitude": 25.276987,
"longitude": 55.296249,
"timezone": "Asia/Dubai"
}

Parse the JSON data:

SELECT
json_value(:json_data, '$.ip') AS ip,
json_value(:json_data, '$.city') AS city,
json_value(:json_data, '$.region') AS region,
json_value(:json_data, '$.country_name') AS country,
json_value(:json_data, '$.latitude') AS latitude,
json_value(:json_data, '$.longitude') AS longitude,
json_value(:json_data, '$.timezone') AS timezone
FROM dual;

Complete Working Example

Here's a complete procedure that combines all steps:

CREATE OR REPLACE PROCEDURE get_client_location(
p_ip IN VARCHAR2 DEFAULT NULL
) AS
l_http_request   UTL_HTTP.req;
l_http_response  UTL_HTTP.resp;
l_response_text  CLOB;
l_line           VARCHAR2(32767);
l_ip             VARCHAR2(100);
BEGIN
-- Determine IP address
IF p_ip IS NOT NULL THEN
l_ip := p_ip;
ELSE
-- Try X-FORWARDED-FOR first
l_ip := SUBSTR(owa_util.get_cgi_env('X-FORWARDED-FOR'), 1, 
               INSTR(owa_util.get_cgi_env('X-FORWARDED-FOR') || ',', ',') - 1);

-- Fallback to direct IP
IF l_ip IS NULL THEN
    l_ip := SYS_CONTEXT('USERENV', 'IP_ADDRESS');
END IF;
END IF;

DBMS_OUTPUT.PUT_LINE('Querying location for IP: ' || l_ip);

-- Call GeoIP API
l_http_request := UTL_HTTP.begin_request(
'https://ipapi.co/' || l_ip || '/json/'
);

l_http_response := UTL_HTTP.get_response(l_http_request);

-- Read response
LOOP
UTL_HTTP.read_line(l_http_response, l_line, TRUE);
l_response_text := l_response_text || l_line;
END LOOP;

EXCEPTION
WHEN UTL_HTTP.end_of_body THEN
UTL_HTTP.end_response(l_http_response);

-- Parse and display results
DBMS_OUTPUT.PUT_LINE('IP: ' || json_value(l_response_text, '$.ip'));
DBMS_OUTPUT.PUT_LINE('City: ' || json_value(l_response_text, '$.city'));
DBMS_OUTPUT.PUT_LINE('Region: ' || json_value(l_response_text, '$.region'));
DBMS_OUTPUT.PUT_LINE('Country: ' || json_value(l_response_text, '$.country_name'));
DBMS_OUTPUT.PUT_LINE('Timezone: ' || json_value(l_response_text, '$.timezone'));

WHEN OTHERS THEN
IF l_http_response.status_code IS NOT NULL THEN
    UTL_HTTP.end_response(l_http_response);
END IF;
DBMS_OUTPUT.PUT_LINE('Error: ' || SQLERRM);
RAISE;
END;
/

Usage

Enable server output and execute the procedure:

SET SERVEROUTPUT ON;
EXEC get_client_location();

Or test with a specific IP:

EXEC get_client_location('8.8.8.8');

Important Considerations

  • Security: Using '*' in ACL grants access to all hosts. Restrict to specific domains in production environments.
  • API Limits: Free geolocation APIs often have rate limits. Consider using paid services or implementing caching for production use.
  • IPv6 Support: The code handles both IPv4 and IPv6 addresses. The X-FORWARDED-FOR parsing works for both formats.
  • Error Handling: Always implement proper exception handling when making external HTTP calls, as network issues can occur.
  • Oracle Wallet: For HTTPS connections, ensure your Oracle Wallet is properly configured with trusted certificates.

Conclusion

This tutorial provides a foundation for integrating geolocation services into your Oracle Database 23ai applications, enabling location-aware features and analytics.


Share this post

Comments (0)

    No comment

Leave a comment

All comments are moderated. Spammy and bot submitted comments are deleted. Please submit the comments that are helpful to others, and we'll approve your comments. A comment that includes outbound link will only be approved if the content is relevant to the topic, and has some value to our readers.


Login To Post Comment