导航:
论坛 -> DELPHI技术
斑竹:liumazi,sephil
作者:
2024/1/27 0:18:33
标题:
加入我的收藏
楼主:
mormot2是一个很强大的框,网上能搜到的有内容的文章很少,此贴算抛砖引玉,希望有更多的人能能使用上它,有不正确的地方请多指正。
----------------------------------------------
学无止境
作者:
2024/1/27 0:45:21
1楼:
先从数据库说一说个人遇到的坑。 一、com资源释放的问题。 如果是进行后端web服务开发,建议使用ODBC连接方式,副作用就要装odbc驱动(可从微软下载最新18版本).这样在服务线程中不会有coinitxxx, couinitxxxx的问题。 如果想用oledb驱动,那么就要要进行com的counit....,com的初始化工作mormot架框在newsafeconnect中已经做了,但线程在回收入池的时候,需要couninitxxxx,这必须手动代码实现,这在框架文档和源代码中都有说明。如果觉得每个方法中都要写一下麻烦,可以在一个线程回调的全局方法(事件)中加一行代码,具体是哪个方法记不清了。 二、odbc调用储存过程的问题 如果使用ODBC方式,那么在存储过程的代码中,一定要加上set nocount on, 这个问题困扰我一段时间。 ODBC连接方式,我感觉最好用的方式是这样的: var dsn := rawbytestring('FILEDSN='+ExtractFilePath(ParamStr(0))+'db.dsn'); var db := TSqlDBODBCConnectionProperties.Create('', dsn, '', ''); 这和古老的ado很像,此外,用户名和密码可以出现在这个dsn文件中。 另外一种方式就是直接在 控制面板 里的odbc中先创建好连接,比如先创建了一个名字是321的连接,再写下面这样的代码: TSqlDBOdbcConnectionProperties.Create('321', '', 'sa', '12345'); //已经建好321连接 还有其它的方式 TODBCProp.Create('', 'DRIVER=ODBC Driver 18 for SQL Server;UID=sa;PWD=12345;Trusted_Con_nection=No;SERVER=192.168.1.50;MARS_Con_nection=yes;DATABASE=MyDB', '', ''); TODBCProp.Create('', 'DSN=123;UID=sa;PWD=Sa12345;DATABASE=MyDB', '', ''); 要注意的一个属性: [ODBC] DRIVER=ODBC Driver 18 for SQL Server UID=sa TransparentNetworkIPResolution=Disabled Region_al=No TrustServerCertificate=Yes Encrypt=Optional //这个属性 LANGUAGE=简体中文 DATABASE=MyDB WSID=JJW-PC APP=Microsoft? Windows? Operating System Trusted_Con_nection=No SERVER=192.168.1.50 PWD=12345 如果想调出系统的odbc连接对话框,需要设置这个属性 TSqlDBOdbcConnectionProperties.SqlDriverConnectPrompt := TRUE;
----------------------------------------------
学无止境
作者:
2024/1/27 0:53:23
1楼:
内容呢?
----------------------------------------------
-
作者:
2024/1/27 1:04:13
2楼:
如果已经通过TSqlDBXXXXDBMSSQLConnectionProperties建立了数据库连接,那么就可以直接执行sql来返回结果了,例如调用ExecuteInlined(RawUtf8(sql), true)返回一个ISqlDBRows接口,再 while true do begin var jsonStr := string(rows.StepAsJson()); if jsonStr = '' then Exit; ... end; 或者读取num字段的值 rows.ColumnInt('num'); 或者直接把json数据写到一个stream中, var strStream := TStringStream.Create('', TEncoding.UTF8, True); FetchAllToJson(strStream, True); 返回的接口带来的一个好处是不用try finally end 来释放了,代码更清爽。 TSqlDBXXXXDBMSSQLConnectionProperties有多种方法来执行sql,每个方法怎么用实践中再根据情况调用。 另外,mormot框架很好为解决了从数据到json的问题的。
----------------------------------------------
学无止境
作者:
2024/1/27 1:41:24
3楼:
前面大体上说了下数据库的基本使用,接着再说一下http服务。 mormot框架中,mormot.xxx.server的单元不只一个,这里先提一下核心类 THttpServerGeneric,它是其它几个server的父类,mormot2框架是跨平台的,THttpRestServer内部使用的都是它的子类,具体使用哪个子类,可以在create的参数中可以指定。 这里考虑跨平台的问题,就拿THttpServer来说,(如果是windows系统,最好使用THttpApiServer)。 和THttpServer一起干活的两个重要的类:TUriRouter和THttpServerRequestAbstract TUriRouter封装了路由,是THttpServer的一下属性:Router. 而 THttpServerRequestAbstract封装了request和response。 如果想让httpserver跑起来处理请求,就需要添加路由,添加路由有多种方式,这里仅说一种方法。 HttpServer.Route.Run( [urmGet], //方法类型, get post delete等 '\home', //url DoRequest); DoRequest方法的定义: TOnHttpServerRequest = function(Ctxt: THttpServerRequestAbstract): cardinal of object; 这个写一些示例代码,是个人刚开始接触mormot时候的实现方法: //基类 TAbstractRoute = class function DoRequest(Ctxt: THttpServerRequestAbstract): Cardinal; virtual; abstract; end; //贴标签用 TRouteAttr = class(TCustomAttribute) private FUrl: string; protected function GetUrl: string; virtual; public constructor Create(AUrl: string); overload; property Url: string read GetUrl; end; //设备连接 [TGet('home')] //url TGetXXXXX = class(TAbstractRoute) public procedure DoResponse(SN: string); override; end; //创建路由 Result := TMyHttpServer.Create(UTF8String(Port.ToString)); // Result.OnConnect := OnStartEvent; // Result.OnDisconnect := OnStopEvent; // Result.OnRequest := RequestEvent; var List := TList<TAbstractRoute>.Create; List.Add(TGetXXXXX1.Create); List.Add(TGetXXXXX2.Create); ... var LCtx := TRttiContext.Create; for var R in List do begin var LType := LCtx.GetType(R.ClassInfo); for var LAttr in LType.GetAttributes do begin if LAttr is TGet then Result.AddRoute(TUriRouterMethod.urmGet, TRouteAttr(LAttr).Url, R) else if LAttr is TPost then Result.AddRoute(TUriRouterMethod.urmPost, TRouteAttr(LAttr).Url, R) else raise Exception.Create('URL方法类型错误。'); end; end; //添加路由 procedure TMyHttpServer.AddRoute(MethodType: TUriRouterMethod; AUrl: string; Value: TAbstractRoute); begin FRoutes.Add(Value); FHttpServer.Route.Run([MethodType], RawByteString(AUrl), Value.DoRequest); end;
----------------------------------------------
学无止境
作者:
2024/1/27 1:47:06
4楼:
说一下取url中参数的坑 Ctxt.UrlParam(RawUTF8('XXXX='.ToUpper), val) 1、取参数值的时候要带上 = 2、要参数名要大写 THttpServerRequestAbstract的incontent是请求body的数据。 outContent是返回的数据。 TOnHttpServerRequest = function(Ctxt: THttpServerRequestAbstract): cardinal of object; 的返回值就是response的code, 比如200, 404, 50x等 至于json2object或object2json就不写,用mormot框架的或用delphi自带的都行。
----------------------------------------------
学无止境
作者:
2024/1/27 1:54:30
5楼:
ThttpServer在mormot2框架中相对业来说比较原始底层的,个人觉得已经很好用了,但mormot并不限于此,还提供了更高一级的restserver框架,使用起来也不算复杂,先贴上代码,后续再写。 FRestServer := TRestServerFullMemory.CreateWithOwnModel([], False, 'root'); FRestServer.CreateMissingTables(); var f := FRestServer.ServiceDefine(TMyService, [IMyService], sicShared). SetOptions([], [optExecLockedPerInterface, optNoLogInput, optNoLogOutput]); // thread-safe fConnected[] f.ByPassAuthentication := true; f.ResultAsJsonObjectWithoutResult := True; f.ResultAsJsonObject := False;
----------------------------------------------
学无止境
作者:
2024/1/27 10:41:11
6楼:
mormot的文档最新到1.18,页数有2K+。里面有很多SOA,DDD,MVC以及mormot在此基础上如何设计的描述。 简单的和datasnap对比一下, THttpServer相当于WebBroker,HttServer的router相当于webModule的Actions。而restServer相当于datasnap rest中的webmodule上的那些ds开头的组件。 datasnap框架的大体写法是先定义自己类,然后与dsserver绑定,这个过程中并没有orm相关的东西,需要自己写代码进行orm。在mormot中是有orm这一块东西的(可选的,看是否涉及到数据库) 简单来说restserver主要作用有两个:一是路由,二是操纵orm。restserver也有个router,是resturiRouter,不是uriRouter。 TRestServerFullMemory.CreateWithOwnModel([], False, 'root'); 第一个参数是ormModel, 第二个参数设定是否验证,第三个参数是设定restrouter的根路径.
----------------------------------------------
学无止境
作者:
2024/1/27 10:56:40
7楼:
创建完restServer后下面的这行代码一定要带上(具体原因展开说就太多了), FRestServer.CreateMissingTables(); 主要说下ServiceDefine方法, var f := FRestServer.ServiceDefine(TMyService, [IMyService], sicShared). SetOptions([], [optExecLockedPerInterface, optNoLogInput, optNoLogOutput]); 这个方法主要作用相当于datasnap中的TDSServerClass到具体类的绑定。 首先要写一个接口,继承IInvokable,(不一定非要从这个接口继承,见mormot方法或ddemo) IMyService = interface(IInvokable) ['{C828803C-77DA-44E7-A599-0569DD52D157}'] function GetTime: string end; 注册接口是必须的: initialization TInterfaceFactory.RegisterInterfaces([ TypeInfo(IAccessControllerService) ]); 然后再写一个类实现接口方法 TMyService = class(TInterfacedObject, IMyService) function GetTime: string; end; 接再把restserver注入到httpserver. FHttpServer := TRestHttpServer.Create(APort, [FRestServer], '+'); 以上代码就相当于完成了datasnap的全部,不同之处就是从IDE向导一步步的创建改为手动建。
----------------------------------------------
学无止境
作者:
2024/1/27 11:09:00
8楼:
f.ResultAsJsonObjectWithoutResult := True; f.ResultAsJsonObject := False; 这两行的代码用途,主要是控制返回结果的json格式。 在datasnap中,好像默认返回的json格式是[result: [xxx]],mormot同样类似。 关于路由重定向的三个方法: TRestHttpServer.Route.Rewrite TRestHttpServer.RootRedirectToUri TRestHttpServer.DomainHostRedirect
----------------------------------------------
学无止境
作者:
2024/1/27 14:37:16
9楼:
补充说一个路由重定向的坑,先从一个request请求开始。 TRestHttpServer = class(TSynPersistent) protected fShutdownInProgress: boolean; fHttpServer: THttpServerGeneric; TRestHttpServer并不是一个真正httpServer,它内部使用了THttpServerGeneric(它是一个真正在的httpServer类),此外,TRestHttpServer.Create时注册进来了一个RestServer,它先把THttpServerGeneric.onRequest事件接过来(赋值给自己的Request方法),然后再把request请求转发给restserver.uri方法,RestServer再转到Service完全一次调用请求。 之前说过,restServer有个RestUriRouter, httpServer有个UriRouter, 所以一次http请求会先经过RestUriRouter处理,如果未处理,再由UriRouter处理。 TRestHttpServer在create的时候,会先判断能不能用THttpApiServer,如果是winddows平台,并且可以创建THttpApiServer, 那么fHttpServer就是THttpApiServer,否则就是其它httpServer(THttpServerGeneric的其它子类)。
----------------------------------------------
学无止境
作者:
2024/1/27 15:03:10
10楼:
A_HttpServer := TRestHttpServer.Create(APort, [FRestServer], '+', useBidirSocket); B_HttpServer := TRestHttpServer.Create(APort, [FRestServer], '+', HTTP_DEFAULT_MODE); 上面创建了两个TRestHttpServer实例,在windows平台下,你会发现下面的重定向对于A_HttpServer可行,而B_HttpServer不可行。 HttpServer.Route.Rewrite(urmGet, '/GetTime', urmGet, '/root/MyService/GetTime'); 主要原因是httpApiServer使用了httpapi.dll(详见mormot.lib.winhttp)的AddUrl注册uri,导致 /GetTime请求根本就没进来。 解决方案(解决方案不只这一种) FHttpServer := TRestHttpServer.Create(APort, [FRestServer], '+', HTTP_DEFAULT_MODE, 32, secNone, '/' {aAdditionalUrl}); 就是把aAdditionalUrl也带上。
----------------------------------------------
学无止境
作者:
2024/1/27 15:45:18
11楼:
关于开启websocket 调用TRestHttpServer.WebSocketsEnable即可,并不是所有的THttpServerGeneric子类都可以,只有属于HTTP_BIDIR = [useBidirSocket, useBidirAsync]的类才可以。 关于使用orm,标准创建过种大体上这样, FDBCon := TSqlDBOdbcConnectionProperties.Create('','FILEDSN=d:\db.dsn', '', ''); FOrmModel := TOrmModel.Create([TPerson, TCar], 'root'); OrmMapExternalAll(FOrmModel, FDBCon, []); FRestServer := TRestServerDB.Create(FOrmModel); FRestServer.ServiceDefine(TPersonService, [IPersonService], TServiceInstanceImplementation.sicShared); FRestServer.CreateMissingTables(); FHttpServer := TRestHttpServer.Create('8000', [FRestServer], '+', useHttpSocket); FHttpServer.AccessControlAllowOrigin := '*'; 注意事项: 1、 TRestServerDB 代替了 TRestServerFullMemory 2、DTO类继承TOrm,如:TPerson = class(TOrm) 3、把Orm注册到TOrmModel, 4、把OrmModel与数据库关键起来 5、把OrmModel注册到RestServer中 其它细节可以参考mormot的demo 个人对于mormot2摸索大体上如此,框架中其它好玩的东西并未涉及。以上所写的内容都比较肤浅,仅仅是针对datasnap做了比较泛的对比,希望能给想使用mormot的人一点点引导。才疏学浅,让大伙见笑了。
----------------------------------------------
学无止境
作者:
2024/1/29 8:29:01
12楼:
都不想打扰楼主的分享,太有爱了。
----------------------------------------------
-
作者:
bdl1 (bdl1)
▲▲▲▲▲
-
普通会员
2024/1/29 9:45:19
13楼:
感谢楼主分享!!!
----------------------------------------------
-我的博客
作者:
2024/1/29 12:08:26
14楼:
好资料,楼主多多研究、多多分享!
----------------------------------------------
-delphi新资讯站 http://www.delphigear.cn
作者:
dacsd (ddd)
★☆☆☆☆
-
盒子活跃会员
2024/1/29 13:45:03
15楼:
好资料,多多分享!
----------------------------------------------
-
作者:
blade (百刃刀客)
★☆☆☆☆
-
盒子活跃会员
2024/1/29 22:30:40
16楼:
感谢楼主!
----------------------------------------------
-
作者:
ritapl (ritapl)
★☆☆☆☆
-
盒子活跃会员
2024/1/30 10:27:40
17楼:
这样的文章很有价值,我用的还是1.18用好几年了,该升级了
----------------------------------------------
-
作者:
2024/2/2 16:15:54
18楼:
赞楼主。虽然这个框架我没用过。
----------------------------------------------
-
作者:
2024/2/2 18:28:28
19楼:
先谢谢楼上兄弟们回复. 补充一个接口调用的注意事项: IMyService = interface(IInvokable) ['{C828803C-77DA-44E7-A599-0569DD52D157}'] function QueryUser(Id, Code: Integer): TServiceCustomAnswer; function AddUser(Id: Integer; const userInfo: TUserRec): TServiceCustomAnswer; end; 1、返回类型TServiceCustomAnswer(框架自带的一个record),可以解决[result:xxxx]的问题,返回结果不会有result项,你返回什么就是什么。 2、const userInfo: TUserRec,非简单类型时要加上const/var/out修饰。 3、get/post调用的细节,如果get方式,参数从?paramA=xx?paramB=xx取,如果是post方式,post中url = xxx?paramA=xx?paramB=xx 的参数会被忽略,框架只会从body中取参数值。 下面举个例子: 调用第一个方法: fetch('http://127.0.0.1:8088/root/MyService/QueryUser?Id=88&Code=99& #39;) 第二个方法的post调用(get也可以,但比较麻烦,不解释): const r = fetch('http://127.0.0.1:8088/root/MyService/AddUser& #39;, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ Id: 75, userInfo: { No:'1', Name: "aaa", Password:123456, Code :888087 } }) })
----------------------------------------------
学无止境
作者:
2024/2/3 21:01:57
20楼:
中间件我只用rtc,原生、跨平台、开源,无论win/linus/android/ios/x86/arm/loongarch都可以用,不过很感谢楼主。
----------------------------------------------
-
作者:
2024/2/4 14:15:07
21楼:
mormot 使用variant进行json的解析,序列化、反序列化功能确实强大,最新增加的IDocList,IDocDict 是对TdocVariantData的二次包装,实现了python中的list,dict的功能,更方便了。就像js,python等动态语言处理一样。
----------------------------------------------
-
作者:
2024/2/5 10:09:44
22楼:
感谢楼主!
----------------------------------------------
-
作者:
2024/2/21 8:31:54
23楼:
楼主大爱,我等默默看了好几遍。
----------------------------------------------
Delphi爱好者。
作者:
2024/2/23 10:20:17
24楼:
补充websocket的使用。 这是最简单的方法一: FHttpServer := TRestHttpServer.Create('8888', [FRestServer], '+', WEBSOCKETS_DEFAULT_MODE); FHttpServer.AccessControlAllowOrigin := '*'; with FHttpServer.WebSocketsEnable('', '', True, [])^ do begin OnClientConnected := ClientConnectedHandle; OnClientDisconnected := ClientDisconnectedHandle; end; //---------- procedure TForm1.ClientConnectedHandle(Sender: TObject); begin FClients.Add(TWebSocketAsyncProcess(Sender)); end; procedure TForm1.ClientDisconnectedHandle(Sender: TObject); begin FClients.Remove(TWebSocketAsyncProcess(Sender)); end; 和datasnap的tcp/ip连接很类似.
----------------------------------------------
学无止境
作者:
2024/2/23 10:22:16
25楼:
方法二: FHttpServer := TRestHttpServer.Create('8888', [FRestServer], '+', WEBSOCKETS_DEFAULT_MODE); FHttpServer.AccessControlAllowOrigin := '*'; TWebSocketAsyncServerRest(FHttpServer.HttpServer).ProcessClass := TWebSocketAsyncProcessEx; //---------- TWebSocketAsyncProcessEx = class(TWebSocketAsyncProcess) protected function ComputeContext( out RequestProcess: TOnHttpServerRequest): THttpServerRequestAbstract; override; public function CanGetFrame(TimeOut: cardinal; ErrorWithoutException: PInteger): boolean; override; function ReceiveBytes(P: PAnsiChar; count: PtrInt): integer; override; function SendBytes(P: pointer; Len: PtrInt): boolean; override; procedure SendFrameAsync(const Frame: TWebSocketFrame); override; end; 重写相应的方法。
----------------------------------------------
学无止境
作者:
2024/2/23 10:24:27
26楼:
方法三: FHttpServer := TRestHttpServer.Create('8888', [FRestServer], '+', WEBSOCKETS_DEFAULT_MODE); FHttpServer.AccessControlAllowOrigin := '*'; var wsServer := TWebSocketAsyncServerRest(FHttpServer.HttpServer); wsServer.OnWebSocketConnect := WSConnect; wsServer.OnWebSocketDisconnect := WSDisconnect; wsServer.OnWebSocketUpgraded := WSUpgraded; wsServer.OnWebSocketClose := WSClose; //---------- procedure TForm1.WSClose(Protocol: TWebSocketProtocol); begin TThread.Queue(nil, procedure begin Memo1.Lines.Add('Close'); end); end; procedure TForm1.WSConnect(Sender: TWebSocketAsyncConnection); begin ted TThread.Queue(nil, procedure begin Memo1.Lines.Add('Connect'); end); end; procedure TForm1.WSDisconnect(Sender: TWebSocketAsyncConnection); begin TThread.Queue(nil, procedure begin Memo1.Lines.Add('Disconnect'); end); end; function TForm1.WSUpgraded(Protocol: TWebSocketProtocol): integer; begin Result := 200; //!!!重点 TThread.Queue(nil, procedure begin Memo1.Lines.Add('Upgraded'); end); end;
----------------------------------------------
学无止境
作者:
2024/2/23 10:28:10
27楼:
方法四: FHttpServer := TRestHttpServer.Create('8888', [FRestServer], '+', WEBSOCKETS_DEFAULT_MODE); FHttpServer.AccessControlAllowOrigin := '*'; var wsServer := TWebSocketAsyncServerRest(FHttpServer.HttpServer); wsServer.WebSocketProtocols.Add(TMyWSProtocol.Create('myProtocol', 'jjw')); //---------- TMyWSProtocol = class(TWebSocketProtocol) protected procedure ProcessIncomingFrame(Sender: TWebSocketProcess; var request: TWebSocketFrame; const info: RawUtf8); override; public function Clone(const aClientUri: RawUtf8): TWebSocketProtocol; override; end; 重写这两个方法即可。
----------------------------------------------
学无止境