Share Button

Um servidor Datasnap é na excência um servidor HTTP nativo…. SIM é possível implementar um HTTP Server no mesmo servidor de Datasnap.

1. Criar um Servidor Datasnap padrão

Ao criar um Servidor DataSnap (pelo expert do Delphi), várias UNITs são adicionadas.
Se observar a UNIT que nos importa aqui é a WebModuleUnit1;

Toda vez que é feito uma chamada a um recurso na porta do HTTP do servidor e for um arquivo será executado o evento “WebModuleBeforeDispatch” o que permite inserir códigos que execute algum procedimento diferente daqueles previstos.

No nosso caso queremos chamar um arquivo externo “index.html”, “jquery.js”, “minhafoto.png”, “my.pdf”… enfim, um arquivo que esteja armazenado na pasta base do site “www”.

2. Inserindo código para suporte ao HTTP Server

Tentando facilitar o trabalho introduzi um novo componente através da  “UNIT DataSnap.HTTPModule;”


// incluir na USES
uses ... DataSnap.HTTPModule...
..

// Incluir um Private para o objeto 
   {Componente que implementa o código de resposta para o HTTP Server - por Amarildo Lacerda}
   FHttpModule:TDataSnapHTTPModule;
..

// alterar o Create para
procedure TWebModule1.WebModuleCreate(Sender: TObject);
begin
  {
     incia o componente indicando a pasta onde será hospedado a URLBase para oa arquivos de HTTP Server
     [pastaBase]/index.html
  }
  FHttpModule:=TDataSnapHTTPModule.Create(self);
  FHttpModule.URLPath := ExtractFilePath(paramStr(0)) + 'www';
  ForceDirectories(FHttpModule.URLPath);

  FServerFunctionInvokerAction := ActionByName('ServerFunctionInvokerAction');
end;

// alterar 
procedure TWebModule1.WebModuleBeforeDispatch(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
  { Redireciona a chamada HTML, JavaScript ou outro tipo de arquivo que exista na pasta padrão - por Amarildo Lacerda}
  FHttpModule.GoURLDocument(Request, Response, Handled);
  if Handled then
    exit;


  if FServerFunctionInvokerAction <> nil then
    FServerFunctionInvokerAction.Enabled := AllowServerFunctionInvoker;
end;

Pronto… Seu Servidor Datasnap já sabe como responder como um HTTP Server nativo.

Digite: http://localhost:8080 – e o servidor irá procurar por uma página padrão: index.html

Código de Exemplo: Datasnap.HttpModule

Share Button

Onde mesmo esta instalado o servidor Datasnap…. gostaria de descobrir a configuração do servidor:  local (ip) onde (porta) como (path)…

Olhando como o Indy-10 trabalha – não é tão intuitivo em se tratando de broadcast – então é preciso trabalhar um pouco.

Utilizar   TIdUDPServer – No Indy-10 o TIdUDPClient não obtive sucesso em pegar o retorno com broadcast… isto mudou o rumo da implementação – passei a pensar em montar DOIS servidores diferentes, um para o Servidor – outro para o Cliente.

Ver Código da Classe:  TIdZeroConfServer     e    TIdZeroConfClient

Projetos exemplos para Servidor e Cliente

Implementação

  1. No servidor datasnap preparar para receber o pedido do cliente solicitando os dados de configuração do servidor.
    
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      FServer := TIdHTTPWebBrokerBridge.Create(Self);
      // criar o servidor zeroConf
      // ----------------------------------------------------------------------
      FZeroConf := TIdZeroConfServer.create(self);
    end;
    
    procedure TForm1.ButtonStopClick(Sender: TObject);
    begin
      TerminateThreads;
      FServer.Active := False;
      FServer.Bindings.Clear;
      // parar o servidor broadcast
      // ----------------------------------------------------------------------
      FZeroConf.active := false;
    end;
    
    
    procedure TForm1.StartServer;
    begin
      if not FServer.Active then
      begin
        FServer.Bindings.Clear;
        FServer.DefaultPort := StrToInt(EditPort.Text);
        FServer.Active := True;
        // configura o ZeroConf
        // --------------------------------------------------------------------
        FZeroConf.active  := false;
        FZeroConf.AppDefaultPort := FServer.DefaultPort; // Porta do servidor Datasnap
        FZeroConf.AppDefaultHost := FZeroConf.LocalHost; // IP de onde se encontra o Servidor da Aplicação Datasnap
        FZeroConf.AppDefaultPath :='/';     // path base do servidor
        FZeroConf.active := true;           // ativar o servidors
      end;
    end;
    
    
  2. Implementar no cliente Datasnap:
    procedure TForm4.FormCreate(Sender: TObject);
    begin
    
      // incia o cliente
      // -------------------------------------------------
      FZeroConfClient := TIdZeroConfClient.create(self);
      FZeroConfClient.OnResponseEvent := DoReceberDados;
    
    end;
    
    procedure TForm4.Button1Click(Sender: TObject);
    begin
      if not FZeroConfClient.active then
        FZeroConfClient.active := true;
      Memo1.Lines.Add('Envia comando de procurar servidor ('+FormatdateTime('hh:mm:ss',now)+')');
      FZeroConfClient.BroadcastIP := '';//'192.168.56.1';
      FZeroConfClient.Send;
    end;
    
    procedure TForm4.DoReceberDados(Sender: TObject; AMessage: String);
    begin
      // AMessage - retorna o JSON com os dados do servidor
      Memo1.Lines.Add('Resposta('+FormatdateTime('hh:mm:ss',now)+'):'+AMessage);
      Memo1.Lines.Add('');
      FZeroConfClient.Active := false; // desliga
    end;
    
    
    

 

Como funciona a mecânica

Quando iniciar o servidor  TIdZeroConfServer, ele criar um servidor UDP que fica esperando um broadcast na porta 53330 (configurável) ao ativar o ZeroConf passar os parametros do servidor Datasnap que será utilizado para responder as solitações dos clientes;

Do lado do cliente, ao ativar o TidZeroConfClient, ele criar uma escuta na porta 53331 e envia (send) comando solicitando configuração do servidor… recebe a resposta no evento – DoReceberDados(…);

Formato da Resposta

A reposta é um JSON:   {“service”:”ZeroConf”,”command”:”response”,”payload”:”yyyy-dd-mm hh:mm:ss”,”source”:”ip do servidor”,”host”;”ip onde o datasnap esta respondendo”,”port”:”porta do datasnap”,”path”:”caminho http”}

 

Cuidados/Limitações

Alguns firewall tendem a bloquear mensagem de broadcast, já que não é visto com bons olhos pelos gerenciadores de rede.

Usando broadcast por UDP, o pacote circula somente na rede local – não saindo para outras redes.

Multiplos aplicativos tentando utilizar a mesma porta… alterar para utilizar portas diferentes para aplicativos diferentes – provavelmente será necessário tratar as exceções para os casos de tentativa de abrir portas que  estão em uso.

 

 

 

 

Share Button

Construindo o Servidor RESTServer com exemplos… retornar um TDataset em um JSONObject X “Reflection”.

Para iniciar utilizei o modelo criado pelo delphi: new -> Datasnap REST Application. Criado o projeto alterei a unit abaixo:

GIT com fontes para as dependências


unit ServerMethodsUnit1;

interface

uses System.SysUtils, System.Classes, System.Json,
  Data.FireDACJSONReflect,
  Datasnap.DSServer, Datasnap.DSAuth, Datasnap.DSProviderDataModuleAdapter,
  FireDAC.Stan.StorageJSON, FireDAC.Stan.StorageBin;

type
{$METHODINFO ON}
  TServerMethods1 = class(TDataModule)
    FDStanStorageJSONLink1: TFDStanStorageJSONLink;
    FDStanStorageBinLink1: TFDStanStorageBinLink;
  private
    { Private declarations }
  public
    { Public declarations }
    // original criado pelo DELPHI
    function EchoString(Value: string): string;
    function ReverseString(Value: string): string;

    // passa o codigo do cliente e retorna os dados do cliente
    function GetCliente(codigo: int64): TJSONValue;  // exemplo retornando um JSONObject
    function GetCliente2(codigo: int64): TFDJSONDataSets;  // usando Reflection

    // retorna na mesma resposta CLIENTE + ITENS
    function GetNotaFiscal(ANumero: integer): TFDJSONDataSets;

  end;
{$METHODINFO OFF}

implementation

{$R *.dfm}

uses System.StrUtils, FireDAC.ObjectDataSet, Data.db.helper;

function TServerMethods1.EchoString(Value: string): string;
begin
  Result := Value;
end;

function TServerMethods1.ReverseString(Value: string): string;
begin
  Result := System.StrUtils.ReverseString(Value);
end;

Criando as classes a serem carregadas no TDataset usando RTTI:

type
  TClientes = class
  private
    FCodigo: int64;
    FNome: string;
    FCidade: string;
    FDebitos: double;
    FEndereco: string;
    FEstado: String;
    procedure SetCidade(const Value: string);
    procedure SetCodigo(const Value: int64);
    procedure SetDebitos(const Value: double);
    procedure SetEndereco(const Value: string);
    procedure SetEstado(const Value: String);
    procedure SetNome(const Value: string);
  public
    property codigo: int64 read FCodigo write SetCodigo;
    property Nome: string read FNome write SetNome;
    property Cidade: string read FCidade write SetCidade;
    property Estado: String read FEstado write SetEstado;
    property Endereco: string read FEndereco write SetEndereco;
    property Debitos: double read FDebitos write SetDebitos;
  end;

  TNotaFiscalItens = class
  private
    FPreco: double;
    FCodigo: string;
    FQtde: double;
    FNome: string;
    procedure SetCodigo(const Value: string);
    procedure SetNome(const Value: string);
    procedure SetPreco(const Value: double);
    procedure SetQtde(const Value: double);
  public
    property codigo: string read FCodigo write SetCodigo;
    property Nome: string read FNome write SetNome;
    property Qtde: double read FQtde write SetQtde;
    property Preco: double read FPreco write SetPreco;
  end;

Quando o cliente fizer chamada para GetCliente irá passar o código do cliente, internamente o servidor irá procurar no banco de dados e retornar um JSONObject com os dados do cliente. Este processo permite retornar uma informação mais genérica, importante quando os clientes que irão consumir tenham múltiplas linguagens ou múltiplos desenvolvedores que nem sempre serão Delphianos.


function TServerMethods1.GetCliente(codigo: int64): TJSONValue;
// TFDJSONDataSets;
var
  ds: TObjectDataSet;
  cli: TClientes;
begin
  // buscar os dados no banco de dados com codigo passado pelo cliente...

  // resposta para o cliente;

  // meus dados no firedac
  // usei um ObjectDataset somento para não precisar criar uma conexão e um query
  ds := TObjectDataSet.Create(self, TClientes);  // usa RTTI para mapear coluna do TDataset
  try
    ds.Open;
    ds.append;
    with ds do
    begin
      FieldByName('codigo').Value := 1;
      FieldByName('nome').Value := 'Embarcadero SA';
      FieldByName('Endereco').Value := 'Rua...xxxx...,10';
      FieldByName('Cidade').Value := 'Sao Paulo';
      FieldByName('Estado').Value := 'SP';
      FieldByName('Debitos').Value := 100000.12;
    end;
    ds.Post;

    // retorna um JsonObject
    Result := TJSONObject.ParseJSONValue(ds.ToJson);
  finally
    ds.Free;
  end;
end;

Se os clientes que irão consumir o serviço forem sempre RestClient de Delphi, então retornar um objeto mais preparado para a estrutura do RestClient pode ser um opção. Retornando um objetos TFDJSONDataSets aumenta os dados a serem retornados ao cliente mas pode facilitar a implementação para os cliente Delphi.

// retorna um objeto FireDAC JSON  Reflection
// isto estabelece uma boa integração entre aplicativos que usam FireDAC nos clientes.
// se o cliente não for FireDAC... talves seja interessante pensar em retornar um
//   formato mais generico

function TServerMethods1.GetCliente2(codigo: int64): TFDJSONDataSets;
var
  ds: TObjectDataSet;
  cli: TClientes;
begin
  // buscar os dados no banco de dados com codigo passado pelo cliente...

  // resposta para o cliente;
  Result := TFDJSONDataSets.Create;
  // meus dados no firedac
  // usei um ObjectDataset somento para não precisar criar uma conexão e um query
  ds := TObjectDataSet.Create(nil, TClientes);
  try
    ds.Open;
    ds.append;
    with ds do
    begin
      FieldByName('codigo').Value := 1;
      FieldByName('nome').Value := 'Embarcadero SA';
      FieldByName('Endereco').Value := 'Rua...xxxx...,10';
      FieldByName('Cidade').Value := 'Sao Paulo';
      FieldByName('Estado').Value := 'SP';
      FieldByName('Debitos').Value := 100000.12;
    end;
    ds.Post;

    TFDJSONDataSetsWriter.ListAdd(Result, 'CLIENTE', ds);
  finally
    // ds.Free;   -- eh destruido pelo Writer
  end;
end;

Para retornar mais de uma estrutura de informações no mesma chamada, pode-se utilizar TFDJSONDataSets que permite como no exemplo retornar os dados do Cliente e também os dados dos itens da nota fiscal (no exemplo) no retorno da mesma chamada.


// retornando mais de uma tabala na mesma consulta
// retorna  CLIENTE  e ITENS da nota fiscal
function TServerMethods1.GetNotaFiscal(ANumero: integer): TFDJSONDataSets;
var
  ds: TObjectDataSet;
  cli: TClientes;
  dsItens: TObjectDataSet;
  itens: TNotaFiscalItens;
begin
  // buscar os dados no banco de dados com codigo passado pelo cliente...

  // resposta para o cliente;
  Result := TFDJSONDataSets.Create;
  // meus dados no firedac
  // usei um ObjectDataset somento para não precisar criar uma conexão e um query
  ds := TObjectDataSet.Create(nil, TClientes);
  try
    ds.Open;
    ds.append;
    with ds do
    begin
      FieldByName('codigo').Value := 1;
      FieldByName('nome').Value := 'Embarcadero SA';
      FieldByName('Endereco').Value := 'Rua...xxxx...,10';
      FieldByName('Cidade').Value := 'Sao Paulo';
      FieldByName('Estado').Value := 'SP';
      FieldByName('Debitos').Value := 100000.12;
    end;
    ds.Post;

    TFDJSONDataSetsWriter.ListAdd(Result, 'CLIENTE', ds);

    // usa RTTI para mapear o objeto para TFDMemTable
    dsItens := TObjectDataSet.Create(nil, TNotaFiscalItens);
    dsItens.Open;
    itens := TNotaFiscalItens.Create;
    with itens do
    begin
      codigo := '000001';
      Nome := 'Margarina';
      Qtde := 2;
      Preco := 8.5;
    end;
    dsItens.ObjectList.Add(itens);  // adiciona o objeto ao ObjectDataSet

    with itens do
    begin
      codigo := '000002';
      Nome := 'Pao Frances';
      Qtde := 1;
      Preco := 15;
    end;
    dsItens.ObjectList.Add(itens);

    TFDJSONDataSetsWriter.ListAdd(Result, 'ITENS', dsItens);

  finally
    // ds.Free;   -- eh destruido pelo Writer
  end;
end;

.....
end.