People has asked about how to use the HTTP.sys transport (TkbmMWHTTPSysServerTransport).
First… what is HTTP.sys thing?
It is the foundation for Microsofts Internet Information Server. Its an internal Windows kernel level backbone that handles all communication with browser clients when they are accessing IIS based web servers.
The interesting part is that HTTP.sys is default available on all Windows installations since at least Windows Vista and Windows Server 2003, and perhaps even earlier.
So its usable for applications to take advantage of to provide web server like functionality.
Over the years Microsoft has optimized it significantly, and it takes advantage of many performance features built into Windows, like IO completion ports and other stuff, which means it is generally very fast and it is capable of serving thousands of concurrent clients without spawning an equal number of threads.
So its resource friendly and fast.. .whats not to like?
kbmMW added support for HTTP.sys in one of the early v5 releases, but I have never really documented (except from in a demo app) how to use it. So here it goes.
I was asked… “How to change the RESTBiolife (fishfact) demo from using Indy to using HTTP.sys?” so this it is with that outset this blog post is written.
Steps:
- Add a TkbmMWHTTPSysServerTransport to the RESTBiolife’s unit that currently contains a TkbmMWIndyServerTransport. Lets name it trHttp.
- Set trHttp.Server property to point on the TkbmMWServer instance in the same unit form.
- Set trHttp.URLs property to http://127.0.0.1:11111
- Either remove the TkbmMWIndyServerTransport, or set its Server property to nil/empty to unhook it from the TkbmMWServer instance.
Now run it and click on the Listen button.
Open a browser and type in this address in the address line:
http://localhost:11111/biolife/helloworld
You should get a the response: “Hello world”
Type in: http://localhost:11111/biolife/species
Now you should get a lengthy JSON response containing all information available about all species in the database
Type in: http://localhost:1111/biolife/image/90020
Ok… you get the picture now? He… he… I could’nt resist.
Just for the interested… here is what is contained in the service…. Generally only definitions of calls and database structures and almost no code:
unit Unit2; {$I kbmMW.inc} interface uses SysUtils, Classes, Generics.Collections, Vcl.Imaging.jpeg, Vcl.Graphics, DB, kbmMWNullable, kbmMWSecurity, kbmMWServer, kbmMWServiceUtils, kbmMWGlobal, kbmMWCustomHTTPSmartService, kbmMWRTTI, kbmMWObjectMarshal, kbmMWORM, kbmMWSmartServiceUtils; type // ORM style access to the biolife table // We could have used traditional kbmMW query components // too if we wanted. [kbmMW_Table('name:biolife')] TBiolife = class private FSpeciesNo:kbmMWNullable<integer>; FCategory:kbmMWNullable<string>; FCommonName:kbmMWNullable<string>; FSpeciesName:kbmMWNullable<string>; FLengthCm:kbmMWNullable<double>; FLengthInch:kbmMWNullable<double>; FNotes:kbmMWNullable<string>; FGraphics:kbmMWNullable<TkbmMWMemoryStream>; public [kbmMW_Field('name:"Species No", primary:true',ftInteger)] property SpeciesNo:kbmMWNullable<integer> read FSpeciesNo write FSpeciesNo; [kbmMW_Field('name:"Category"',ftString,15)] property Category:kbmMWNullable<string> read FCategory write FCategory; [kbmMW_Field('name:"Common_Name"',ftString,30)] property CommonName:kbmMWNullable<string> read FCommonName write FCommonName; [kbmMW_Field('name:"Species Name"',ftString,40)] property SpeciesName:kbmMWNullable<string> read FSpeciesName write FSpeciesName; [kbmMW_Field('name:"Length (cm)"',ftFloat)] property LengthCm:kbmMWNullable<double> read FLengthCm write FLengthCm; [kbmMW_Field('name:"Length_In"',ftFloat)] property LengthInch:kbmMWNullable<double> read FLengthInch write FLengthInch; [kbmMW_Field('name:"Notes"',ftMemo)] property Notes:kbmMWNullable<string> read FNotes write FNotes; [kbmMW_Field('name:"Graphic"',ftGraphic)] [kbmMW_Element([mwefInline])] property Graphics:kbmMWNullable<TkbmMWMemoryStream> read FGraphics write FGraphics; end; [kbmMW_VirtualTable(TBiolife)] TBiolifeNoImage = class private FSpeciesNo:kbmMWNullable<integer>; FCategory:kbmMWNullable<string>; FCommonName:kbmMWNullable<string>; FSpeciesName:kbmMWNullable<string>; FLengthCm:kbmMWNullable<double>; FLengthInch:kbmMWNullable<double>; FNotes:kbmMWNullable<string>; public [kbmMW_Field('name:"Species No"')] property SpeciesNo:kbmMWNullable<integer> read FSpeciesNo write FSpeciesNo; [kbmMW_Field('name:"Category"')] property Category:kbmMWNullable<string> read FCategory write FCategory; [kbmMW_Field('name:"Common_Name"')] property CommonName:kbmMWNullable<string> read FCommonName write FCommonName; [kbmMW_Field('name:"Species Name"')] property SpeciesName:kbmMWNullable<string> read FSpeciesName write FSpeciesName; [kbmMW_Field('name:"Length (cm)"')] property LengthCm:kbmMWNullable<double> read FLengthCm write FLengthCm; [kbmMW_Field('name:"Length_In"')] property LengthInch:kbmMWNullable<double> read FLengthInch write FLengthInch; [kbmMW_Field('name:"Notes"')] property Notes:kbmMWNullable<string> read FNotes write FNotes; end; [kbmMW_Service('name:biolife')] [kbmMW_Rest('path:/biolife')] TkbmMWCustomSmartService3 = class(TkbmMWCustomHTTPSmartService) private { Private declarations } protected { Protected declarations } public { Public declarations } [kbmMW_Rest('method:get, path:helloworld')] function HelloWorld:string; [kbmMW_Rest('method:get, path:species')] function GetSpecies:TObjectList<TBiolifeNoImage>; [kbmMW_Rest('method:get, path:"specie/{no}"')] function GetSpecie([kbmMW_Rest('value:"{no}"')] const ASpeciesNo:integer):TBiolifeNoImage; [kbmMW_Rest('method:get, path:"specieByCategory/{category}"')] function GetSpecieByCategory([kbmMW_Rest('value:"{category}"')] const ACategory:string):TBiolifeNoImage; [kbmMW_Rest('method:get, path:"image/{no}", responseMimeType:"image/jpeg"')] function GetSpeciesImage([kbmMW_Rest('value:"{no}"')] const ASpeciesNo:integer):TkbmMWBytes; end; implementation uses kbmMWExceptions, Unit1; {$R *.dfm} // Service definitions. //--------------------- function TkbmMWCustomSmartService3.HelloWorld:string; begin Result:='Hello world'; end; function TkbmMWCustomSmartService3.GetSpecies:TObjectList<TBiolifeNoImage>; begin Result:=frmMain.ORM.QueryList<TBiolifeNoImage>; end; function TkbmMWCustomSmartService3.GetSpecie(const ASpeciesNo:integer):TBiolifeNoImage; begin Result:=frmMain.ORM.Query<TBiolifeNoImage>([ASpeciesNo]); end; function TkbmMWCustomSmartService3.GetSpecieByCategory(const ACategory:string):TBiolifeNoImage; begin Result:=frmMain.ORM.Query<TBiolifeNoImage>(['Category'],[ACategory]); end; function TkbmMWCustomSmartService3.GetSpeciesImage(const ASpeciesNo:integer):TkbmMWBytes; var bl:TBiolife; jpg:TJPEGImage; bmp:TBitmap; ms:TkbmMWMemoryStream; begin Result:=nil; bl:=frmMain.ORM.Query<TBiolife>([ASpeciesNo]); if bl<>nil then begin try // Convert from ftGraphics format to JPG. // ftGraphics format includes 8 byte header that must be skipped to // get to the BMP data. jpg:=TJPEGImage.Create; try bmp:=Tbitmap.Create; try bl.Graphics.Value.Position:=8; bmp.LoadFromStream(bl.Graphics.Value); jpg.Assign(bmp); ms:=TkbmMWMemoryStream.Create; try jpg.SaveToStream(ms); ms.Position:=0; Result:=TkbmMWPlatformMarshal.Stream2Bytes(ms); finally ms.Free; end; finally bmp.Free; end; finally jpg.Free; end; finally bl.Free; end; end; end; initialization TkbmMWRTTI.EnableRTTI([TkbmMWCustomSmartService3, TBiolife, TObjectList<TBiolife>, TBiolifeNoImage, TObjectList<TBiolifeNoImage>]); kbmMWRegisterKnownClasses([TBiolife, TObjectList<TBiolife>, TBiolifeNoImage, TObjectList<TBiolifeNoImage>]); end.
However… I now hear you state…. “But I want my web server to run on port 80, not 11111… and I want it to be accessible from other places than from my local computer!”
To do that, you will need to change the URLs property of trHttp to: http://+:80/
However now Microsoft require you to run your RESTBiolife demo application server as an administrator, otherwise you will be getting “Access denied” when you click Listen.
So rightclick your RESTBiolife.exe file, and select Run as administrator. Then it will work. Your server is now accessible from the outside on port 80, provided you have opened the firewall to allow access.
What are the properties trHttp.ServiceName and trHttp.ServiceVersion for?
I’ll explain… When you type in the URL http://…./biolife/…. you can see that the name biolife is part of the URL. kbmMW will automatically use that information to find the appropriate service (you may have more than one in a kbmMW based server) to send the REST request to. However if kbmMW is unable to figure out what service a request belongs to, because the URL does not match with anything defined in the service’s kbmMW_Rest attribute, the default will be called.
Thus ServiceName and ServiceVersion (optional) specifies the default service to call if everything else fails to match. If nothing is defined, kbmMW will attempt to find a local file to send, provided a file pool is available… and if that also fails (it does in the RESTBiolife demo since no filepool has been defined), an error will be returned to the called.