cross socket ICrossHttpServer

发布时间 2023-06-15 16:22:49作者: delphi中间件

cross socket ICrossHttpServer

cross socket是delphi跨平台的一个socket库。

/// <author>cxg 2023-2-12</author>
/// TStream(ARequest.body),auto be free

unit sock.CrossHttpSvr;

interface

uses
  system.JSON, json.help, keyValue.serialize, api.router,
  Net.CrossSocket.Base, global, yn.log, Net.CrossHttpServer,
  Net.CrossSslSocket, Net.CrossSslDemoCert, System.Math,
  System.IniFiles, System.SysUtils, System.Classes;

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
  TcrossHttpSvr = class
  private
    FHttpServer: ICrossHttpServer;
    FShutdown: Boolean;
    procedure Process;
  public
    constructor Create; overload;
    destructor Destroy; override;
  public
    procedure Start;
    procedure Stop;
    property HttpServer: ICrossHttpServer read FHttpServer;
  end;

implementation

function strof(const aStream: TStream): RawByteString; overload;
begin
  SetLength(Result, aStream.Size);
  aStream.Position := 0;
  aStream.Read(Result[1], aStream.Size);
end;

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

procedure setHeader(const Ctxt: ICrossHttpResponse; const ContentType: string);
begin
  Ctxt.Header['Access-Control-Allow-Origin'] := '*';
  Ctxt.Header['Access-Control-Allow-Methods'] := '*';
  Ctxt.Header['Access-Control-Allow-Headers'] := '*';
  Ctxt.ContentType := ContentType;
end;

procedure router(const Ctxt: ICrossHttpRequest; const Ctxt2: ICrossHttpResponse);
begin
  if Pos('/bin', Ctxt.RawPathAndParams) > 0 then      //二进制 API
  begin
    setHeader(Ctxt2, cBin);
    var req: TSerialize := tserialize.Create;
    var res: TSerialize := tserialize.Create;
    var ms: TStream := TStream(Ctxt.Body);
    if ms <> nil then
      req.unMarshal(ms);
    var url: string := Ctxt.RawPathAndParams;
    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;
    Ctxt2.Send(res.marshal2,
      procedure(const AConnection: ICrossConnection; const ASuccess: Boolean)
      begin
        req.Free;
        res.Free;
      end);
  end
  else if Pos('/rest', Ctxt.RawPathAndParams) > 0 then    //JSON API
  begin
    setHeader(Ctxt2, cJson);
    var url: string := Ctxt.RawPathAndParams;
    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;
    var ms: TStream := TStream(Ctxt.Body);
    var body: RawByteString;
    if ms <> nil then
      body := strof(ms);
    req.asStr['body'] := body;
    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;
    Ctxt2.Send(UTF8Encode(res.asStr['res']),
      procedure(const AConnection: ICrossConnection; const ASuccess: Boolean)
      begin
        req.Free;
        res.Free;
      end);
  end
  else if Pos('/protobuf', Ctxt.RawPathAndParams) > 0 then   //PROTOBUF API
  begin
    setHeader(Ctxt2, cProtobuf);
    var url: string := Ctxt.RawPathAndParams;
    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;
    var ms: TStream := TStream(Ctxt.Body);
    var body: TBytes;
    if ms <> nil then
      strof(ms);
    req.asBytes['body'] := body;
    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;
    Ctxt2.Send(res.asBytes['res'],
      procedure(const AConnection: ICrossConnection; const ASuccess: Boolean)
      begin
        req.Free;
        res.Free;
      end);
  end;
end;

constructor TcrossHttpSvr.Create;
begin
  var ini: TIniFile := TIniFile.create(SvrCfg);
  var threadnum: Integer := ini.readinteger('config', 'threadnum', 32);
  if threadnum > 256 then
    threadnum := 256;
  var ssl: Boolean := ini.readbool('config', 'ssl', False);
  FHttpServer := TCrossHttpServer.create(threadnum, ssl); // New http server
  if FHttpServer.SSL then
  begin
    FHttpServer.SetCertificate(SSL_SERVER_CERT);
    FHttpServer.SetPrivateKey(SSL_SERVER_PKEY);
  end;

  FHttpServer.Addr := IPv4v6_ALL;
  FHttpServer.Port := ini.readinteger('config', 'httpport', 0); // port
  ini.Free;
  {$IFDEF console}
  Writeln('New cross http server');
  Writeln('Http port: ', FHttpServer.Port);
  Writeln('Thread num: ', threadnum);
  {$ENDIF}
  FHttpServer.Compressible := True; // zip?
  Process;
  Self.Start;
end;

destructor TcrossHttpSvr.Destroy;
begin
  Self.Stop;
  FHttpServer := nil;
  inherited;
end;

procedure TcrossHttpSvr.Start;
begin
  FHttpServer.Start;
end;

procedure TcrossHttpSvr.Stop;
begin
  FHttpServer.Stop;
  FShutdown := True;
  Sleep(150);
end;

procedure TcrossHttpSvr.Process;
begin
  try
    FHttpServer.All('*',
      procedure(const ARequest: ICrossHttpRequest; const AResponse: ICrossHttpResponse)
      begin
        router(ARequest, AResponse);
      end);
  except
    on E: Exception do
    begin
      writelog('TcrossHttpSvr.Process()' + E.message);
    end;
  end;
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.