mormot2 THttpAsyncServer

发布时间 2023-06-15 16:39:01作者: delphi中间件

mormot2 THttpAsyncServer

支持delphi和lazarus。

/// <author>cxg 2023-2-12</author>
/// mormot2 异步httpserver 支持delphi+lazarus
unit sock.mormot2.httpserver;
{$IFDEF fpc}
  {$MODE DELPHI}{$H+}
{$ENDIF}

interface

uses
  classes, keyValue.serialize, api.router, IniFiles,
  mormot.Net.sock, SysUtils, system.JSON, json.help, mormot.net.async,
  mormot.net.http, mormot.net.server;

var
  BinApis: TJSONObject;    //二进制API
  RestApis: TJSONObject;   //REST API,包括json,protobuf

const  //content-type
  cBin = 'application/octet-stream; charset=utf-8';
  cJson = 'application/json; charset=utf-8';
  cProtobuf = 'application/protobuf; charset=utf-8';

type
  THttpSVR = class
  private
    FServer: THttpAsyncServer;
    function process(Ctxt: THttpServerRequestAbstract): cardinal;
  public
    constructor Create;
    destructor Destroy; override;
  end;

implementation

function ReadJsonFile(const FileName: string): string;
begin
  var f: TStringList := TStringList.Create;
  f.LoadFromFile(FileName, TEncoding.UTF8);
  Result := f.Text;
  f.Free;
end;

function strof(const aBytes: TBytes): RawByteString; overload;
begin
  SetLength(Result, Length(aBytes));
  Move(aBytes[0], Result[1], Length(aBytes));
end;

procedure setHeader(const Ctxt: THttpServerRequestAbstract; const ContentType: string);  //mormot2 binary http header
begin
  Ctxt.OutContentType := ContentType;
  Ctxt.OutCustomHeaders := 'Access-Control-Allow-Origin:*' + #13#10 + 'Access-Control-Allow-Methods:*' + #13#10 + 'Access-Control-Allow-Headers:*';
end;

procedure router(const Ctxt: THttpServerRequestAbstract);  //mormot2 router
begin
  if Pos('/bin', Ctxt.URL) > 0 then      //二进制 API
  begin
    setHeader(Ctxt, cBin);
    var req: TSerialize := tserialize.Create;
    var res: TSerialize := tserialize.Create;
    if Ctxt.InContent > '' then
      req.unMarshal(Ctxt.InContent);
    var url: string := Ctxt.URL;
    var arr: TArray<string> := url.Split(['/']);
    var funcName: string := arr[2];
    var ja: TJSONArray := BinApis.A['bin'];
    for var i: Integer := 0 to ja.Count - 1 do
    begin
      var o: TJSONObject := ja.Items[i] as TJSONObject;
      if o.S['funcname'] = funcName then
      begin
        RouterAPI(o.S['classname'], o.S['funcname'], [req, res]);
        Break;
      end;
    end;
    Ctxt.OutContent := res.marshal3;
    req.Free;
    res.Free;
  end
  else if Pos('/rest', Ctxt.URL) > 0 then    //JSON API
  begin
    setHeader(Ctxt, cJson);
    var url: string := Ctxt.URL;
    var arr: TArray<string> := url.Split(['/']);
    var resource: string := arr[2];
    var req: TSerialize := tserialize.Create;
    var res: TSerialize := tserialize.Create;
    req.asStr['url'] := url;
    if Ctxt.InContent > '' then
      req.asStr['body'] := Ctxt.InContent;
    req.asStr['type'] := 'json';
    var ja: TJSONArray := RestApis.A[resource];
    for var i: Integer := 0 to ja.Count - 1 do
    begin
      var o: TJSONObject := ja.Items[i] as TJSONObject;
      if o.S['method'] = Ctxt.Method then
      begin
        RouterAPI(o.S['classname'], o.S['funcname'], [req, res]);
        Break;
      end;
    end;
    Ctxt.OutContent := UTF8Encode(res.asStr['res']);
    req.Free;
    res.Free;
  end
  else if Pos('/protobuf', Ctxt.URL) > 0 then   //PROTOBUF API
  begin
    setHeader(Ctxt, cProtobuf);
    var url: string := Ctxt.URL;
    var arr: TArray<string> := url.Split(['/']);
    var resource: string := arr[2];
    var req: TSerialize := tserialize.Create;
    var res: TSerialize := tserialize.Create;
    req.asStr['url'] := url;
    if Ctxt.InContent > '' then
      req.asBytes['body'] := BytesOf(Ctxt.InContent);
    req.asStr['type'] := 'protobuf';
    var ja: TJSONArray := RestApis.A[resource];
    for var i: Integer := 0 to ja.Count - 1 do
    begin
      var o: TJSONObject := ja.Items[i] as TJSONObject;
      if o.S['method'] = Ctxt.Method then
      begin
        RouterAPI(o.S['classname'], o.S['funcname'], [req, res]);
        Break;
      end;
    end;
    Ctxt.OutContent := strof(res.asBytes['res']);
    req.Free;
    res.Free;
  end;
end;

{ THttpSVR }

constructor THttpSVR.Create;
var
  ini: tinifile;
  httpport: string;
  threadnum: integer;
  ssl: Boolean;
  KeepAliveTimeOut: Integer;
  queueLen: Integer;
  tls: TNetTlsContext;
begin
  ini := tinifile.create(extractfilepath(paramstr(0)) + 'server.conf');
  httpport := ini.readstring('config', 'httpport', '1122');
  threadnum := ini.readinteger('config', 'threadnum', 32);
  if threadnum > 256 then
    threadnum := 256;
  ssl := ini.ReadBool('config', 'ssl', False); //https?
  ini.free;
  KeepAliveTimeOut := 30000;
  if not ssl then
    FServer := THttpAsyncServer.Create(httpport, nil, nil, 'yn', threadnum, KeepAliveTimeOut, [])
  else
    FServer := THttpAsyncServer.Create(httpport, nil, nil, 'yn', threadnum, KeepAliveTimeOut, [hsoEnableTls]);
  queueLen := 100000;
  FServer.HttpQueueLength := queueLen;
  FServer.OnRequest := process;
  if not ssl then
    FServer.WaitStarted
  else
  begin
    InitNetTlsContextSelfSignedServer(tls);  //自签名
    try
      FServer.WaitStarted(10, @tls);
    finally
      DeleteFile(Utf8ToString(tls.CertificateFile));
      DeleteFile(Utf8ToString(tls.PrivateKeyFile));
    end;
  end;
  {$IFDEF console}
  Writeln('New THttpAsyncServer server');
  if not ssl then
    Writeln('Http port: ', httpport)
  else
    Writeln('Https port: ', httpport);
  Writeln('Thread num: ', threadnum);
  {$ENDIF}
end;

destructor THttpSVR.Destroy;
begin
  FreeAndNil(FServer);
  inherited;
end;

function THttpSVR.process(Ctxt: THttpServerRequestAbstract): cardinal;
begin
  router(Ctxt);
  Result := 200;
end;

initialization
  binapis := TJSONObject.Create;
  restapis := TJSONObject.Create;
  binapis.Parse(ReadJsonFile(ExtractFilePath(ParamStr(0)) + 'binrouter.json'));
  restapis.Parse(ReadJsonFile(ExtractFilePath(ParamStr(0)) + 'restrouter.json'));

finalization
  FreeAndNil(binapis);
  FreeAndNil(restapis);

end.