О чём эта страница

  • Что такое «сырые байты» (raw bytes) и чем они отличаются от текста
  • Как Oracle хранит бинарные данные (RAW, BLOB)
  • Примеры преобразований: STRING ↔ RAW, HEX, Base64
  • Пример отправки BLOB в HTTP API через UTL_HTTP (поблочно)
  • Что такое Base64 при передаче в API и в чем разница?

Что такое «сырые байты» (raw bytes)

«Сырые байты» — это последовательность восьмибитных значений (octets). У них нет смысла, пока вы явно не укажете, как их интерпретировать (кодировка текста, формат изображения, структура числа и т.д.).

Ключевые моменты:

  • Это не текст: текст — это байты + кодировка (UTF‑8, CP1251 и т.п.).
  • Без знания кодировки результат интерпретации байтов как текста непредсказуем.
  • В Oracle: RAW/RAW(n) — для небольших бинарных фрагментов, BLOB — для больших потоков.
  • Для отображения или передачи бинарных данных часто используют HEX или Base64.
  • Преобразования выполняются явными функциями: HEX↔RAW, STRING↔RAW (с указанием charset), Base64 encode/decode.

Примеры: строка → байты → HEX / Base64

SQL/PLSQL примеры для Oracle.

1) Получение RAW (UTF‑8 байты) и его HEX-представления

SELECT UTL_I18N.STRING_TO_RAW('Привет, мир!', 'AL32UTF8') FROM dual;
-- В SQL-клиентах RAW обычно показывают в виде HEX.

SELECT RAWTOHEX(UTL_I18N.STRING_TO_RAW('Привет, мир!', 'AL32UTF8')) AS hex_bytes FROM dual;
-- -> D09FD180D0B8D0B2D0B5D1822C20D0BCD0B8D18021

Это шестнадцатеричное представление байтов, которое SQL Developer или sqlplus отображают для RAW.

2) Декодирование HEX → строка (UTF‑8)

SELECT UTL_I18N.RAW_TO_CHAR(HEXTORAW('D09FD180D0B8D0B2D0B5D1822C20D0BCD0B8D18021'), 'AL32UTF8') AS text
FROM dual;
-- -> Привет, мир!

3) Base64 — удобный формат для передачи бинарных данных в JSON/XML

-- Base64 текстовую строку нужно получить из RAW как VARCHAR2
SELECT UTL_RAW.CAST_TO_VARCHAR2(
         UTL_ENCODE.BASE64_ENCODE(
           UTL_I18N.STRING_TO_RAW('Привет, мир!', 'AL32UTF8')
         )
       ) AS b64_text
FROM dual;
-- -> 0J/RgNC40LLQtdGCLCDQvNC40YAh

-- Если посмотреть без CAST, вы увидите HEX-репрезентацию RAW с base64-байтами
SELECT RAWTOHEX(UTL_ENCODE.BASE64_ENCODE(UTL_I18N.STRING_TO_RAW('Привет, мир!', 'AL32UTF8')))
FROM dual;

Короткое сравнение размеров (пример):

SELECT 
  LENGTH(UTL_I18N.STRING_TO_RAW('Привет, мир!', 'AL32UTF8')) AS bytes_len,         -- байтовая длина
  LENGTH(RAWTOHEX(UTL_I18N.STRING_TO_RAW('Привет, мир!', 'AL32UTF8'))) AS hex_len,   -- HEX длина
  LENGTH(UTL_RAW.CAST_TO_VARCHAR2(UTL_ENCODE.BASE64_ENCODE(UTL_I18N.STRING_TO_RAW('Привет, мир!', 'AL32UTF8')))) AS b64_len
FROM dual;
-- HEX = 2 * bytes, Base64 ≈ 4/3 * bytes

Немного о кодировках в Oracle

  • AL32UTF8 — имя кодировки UTF‑8 в Oracle.
  • Windows‑1251 обычно обозначается как CL8MSWIN1251 в Oracle.

Пример конвертации строк:

SELECT CONVERT('Привет', 'CL8MSWIN1251', 'AL32UTF8') FROM dual;

При преобразовании строк ↔ RAW используйте UTL_I18N.STRING_TO_RAW и UTL_I18N.RAW_TO_CHAR с явным указанием кодировки.


Разбор HEX-строки D09FD180D0B8D0B2D0B5D1822C20D0BCD0B8D18021

Это UTF‑8 в hex. Разбиение по байтам и соответствие:

  • D0 9F → U+041F → П
  • D1 80 → U+0440 → р
  • D0 B8 → U+0438 → и
  • D0 B2 → U+0432 → в
  • D0 B5 → U+0435 → е
  • D1 82 → U+0442 → т
  • 2C → ,
  • 20 → пробел
  • D0 BC → м
  • D0 B8 → и
  • D1 80 → р
  • 21 → !

Результат: Привет, мир!

Декодировать в Oracle:

SELECT UTL_I18N.RAW_TO_CHAR(HEXTORAW('D09FD180D0B8D0B2D0B5D1822C20D0BCD0B8D18021'), 'AL32UTF8') FROM dual;

Пример: отправка BLOB в HTTP API (поблочно) — пояснения

Ниже — рабочий пример PL/SQL, который собирает CLOB в BLOB, отправляет его поблочно через UTL_HTTP.write_raw и читает ответ как RAW‑чанки.

DECLARE
    p_source_data clob := 'some_source_data';
    l_auth_header varchar2(2000) := 'your diadoc Auth header data...';

    v_resp_clob  CLOB;
    l_api_base_url varchar2(2000) := 'http://api.diadoc.ru/v4/endpoint';
    l_blob         BLOB;
    -- переменные для LOB/HTTP
    l_req          UTL_HTTP.req;
    l_resp         UTL_HTTP.resp;
    l_buffer       RAW(32767);        -- буфер для чтения BLOB
    l_amount       BINARY_INTEGER;
    l_offset       INTEGER := 1;
    l_buf_len      BINARY_INTEGER;
BEGIN
    -- Создаём временный BLOB и заполняем его данными из p_source_data
    DBMS_LOB.createtemporary(l_blob, TRUE);
    DBMS_LOB.OPEN(l_blob, DBMS_LOB.LOB_READWRITE);
    DECLARE
        v_pos INTEGER := 1;
        v_chunk VARCHAR2(32767 CHAR);
        v_raw RAW(32767);
        v_chunk_len INTEGER := 32767;
    BEGIN
        LOOP
            v_chunk := DBMS_LOB.SUBSTR(p_source_data, v_chunk_len, v_pos);
            EXIT WHEN v_chunk IS NULL OR LENGTH(v_chunk) = 0;
            -- Если нужна определённая кодировка для байтов  заменить UTL_RAW.CAST_TO_RAW на UTL_I18N.STRING_TO_RAW(v_chunk, 'AL32UTF8'/'CL8MSWIN1251')
            v_raw := UTL_RAW.CAST_TO_RAW(v_chunk);
            DBMS_LOB.WRITEAPPEND(l_blob, UTL_RAW.LENGTH(v_raw), v_raw);
            v_pos := v_pos + v_chunk_len;
        END LOOP;
        DBMS_LOB.CLOSE(l_blob);
    END;

    -- Начинаем HTTP-запрос
    l_req := UTL_HTTP.begin_request(l_api_base_url, 'POST', 'HTTP/1.1');
    UTL_HTTP.set_header(l_req, 'Content-Type', 'application/octet-stream'); -- отправляем сырые байты
    UTL_HTTP.set_header(l_req, 'Accept', 'application/json');
    UTL_HTTP.set_header(l_req, 'Authorization', l_auth_header);
    UTL_HTTP.set_header(l_req, 'Transfer-Encoding', 'chunked'); -- учесть поддержку на стороне сервера

    -- Пишем BLOB по частям
    l_amount := DBMS_LOB.getlength(l_blob);
    l_offset := 1;
    l_buf_len := 2000; -- безопасный размер для write_raw; можно увеличить до 32767 при тестировании
    WHILE l_offset <= l_amount LOOP
        IF l_offset + l_buf_len - 1 > l_amount THEN
            l_buf_len := l_amount - l_offset + 1;
        END IF;
        DBMS_LOB.READ(l_blob, l_buf_len, l_offset, l_buffer);
        UTL_HTTP.write_raw(l_req, l_buffer);
        l_offset := l_offset + l_buf_len;
    END LOOP;

    -- Получаем ответ
    l_resp := UTL_HTTP.get_response(l_req);

    -- Чтение ответа как RAW‑чанки и сбор в CLOB с корректной кодировкой
    DECLARE
        v_resp_chunk_raw RAW(32767);
        v_resp_chunk_clob CLOB;
        v_resp_chunk_len INTEGER;
        v_charset VARCHAR2(50) := 'CL8MSWIN1251'; -- установить согласно ожидаемой кодировке ответа
    BEGIN
        DBMS_LOB.createtemporary(v_resp_clob, TRUE);
        LOOP
            UTL_HTTP.read_raw(l_resp, v_resp_chunk_raw, 32767);
            v_resp_chunk_len := UTL_RAW.LENGTH(v_resp_chunk_raw);
            EXIT WHEN v_resp_chunk_len IS NULL OR v_resp_chunk_len = 0;
            -- Корректное преобразование: RAW -> VARCHAR2 (используя DB charset) и конвертация в AL32UTF8
            v_resp_chunk_clob := CONVERT(UTL_RAW.CAST_TO_VARCHAR2(v_resp_chunk_raw), v_charset, 'AL32UTF8');
            DBMS_LOB.writeappend(v_resp_clob, LENGTH(v_resp_chunk_clob), v_resp_chunk_clob);
        END LOOP;
        -- здесь можно обработать v_resp_clob (парсинг JSON и т.д.)
    EXCEPTION
        WHEN UTL_HTTP.end_of_body THEN
            UTL_HTTP.end_response(l_resp);
        WHEN OTHERS THEN
            dbms_output.put_line(sqlerrm);
            dbms_output.put_line(DBMS_UTILITY.FORMAT_ERROR_BACKTRACE());
    END;

EXCEPTION
    WHEN OTHERS THEN
        dbms_output.put_line(sqlerrm);
        dbms_output.put_line(DBMS_UTILITY.FORMAT_ERROR_BACKTRACE());
        BEGIN
            UTL_HTTP.end_response(l_resp);
        EXCEPTION WHEN OTHERS THEN NULL; END;
        -- освободить временные LOB
        BEGIN IF DBMS_LOB.ISTEMPORARY(l_blob) = 1 THEN DBMS_LOB.FREETEMPORARY(l_blob); END IF; END;
END;

Что такое Base64 при передаче в API и в чем разница?

Base64 — это текстовое представление бинарных данных, удобное для включения в JSON, XML или другие текстовые протоколы. Когда вы передаёте файл, изображение или произвольный BLOB через API, чаще всего вам нужно преобразовать байты в строку, потому что JSON и XML ожидают текст, а не «сырые» байты.

Кроме того, это необходимо, поскольку передаваемые данные могут содержать байты, которые не являются допустимыми символами в текстовых форматах (например, амперсанды или специальные символы в XML и JSON). Base64 помогает избежать проблем с кодировкой и интерпретацией таких данных.

Ключевые различия и заметки:

  • Формат и размер
  • HEX кодирует каждый байт двумя шестнадцатеричными символами → длина строки = 2 × bytes.
  • Base64 кодирует каждые 3 байта в 4 символа → длина ≈ 4/3 × bytes (с возможным паддингом '=').
  • Base64 обычно экономичнее по размеру по сравнению с HEX и поэтому предпочтительнее для передачи больших бинарных фрагментов.

  • Семантика и совместимость

  • Base64 — де-факто стандарт для встраивания бинарных данных в JSON и XML (MIME, data URI, многие SDK и сервисы ожидают именно base64).
  • HEX чаще используется для представления хешей (sha/sha1/md5) и отладки, но реже для встраивания файлов в JSON.

  • Безопасность

  • Base64 — это кодирование, а не шифрование. Данные остаются читаемыми после декодирования любым, кто имеет доступ. Для защиты используйте HTTPS и/или шифрование до кодирования.

  • URL и URL-safe

  • Стандартный Base64 использует символы "+" и "/" и паддинг "="; для URL иногда используют variant (base64-url), где "+"→"-" и "/"→"_".

  • Практические нюансы при потоковой обработке

  • При поблочном кодировании BLOB в Base64 полезно читать BLOB кусками, размер которых кратен 3, чтобы не вводить лишние паддинги в промежуточных частях; UTL_ENCODE.BASE64_ENCODE работает с RAW-чанками и возвращает RAW.

Примеры в Oracle:

1) Получить Base64-строку из текста (UTF-8):

SELECT UTL_RAW.CAST_TO_VARCHAR2(
         UTL_ENCODE.BASE64_ENCODE(
           UTL_I18N.STRING_TO_RAW('Привет, мир!', 'AL32UTF8')
         )
       ) AS b64_text
FROM dual;
-- -> 0J/RgNC40LLQtdGCLCDQvNC40YAh

Практика отправки через JSON: формируем объект с base64-полем и отправляем Content-Type: application/json. Не используйте заголовок Content-Transfer-Encoding: base64 для обычного HTTP/REST — этот заголовок исторически используется в MIME.

{
  "fileName": "image.png",
  "content": "iVBORw0KGgoAAAANS..."
}