Jump to content

Mark Williams

  • Content Count

  • Joined

  • Last visited

Community Reputation

3 Neutral

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

  1. Mark Williams

    ShellExecute and passing of password

    Thanks again. I have decided to go down a different route altoghether and instead of having separate apps, house them all within the same app.
  2. Mark Williams

    ShellExecute and passing of password

    Thanks to all. I probably need to rethink my approach.
  3. Mark Williams

    ShellExecute and passing of password

    I would like to shellexecute one app from another. Both apps require username and password. I would like to be able to pass the password securely from one app to the other. Obviously, command-line parameters are out and I would imaging a memory mapped file will also not be secure. Is there any relatively simple and secure way of achieving this.
  4. Mark Williams

    ISAPI DLL concurrent requests

    Thanks for this information. I will go through it all asap. Likely, I will have some questions if you don't mind. Bit of a newbie to ISAPI!
  5. Mark Williams

    ISAPI DLL concurrent requests

    Spot on! That was the problem. As for WebGarden, I have check and you create a WebGarden by increasing the ApplicationPool's Maximum Worker Processes above 1. I am now running as non-web garden. I have done a lot of research based on your helpful points above and eventually the mist began to clear. I now have a working ISAPI DLL which appears to be thread safe. Unfortunately, I couldn't find any good examples of how to do what I was trying to do. My DLL uses global variables stored in an inifile. I don't want to load the inifile each time a new thread is fired to avoid repeated loading of the ini file.. So I need to load the inifile once when the DLL is first loaded. Also, I need to have a threadsafe logging system and finally I use a MimeTable in the DLL and I don't want to load that for each request. Below is my relevant code (with the detail removed) to show I have gone about the above. It is working as far as I can see, but I would appreciate any tips for improvement or re potential pitfalls. Hopefully, this will then help others who are new ISAPI dll. It took me many hours of head scratching to get this far. The dpr library WebServer; uses Winapi.ActiveX, System.Win.ComObj, System.SysUtils, Web.WebBroker, Web.Win.ISAPIApp, Web.Win.ISAPIThreadPool, WinAPI.Isapi2, WinAPI.Windows, System.SyncObjs, WebModule in 'WebModule.pas' {WebModule1: TWebModule}; {$R *.res} //Read online somewhere that it is best to initialize/Finalize your globals in the following function //rather than in Initialization/Finalization sections of the webmodule} function GetExtensionVersion(var Ver: THSE_VERSION_INFO): BOOL; stdcall; begin Result := Web.Win.ISAPIApp.GetExtensionVersion(Ver); CriticalSection := TCriticalSection.Create; StartServer; end; function TerminateVersion(var Ver: THSE_VERSION_INFO): BOOL; stdcall; begin Result := Web.Win.ISAPIApp.GetExtensionVersion(Ver); CloseServer; CriticalSection.Free; end; exports GetExtensionVersion, HttpExtensionProc, TerminateExtension; begin CoInitFlags := COINIT_MULTITHREADED; Application.Initialize; Application.WebModuleClass := WebModuleClass; {Application.MaxConnections:=100;} //if you want to increase beyond the default 32 for example NumberOfThreads:=Application.MaxConnections; //see comments following code samples. This is necessary even if you do not want to change the MaxConnections Application.Run; end. Stripped down WebModule unit WebModule; interface uses WinApi.Windows, System.SysUtils, System.Classes, Web.CGIHTTP, Web.HTTPApp, Web.ReqMulti, System.SyncObjs, ThreadFileLog, IDGlobalProtocols {, etc...}; type TLogType = (leNone, leCriticalError, leMajorError, leMinorError, leMajorEvents, leDevelopment); type TWebModule1 = class(TWebModule) FDQuery: TFDQuery; FDPhysPgDriverLink: TFDPhysPgDriverLink; FDConnection: TFDConnection; procedure WebModule1HeadAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); procedure WebModuleBeforeDispatch(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); procedure WebModuleAfterDispatch(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); private { Private declarations } public { Public declarations } end; var WebModuleClass: TComponentClass = TWebModule1; CriticalSection: TCriticalSection; const EncryptionKey='Q9__M_f_qc1EX8ce98HsQrYG_s4aRGQj5G_Ixu4XTQs___esfxdH_kf_lg5_8lnk'; Procedure StartServer; Procedure CloseServer; implementation Uses web.WebBroker; var //these will be accessible to all threads ServerStarted:boolean; LogType:TLogType; LogFile, PGVendorLib:string; FThreadFileLog : TThreadFileLog; MimeTable:TIDMimeTable; {%CLASSGROUP 'System.Classes.TPersistent'} {$R *.dfm} procedure AddToLog(str: string; whichLogType:TLogType); var Log:string; begin try if (LogType=leNone) or (ord(whichLogType)>ord(LogType)) then //we are not logging these types of events or errors exit; case whichLogType of leCriticalError: Log:='CRITICAL ERROR: '; leMajorError: Log:='MAJOR ERROR: '; leMinorError: Log:='MINOR ERROR: '; leMajorEvents: Log:='MAJOR EVENT: '; leDevelopment: Log:='DEVELOPMENT: '; end; Log:=Log+str; FThreadFileLog.Log(LogFile, Log); Except end; end; Procedure CreateMimeTable; begin try if not assigned(MimeTable) then begin MimeTable:=TIDMimeTable.create; AddToLog('Mime Table Create', leDevelopment); end; except on E: Exception do AddToLog('Error CreateMimeTable: '+e.Message, leMajorError) end; end; Procedure StartServer; var appPath:String; iniFile:TIniFile; begin if serverStarted then //Probably overkill as should only be called once exit; CriticalSection.Enter; try try FThreadFileLog := TThreadFileLog.Create(); appPath:=GetModuleName(HInstance); if pos('\\?\', appPath)>0 then //this is something to do with the Windows function GetModuleFileName and uncpaths appPath:=copy(appPath, 5, MaxInt); iniFile:=TIniFile.Create(changeFileExt(appPath, '.ini')); try if not FileExists(iniFile.FileName) then begin LogFile:='c:\ProgramData\Log.txt'; AddToLog('IniFile does not exist at location: "'+iniFile.FileName, leCriticalError); Exit; end; AddToLog('Server Started', leMajorEvents); LogType:=TLogType(iniFile.ReadInteger('GENERAL', 'log_type', 1)); LogFile:=inifile.ReadString('GENERAL', 'log_file', ''); if (LogType<>leNone) and (LogFile<>'') then begin if not DirectoryExists(ExtractFilePath(LogFile)) and not ForceDirectories(ExtractFilePath(LogFile)) then LogFile:='c:\ProgramData\Log.txt'; end; PGVendorLib:=inifile.ReadString('LOCATIONS', 'pg_vendor_lib', ''); finally iniFile.Free; end; AddToLog('LogType:'+inttostr(ord(logType)), leDevelopment); AddToLog('LogFile: '+LogFile, leDevelopment); AddToLog('FDPhysPGDriverLink.VendorLib: '+PGVendorLib, leDevelopment); AddToLog('Server Started', leMajorEvents); ServerStarted:=true; except try AddToLog('Server could not be loaded: '+SysErrorMessage(GetLastError), leCriticalError); except end; end; finally CriticalSection.Leave; end; end; Procedure CloseServer; begin CriticalSection.Enter; try if assigned(MimeTable) then try MimeTable.Free; except end; if assigned(FThreadFileLog) then try FThreadFileLog.Free; except end; try AddToLog('Server Closed', leMajorEvents); except end; finally CriticalSection.Leave; end; end; procedure TWebModule1.WebModule1HeadAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var fileName:string; begin Handled:=true; try //If this action needed the mimetable it would call CreateMimeTable at this point FileName:=trim(Request.GetFieldByName('Location')); if FileName='' then begin SetResponse(Response, 400, 'Bad Request', 'Head'); exit; end; if fileExists(FileName) then SetResponse(Response, 200, 'OK') else SetResponse(Response, 404, 'NotFound'); except end; end; procedure TWebModule1.WebModuleBeforeDispatch(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); begin FDPhysPgDriverLink.VendorLib:=PGVendorLib; AddToLog('Before Dispatch - PathInfo: '+Request.PathInfo, leDevelopment); AddToLog('Before Dispatch - Method: '+Request.Method, leDevelopment); AddToLog('MaxConnections: : '+Application.MaxConnections.ToString, leDevelopment); AddToLog('ActiveConnections: : '+Application.ActiveCount.ToString, leDevelopment); AddToLog('InactiveConnections: : '+Application.InActiveCount.ToString, leDevelopment); AddToLog('CachedConnections: : '+Ord(Application.CacheConnections).ToString, leDevelopment); end; procedure TWebModule1.WebModuleAfterDispatch(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); begin AddToLog('After Dispatch - Method: '+Request.Method, leDevelopment); end; end. ThreadFileLog unit unit ThreadFileLog; interface uses Windows, ThreadUtilities, System.Classes; type PLogRequest = ^TLogRequest; TLogRequest = record LogText : String; FileName : String; end; TThreadFileLog = class(TObject) private FThreadPool: TThreadPool; procedure HandleLogRequest(Data: Pointer; AThread: TThread); public constructor Create(); destructor Destroy; override; procedure Log(const FileName, LogText: string); end; implementation uses System.SysUtils; procedure LogToFile(const FileName, LogString: String); var c1, c2 : dword; i:integer; s:string; begin try c1 := CreateFile(PChar(FileName), GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_ALWAYS, 0, 0); i:=0; while c1=INVALID_HANDLE_VALUE do begin if i>200 then //2 secs elapsed and haven't been able to access log exit else begin inc(i); Sleep(10); c1 := CreateFile(PChar(FileName), GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_ALWAYS, 0, 0); end; end; if c1 <> INVALID_HANDLE_VALUE then begin SetFilePointer(c1, 0, nil, FILE_END); S:=DateTimeToStr(Now)+': '+LogString+sLineBreak+SLineBreak; WriteFile(c1, s[1], length(s)*SizeOf(Char), c2, nil); CloseHandle(c1); end except end; end; constructor TThreadFileLog.Create(); begin FThreadPool := TThreadPool.Create(HandleLogRequest, 1); end; destructor TThreadFileLog.Destroy; begin FThreadPool.Free; inherited; end; procedure TThreadFileLog.HandleLogRequest(Data: Pointer; AThread: TThread); var Request: PLogRequest; begin Request := Data; try LogToFile(Request^.FileName, Request^.LogText); finally Dispose(Request); end; end; procedure TThreadFileLog.Log(const FileName, LogText: string); var Request: PLogRequest; begin New(Request); Request^.LogText := LogText; Request^.FileName := FileName; FThreadPool.Add(Request); end; end. Other possible useful information: The Application The application created in the project file is a TWebApplication not a TApplication. It exposes MaxConnections, ActiveConnections and InActiveConnections, CachedConnection. MaxConnections The maximum no of connections to your DLL. By default this is 32. So up to 32 new threads will be spawned (but note comments re NumberOfThreads below). You can increase or decrease this number as requi ActiveConnections This reports the number of connections currently in use. InactiveConnections If CachedConnections is true new threads are not disposed of after use, but cached for future use. whilst cached they are recorded as InactiveConnections. ISAPIThreadPool unit This is added by default and deals with threading. NumberOfThreads Global variable declared in the ISAPIThreadPool unit. It is set by default to 25. So whilst the default MaxConnections is 32 (so you can theoretically have 32 connections) unless you increase the NumberOfThreads you will in effect be allowed only 25 connections! The example project code deals with this.
  6. Mark Williams

    ISAPI DLL concurrent requests

    WebGarden? Is this when you increase the Maximum Worker Processes of the Application Pool above 1? If so, it works when I do this, but I appreciate it is not the solution and I have reduced it again to 1. Nothing in WebModule's OnCreate and onDestroy. BTW what event should I use to free objects etc? Nothing in the Event Viewer. I am writing everything to a log file. See my last but one post. Also, can you advise me how I can monitor the ActiveConnections property from the WebModule? I can't figure out how to access the Application object. BTW thanks. I'll get some test projects uploaded asap.
  7. Mark Williams

    ISAPI DLL concurrent requests

    Web Web.config: <?xml version="1.0" encoding="UTF-8"?> <configuration> <system.net> <mailSettings> <smtp from=""> <network host="" /> </smtp> </mailSettings> </system.net> </configuration> I'll have to work on a MCVE. But do you have any suggestionsat this point why multi-threading may not to be working?
  8. Mark Williams

    ISAPI DLL concurrent requests

    I think maybe I didn't explain myself very well. Apologies. Plus I have managed to improve things a little since my first post. I noticed that my DLL was including Madshi's exception units. I have removed these and there is now an improvement. Whereas the DLL appeared to be hanging on concurrent calls resulting in a timeout error, my client app now gets a ENetHTTPClientException "Server returned an invalid or unrecognised response". But whereas the DLL was freezing it now continues to run albeit rejecting any concurrent requests. I don't know what code to post as I am not getting any exceptions for any of my event handlers (I do have the event wrapped in try except blocks). I add a log entry in the BeforeDispatch event to advise what method etc has been called and one in the AfterDispatch to advise when it is done. I log the http response codes issued by the handlers when they are done. I have set up a test app to fire off rapid requests via threads to download 14 files at slightly staggered intervals (10ms) so that they overlap. Accordingly, there are probably 14 concurrent requests being made at the same time. Only 1 returns successfully all the others fire a ENetHTTPException. The first request executes correctly. I log the BeforeDispatch and AfterDispatch events and these fire ok. I also log the Response returned by the dlls' download method and this logs correctly for the first call, which means it has completed okay. For concurrent calls, the BeforeDispatch event is not called. Also, within each method I log the fact it has been called as the first line of the method's code. For concurrent calls, the DLL is not even entering the handler for the method being called. It does not trigger any exception even though wrapped in a try except block. I think the problem is a threading problem within the DLL, but I don't understand why. As mentioned above, if within the ISS ApplicationPool for the DLL I increase the number of Maximum Working Processes above 1 then all is okay (presumably as long as I don't make concurrent requests that exceed the max working processes) save for the fact that each (concurrent?) request seems to run a new DLL instance rather than making use of DLL threading. Whilst this sort of resolves the problem, I am certain it is not the way to do this from all I have read on the web. To get threading working I can't work out whether there is something I need to configure on my website (it is configured for limitless connections), the ApplicationPool (can't see anything) or the app (and I used the wizard to create it so I would have thought not.. Below is the code from the DLL dpr file: library MWCWebServer; uses Winapi.ActiveX, System.Win.ComObj, System.SysUtils, Web.WebBroker, Web.Win.ISAPIApp, Web.Win.ISAPIThreadPool, WebModule in 'WebModule.pas' {WebModule1: TWebModule}; {$R *.res} exports GetExtensionVersion, HttpExtensionProc, TerminateExtension; begin try CoInitFlags := COINIT_MULTITHREADED; Application.Initialize; Application.WebModuleClass := WebModuleClass; {Application.MaxConnections:=200; NumberOfThreads:=Application.MaxConnections; Application.CacheConnections:=true;} Application.Run; end. I have tried setting MaximumConnections, CacheConnections. NumberOfThreads and none of these seem to do anything. But I can't imagine for a moment that the default no of threads for a webmodule is 1. So I guess it has to be something else. I have tried to monitor how many connections there are. But within the webModule I cannot figure out how to access the Application property!
  9. I have written an ISAPI DLL (Delphi 10.3) for IIS to carry out various functions (download/upload of documents, query tables etc). It was working fine until I started firing more than one request at a time then the DLL simply crashed and I had to restart WWW service. The DLL is being used to request smallish files (20-30KB) over a local network. These are being requested one at a time. If I make these requests the same time as one or two Head requests that all goes Kaput. I though it may be the TWebRequestHandler's maxConnections so I set that to 200. It made no difference. After a bit of digging around I noticed that the IIS ApplicationPool that I created for the DLL has a "Maximum Worker Processes" property and that this was set to 1. I have set this to 20 and now the DLL seems to be able to handle more than one concurrent request. However, I noticed from my DLL's log file that it was now creating a new instance of the DLL for every request. I had a dig around on the web and from the information I found I have come to the conclusion that Maximum Worker Processes is probably not what I need. It seems to be designed for long-running processes, which is not what my DLL is designed for. If not Maximum Worker Processes and increasing MaxConnections doesn't work how do I get my DLL to deal with concurrent requests.
  10. Thanks. I do write both server and client. I have it working with multiformdata at moment and am going to leave it at that for now as this is a fairly minor part of a hefty development and I have managed to get myself bogged down in it. I will revisit it when I have a bit more time and do some research online. But thanks for all your help.
  11. I tried Base64. This may have worked, but I think I'm running into a limitation as to permissible size of header. I absolutely agree that it is food for the payload, but I have tried and failed to submit/receive content using THTTPClient and TWebRequest components. My code for submitting the component is below, but when I try and access the TWebRequest's content it remains stubbornly empty. FHttpClient:=THttpClient.Create; try With FHttpClient do begin data:=TStringStream.Create; try data.WriteString(content); ContentType:='text/html'; Accept:='text/plain, */*'; AcceptCharSet:='utf-8'; LResponse:=FHttpClient.Post(Script, data); finally data.Free; end; end; I have now resorted to submitting it as multipartdata, which works, but seems overkill just to submit a string.
  12. Hi Thanks, As far as I can see (and with my limited understanding of the protocols) it is compliant with the requirements for headers. Each line break is coded +Chr(32)+sLineBreak; There are no line breaks inserted other than in this manner, but I still get the error. Does anyone know of any function for encoding text in away that will satisfy the protocol?
  13. This is in connection with the AddHeader function of the IHTTPRequest. I'm trying to add a multiline text which also contains numerous characters such as = signs etc. I am getting the above error as a result. Is it even possible to do this? Should I be formatting the text in some way before adding it? UrlEncode?
  14. Mark Williams

    FireDAC TFDMetaDataQuery default field value

    Thanks Lars. There is a query for PostgreSQL which will retrieve this info, but I was hoping to do it all with the MetaDataQuery.
  15. Trying to analyse the structure of a table using TFDMetaDataQuery with MetaDataKind set to mkTableFields. This returns just about everything I need apart from (as far I can see) a field's default value. I can see it returns TFDDataAttributes and that this includes an attribute caDefault which advises if a field has a default value, but I can see now way of ascertaining what the default value is. The metadata structure can be found at: http://docwiki.embarcadero.com/RADStudio/Rio/en/Metadata_Structure_(FireDAC)