I am using Delphi 10.2.3 to consume a SOAP web service. My problem is web service replies are not assigned to response classes defined in wsdl generated using WSDLImp.exe.
I see that raw response XML contains all information in it as can be seen below:
HTTP/1.1 200
X-HP-CAM-COLOR: V=1;ServerAddr=5F+cgNOCZPdfvCQ4naSfjw==;GUID=1|xpjJ6o0v_kd3rKz0c1ASIUXW--xZpHqeH8lJ3S2l30SxOi2DzpzfGfLpdqdxt0lg|L2VhcnNpdi93cy9FYXJzaXZXZWJTZXJ2aWNl
Content-Type: text/xml;charset=utf-8
Transfer-Encoding: chunked
Date: Wed, 24 Oct 2018 15:54:46 GMT
Server: EARSIV
Strict-Transport-Security: max-age=157680000
Set-Cookie: cookiesession1=1BEDB4A4PX73WJCTCIRPOUP46FNNFC28;Path=/;HttpOnly
1da
<?xml version="1.0" ?><S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"><S:Body><ns2:faturaOlusturResponse xmlns:ns2="http://service.earsiv.uut.cs.com.tr/"><return xmlns=""><resultCode>AE00067</resultCode><resultExtra></resultExtra><resultText>Web servis çağrısı "input" alanından gerekli bilgiler elde edilemedi. Eksik alanlar: null bu alan(lar)ın boş olmadığından emin olunuz!</resultText></return></ns2:faturaOlusturResponse></S:Body></S:Envelope>
0
return in faturaOlusturResponse is nil as I see while debugging. Trying to access to return.resultCode raises an access violation.
What you see is correct that there are garbage chars just before the line XML starts and right after it. I suspected that maybe the reason.
But;
1- I see Fiddler has no problem parsing this and displaying formatted XML view with all correct information.
2- I tried to use OnAfterExecute() event of THTTPRIO. I see that SOAPResponse.Size is 474 which is equal to XML line length in above raw response.
I cannot think of what maybe the problem and could not fix it. Internet searches did not help.
Any help is appreciated.
Thanks.
WSDL URL: https://earsiv.efinans.com.tr/earsiv/ws/EarsivWebService?wsdl
EDIT:
Consuming web service method is done using AFaturaOlustur() function below. There is SetSecurityHeader() procedure which requires another unit code. I did not include them all not to post a lot of code in here. Json routines are from mORMot framework. There are some constants used in some parts of the code as well. When I read file response.xml written in HTTPRIO1AfterExecute() procedure after running function, there is complete xml. It is identical to the one in raw response above:
class procedure TMyType.HTTPRIO1AfterExecute(const MethodName: string; SOAPResponse: TStream);
var
List: TStringList;
begin
List := TStringList.Create();
try
SOAPResponse.Position := 0;
List.LoadFromStream(SOAPResponse);
List.SaveToFile('response.xml');
finally
List.Free();
end;
end;
function AFaturaOlustur(const Input: TFaturaOlusturInput; const GidenBelgeFormati: TGidenBelgeFormatlari; const GidenDosya, GelenDosya: string): Boolean;
var
RIO: THTTPRIO;
WS: EarsivWebService;
Request: faturaOlustur;
Response: faturaOlusturResponse;
Json: RawUTF8;
begin
if not TFile.Exists(GidenDosya) then
begin
LastError := 'AFaturaOlustur(): GidenDosya mevcut değil!';
end;
RIO := THTTPRIO.Create(nil);
RIO.OnAfterExecute := TMyType.HTTPRIO1AfterExecute;
RIO.URL := URLEArsivFatura;
WS := (RIO as EarsivWebService);
SetSecurityHeader(WS);
Json := RecordSaveJSON(Input, TypeInfo(TFaturaOlusturInput));
Request := nil;
Response := nil;
try
Request := faturaOlustur.Create();
Request.input := string(Json);
Request.fatura := belge.Create();
case GidenBelgeFormati of
UBL: Request.fatura.belgeFormati := belgeFormatiEnum.UBL;
CSXML: Request.fatura.belgeFormati := belgeFormatiEnum.CSXML;
HTML: Request.fatura.belgeFormati := belgeFormatiEnum.HTML;
PDF: Request.fatura.belgeFormati := belgeFormatiEnum.PDF;
CUSTOM: Request.fatura.belgeFormati := belgeFormatiEnum.CUSTOM;
PDF_CUSTOM: Request.fatura.belgeFormati := belgeFormatiEnum.PDF_CUSTOM;
PDF_UBL: Request.fatura.belgeFormati := belgeFormatiEnum.PDF_UBL;
CSXML1: Request.fatura.belgeFormati := belgeFormatiEnum.CSXML1;
CSXML2: Request.fatura.belgeFormati := belgeFormatiEnum.CSXML2;
CSXML3: Request.fatura.belgeFormati := belgeFormatiEnum.CSXML3;
PDF_CSXML1: Request.fatura.belgeFormati := belgeFormatiEnum.PDF_CSXML1;
PDF_CSXML2: Request.fatura.belgeFormati := belgeFormatiEnum.PDF_CSXML2;
PDF_CSXML3: Request.fatura.belgeFormati := belgeFormatiEnum.PDF_CSXML3;
end;
Request.fatura.belgeIcerigi := ToByteDynArray(TFile.ReadAllBytes(GidenDosya));
try
Response := WS.faturaOlustur(Request);
except
on E: Exception do
begin
LastError := 'AFaturaOlustur(): ' + E.Message;
Exit(False);
end;
end;
Result := Assigned(Response.return);
if Result then Result := Response.return.resultCode = rcSuccess;
if Result then
begin
TFile.WriteAllBytes(GelenDosya, ToBytes(Response.output.belgeIcerigi));
end
else
begin
LastError := 'AFaturaOlustur(): ' + Response.return.resultText;
end;
finally
Request.Free();
Response.Free();
end;
end;
Related
I am using Delphi XE8 and I am sendig a PUT message via idHttp.
Http.Request.CustomHeaders.Clear;
Http.Request.BasicAuthentication := false;
http.Request.Method := 'PUT';
Http.Request.Accept := '*/*';
Http.Request.ContentType := 'application/json';
http.Request.CustomHeaders.AddValue('apiKey','T_API23207_169');
http.Request.CustomHeaders.AddValue('transactionId','20200924_015');
http.Request.CustomHeaders.AddValue('usziID','1');
Http.Request.AcceptEncoding := '*';
http.Request.CharSet := 'utf-8';
kuldes_header.Text := http.Request.CustomHeaders.Text;
http.Intercept := IdLogEvent1;
IdLogEvent1.Active := true;
jsonToSend := TStringStream.create(json_adat.Text,system.sysUtils.TEncoding.UTF8);
kuldes_body.Lines.LoadFromStream(jsonToSend);
try
try
send_text := http.Put('http://10.109.132.24:8090/rest/usziIroda/1',jsonToSend);
resp := http.ResponseText;
code := http.ResponseCode;
jsonToSend.Position := 0;
except
on E: EIdHTTPProtocolException do
begin
code := e.ErrorCode;
error_message := e.ErrorMessage;
end;
end;
hiba_kod.Lines.Add(IntToStr(code));
valasz_uzenet.Text := send_text;
hiba_uzenet.Text := error_message; enter code here
The returned error message has strange characters:
"Megadott tranzakció azonosÃtóval már történt API hÃvás"
But it should be like this:
"Megadott tranzakció azonosítóval már történt API hívás"
How can I convert the returned message to normal string?
Thank you!
The result you showed - Megadott tranzakció azonosÃtóval már történt API hÃvás - is the UTF-8 encoded form of Megadott tranzakció azonosítóval már történt API hívás being misinterpreted in Latin-1/ISO-8859-1. Which most likely means that the response did not specify a UTF-8 charset in its Content-Type header (since you have an Intercept assigned, you can easily verify this for yourself), so Indy would fall back to a default charset instead.
The original UTF-8 bytes have been decoded and lost before you can access the response data in send_text or error_message. However, since ISO-8859-1 basically has a 1:1 relationship between byte values and Unicode codepoint values, what you can try doing in this specific situation is copy the ErrorMessage's Char values as-is to a RawByteString(65001) or UTF8String, and then let the RTL decode that as UTF-8 back into a proper UTF-16 (Unicode)String, eg:
function DecodeISO88591AsUTF8(const S: string): string;
var
utf8: UTF8String;
I: Integer;
begin
SetLength(utf8, Length(S));
for I := Low(S) to High(S) do
utf8[I] := AnsiChar(S[I]);
Result := string(utf8);
end;
...
error_message := e.ErrorMessage;
//if not TextIsSame(http.Response.CharSet, 'utf-8') then
if TextIsSame(http.Response.CharSet, 'ISO-8859-1') then
error_message := DecodeISO88591AsUTF8(error_message);
Alternatively, you can instead call the overloaded version of TIdHTTP.Put() that fills a response TStream instead of returning a decoded String, and then you can decode the original raw bytes however you want. Just be sure to enable the hoNoProtocolErrorException and hoWantProtocolErrorContent flags in the TIdHTTP.HTTPOptions property so that any error response is stored in the TStream, then you don't need a try/except to handle the EIdHTTPProtocolException separately.
http.HTTPOptions := http.HTTPOptions + [hoNoProtocolErrorException, hoWantProtocolErrorContent];
...
RespStrm := TMemoryStream.Create;
try
http.Put('http://10.109.132.24:8090/rest/usziIroda/1', jsonToSend, RespStrm);
resp := http.ResponseText;
code := http.ResponseCode;
jsonToSend.Position := 0;
RespStrm.Position := 0;
if (code div 100) = 2 then
begin
send_text := decode RespStrm as needed...;
end else
begin
error_message := decode RespStrm as needed...;
end;
finally
RespStrm.Free;
end;
...
I am using the trial version of DevArt's SecureBridge product. I am trying to process POST, but somehow I could not print the request data.
XML:
<test>
<a>test1</a>
<b>test2</b>
</test>
Delphi:
ScHttpWebRequest1.Method := rmPOST;
ScHttpWebRequest1.ContentType := 'text/xml';
ScHttpWebRequest1.RequestUri := 'https://test.com/api';
ScHttpWebRequest1.KeepAlive := True;
ScHttpWebRequest1.ContentLength := Length(XML);
ScHttpWebRequest1.WriteBuffer(pAnsiChar(XML), 0, Length(XML)); ///I think I'm making a mistake here.
ShowMessage(ScHttpWebRequest1.GetResponse.ReadAsString);
I have reviewed the documents, but there is a feature called RequestStream. This feature was not available in the version I downloaded. I think WriteBuffer is used instead or different. all I want to do is request a POST with XML content on the relevant site. How can I do it?
Thanks.
Here's a chunk of code that has worked for me:
var
Response: TScHttpWebResponse;
ResponseStr: string;
buf: TBytes;
begin
ScHttpWebRequest1.Method := rmPOST;
ScHttpWebRequest1.ContentType := 'text/xml';
ScHttpWebRequest1.RequestUri := 'https://test.com/api';
ScHttpWebRequest1.KeepAlive := True;
buf := TEncoding.UTF8.GetBytes(xml);
ScHttpWebRequest1.ContentLength := Length(buf);
ScHttpWebRequest1.WriteBuffer(buf);
Response:=ScHttpWebRequest1.GetResponse;
ResponseStr:=Response.ReadAsString;
end;
Based on Devart forums information you can post/put stream or strings parameters as below:
var
Request: TScHttpWebRequest;
Response: TScHttpWebResponse;
ResponseStr: string;
Stream: TFileStream;
begin
Request := TScHttpWebRequest.Create(URL);
Stream := TFileStream.Create(FileName, fmOpenRead);
try
Request.Method := rmPut;
Request.ContentType := 'application/pdf';
Request.TransferEncoding := 'binary';
Request.Headers.Add('Content-Disposition', 'form-data; name="FormFile"; filename="Document1.pdf"');
Request.ContentLength := Stream.Size;
Request.SendChunked := True;
Request.RequestStream := Stream;
Response := Request.GetResponse;
ResponseStr := Response.ReadAsString;
Response.Free;
finally
Stream.Free;
Request.Free;
end;
end;
I have Delphi 10.3.2
I do not understand this situations:
1)
Uploading photo about 1M
image1.Bitmap.LoadFromFile('test.jpg');
Then I save the same photo
image1.Bitmap.SaveToFile('test_new.jpg');
and test_new.jpg is about 3M. Why ???
2)
I want to send a photo from the TImage (test1.jpg - 1MB) object using IdHTTP and POST request to server.
I use the function Base64_Encoding_stream to encode image.
Image size (string) after encoding the function is 20 MB! ? Why if the original file has 1MB ?
function Base64_Encoding_stream(_image:Timage): string;
var
base64: TIdEncoderMIME;
output: string;
stream_image : TStream;
begin
try
begin
base64 := TIdEncoderMIME.Create(nil);
stream_image := TMemoryStream.Create;
_image.Bitmap.SaveToStream(stream_image);
stream_image.Position := 0;
output := TIdEncoderMIME.EncodeStream(stream_image);
stream_image.Free;
base64.Free;
if not(output = '') then
begin
Result := output;
end
else
begin
Result := 'Error';
end;
end;
except
begin
Result := 'Error'
end;
end;
end;
....
img_encoded := Base64_Encoding_stream(Image1);
.....
procedure Send(_json:String );
var
lHTTP : TIdHTTP;
PostData : TStringList;
begin
PostData := TStringList.Create;
lHTTP := TIdHTTP.Create(nil);
try
PostData.Add('dane=' + _json );
lHTTP.Request.UserAgent := 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0';
lHTTP.Request.Connection := 'keep-alive';
lHTTP.Request.ContentType := 'application/x-www-form-urlencoded';
lHTTP.Request.Charset := 'utf-8';
lHTTP.Request.Method := 'POST';
_dane := lHTTP.Post('http://......./add_photo.php',PostData);
finally
lHTTP.Free;
PostData.Free;
end;
To post your original file using base64 you can basically use your own code. You only need to change the used stream inside your base64 encoding routine like this:
function Base64_Encoding_stream(const filename: string): string;
var
stream_image : TStream;
begin
try
// create read-only stream to access the file data
stream_image := TFileStream.Create(filename, fmOpenRead or fmShareDenyWrite);
// the stream position will be ‘0’, so no need to set that
Try
Result := TIdEncoderMIME.EncodeStream(stream_image);
Finally
stream_image.Free;
End;
if length(result) = 0 then
begin
Result := 'Error';
end;
except
Result := 'Error'
end;
end;
Also, I refactored your code a bit with some try/finally sections, to ensure no memory leaks when errors occur. And I removed the begin/end inside the try/except as those are not needed.
Also removed the local string variable to avoid double string allocation and the unnecessary construction of the TIdEncoderMIME base64 object.
I'm trying to upload a file using TIdHTTP. The problem is the access token gets changed when the request is sent to the server.
The access token that I'm using is fJNhDM6TlcpeVmD8h3jFuPJS71sxwZB8bZBXajTRB5TNAcRa6PNXfv4J7mPxIvMdMhjy7oKdTLbsRYthpBCCqGVkj4vlojJ4BRBkLAVIBJ1DZAnMZD
The API returns
HTTP/1.1 400 Bad Request
OAuth "invalid_token" "Malformed access token fJNhDM6TlcpeVmD8h3jFu=\r\nPJS71sxwZB8bZBXajTRB5TNAcRa6PNXfv4J7mPxIvMdMhjy7oKdTLbsRYthpBCCqGVkj4v=\r\nlojJ4BRBkLAVIBJ1DZAnMZD"
There is =\r\n added to my token twice.
My code is:
function TFoo.Post(const AToken, guID, AMessage, AImageFileName: string): Boolean;
var
lParam : TIdMultipartFormDataStream;
begin
Result := False;
if not FileExists(AImageFileName) then begin
LastError := 'File not found ' + AImageFileName;
Exit;
end;
ProxyCheck;
lParam := TIdMultipartFormDataStream.Create;
try
lParam.AddFormField('message', AMessage);
lParam.AddFormField('access_token', AToken);
lParam.AddFile('source', AImageFileName);
idHTTP.Request.ContentType := 'application/x-www-form-urlencoded';
try
idHTTP.Post( UrlAPI + guID + '/photos', lParam);
Result := True;
except;
LastError := idHTTP.ResponseText + sLineBreak + idHTTP.Response.WWWAuthenticate.Text;
end;
finally
lParam.Free;
end;
end;
What am I missing here ?
By default, AddFormField() sets the TIdFormDataField.ContentTransfer property to MIME's quoted-printable format. That is where the extra =\r\n is coming from. It is a "soft" line break being inserted by quoted-printable every 76 characters. Any server that supports quoted-printable would remove "soft" line breaks during decoding. But maybe your server does not.
If you want to disable the quoted-printable behavior, you can set the ContentTransfer property to either:
a blank string:
lParam.AddFormField('access_token', AToken).ContentTransfer := '';
'7bit' (since it does not contain any non-ASCII characters):
lParam.AddFormField('access_token', AToken).ContentTransfer := '7bit';
'8bit' or binary:
lParam.AddFormField('access_token', AToken).ContentTransfer := '8bit';
lParam.AddFormField('access_token', AToken).ContentTransfer := 'binary';
In this case, I would suggest #1.
On a side note, do not set the HTTP content type when posting a TIdMultipartFormDataStream. Not only are you using the wrong media type to begin with (it should be multipart/form-data instead), but the TIdMultipartFormDataStream version of Post() will simply overwrite it anyway.
function TFoo.Post(const AToken, guID, AMessage, AImageFileName: string): Boolean;
var
lParam : TIdMultipartFormDataStream;
begin
Result := False;
if not FileExists(AImageFileName) then begin
LastError := 'File not found ' + AImageFileName;
Exit;
end;
ProxyCheck;
lParam := TIdMultipartFormDataStream.Create;
try
lParam.AddFormField('message', AMessage);
lParam.AddFormField('access_token', AToken).ContentTransfer := '';
lParam.AddFile('source', AImageFileName);
try
idHTTP.Post(UrlAPI + guID + '/photos', lParam);
Result := True;
except;
LastError := idHTTP.ResponseText + sLineBreak + idHTTP.Response.WWWAuthenticate.Text;
end;
finally
lParam.Free;
end;
end;
Can anyone tell me why i'm having trouble accessing my calendar information? I'm getting 403 forbidden.
procedure TForm1.Button1Click(Sender: TObject);
var
stringStream: TStringStream;
slPost, slReply: TStringList;
sPostResult: string;
begin
slPost := TStringList.Create;
slReply := TStringList.Create;
try
slPost.LineBreak := '&';
slPost.Values['Email'] := 'me#gmail.com';
slPost.Values['Passwd'] := 'pass';
slPost.Values['service'] := 'cl';
slPost.Values['source'] := 'company-program-version';
stringStream := TStringStream.Create(slPost.Text);
try
IdHTTP1.Request.ContentType := 'application/x-www-form-urlencoded';
sPostResult := IdHTTP1.Post('https://www.google.com/accounts/ClientLogin', stringStream);
slReply.LineBreak:=#10;
slReply.Text:=sPostResult;
slReply.LineBreak:=#13#10;
Memo1.Lines.Add(slReply.Text);
Memo1.Lines.Add('response=' + IdHTTP1.ResponseText);
// 200 OK
sPostResult := IdHTTP1.Post('https://www.google.com/accounts/ClientLogin', stringStream);
IdHTTP1.Request.CustomHeaders.FoldLines:=false;
IdHTTP1.Request.CustomHeaders.Clear;
IdHTTP1.Request.CustomHeaders.Values['GData-Version']:='2.0';
IdHTTP1.Request.CustomHeaders.Values['Authorization']:='GoogleLogin auth=' + slReply.Values['auth'];
(* custom headers:
GData-Version: 2.0
Authorization: GoogleLogin (line continues) auth=DQwhateverwhateverwhateverwhateverwhateverwhateverwhateverwhateverwhateverwhateverwhateverwhateverwhateverwhateverwhateverwhateverwhateverwhateverwhateverwhateverwhateverwhatever *)
IdHTTP1.Request.ContentType := 'application/atom+xml';
// 403 Forbidden
memo1.Lines.Add(IdHTTP1.Get('https://www.googleapis.com/calendar/v3/users/me/calendarList'));
finally
stringStream.Free;
end;
finally
slPost.Free;
slReply.Free;
end;
end;
thank you!
mp
After some reading, I think you need to deal with Redirect. So If response is redirect, get the new url, reattach the authorization to the new request header with the new url. Otherwise your redirection request will be missing the required authorization and give you 403 error.