PDA

View Full Version : BLOB mit BASE64 encoden für REST Service im IBM i IWS



Seiten : [1] 2

ismiavoiwuascht
25-04-20, 12:26
Hallo zusammen



Ich habe die Anforderung aus einem BLOB Feld einer AS400 Tabelle, welches ein Programmobjekt für eine Android App beinhält (ca. 5 MB), mittels REST Service über den IBM i integrated web services server, dieses als BASE64 String zur Verfügung zu stellen.



Grundsätzlich dachte ich, das mit folgendem SQL zu machen, aber das funktioniert leider nicht, da der BLOB Inhalt zu groß ist. SYSTOOLS.BASE64ENCODE ist auf 2732 Zeichen beschränkt.:rolleyes:




Select cast(systools.base64encode(Datei) AS clob ccsid 1208) as Datei from Versionsverwaltung

where programm = 'StaplerTerminal.apk' and versionsnr = '1.0.5';



Daher war dann mein Ansatz, das BLOB Feld in einem CLOB (CCSID 1208) als BASE64 zu speichern. Dies wollte ich mittels apr_base64_encode_binary bewerkstelligen. Die Funktion habe ich aus einem Foreneintrag übernommen. Nur komme ich auf keinen grünen Zweig, da ich, egal was ich anstelle, immer falsche BASE64 Daten erzeuge.






dcl-s Blob sqltype(BLOB:16773100);

dcl-s Clob sqltype(CLOB:16773100) CCSID(1208);

dcl-s DateiClob sqltype(CLOB:16773100) CCSID(1208);

dcl-s ClobOut sqltype(CLOB:16773100) CCSID(1208);

dcl-s LocString like(Clob);

dcl-s LocBase64 like(Clob);



exec sql

select datei into :Blob,

from Versionsverwaltung

where programm = trim(:Programm) and versionsnr = trim(:Version)

limit 1;



if sqlcode = 0;

ClobOut = getBase64(Blob);

ClobOut_Len = %len(%trimr(ClobOut));

exec sql

update Versionsverwaltung set DateiClob = :ClobOut

where aktuell='1' and

where programm = trim(:Programm) and versionsnr = trim(:Version);

endif;



*inlr = *on;

return;



// __________________________________________________ _____________

// getBASE64 Encoded String ____________________________________



dcl-proc getBase64;

dcl-pi *n like(Clob) rtnparm; // Encoded String

PiString like(Blob);

end-pi;



dcl-s LocString like(Blob) inz; // Source String

dcl-s LocBase64 like(Clob) inz; // Encoded String

dcl-s Loclength int(10); // StringLength

dcl-s LocEnclen int(10); // EncodedLength



LocString = %trimr(PiString);

LocLength = %len(%trimr(LocString)); // String-Length



LocEnclen = encbase64bin(%addr(LocBase64): // Encode Base64

%addr(LocString):LocLength);



if LocEnclen > *zero; // Encoded Length

return %subst(LocBase64:1:LocEnclen - 1);// without last null

else;

return *blanks;

endif;



end-proc;





Hat jemand eine Idee, was ich hier falsch mache oder ist meine Herangehensweise falsch? Bin für jeden Tipp dankbar.



Grüße, Ingo

RobertMack
25-04-20, 12:57
Von Scott Clement gibt es ein "Base64 Encode/Decode for ILE RPG":
https://www.scottklement.com/base64/

Falls Du ein Beispiel brauchst, müsste ich eins raussuchen...

(nedoisiswuascht ;- )

Fuerchau
25-04-20, 16:26
Dies könnte auch mit deinem Basiscode der Quelle zusammenhängen.
Du speicherst das zwar als Blob, aber ist es auch Codepage 1252 oder UTF8?
Vielleicht ist es ja auch EBCDIC (273, o.ä.), somit kommt beim Decode auch nur EBCDIC wieder raus.

ismiavoiwuascht
25-04-20, 18:54
Da geb ich dir grundsätzlich recht, was die Codepages betrifft. Mich verwirrt nur, dass das SQL (getestet mit kleinem Programmobjekt) richtig codierte Daten zurückliefert.

Select cast(systools.base64encode(Datei) AS clob ccsid 1208) as Datei from Versionsverwaltung
where programm = 'StaplerTerminal.apk' and versionsnr = '1.0.5';

Fuerchau
26-04-20, 00:31
Das ist schon korrekt wenn du auf dem selben System codierst und decodierst.
Da du BAS64 aber wohl mit Nicht-IBMi austauschen willst wollen die wohl beim dekodieren keinen EBCDIC-Code.
Du kodierst das A in 273 und dekodierst dies in UTF8 (1252) = Á

Die andere Frage ist, nimmt Scott auch dieselbe Base64-Zeichenfolge wie die Windows-Welt?

https://de.wikipedia.org/wiki/Base64

Rainer Ross
26-04-20, 13:08
Hallo Ingo,
die Anforderung ist nicht so ganz einfach, weil man hier leicht an die 16MB Grenze im RPG stößt. Deine Idee ist schon richtig mit der API apr_base64_encode_binary, denn die kann auch über Pointer anprogrammiert werden und da ist die Grenze viel größer. Ich habe eine Lösung gebaut, die für CLOB Daten funktioniert. BLOB geht genauso. Es ist zu beachten, dass die API die Daten im UTF8 Format = CCSID(*UTF8) verlangt.

Damit es mit dem Compilieren einfacher ist, habe ich ein CL gebaut, das ein BNDDIR für die BASE64 API's erstellt



/************************************************** *******************/
/* */
/* CREATE BASUTL BINDING-DIRECTORY - BASE64 */
/* */
/****************** */
/* R.ROSS 03.2017 * */
/************************************************** *******************/

PGM

DCL VAR(&LIB) TYPE(*CHAR) LEN(10) VALUE(DEVO)

MONMSG MSGID(CPF0000)

/************************************************** *******************/
/* VERARBEITUNG */
/************************************************** *******************/

DLTBNDDIR BNDDIR(&LIB/BASUTL)
CRTBNDDIR BNDDIR(&LIB/BASUTL) TEXT('BASE64 Utilities')

ADDBNDDIRE BNDDIR(&LIB/BASUTL) OBJ((QSYSDIR/QAXIS10HT *SRVPGM))

/************************************************** *******************/
/* ENDE */
/************************************************** *******************/
ENDE: ENDPGM
/************************************************** *******************/



Anbei das RPG für das Erzeugen von BASE64-Daten aus CLOB oder BLOB



ctl-opt main(main) alloc(*teraspace) bnddir('BASUTL');
//------------------------------------------------------------------//
// //
// Encode BASE64 with Pointer //
// //
//----------------- //
// R.Ross 04.2020 * //
//------------------------------------------------------------------//
// BASE64 - Encode String - SRVPGM(QSYSDIR/QAXIS10HT) //
//------------------------------------------------------------------//

dcl-pr encBase64Bin int(10) extproc('apr_base64_encode_binary');
PiEnc_p pointer value; // Encoded-Pointer
PiSrc_p pointer value; // Source-Pointer
PiSrclen int(10) value; // Source-Length
end-pr;

//------------------------------------------------------------------//
// MemCopy //
//------------------------------------------------------------------//

dcl-pr memcpy extproc(*dclcase);
mctarget pointer value;
mcsource pointer value;
mclength uns(10) value;
end-pr;

//------------------------------------------------------------------//
// Array BASE64 Ergebnis //
//------------------------------------------------------------------//

dcl-ds DsBase64 qualified;
String_p pointer;
Length int(10);
end-ds;

//------------------------------------------------------------------//
// Main //
//------------------------------------------------------------------//
dcl-proc Main;

dcl-s LocClob SQLType(CLOB:5000000) ccsid(*utf8); // 5 MB
dcl-s LocClob64 SQLType(CLOB:5000000) ccsid(*utf8); // 5 MB

exec sql set :LocClob = 'test';

// Daten in BASE64 codieren

DsBase64 = encodeBase64(%addr(LocClob_data):LocClob_len);

// BASE64-Daten aus Pointer in CLOB LocClob64 laden - test -> dGVzdA==

memcpy(%addr(LocClob64_data):DsBase64.String_p:DsB ase64.Length);
LocClob64_len = DsBase64.Length;

end-proc;
//------------------------------------------------------------------//
// Encode Data to BASE64 //
//------------------------------------------------------------------//
dcl-proc encodeBase64;
dcl-pi *n likeds(DsBase64); // Encoded Data
PiString_p pointer const; // String Pointer
PiLength int(10) const; // String Length
end-pi;

dcl-ds PsBase64 likeds(DsBase64) inz;

dcl-s LocEnclen int(10); // Encoded Length

if PiLength < 50;
PsBase64.String_p = %alloc(PiLength * 10); // alloc Pointer
else;
PsBase64.String_p = %alloc(PiLength * 5); // alloc Pointer
endif;

LocEnclen =
encBase64Bin(PsBase64.String_p:PiString_p:PiLength );

if LocEnclen > *zero; // Encoded Length
PsBase64.Length = LocEnclen - 1; // without last null
endif;

return PsBase64;

end-proc;
//------------------------------------------------------------------//




Bei Fragen kannst Du dich gern an mich wenden

Herzliche Grüße
Rainer

Rainer Ross
26-04-20, 13:48
Hallo Ingo,

noch ein Tipp. Bei einem CLOB-Feld wie diesem

dcl-s Clob sqltype(CLOB:16773100) CCSID(1208);

erzeugt der Compiler eine Struktur mit 2 Feldern

CLOB_Data für die Daten und
CLOB_LEN für die Länge der Daten

In diesem Fall musst Du die Adresse der Daten nicht mit %Addr(CLOB)
sondern mit %Addr(CLOB_Data) an die Prozedur übergeben.

@Forum - wenn es eine einfachere Lösung gibt, dann lasst es mich wissen.

Herzliche Grüße
Rainer

ismiavoiwuascht
27-04-20, 10:41
Hallo Rainer

Ja, das war's, danke gleich mal. Hab mit CLOB anstelle von CLOB_Data das Eincoding gemacht. Steht dann auch der richtig codierte Inhalt in der RPG Clob Variablen. Aber eine Frage hab ich noch, wenn ich nun die CLOB Spalte (utv-8) in der Tabelle mittels SQL mit dieser Hostvariablen aktualisiere steht da Schrott drinnen. Muss ich da noch irgendwo konvertieren?

Soll BASE64: UEsDBBQAAAAIACEIIQLtOXirrAE
Ist BASE64: 䅢Ă˜AAɁÅɉؓ����AŒŁA偁A觕

lg, Ingo

Fuerchau
27-04-20, 11:49
Wie schaust du dir den Inhalt an?
Base64 beibt ASCII-Code!
Im Debugger kannst du das nur Hex kontrollieren.
Wenn du den Base64-Wert per SQL in eine UTF-8 -Spalte schreibst kannst du ihn per SQL in 273 wieder ansehen.

Du kannst den Inhalt auch temporär in eine Char/UCS2-Variable schieben. Die Umwandlung erfolgt dann auch automatisch.

ismiavoiwuascht
27-04-20, 12:27
Im Debug sehe ich den richtigen Inhalt


EVAL locClob64 LOCCLOB64_LEN OF LOCCLOB64 = 7206264
LOCCLOB64_DATA OF LOCCLOB64 =
....5...10...15...20...25...30...35...40...45...50 ...55...60
1 'UEsDBBQAAAAIACEIIQLtOXirrAEAAEkEAAAVAAAAYXNzZXRzL 0VNREtDb25m'
61 'aWcueG1sjVNLb5tAEL5Xyn+Ycq3AYKdNZGFHkZ1WVUVVyaSHX qzJMuBVYNda'
121 '1k7Jr+/gxyYo0FbitDPfY74Z4pvfVQl7MrXUauZFQegBKaEzqYqZd59+9 q+9'
181 'm3n83vfTjayBP1SAO6uhIEUGLWWQabGrSNkAFhtUBdXAZdu2n ytQYQMCdzWB'

Variablendefinition: dcl-s LocClob64 sqltype(CLOB:16773100) CCSID(*UTF8);

Jobdefinitionsattribute:

Sprachen-ID . . . . . . . . . . . . . . . . . . . : DEU Landes- oder Regions-ID . . . . . . . . . . . . . : AT
ID des codierten Zeichensatzes (CCSID) . . . . . : 65535
Standard-ID des codierten Zeichensatzes . . . . . : 273
Steuerung für Zeichen-ID . . . . . . . . . . . . : *DEVD

Die Abfrage und das Ergebnis der Daten über Webservice IWS sieht dann so aus:

566

565


lg, Ingo