You are not logged in.
Pages: 1
Thank you for your all suggestions
I use mormot 1 for a server side webservices with Http.sys handling the ssl communication .
We have just made registry change and verified that SSL3 is not available anymore after reboot.
UInfortunatelly our client has only one virtual server and it is not an option for them to setup another virtual server to act as a Linux proxy.
Nevertheless we use Win-ACME Windows implementation of LETS encrypt certificates system
When doing online SSL tests against my mORMot powered webservices I get a warning about insecure SSL3 protocol,
What is the recomended way to disable the SSL3 option?
ok, thx for explanatation
Hello,
I don't know if my proposal doesn't go beyond the logic of the FetchAllToJSON function, but I would welcome if FetchAllToJSON function returns eally all records ( not only the rest of records from the CurrentRow position)
f.e.
instead of
function TSQLDBStatement.FetchAllToJSON(JSON: TStream; Expanded: boolean): PtrInt;
...
while Step do begin
ColumnsToJSON(W);
W.Add(',');
inc(result);
end;
...
to use:
function TSQLDBStatement.FetchAllToJSON(JSON: TStream; Expanded: boolean): PtrInt;
var lFirst : boolean;
...
lFirst := true;
while Step(lFirst) do begin
lFirst := false;
ColumnsToJSON(W);
W.Add(',');
inc(result);
end;
...
you are include into pdf really huge image 4916 x 1830 (approx) and moreover ( at least to seems to me) you used JPEG compression - therefore it is splitted (imho - i can be wrong of course) . ( 4916 is width of your picture rounded up and height I calculated by proportinality)
try to play with less image, without compression..
But to be true I couldn't afford such big images in my pdfs. You should invent some more efficient way how and in which form to put logo images into your pdf.
By "directly draw the logo" I ment, that you are able to draw your logo into canvas - not like a big image but - mainly the text - as curves
there are many ways
first of all I would split your logo into three parts:
text
background (=blue gradient)
middle rectangle
the 2nd and 3rd items I would save into separate files (2nd with dimensions 1px x height of logo rectangle, 3rd in real dimension)
the text you can handle f.e.
you can write text (as curves) from your logo using syngdiplus ( or other graphics library) and save as wmf file and render this file in your pdf
you can write text directly into pdfcanvas (without or with ObjectX)
you can also use mormotReport library for text
I did a small example ( by eye, without any precise - using the first sample "SynPdfFormCanvas" that i met) and f.e. b) option
the pdf result of code bellow
function getPdfImage(aPDF:TPdfDocument; aName:PDFString):TPdfImage;
var
FS: TFileStream;
Graphic: TGraphic;
begin
Graphic := TPngImage.Create;
FS := TFileStream.Create(aName, fmOpenRead);
try
FS.Seek(0, soFromBeginning);
Graphic.LoadFromStream(FS);
Result:= TPdfImage.create(aPDF,Graphic,true);
finally
Graphic.Free;
FS.Free;
end;
end;
var
obPDF: TPdfDocument;
obFormCanvas: TPdfFormWithCanvas;
obFormCanvasGrad: TPdfFormWithCanvas;
oGrad,oMiddle : TPdfImage;
begin
obPDF := TPdfDocument.Create(false,0,false);
obPDF.GeneratePDF15File := true;
obPDF.DefaultPaperSize := psA4;
obPDF.DefaultPageLandscape := false;
obPDF.CompressionMethod := cmNone;
//------------------------------ logo-------------------------
oGrad:= getPdfImage(obPDF,'grad.png');
obPDF.AddXObject('imgLogoGrad', oGrad);
oMiddle:= getPdfImage(obPDF,'middle.png');
obPDF.AddXObject('imgLogoMiddle', oMiddle);
obFormCanvas := TPdfFormWithCanvas.Create(obPDF,Trunc(20.828*PDFFactor),Trunc(20.828/2.7*PDFFactor));
obPDF.AddXObject('FORMOBJECT',obFormCanvas);
obFormCanvas.Canvas.SetTextRenderingMode(trFill);
obFormCanvas.Canvas.SetFont('Verdana',54.0,[pfsBold]);
obFormCanvas.Canvas.SetHorizontalScaling(90);
obFormCanvas.Canvas.SetCMYKFillColor(65,52,0,39);
obFormCanvas.Canvas.TextOut(0,5*PDFFactor,'Live long and prosper');
obFormCanvas.CloseCanvas;
//------------------------------ logo-------------------------
obPDF.AddPage;
// ----------------------------- your content ---------------------------
obPDF.Canvas.SetTextRenderingMode(trFill);
obPDF.Canvas.SetFont('Arial',10.0,[]);
obPDF.Canvas.SetLineWidth(0.01*PDFFactor);
obPDF.Canvas.Rectangle(1.0*PDFFactor,1.0*PDFFactor,19.0*PDFFactor,27.9*PDFFactor);
obPDF.Canvas.Stroke;
obPDF.Canvas.TextOut(2.0*PDFFactor,27.0*PDFFactor,'XObject form canvas sample');
//------------------------------ logo background (=gradient)-------------------------
obPDF.Canvas.DrawXObject(0,23*PDFFactor,20.828*PDFFactor,Trunc(20.828/2.7*PDFFactor),'imgLogoGrad');
//------------------------------ logo middle rectangle -------------------------
obPDF.Canvas.DrawXObject(20.828*PDFFactor/2-oMiddle.pixelWidth/3,
25*PDFFactor -oMiddle.pixelHeight/3,
2/3*oMiddle.pixelWidth,2/3*oMiddle.pixelHeight,'imgLogoMiddle');
//------------------------------ logo text -------------------------
obPDF.Canvas.DrawXObject(0,23*PDFFactor,1,1,'FORMOBJECT');
obPDF.SaveToFile(ChangeFileExt(ExeVersion.ProgramFileName,'.pdf'));
FreeAndNil(obPDF);
end.
small note:
if you use TPdfFormWithCanvas for drawing of text, fix yourself a small bug in this class:
constructor TPdfFormWithCanvas.Create(aDoc: TPdfDocument; W, H: single;aXForm:PXForm);
...
// instead of FAttributes.AddItem('BBox',TPdfArray.Create(nil,[0,0,H,W])); write
FAttributes.AddItem('BBox',TPdfArray.Create(nil,[0,0,W,H]));
...
end;
hello,
in my project I use mORMotReport + SynPdf for generate output into PDF. The resulting PDF is rather complicated - with many drawings, several fonts, images, tables etc.
Everything worked fine till the last week.
Last week my client reported some errors that occured when he tried to print the generated pdf in Adobe Reader; he also reported another problem of incomplete drawing.
When I tried to print/display/analyze the generated PDF in other softwares (Edge, Mozilla, XFINIUM.PDF, Winking PDF analyzer) there was no error.
When analyzing the generated PDF in Adobe Acrobat DC - preflight module I have found three unusual problems:
1) General File/Format error The key /First must be present when key /Count is used
this error was caused by this part !!
1 0 obj
<</Type/Catalog/PageLayout/SinglePage/Outlines 2 0 R/Pages 4 0 R>>
endobj
2 0 obj
<</Type/Outlines/Count 0>>
endobj
To avoid this error I have set
UseOutlines := false;
2) when I checked the incomplete drawing I found out there are at no PDF syntax error on the page, however the list of the used fonts contains entries for Courier font ( which is not used in my PDF at all) and the texts that were reportedly using Courier were missing in the incomplete drawing
From this I have inferred that Adobe requires explicit setFont in front of each text element now - this requires a small change in TPdfCanvas.SetPDFFont procedure
(SynPdf)
procedure TPdfCanvas.SetPDFFont(AFont: TPdfFont; ASize: Single);
begin
// check if this font is already the current font
// if (AFont=nil) or ((FPage.Font=AFont) and (FPage.FontSize=ASize)) then <-- commented
// Exit; <-- commented
// add this font to the resource array of the current page
if FPageFontList.ValueByName(AFont.ShortCut)=nil then
FPageFontList.AddItem(AFont.ShortCut, AFont.Data);
// change the font
if FContents<>nil then
SetFontAndSize(AFont.ShortCut, ASize); // e.g. SetFontAndSize('F0',12)
FPage.Font := AFont;
FPage.FontSize := ASize;
end;
this change helped
3) the third reported warnings are of type Invalid content stream state (PathObject) for operator
and the mentioned operators are: Tf, Tw, rg and BT. I have no clue what is the problem.
For the reference I give the code which matters:
/F1 14.04 Tf
0.18 Tw
0.28 0.17 0.42 rg
BT
28.32 800.36 Td
(Project: project 1) Tj
ET
The embeded image ( in the reasonable size) looks never well for bigger scaling.
Your logo is simple for direct drawing. I would directly draw the logo into a pdf instead of placing image.
the advantage of this step is: the pdf will be less, the logo looks the best for all scaling
HEllo,
I had to solve clipping problem too for my project (pdf + patterns).
I have extracted only my changes related on clipping into separate branch:
https://github.com/Eva-F/SynPDF/tree/Ev … lip-branch
try it, plz ...
I am appologizing I have currently no time to test it
did you draw with
with TGDIPages.create(self) do
begin
..
ForceScreenResolution := true;
..
end;
?
did you try to change orientation just before "Newpage" ?
something like:
with TGDIPages.Create(self) do
try
Orientation := poLandscape;
BeginDoc;
...
Orientation := poPortrait;
newPage;
...
Orientation := poLandscape;
newPage;
...
endDoc;
..
finally
free;
end;
At the beginning I have to apologize for directly modifying mORMOtReport unit again
(I hoped that I solve my issue by creating own class based on TGDIPages but i needed to expand some data structures and therefore direct modifying of mORMotReport seemed to me more convenient).
I got a PDF sample template that I need to keep exactly.
Therefore I added into TGDIpages:
- vertical align
- text styles
- linespacing into header/footer
- drawing of RoundRectangle
- extending Columns (option to use text styles, vertical aligning, word wrap of column headers)
- rendering of external metafiles directly into PDF (= skip rendering of them into GDIPages.canvas)
more at:
https://github.com/Eva-F/SynPDF/tree/Ev … Report.pas
In my project I have to generate and include into pdf many pictures (from ten to hundred - depends on customer design) and the pictures have to be very readable and illustrative and of course the smallest as possible. Therefore I use texture brush for drawing them. (f.e. brush n x 1 pixels).The drawing picture is stored as metafile (.wmf), that can be rendered into pdf. But a recognization of texture brush bitmap from metafile is difficult and time/memory consuming because rounding color leads to similiar (but not equal) bitmaps.
Therefore I decided to such step: instead of real texture brush to use dummy brush of 1x1 pixel(to minimalize wmf metafile itself) and add into GDI comments information about bitmap, that will be used as texture brush for filling shapes right in pdf
Pdf has the tools for texture/pattern brush - there are Pattern and XObject/Form
Because I need primarily to render metafiles into pdf, I have prepared the option to read info about texture brushes from metafile and write into pdf the filling the shapes by texture brushes by efficient way.
more at:
https://github.com/Eva-F/SynPDF/tree/Ev … re-pattern
examples at
https://github.com/Eva-F/SynPDF/tree/Ev … /documents
I am continuing in the study of synPDF and mORMotReport and I have found out, that in some cases (when the font of pageNrs is not diffferent from the font of header (or footer))
then Adobe Acrobat notifies the error in generated PDF.
It can be fixed by swapping order of page draw and PageNr text:
procedure TGDIPages.EndDoc;
...
for i := 0 to n-1 do begin
Page := CreateMetaFile(fPages[i].SizePx.X,fPages[i].SizePx.Y);
try
fCanvas := CreateMetafileCanvas(Page);
//- fCanvas.Draw(0,0,GetMetaFileForPage(i)); // re-draw the original page
s := format(fPagesToFooterText,[i+1,n]); // add 'Page #/#' caption
aX := fPagesToFooterAt.X;
if aX<0 then
aX := fPages[i].SizePx.X-fPages[i].MarginPx.Right;
SavedState := fPagesToFooterState;
if TextAlign=taRight then
dec(aX,fCanvas.TextWidth(s));
with fPages[i] do
fCanvas.TextOut(aX,SizePx.Y-MarginPx.bottom-fFooterHeight+
fFooterGap+fPagesToFooterAt.Y,s);
fCanvas.Draw(0,0,GetMetaFileForPage(i)); //+ <-- swapping an order of commands
FreeAndNil(fCanvas);
SetMetaFileForPage(i,Page); // replace page content
finally
Page.Free;
end;
...
I - like many others - use FastReport for my reports but the exported PDF files - when pictures are embedded into them - look really bad.
Therefore I have decided to rework all my reports to use SynPDF.
A short introduction:
I have two webservice applications (both based on mORMot) that communicate with each other. One of them generates the picture files (*.png) which are inserted into reports by the second application.
All this can be handled by SynPDF alone. Instead of png files I plan to use the windows metafiles(*.emf) which the second application will render into resulting PDF pages.
I have tried to render my generated emf file and stumbled on some small issues in SynPDF that I would like to have fixed:
They are related to Canvas.SaveDC and Canvas.RestoreDC
procedure TPdfEnum.RestoreDC;
begin
Assert(nDC>0);
dec(nDC);
Canvas.GRestore; // <--- imho this line has to be added
end;
procedure TPdfEnum.SaveDC;
begin
Assert(nDC<high(DC));
DC[nDC+1] := DC[nDC];
inc(nDC);
Canvas.GSave; // <--- imho this line has to be added
end;
and to clipping - I have already read this topic from MtwStark https://synopse.info/forum/viewtopic.php?id=4276 , but I was not digging so deeply and not to such extent as he.
I was focused only on the EMR_SELECTCLIPPATH that is a part of my generated pictures - therefore my suggested change affects only the EMR_SELECTCLIPPATH.
function EnumEMFFunc(DC: HDC; var Table: THandleTable; R: PEnhMetaRecord;
NumObjects: DWord; E: TPdfEnum): LongBool; stdcall;
....
EMR_SELECTCLIPPATH:begin // <--- added EMF_record EMR_SELECTCLIPPATH
if PolyFillMode = ALTERNATE then
E.Canvas.EOClip
else
E.Canvas.Clip;
E.Canvas.NewPath;
end;
EMR_BEGINPATH: begin
E.InPath := true; // <--- added a path bracket flag := true;
E.Canvas.NewPath;
if not Moved then begin
E.Canvas.MoveToI(Position.X,Position.Y);
Moved := true;
end;
end;
EMR_ENDPATH:
begin
E.InPath := false; // <--- added a path bracket flag := false
E.Canvas.fNewPath := false;
end;
When the InPath flag is true, the path defined in path brackets (EMR_BEGINPATH .. EMR_ENDPATH) should not be Filled nor Stroked
procedure TPdfEnum.NeedBrushAndPen;
begin
if InPath then // <--- added a condition
begin
fStrokeColor := -1;
fPenWidth := -1;
fFillColor := -1;
fPenStyle := -1;
end
else begin
if fInlined then begin
fInlined := false;
Canvas.Stroke;
end;
NeedPen;
with DC[nDC] do
if not brush.null then
FillColor := brush.color;
end;
end;
procedure TPdfEnum.NeedPen;
begin
with DC[nDC] do
if not pen.null and not InPath then begin // <--- added a condition not InPath
StrokeColor := pen.color;
...
end else begin
// pen.null need reset values
fStrokeColor := -1;
fPenWidth := -1;
fPenStyle := -1;
end;
end;
and in EnumEMFFunc procedure an PolyPoly procedure
function EnumEMFFunc(DC: HDC; var Table: THandleTable; R: PEnhMetaRecord;
NumObjects: DWord; E: TPdfEnum): LongBool; stdcall;
...
EMR_POLYGON, EMR_POLYLINE, EMR_POLYGON16, EMR_POLYLINE16:
if not brush.null or not pen.null and not E.InPath then begin // <--- added a condition not InPath
...
end;
procedure TPdfEnum.PolyPoly(data: PEMRPolyPolygon; iType: Integer);
...
if not InPath then // <--- added a condition not InPath
begin
if iType in [EMR_POLYPOLYLINE, EMR_POLYPOLYLINE16] then begin // stroke
...
end;
end;
With these small changes applied my generated emf's are rendered correctly
Hi,
I forgot to comment some debug statements - sorry. The fixed version of WPRESTAPI.pas file is on git.
I checked only WordpressREST ( sorry - a few time). I have installed new version (Oauth 0.3.0, Wordpress 4.7 + Mormot Nigth Build -mORMot_and_Open_Source_friends_2016-12-21_193435_df194baa76) and made a couple of the small changes in WordpressREST sources.
The adding, updating and listing Wordpress users works now.
About the Error 404: rest_no_route
Please check your Wordpress endpoint address
=[WP-REST-API-Url] - http[https]://[your-WP-server]/wp-json/ in WPRESTAPI.json
and
=[WOO-REST-API-Url] - http[https]://[your-WP-server]/wc-api/[Woocommerce API version]/ in WooRESTAPI.JSON
f.e.
http://somewordpress.blabla.com/wc-api/v2/
I will check all new versions and after that let you know...
E.
I use Smart Mobile Studio to communicate with mORMot server (based on RegressionTestsServer). For my project the most suitable method is to use the sicPerSession implementation with asynchronous responses. More precisely - I need to put my requests into a FIFO queue (without waiting for each response) and let the mormot client manage the queue .
I have looked at the SynCrossPlatformREST unit, but didn't find the option how to implement such approach (I may be wrong)
So here are my questions (for the implementation sicPerSession):
1) is it possible to ensure that the request is delayed until the server completes the processing of the previous request?
2) I tried to implement simple queueing in SynCrossPlatformREST unit. It works but I wonder if there is a more efficient way to achive it?
TCallQueue=class <---added
Caller: TServiceClientAbstract;
CallMethod: string;
CallParams: string;
onSuccess: procedure(res: array of Variant);
onError: TSQLRestEvent;
ReturnsCustomAnswer: boolean;
ExpectedOutputParamsCount : integer;
end;
/// REST client access class
TSQLRestClientURI = class(TSQLRest)
protected
fAuthentication: TSQLRestServerAuthentication;
fOnlyJSONRequests: boolean;
fRunningClientDriven: TStringList;
{$ifdef ISSMS}
fAsynchCount: integer;
fCallsQueue: Array of TCallQueue; <---added
fCallIsProcessed: boolean; <---added
fAsynchPendingText: array of string;
procedure SyncCallRemoteServiceASynchQueue; <---added
...
public
...
procedure CallRemoteServiceAsynch(aCaller: TServiceClientAbstract;
const aMethodName: string; aExpectedOutputParamsCount: integer;
const aInputParams: array of variant;
onSuccess: procedure(res: array of Variant); onError: TSQLRestEvent;
aReturnsCustomAnswer: boolean=false);
procedure SyncCallRemoteServiceAsynch(aCaller: TServiceClientAbstract; <---added
const aMethodName: string; aExpectedOutputParamsCount: integer;
const aInputParams: array of variant;
onSuccess: procedure(res: array of Variant); onError: TSQLRestEvent;
aReturnsCustomAnswer: boolean=false);
...
end;
implementation
...
procedure TSQLRestClientURI.SyncCallRemoteServiceASynch(aCaller: TServiceClientAbstract;
const aMethodName: string; aExpectedOutputParamsCount: integer;
const aInputParams: array of variant;
onSuccess: procedure(res: array of Variant); onError: TSQLRestEvent;
aReturnsCustomAnswer: boolean);
var CallQueueItem : TCallQueue;
begin
CallQueueItem := TCallQueue.create;
CallQueueItem .caller := aCaller;
CallQueueItem .callMethod := aMethodName;
CallQueueItem .callParams := JSON.Stringify(variant(aInputParams));
CallQueueItem .onError := onError;
CallQueueItem .OnSuccess := onSuccess;
CallQueueItem .ReturnsCustomAnswer := aReturnsCustomAnswer;
CallQueueItem .ExpectedOutputParamsCount := aExpectedOutputParamsCount;
fCallsQueue.add(CallQueueItem ); // the length is set in constructor Create (fCallsQueue.SetLength(0); )
SyncCallRemoteServiceASynchQueue;
end;
procedure TSQLRestClientURI.SyncCallRemoteServiceASynchQueue;
var Call: TSQLRestURIParams;
CallQueueItem : TCallQueue;
begin
SetAsynch(Call,
lambda
var i:= 0;
fCallIsProcessed:= false;
CallQueueItem := fCallsQueue[ Low(fCallsQueue)];
for i := Low(fCallsQueue) + 1 to High(fCallsQueue) do
fCallsQueue[i-1] := fCallsQueue[i];
fCallsQueue.SetLength(high(fCallsQueue));
if high(fCallsQueue)>=Low(fCallsQueue) then
SyncCallRemoteServiceASynchQueue;
if not assigned(CallQueueItem.onSuccess) then
exit; // no result to handle
if CallQueueItem.ReturnsCustomAnswer then begin
if Call.OutStatus=HTTP_SUCCESS then begin
var result: TVariantDynArray;
result.Add(Call.OutBody);
CallQueueItem.onSuccess(result);
end else
if Assigned(CallQueueItem.onError) then
CallQueueItem.onError(self);
exit;
end;
var outID: integer;
var result := CallGetResult(Call,outID); // from {result:...,id:...}
if VarIsValidRef(result) then begin
if (CallQueueItem.Caller.fInstanceImplementation=sicClientDriven) and (outID<>0) then
(CallQueueItem.Caller as TServiceClientAbstractClientDriven).fClientID := IntToStr(outID);
if CallQueueItem.ExpectedOutputParamsCount=0 then
CallQueueItem.onSuccess([]) else begin
var res := TJSONVariantData.CreateFrom(result);
if (res.Kind=jvArray) and (res.Count=CallQueueItem.ExpectedOutputParamsCount) then
CallQueueItem.onSuccess(res.Values) else
if Assigned(CallQueueItem.onError) then
CallQueueItem.onError(self);
end;
end else
if Assigned(CallQueueItem.onError) then
CallQueueItem.onError(self);
end,
lambda
if Assigned(CallQueueItem.onError) then
CallQueueItem.onError(self);
end,
lambda
result := (Call.OutStatus=HTTP_SUCCESS) and (Call.OutBody<>'');
end);
CallQueueItem:= fCallsQueue[Low(fCallsQueue)];
if assigned(CallQueueItem) and not fCallIsProcessed then
begin
fCallIsProcessed:= true;
CallRemoteServiceInternal(Call,CallQueueItem.Caller,CallQueueItem.CallMethod,CallQueueItem.CallParams);
end;
end;
Eva
P.S. Thanks a lot for creating the SMS crossplatform and I also would like to thank for warleyalex for illustrative examples and for creating videotutorials
I had to solve the saving Blobs into MSSQL a month ago (http://synopse.info/forum/viewtopic.php?id=3106).
I used a little bit different coding, but the principle is the same:
procedure TOleDBStatement.ExecutePrepared;
...
...
ftBlob: begin
B^.dwPart := DBPART_VALUE or DBPART_LENGTH or DBPART_STATUS;
B^.obValue := PAnsiChar(@P^.VBlob)-pointer(fParams);
B^.cbMaxLen := sizeof(Pointer);
P^.VInt64 := length(P^.VBlob);
B^.obLength := PAnsiChar(@P^.VInt64)-pointer(fParams);
// ---------------ParamBindInfo.ulParamSize-------------------
// For parameters that use a variable-length data type
// The maximum length of the data type in characters (for DBTYPE_STR and DBTYPE_WSTR) or in bytes (for DBTYPE_BYTES and DBTYPE_VARNUMERIC),
BI^.ulParamSize := P^.VInt64; // <--- added the setting of length
end;
ftUTF8: begin
B^.obValue := PAnsiChar(@P^.VText)-pointer(fParams);
...
end;
end;
end;
if BI^.ulParamSize = 0 then
BI^.ulParamSize := B^.cbMaxLen; // <--- Here is set default length - but it is valid only
// for parameters that use a fixed-length data type
Still remains the question how to insert Null value into a varbinary(MAX) (=BLOB) field
I modified procedure ExecutePrepared this way, but I cannot see whether there will be other consequences
case P^.VType of
ftNull: begin
P^.VStatus := ord(stIsNull);
// BI.pwszDataSourceType := 'DBTYPE_WVARCHAR'; <-- commented by me
BI.dwFlags := BI^.dwFlags or DBPARAMFLAGS_ISNULLABLE;
Was I mistaken?
Again TOleDBStatement.ExecutePrepared procedure
I ran into next problem related to Blob parameters - whenever I try to store a picture into varbinary(max) field ( using BindBlob - see code above ), in MSSQL database is stored only first 8 bytes.
0x89504E47
the reason is imho that wrong ParamBindInfo.ulParamSize is set for blob field.
procedure TOleDBStatement.ExecutePrepared;
...
...
ftBlob: begin
B^.dwPart := DBPART_VALUE or DBPART_LENGTH or DBPART_STATUS;
B^.obValue := PAnsiChar(@P^.VBlob)-pointer(fParams);
B^.cbMaxLen := sizeof(Pointer);
P^.VInt64 := length(P^.VBlob);
B^.obLength := PAnsiChar(@P^.VInt64)-pointer(fParams);
// ---------------ParamBindInfo.ulParamSize-------------------
// For parameters that use a variable-length data type
// The maximum length of the data type in characters (for DBTYPE_STR and DBTYPE_WSTR) or in bytes (for DBTYPE_BYTES and DBTYPE_VARNUMERIC),
BI^.ulParamSize := P^.VInt64; // <--- added the setting of length
end;
ftUTF8: begin
B^.obValue := PAnsiChar(@P^.VText)-pointer(fParams);
...
end;
end;
end;
if BI^.ulParamSize = 0 then
BI^.ulParamSize := B^.cbMaxLen; // <--- Here is set default length - but it is valid only
// for parameters that use a fixed-length data type
Hi,
While upgrading of my program with the latest (unstable) version of mormot, I ran into following problem:
I need to insert/update data on server (MSSQL 2012) from client dbs (sqlite) and for a specific reasons I need to do it in general way.
First I retrieve a table structure (field names, field types, nullable, keys,...)
the code segment looks like this:
lSQL := StringToUTf8( ' INSERT INTO ' + lTableName + '(' + lAddFields + 'changedAt,changedBy ) Values ('+ lParameters + 'getDate(), '+quotedStr(fSiteUser)+')');
// lParameters ='?,?,?,...'
// lAddFields comma delimited list of fieldnames of lTablename
with fprops.MainConnection.NewStatementPrepared(lSql , false) do
begin
lFieldCount := 0;
for i := 0 to lField.count - 1 do
begin
lfn := lField.Names[i];
if (lSQLTable.FieldIndex(lfn)>-1) then
begin
inc(lFieldCount);
lDataType := lField.objects[lfn].strings['DATA_TYPE'];
if not(lKey.has[lfn] ) and (lField.Objects[lfn].strings['NULLABLE']='YES') and (lSQLTable.Get(lSQLTable.stepRow,lSQLTable.FieldIndex(lfn)) = '') then
BindNull(lFieldCount)
else if endsText('int',lDataType) then
Bind(lFieldCount,lSQLTable.GetAsInt64(lSQLTable.stepRow,lSQLTable.FieldIndex(lfn)))
...
else if endsText('binary',lDataType) then
BindBlob(lFieldCount,lSQLTable.GetBlob(lSQLTable.stepRow,lSQLTable.FieldIndex(lfn)))
...
end;
end;
try
ExecutePrepared;
except
on E:Exception do
begin
......
end;
end;
and I receive the exception whenever I try to insert Null value into a varbinary(MAX) field
Project .. raised exception class EOleDBException with message 'TOleDBConnection:
OLEDB Error 80040E14 - (line 1): Implicit conversion from data type nvarchar to varbinary(max) is not allowed. Use
the CONVERT function to run this query.
I debugged it and I suspect that the problem is in TOleDBStatement.ExecutePrepared procedure
First I think ( and I may be wrong) that there is a typo error in this procedure and instead of BI.pwszDataSourceType there should be BI^.pwszDataSourceType
and the same goes for BI.dwFlags
case P^.VType of
ftNull: begin
P^.VStatus := ord(stIsNull);
BI.pwszDataSourceType := 'DBTYPE_WVARCHAR';
BI.dwFlags := BI^.dwFlags or DBPARAMFLAGS_ISNULLABLE;
but the reason for my exception is the following line:
BI.pwszDataSourceType := 'DBTYPE_WVARCHAR';
when I modify the this small part of ExecutePrepared procedure:
case P^.VType of
ftNull: begin
P^.VStatus := ord(stIsNull);
BI^.dwFlags := BI^.dwFlags or DBPARAMFLAGS_ISNULLABLE;
then I am able to successfully insert null value into a varbinary field
hm, so nothing - it doesn't help me...
I mainly use fetchAllToJSON for the results of MSSQL queries,further I perform a little bit more complicated calculations using these JSON objects and mormotize only the result of calculation.
Ok, thanks a lot for tip, I will take a look at it
Hi,
I also intercede for the Maciej proposal.
I have to go to new version of mormot and I have to treat the result of FetchallToJSON(true) in many places - I know it's not difficult, but it makes imho more sense to return every time the same type
for Expanded = true i would like to expect allways Array []
for Expanded = false i would like to expect allways Object {}
You could use a TDocVariantData to store the array and convert it to an array of const.
ok, thanks, I did it by your recommended way.
I did not understand the purpose of this function, and its use case...
I will try to explain my idea:
I have intended to call the CallbackGet function that uses open array of TVarRec as a parameter.
function TSQLRestClientURI.CallBackGet(const aMethodName: RawUTF8;
const aNameValueParameters: array of const; ...): integer;
Rather then explicitly enumerate all name, value pairs I would prefer to add them gradually one by one based on the actual authentication stage. Therefore each NameValue pair is copied into a FParameters array
fParameters: array of TVarRec;
and afterwards passed as an aNameValueParameters.
fOauthClient.CallBackGet('request', fParameters, lResponse)
I have fixed the unsatisfactory releasing of fParameters and there are no memory leaks now.
Further I have discovered a bug in OAuth1 plugin and when I have checked it on Github, I have found out that there is a new version of Oauth1 plugin available and that this new version changed the way the signature is created at the third leg of authetication. I have also added the support for OAuth1 plugin version 0.2.1 into source code.
Thanks for the warning, I will take look into - in this case I think that in SynCommons is a suitable subroutine for this purpose, but I could not find it
hereby is source https://github.com/Eva-F/mormot-WP-REST-API
maybe it helps somebody
a little remark:
I am not good in the writing of commentaries neither in my native language let alone in English.
I know only a small fraction of mormot, so some part of my code could use routines of mormot better and more effectively
Hi,
for my project that uses webservice (based on mORMot) I will have to create users in Wordpress with the same account ( username,pwd,email, etc) and maybe later to reading/writing WooCommerce data. Therefore I created a small library to accomplish it.
I tried to use ORM classes for the work with Wordpress records.
The reading looks like this
type
TWPRESTAPI1_0_3LEGS = class(TWPRESTAPI) // my class for dealing with Wordpress REST API include providing authentication
...
TSQLWPClient = class(TSQLHttpClientWinHTTP)
...
TSQLRESTAPIUser = class(TSQLRecord)
protected
fusername: RawUTF8;
fname: RawUTF8;
ffirst_name: RawUTF8;
fnickname: RawUTF8;
fslug: RawUTF8;
...
var
fWP: TWPRESTAPI1_0_3LEGS ;
fWPClient : TSQLWPClient;
fWPUser : TSQLRESTAPIUser;
procedure ReadWPUsers;
begin
fWP := TWPRESTAPI1_0_3LEGS.create('WPRestAPI.json'); // WPRestAPI.json - the file containing TSQLRecord that stores Wordpress server, consumer key, secret consumer key etc.
fWP.Connect;
fWPClient := TSQLWPClient(fWP.getWAPIClient('users', '{"search":"*eva*","orderby":"username}')); //'users' is route of WP REST API and '{"search":...' is filter
fWPUser := TSQLRESTAPIUser.CreateAndFillPrepare(fWPClient,'',[]);
end;
the updating like this:
procedure UpdateWPUser;
begin
if assigned( fWPUser) then
begin
fWPUser.FillRewind;
while fWPUser.FillOne do
begin
if fWPUser.username=EditWPUsername.text then
begin
fWPUser.description := EditWPdescription.text;
fWPClient.Update(fWPUser);
end;
end ;
end;
end;
I had to only take care of the authentication of the requests, everything else was pretty simple thanks mormot.
Hi,
I am not expert for Crypto, but imho in HMAC_SHA256 procedure is a small inaccuracy, that causes the problems for key longer than 64 chars.
I think, that it should be:
procedure HMAC_SHA256(key,msg: pointer; keylen,msglen: integer; out result: TSHA256Digest);
var i: integer;
sha: TSHA256;
k0,k0xorIpad,step7data: array[0..15] of cardinal;
begin
FillcharFast(k0,sizeof(k0),0);
if keylen>64 then
sha.Full(key,keylen,PSHA256Digest(@k0)^) else // < --- originally sha.Full(key,64,PSHA256Digest(@k0)^) else
MoveFast(key^,k0,keylen);
for i := 0 to 15 do
k0xorIpad[i] := k0[i] xor $36363636;
...
ok, thanks for everything
only small question - I cannot find GetMimeContentTypeFromBuffer function
I suppose that it looks something like that:
function GetMimeContentTypeFromBuffer(Content: Pointer; Len: integer;
const ContentType: RawUTF8 =''): RawUTF8;
begin
Result := GetMimeContentType(Content, Len);
if (Result = '') or (Result = 'application/octet-stream') then
Result := ContentType;
end;
Using StrPosI() may be unsafe, since the text may appear as part of a header value, not as a header entry name...
You are right, I will use it your way, thx
ef
hi ab,
thanks for modification - the last wish (hope) is replacing IdemPChar function by some Pos function ( f.e. StrPosI function)
for the case:
lHead := 'Cookie: a=1111111111' +#13#10 +
'Content-Type: application/x-www-form-urlencoded;charset=UTF-8';
can look something like this:
if InternalCheckOpen then begin
if Head<>'' then begin
P := pointer(Head);
P := StrPosI('CONTENT-TYPE:',P);
if assigned(P) then begin
PBegin := P;
inc(P,14);
lContentypeInHead := GetNextLine(P,PEnd);
if not assigned(pEnd) then // Content-type parameter is the last
begin
pEnd := Pointer(Head);
Inc(pEnd, length(Head));
end;
system.delete(Head, PBegin - pointer(Head) + 1, pEnd-pBegin );
...
thanks again
ef
o, sorry, thanks for the info...
But I ran into another problem ( this time I have checked (to be the sure) in the latest unstable version too)
The problem is still related to the TSQLHttpClient. I would like to set both the Content-Type and also Cookies in the request.
something like
lHead := 'Content-Type: application/x-www-form-urlencoded;charset=UTF-8'+#13#10 +
'Cookie: a=1111111111' ;
But InternalURI works like this: if Header contains any Content-type then all other settings in Header are neglected
( // header is processed -> no need to send Content-Type twice )
procedure TSQLHttpClientGeneric.InternalURI(var Call: TSQLRestURIParams);
var Head, Content, ContentType: RawUTF8;
P: PUTF8Char;
begin
..
Head := Call.InHead;
Content := Call.InBody;
if InternalCheckOpen then begin
if Head<>'' then begin
P := pointer(Head);
if IdemPChar(P,'CONTENT-TYPE:') then begin
inc(P,14);
if Content<>'' then begin
ContentType := GetMimeContentType(pointer(Content),Length(Content));
if ContentType='application/octet-stream' then
ContentType := '';
end;
if ContentType='' then
ContentType := GetNextLine(P,PEnd);
Head := ''; // header is processed -> no need to send Content-Type twice
end;
end;
IMHO it would cause trouble also in the case when TSQLHttpClient has set both Content-Type and fSessionHttpHeader
It is possible to preserve other settings in Header?
at least something like this: ( I maybe wrong )
procedure TSQLHttpClientGeneric.InternalURI(var Call: TSQLRestURIParams);
var Head, Content, ContentType, lContentypeInHead: RawUTF8;
P,PBegin, pEnd: PUTF8Char;
begin
...
Head := Call.InHead;
Content := Call.InBody;
if InternalCheckOpen then begin
if Head<>'' then begin
P := pointer(Head);
if IdemPChar(P,'CONTENT-TYPE:') then begin
PBegin := P; // <--- added
inc(P,14);
lContentypeInHead := GetNextLine(P,PEnd); // <--- added
if Content<>'' then begin
ContentType := GetMimeContentType(pointer(Content),Length(Content));
if ContentType='application/octet-stream' then
ContentType := '';
end;
if ContentType='' then
ContentType := lContentypeInHead; // <--- changed
system.delete(Head, PBegin - pointer(Head) + 1, pEnd-pBegin ); // <--- added
end;
end;
...
end;
P.S. I have just checked the IdemPChar function - "Content-Type" should be the first parameter in the request-header because of using this IdemPChar function
Hi,
thanks for a great framework....
I am working on a REST communication with Wordpress ( via Wordpress plugin WP REST API with Oauth 1.0a authenification)
I would like to use the TSQLHttpClient class and CallBackGet function but I ran into troubles with limited GET parameters names - only ['a'..'z','A'..'Z'] charset is allowed in parameter names in mormot. However Oauth 1.0 requires parameters with underscores (f.e. oauth_signature,..).
Is it possible to add underscore into the allowed characters for parameter names?
here is the modification that could accomplish it... As you can see 1 line is changed.
function TSQLRestClientURI.CallBackGet(const aMethodName: RawUTF8;
const aNameValueParameters: array of const; out aResponse: RawUTF8;
aTable: TSQLRecordClass; aID: TID; aResponseHead: PRawUTF8): integer;
var url, header: RawUTF8;
begin
if self=nil then
result := HTML_UNAVAILABLE else begin
{$ifdef WITHLOG}
fLogClass.Enter(Self,pointer(aMethodName),true);
{$endif}
url := Model.getURICallBack(aMethodName,aTable,aID)+
UrlEncode(aNameValueParameters);
....
end;
end;
function UrlEncode(const NameValuePairs: array of const): RawUTF8;
// (['select','*','where','ID=12','offset',23,'object',aObject]);
var A, n: PtrInt;
name, value: RawUTF8;
function Invalid(P: PAnsiChar): boolean;
begin
result := true;
if P<>nil then begin
repeat
if not (P^ in ['a'..'z','_','A'..'Z']) then // <--- originally if not (P^ in ['a'..'z','A'..'Z'])
exit else
inc(P);
until P^=#0;
result := false;
end;
end;
begin
result := '';
n := high(NameValuePairs);
if n>0 then begin
for A := 0 to n shr 1 do begin
VarRecToUTF8(NameValuePairs[A*2],name);
if Invalid(pointer(name)) then
continue;
with NameValuePairs[A*2+1] do
if VType=vtObject then
value := ObjectToJSON(VObject,[]) else
VarRecToUTF8(NameValuePairs[A*2+1],value);
result := result+'&'+name+'='+UrlEncode(value);
end;
result[1] := '?';
end;
end;
Eva
ok, you are right - I could not forsee all ramifications - therefore I used the more cumbersome variant..
I had another two little silly requests:
1) to expand the set (SysCommon.pas)
IsWord: set of byte =
[ord('0')..ord('9'),ord('a')..ord('z'),ord('A')..ord('Z'),ord('{'),ord('}'),ord('-'),ord('_')];
// instead of current set
IsWord: set of byte =
[ord('0')..ord('9'),ord('a')..ord('z'),ord('A')..ord('Z')];
I often use TSynSQLTableDataSet and I need to fast locate the key - mostly of type Guid
2) while searching for a solution to the Onterminate problem, I stumbled on a small potential problem with EnterCriticalSection ( the leaveCriticalSection is not executed if CurrentThreadConnection exists )
function TSQLDBConnectionPropertiesThreadSafe.ThreadSafeConnection: TSQLDBConnection;
var i: integer;
begin
case fThreadingMode of
tmThreadPool: begin
EnterCriticalSection(fConnectionCS);
try
i := CurrentThreadConnectionIndex;
if i>=0 then begin
result := fConnectionPool.List[i];
if result.IsOutdated then
fConnectionPool.Delete(i) else // release outdated connection
exit; // <---------- here
end;
result := NewConnection;
(result as TSQLDBConnectionThreadSafe).fThreadID := GetCurrentThreadId;
fLatestConnectionRetrievedInPool := fConnectionPool.Add(result)
finally
LeaveCriticalSection(fConnectionCS);
end;
end;
tmMainConnection:
result := inherited GetMainConnection;
else
result := nil;
end;
end;
Eva
Hi,
first of all thank you for a great framework.
While troubleshooting memory leaks in my webservices I ran into a problem with releasing memory allocated for OleDBConnections.
I use TInterfacedObject class, in sicPerSession mode.
here is the scenario:
I implement my own OnHttpThreadTerminate and the original OnHttpThreadTerminate is stored in fOnHttpThreadTerminate variable
here is the relevant excerpt:
procedure TJSONServer.MyHttpThreadTerminate(Sender:TThread);
begin
fPropsMSSql.EndCurrentThread;
fOnHttpThreadTerminate(Sender);
end;
...
...
fHTTPServer := TSQLHttpServer.Create(
aPORTNAME,
[fServerDB],
aHostName,
useHttpApiRegisteringURI,
32,
secSSL);
fOnHttpThreadTerminate :=fHTTPServer.HTTPSErver.OnHttpThreadTerminate;
fHTTPServer.HTTPSErver.OnHttpThreadTerminate := MyHttpThreadTerminate;
I wondered why my MyHttpThreadTerminate wasn't called at all.. I have found out that it is caused by clones of THttpApiServer, that still point to the original onTerminated event procedure.
I have modified your code in SynCrtSock.pas like this:
THttpServerGeneric = class(TNotifiedThread)
protected
...
procedure setOnTerminate(pValue: TNotifyThreadEvent);
public
...
property OnHttpThreadTerminate: TNotifyThreadEvent read fOnTerminate write setOnTerminate;
end;
....
...
procedure THttpServerGeneric.setOnTerminate(pValue: TNotifyThreadEvent);
var i : integer;
begin
fOnTerminate := pValue;
if (self is THttpApiServer) and assigned ((self as THttpApiServer).Clones) and
not assigned((self as THttpApiServer).fowner ) then
begin
for i := 0 to (self as THttpApiServer).Clones.count - 1 do
begin
THttpServerGeneric( (self as THttpApiServer).Clones[i]).OnHttpThreadTerminate := pValue;
end;
end;
end;
after this modification the "MyHttpThreadTerminate" is now allways invoked
Eva
Pages: 1