使用http.sys,让delphi 的多层服务飞起来

      一直以来,delphi 的网络通讯层都是以indy 为主,虽然indy 的功能非常多,涉及到网络服务的

各个方面,但是对于大多数多层服务来说,就是需要一个快速、稳定、高效的传输层。Delphi 的 datasnap

主要通过三种实现数据通讯的,一种是大家恨得牙痒痒的indy,另外一种是通过iis 的isapi,最后一种是通过

apache  的动态模块(DSO) 来实现。

     indy 的问题多多,大家基本上都是趋向使用后两种方式,后面两种方式的麻烦是必须安装IIS 或者是

Apache。用起来还要配置很多东西,也不是太方便。

   还好,微软在Windows Vista (server 2008) 以后使用http.sys 作为web 服务的核心,IIS 也是通过这个核心

实现其web 服务的。使用http.sys 都有哪些优势呢?

       1.不用做额外的编码,直接支持https(妈妈再也不用担心ios 10 要 https 了)

       2.内核级的缓冲和内核级的请求队列(大大降低应用服务器自身的压力)

       3.多个应用程序可以使用同一个端口(防火墙表示很欣慰)

       4.内核级的SSL 支持

       5.内核级的静态文件输出支持(下载一个4G的文件试试)

       6.理论上,这是windows 下最快的http 服务,没有之一。

这么多好处,那么我们是否可以在delphi 里面直接使用http.sys ,让delphi 的多层服务在windows 下飞起来?

    答案是肯定的,delphi 完全可以非常顺利的使用http.sys  服务,不光是webbroke, datasanp, 包括我们常用的kbmmw.

目前delphi 的第三方控件里面支持http.sys 的主要有两个,一个是著名的控件商TMS, 其专门有一个控件叫TMS Sparkle

主要就是封装http.sys 服务,这个公司的其他的一些多层控件都是架构在这个控件上的,唯一不好的是,它是商业软件,需要

付费购买。另外一个就是著名的开源框架mormot。此作者的功力已经是恐龙级,可以进delphi  界牛人前十名。他在mormot

里面也封装了 http.sys. 由于是开源的,所以是需要自己把对应封装的代码拿出来,实现与delphi 现有的多层应用适配。

   下面以mormot  封装的 THttpApiServer 为例,说明一下在多层应用中如何使用适配使用http.sys.

我们首先解决webbroker 中如何使用THttpApiServer?

 其实如果大家对webbroker  比较了解的话,就知道webbroker 的工作原理就是把客户端来的请求分发到webbroker 的处理过程,

然后再把返回结果响应给客户端。那么我们需要做一个winapiWebBrokerBridge,功能就是完成以上要求。

首先下载mormot 源码,添加相关目录。

然后加入我们的单元,需要使用的相关对象声明如下:

unit winapiWebBrokerBridge;

{
by xalion  2016.12.25
}

interface

uses
  Classes,
  HTTPApp,
  SysUtils,
  system.NetEncoding,
  SynCommons,
  SynZip,
  SynCrtSock ,

  WebBroker, WebReq;

type
  EWBBException = class(EWebBrokerException);
  EWBBInvalidIdxGetDateVariable = class(EWBBException);
  EWBBInvalidIdxSetDateVariable = class(EWBBException );
  EWBBInvalidIdxGetIntVariable = class(EWBBException );
  EWBBInvalidIdxSetIntVariable = class(EWBBException );
  EWBBInvalidIdxGetStrVariable = class(EWBBException);
  EWBBInvalidIdxSetStringVar = class(EWBBException);
  EWBBInvalidStringVar = class(EWBBException);

 Twinapirequestinfo=class(Tobject)
 protected
   FHttpServerRequest:THttpServerRequest;
   Finrawheaders:Tstringlist;
   FContentStream : TStream;
   FFreeContentStream : Boolean;
   Fhost:string;
   Fport:string;
   Fcontent:string;
   FURL:string;
   Fremoteip:string;
   Fcontentlength:integer;
   fInContentType:string;

   Fcommand:string;
 public
    constructor Create(C: THttpServerRequest);
    destructor Destroy; override;
 end;

 Twinapiresponseinfo=class(Tobject)
  protected
   FHttpServerRequest:THttpServerRequest;
   Foutrawheaders:Tstringlist;
   FContentStream : TStream;
   FFreeContentStream : Boolean;
   Fhost:string;
   Fport:string;
   Fcontent:string;
   Fcontenttype:string;
   Fcontentlength:integer;
   Fstatuscode:integer;
   FCookies: TCookieCollection;
 public
    constructor Create(C: THttpServerRequest);
    destructor Destroy; override;
    procedure AddCookiestohead;
 end;

 TwinapiAppRequest = class(TWebRequest)
  protected
    FRequestInfo   : TwinapiRequestInfo;
    FResponseInfo  : TwinapiResponseInfo;
      FFreeContentStream : Boolean;
    FStatusCode:integer;
    //
    function GetDateVariable(Index: Integer): TDateTime; override;
    function GetIntegerVariable(Index: Integer): Integer; override;
    function GetStringVariable(Index: Integer): string; override;
    function GetRemoteIP: string; override;
    function GetRawPathInfo:string; override;
    function GetRawContent: TBytes; override;

  public
    constructor Create(arequestinfo:Twinapirequestinfo; aresponseinfo:Twinapiresponseinfo);
    destructor Destroy; override;
    function GetFieldByName(const Name: string): string; override;

    function ReadClient(var Buffer; Count: Integer): Integer; override;
    function ReadString(Count: Integer):string; override;
     function TranslateURI(const URI: string): string; override;

    function WriteHeaders(StatusCode: Integer; const ReasonString, Headers: string): Boolean; override;

  end;

  TwinapiAppResponse = class(TWebResponse)
  protected

     FRequestInfo   : TwinapiRequestInfo;
    FResponseInfo  : TwinapiResponseInfo;
   function GetContent: string; override;
     function GetStatusCode: Integer; override;
     procedure SetContent(const AValue: string); override;
    procedure SetContentStream(AValue: TStream); override;
    procedure SetStatusCode(AValue: Integer); override;
    procedure SetStringVariable(Index: Integer; const Value:string); override;
    procedure SetDateVariable(Index: Integer; const Value: TDateTime); override;
    procedure SetIntegerVariable(Index: Integer; Value: Integer); override;

  public
    constructor  Create(AHTTPRequest: TWebRequest;arequestinfo:Twinapirequestinfo; aresponseinfo:Twinapiresponseinfo);
     destructor Destroy; override;
    procedure SendRedirect(const URI: string); override;
    procedure SendResponse; override;
    procedure SendStream(AStream: TStream); override;
    function Sent: Boolean; override;
  end;

  TwinapiWebBrokerBridge = class(THttpApiServer)
  private
   // procedure RunWebModuleClass(C : THttpServerRequest);
  protected
    FWebModuleClass: TComponentClass;
   function Request(C : THttpServerRequest): cardinal;override;

  public
    procedure RegisterWebModuleClass(AClass: TComponentClass);

  end;

 

然后我们就可以使用这个,实现我们的webbroker 应用了。

我们使用delphi 自带的向导,开始建一个webserver.

 

 点ok,继续

 

 点完成。

生成对应的工程文件,然后我们替换主窗体的代码。

 

 

主程序对应的代码很简单。

unit mainp;

interface

uses  Winapi.Messages, System.SysUtils, System.Variants,  SynCrtSock,  System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,  Vcl.AppEvnts, Vcl.StdCtrls, winapiWebBrokerBridge, Web.HTTPApp;

type  TForm1 = class(TForm)    ButtonStart: TButton;    ButtonStop: TButton;    EditPort: TEdit;    Label1: TLabel;    ApplicationEvents1: TApplicationEvents;    ButtonOpenBrowser: TButton;    procedure ApplicationEvents1Idle(Sender: TObject; var Done: Boolean);    procedure ButtonStartClick(Sender: TObject);    procedure ButtonStopClick(Sender: TObject);    procedure ButtonOpenBrowserClick(Sender: TObject);  private    FServer: TwinapiWebBrokerBridge;    procedure StartServer;    { Private declarations }  public    { Public declarations }  end;

var  Form1: TForm1;

implementation

{$R *.dfm}

uses  WinApi.Windows, Winapi.ShellApi;procedure TForm1.ApplicationEvents1Idle(Sender: TObject; var Done: Boolean);
begin
  if fserver=nil then
    begin
        ButtonStart.Enabled :=True;
         ButtonStop.Enabled :=false;
         EditPort.Enabled := True;
    end
    else
      begin
         ButtonStart.Enabled := not FServer.Started;
         ButtonStop.Enabled := FServer.Started ;
         EditPort.Enabled := not FServer.Started;
      end;
end;

procedure TForm1.ButtonOpenBrowserClick(Sender: TObject);
var
  LURL: string;
begin

  LURL := Format('http://localhost:%s', [EditPort.Text]);
  ShellExecute(0,
        nil,
        PChar(LURL), nil, nil, SW_SHOWNOACTIVATE);
end;

procedure TForm1.ButtonStartClick(Sender: TObject);
begin
  StartServer;
end;

procedure TForm1.ButtonStopClick(Sender: TObject);
begin

   freeandnil( FServer);

end;

procedure TForm1.StartServer;
begin

  FServer := TwinapiWebBrokerBridge.Create(True);

  Fserver.Clone(10);// 开始10个进程
  Fserver.AddUrl('/','8080',false,'+',true);
  fserver.Start;

end;

webmodel 里面就很简单了

procedure TWebModule1.WebModule1DefaultHandlerAction(Sender: TObject;
  Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);

begin

    response.Content:='你好!'

end;

 

然后我们开始运行这个程序。

打开浏览器,就会发现,我们的webbroker 程序运行正常。

 

 webbroker 服务器成功了,那么常用的webservice 也就不在话下了。

根据自带的向导,替换对应的主主窗体的文件,运行,棒棒哒。

有同学质疑,这个真的是http.sys提供的服务吗?

那么有图有真相:

 

 datasnap  的·例子就不再演示了,方法与上面差不多。

 

最后,对于不使用datasnap,使用kbmmw  的同学,不用担心,在kbmmw   里面照样可以使用http.sys ,

只不过是要写对应的transport.下面给出服务端和客户端的对象声明。

unit kbmMWHTTPAPIServerTransport;

{$define httpsyslog}

interface

uses
  Classes, Sysutils,
  kbmMWCustomTransport,kbmMWServer,kbmMWGlobal, variants, kbmMWHTTPUtils,
   {$ifdef httpsyslog}
       kbmMWLog,
  {$endif}

  SynCommons,
  SynZip,
  SynCrtSock;

type

  TProtServer = class(TkbmMWServer);
  TxalionTransport=class(TkbmMWCustomServerTransport);

  Txalioninfo=class(TkbmMWServerTransportInfo);

  Txalionserver = class
  private
         FServer:Tkbmmwserver;
         FTransport: TkbmMWCustomServerTransport;

         fPath: TFileName;
         fapiServer: THttpApiServer;
      function Process(C : THttpServerRequest): cardinal;
  public

    destructor Destroy; override;

  end;

  TkbmMWCustomhttpapiServerTransport = class(TkbmMWCustomServerTransport)
  private
    { Private declarations }

      FhttpsysServer: TxalionServer;

      Fhost:string;
      Fport:string;
      FServerUrl:string;
      Fssl:boolean;
      Fversion:string;
      FHTTPQueueLength: integer;

      FServerThreadPoolCount :integer;

  public
    // @exclude
    constructor Create(AOwner:TComponent); override;
    // @exclude
    destructor Destroy; override;

  public
     class function IsSerializedTransport:boolean; override;
     class function IsConnectionlessTransport:boolean; override;

     procedure Listen; override;
     procedure Close; override;
    function IsListening:boolean; override;

  published
    { 设置url   例如/kbmmw}
    property ServerURL:string read Fserverurl write Fserverurl;

    { 服务器 ip    例如   127.0.0.1}
    property Host:string read Fhost write Fhost;

    property Port:string read Fport write Fport;

    property SSL:boolean read fssl write fssl;

    Property Version:string read Fversion;

    property HTTPQueueLength: integer read FHTTPQueueLength write FHTTPQueueLength;

     property ServerThreadPoolCount: integer read FServerThreadPoolCount write FServerThreadPoolCount;

  end;

  TkbmMWhttpapiServerTransport= class(TkbmMWCustomhttpapiServerTransport)
  published
    { Published declarations }

    property Crypt;
    property Compression;
    property StreamFormat;
    property VerifyTransfer;
    property TransportStateOptions;
    property FormatSettings;
    property Plugin;
    property Params;
    property StringConversion;
    property NodeID;
    property ClusterID;
  end;
 {$I httpsysversion.inc}
unit kbmMWNativeHTTPClientTransport;

// by xalion

interface

{$I kbmMW.inc}

{.$define indyhttp}

{.$define httpsyslog}

uses
  Classes, Sysutils, kbmMWCustomTransport,kbmMWClient,

  {$ifdef indyhttp}

    idhttp,
  {$else}
     System.Net.HttpClientComponent,System.Net.HttpClient,
  {$endif}
  {$ifdef httpsyslog}
       kbmMWLog,
  {$endif}

  kbmMWGlobal;

type

{$IFDEF LEVEL16}
  [ComponentPlatformsAttribute({$IFDEF LEVEL23}pidiOSDevice64 or {$ENDIF}{$IFDEF LEVEL18}pidiOSSimulator or pidiOSDevice 

or {$ENDIF}{$IFDEF LEVEL19}pidAndroid or {$ENDIF}pidWin32 or pidWin64{$IFDEF LEVEL17} or pidOSX32{$ENDIF})]
{$ENDIF}
  TkbmMWNativeHTTPClientTransport = class(TkbmMWCustomClientTransport)
  private

     {$ifdef indyhttp}
        FHttpClient:Tidhttp;
    {$else}
       FHttpClient:TNetHTTPClient;
    {$endif}

    FTimeout:integer;
    MyRequestContent:TMemoryStream;
    fhost:string;
    fserverurl:string;
    fssl:boolean;
    Fversion:string;
    FClientType:string;

   public
    constructor Create(AOwner:TComponent); override;
    destructor Destroy; override;

    class function IsSerializedTransport:boolean; override;
    class function IsConnectionlessTransport:boolean; override;

    procedure Connect; override;
    procedure Disconnect; override;
    procedure Assign(ATransport:TPersistent); override;
    function ReceiveStream(AInfo:IkbmMWCustomTransportInfo; const AStream:IkbmMWCustomTransportStream; ALimit:integer):boolean; override;
    procedure TransmitStream(AInfo:IkbmMWCUstomTransportInfo; const AStream:IkbmMWCustomTransportStream); override;
    published
    property Host:string  read fhost write fhost;
    property ServerURL:string read fserverurl write fserverurl;
    property SSL:boolean  read fssl write fssl;
    Property ClientType:string read FClientType;
    Property  Version:string read Fversion;

    property Crypt ;
    property Compression ;
    property StreamFormat;
    property StringConversion;
    property Timeout:integer read FTimeout write FTimeout default 3000;
    property OnException;

    property OnConnectionLost;
    property OnReconnect;
    property MaxRetries;
    property MaxRetriesAlternative;
    property ConnectionString;
    property FallbackServers;
    property AutoFallback;
    property VerifyTransfer;

  end;
  {$I httpsysversion.inc}

 使用http.sys 的应用服务器比使用indy 的速度及稳定性都大大提高。

经过多个实际项目的使用,效果非常好。

总而言之,在windows 上,使用http.sys,就这么自信!

感谢无为、红鱼儿、清幽傲竹、努力的干等同学的支持及测试。

 

时间: 2024-09-21 03:38:43

使用http.sys,让delphi 的多层服务飞起来的相关文章

使用delphi 开发多层应用(三)Delphi常用多层框架介绍

    目前在delphi 开发多层应用的框架基本上集中在以下几种,每种开发框架都有自己的优缺点,没有最好的,大家可以根据 自己的实际需求选择相应框架,我把我本人在使用几种框架的体会与大家分享一下.如有不对之处,可以拍砖.      1. DELPHI 自身提供的DataSnap       从delphi 3 开始,delphi 自身就开始支持多层开发,后期的版本逐步加强三层开发功能,在delphi 6 以后, 把这一功能叫Datasnap,并且每个都有很多变化,包括DBExpress 的数据

使用delphi 开发多层应用(二十一)使用XE5 RESTClient 直接访问kbmmw 数据库

     delphi XE5 出来了,增加了android 的开发支持,另外增加了一个RESTClient 来支持访问REST 服务器. 这个功能非常强大,可以直接使用非常多的REST 服务器.同时也可以支持访问kbmmw 的web 服务器, 并完美的通过JSON支持使用kbmmw 的JSON 数据格式.使我们非常方便的在win32,win64,mac os,ios 和android 上访问kbmmw 的数据库.     首先我们建立一个可以返回JSON 的kbmmw web 服务器.这个可以

Delphi.NET多层应用系统开发技术研讨

从上个星期就开始有朋友问我为什么很久没有更新Blog了,其实我想我写Blog已经很勤快了,这次10多天没有更新Blog想当然是我又开始出差了.上星期台湾进行了『Delphi.NET多层应用系统开发技术研讨会』,在DevCo重新在台湾举办活动1,2个月之后,的确发现慢慢的一些客户开始回流来参加研讨会了,这是个好现象,也让我有机会和一些朋友谈谈他们的工作近况以及他们希望能够听到什么主题的研讨会.而之所以DevCo会在上星期举行『Delphi.NET多层应用系统开发技术研讨会』就是因为许多朋友都在询问

使用delphi 开发多层应用(七)简单的kbmMW多层数据库访问服务

      刚写完几个多层的例子,本来准备再写点其他稍微复杂的例子,很多同学就问数据库访问的方法,既然这样,就先写一下 数据库访问的过程与方法.kbmMW 支持很多数据库访问方式和控件,前面在安装时已经说过了.由于delphi 已经在d7 以后 不再推荐使用其传统的数据库方式BDE了,尤其是在delphi XE2 由于支持win64, 已经不支持使用BDE 了,为了演示方便,本文 后台数据库选择使用完全免费的firebird.firebird 是一个非常短小精悍的关系数据库,支持存储过程.触发器

使用delphi 开发多层应用(十八)使用Basic4android 访问RTC 服务的二进制流(照片)

    上次写了b4a 通过xmlrpc 访问rtc 的远程服务,有网友询问如何通过b4a 访问RTC web 的二进制流,例如如何下载 服务器上的照片,其实访问二进制流和访问字符串类似,不同的地方是,由于是通过http 协议,对于二进制流,需要先转换成 base64 编码,到客户端后再再把base64 转换回去成二进制码,就可以了.(注:由于RTC 的bug,需要RTC 6.08 以上的版本)    RTC服务器端的代码如下: procedure TForm1.RtcFunction3Exec

使用delphi 开发多层应用(四)kbmMW 的安装与配置

更新至kbmmw 5.04.  (2017.12.14) --------------------- 目前的KbmMW 最新版是4.0 beta 版,它支持最新的delphi XE2 ,同时支持win32,win64,mac osx的编译开发. 增加了原生的JSON 的支持(看来JSON 是越来越热了:)),同时提高了http web 的功能,作者已经用最新的KbmMW 替换了 原来使用aspx 的下载网站,其使用kbmMW 的web服务,并使用AJAX 来实现数据列表,经过我的使用,效果还不错

使用delphi 开发多层应用(十三)使用Basic4android 直接访问kbmMW server

 由于目前delphi xe2 还不直接支持Android 的开发,因此kbmMW 客户端的功能也没办法直接在Android 上运行. 由于kbmMW 为了与java 应用程序通讯,在企业版里面提供了一个JavaClinet. 具体文件名为kbmMWClient.jar. 而在basci4android 可以直接使用这个jar. 也就是说通过kbmMW 的javaclient 可以直接访问kbmMW 的服务器. 当然由于客户端没有kbmMemTable的功能,一次只能是访问远程服务过程,无法直接

使用delphi 开发多层应用(一)C/S系统的扩展

   在讨论多层开发前,先谈一下老的C/S 程序.      几个月前,我的一个朋友找我,说是我以前给他做的一个系统,原来是在局域网的运行的,现在有两个校区,另外一个校区也想使用, 两个校区都可以上互联网,让想办法解决.我去看了一下,是我10年前给开发的c/s 系统,源代码早都不见了.我说可以按现在的模式 重新开发一套,但是需要时间和money.朋友要求一周内就要解决,同时认为系统现有的功能已经足够了,而且系统非常稳定,他们已经习惯操作了, 没有必要再开发了.看现有的条件下如何快速.安全的实现两

使用delphi 开发多层应用(十九) ios通过soap 访问kbmmw服务器

      随着delphi XE4 的推出,开始真正意义上支持ios 的开发,由于目前kbmmw 还不完全支持ios 的开发,因此 无法直接使用kbmmw 的客户端访问kbmmw 的服务器(虽然kbmmw 也提供了C 的客户端,可以使用xcode连接 kbmmw的C 客户端来访问kbmmw 服务器,但是功能有限,而且要熟悉xcode),对于急着想在ios 使用kbmmw 服务的同学,可以先使用kbmmw 的webservice 方式访问kbmmw 服务器.   下面我就介绍以下如何在ios 访