source upload

This commit is contained in:
Razor12911
2022-01-17 22:16:47 +02:00
parent 12936d065b
commit 098e8c48de
1778 changed files with 1206749 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
# mORMot
Java client for Synopse mORMot server
Java REST client for fantastic Synopse API. This client is focused to Interface based services.
Example usage:
<pre>
SynClient client = new SynClient("127.0.0.1", 8080, "service", false);
if (client.login("User", "synopse")) {
JSONArray params = new JSONArray().put(2).put(3); // Add(2, 3)
JSONObject response = client.call("Calculator.Add", params);
System.out.println("Summ: " + response);
client.logout();
}
</pre>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_5">
<output url="file://$MODULE_DIR$/target/classes" />
<output-test url="file://$MODULE_DIR$/target/test-classes" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Maven: com.squareup.okhttp3:okhttp:3.14.2" level="project" />
<orderEntry type="library" name="Maven: com.squareup.okio:okio:1.17.2" level="project" />
<orderEntry type="library" name="Maven: org.json:json:20180813" level="project" />
</component>
</module>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>groupId</groupId>
<artifactId>moRMOt</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.14.2</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20180813</version>
</dependency>
</dependencies>
</project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View File

@@ -0,0 +1,31 @@
unit CalcInterface;
interface
uses FileCollect;
/// some common definitions shared by both client and server side
type
ICalculator = interface(IInvokable)
['{9A60C8ED-CEB2-4E09-87D4-4A16F496E5FE}']
function Add(n1,n2: integer): integer;
function GetFileList(path: String; out lst: TFlileCollection): Boolean;
end;
const
ROOT_NAME = 'root';
PORT_HTTP = '8080';
PORT_HTTPS = '8443';
APPLICATION_NAME = 'RestService';
SERVICE_NAME = 'service';
implementation
uses
mORMot;
initialization
// so that we could use directly ICalculator instead of TypeInfo(ICalculator)
TInterfaceFactory.RegisterInterfaces([TypeInfo(ICalculator)]);
end.

View File

@@ -0,0 +1,52 @@
{
Synopse mORMot framework
Sample 04 - HTTP Client-Server
purpose of this sample is to show HTTP Client/Server SQLite3 database usage:
- a TSQLSampleRecord class is defined in shared unit SampleData.pas
- this sample uses two projects, Project04Client.dpr and Project04Server.dpr
- a SQLite3 server is initialized in Project04Server
- the CreateMissingTables method will create all necessary tables in the
SQLite3 database
- one or more client instances can be run in Project04Client
- the purpose of the Client form in Unit1.pas is to add a record to the
database; the Time field is filled with the current date and time
- the 'Find a previous message' button show how to perform a basic query
- since the framework use UTF-8 encoding, we use some basic functions for
fast conversion to/from the User Interface; in real applications,
you should better use our SQLite3i18n unit and the corresponding
TLanguageFile.StringToUTF8() and TLanguageFile.UTF8ToString() methods
- note that you didn't need to write any SQL statement, only define a
class and call some methods; even the query was made very easy (just an
obvious WHERE clause to write)
- thanks to the true object oriented modeling of the framework, the same
exact Unit1 is used for both static in-memory database engine, or
with SQLite3 database storage, in local mode or in Client/Server mode:
only the TForm1.Database object creation instance was modified
- look at the tiny size of the EXE (even with SQLite3 engine embedded), less
than 400KB for the server, and 80KB for the client, with LVCL :)
Version 1.0 - February 07, 2010
Version 1.16
- added authentication to the remote process
}
program Client;
uses
Forms,
SysUtils,
ClientForm in 'ClientForm.pas' {Form1},
SampleData in 'SampleData.pas',
CalcInterface in 'CalcInterface.pas';
{$R *.res}
begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.

View File

@@ -0,0 +1,213 @@
object Form1: TForm1
Left = 231
Top = 220
BorderStyle = bsSingle
ClientHeight = 397
ClientWidth = 966
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -13
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
OnDestroy = FormDestroy
PixelsPerInch = 96
TextHeight = 16
object StatusBar1: TStatusBar
Left = 0
Top = 378
Width = 966
Height = 19
Panels = <>
SimplePanel = True
end
object Panel1: TPanel
Left = 0
Top = 51
Width = 966
Height = 327
Align = alBottom
BevelOuter = bvNone
Caption = 'Panel1'
TabOrder = 1
object Label1: TLabel
Left = 16
Top = 8
Width = 67
Height = 16
Caption = 'Your name:'
end
object Label2: TLabel
Left = 16
Top = 56
Width = 86
Height = 16
Caption = 'Your message:'
end
object lblA: TLabel
Left = 312
Top = 34
Width = 17
Height = 16
Caption = 'A='
end
object lblB: TLabel
Left = 312
Top = 66
Width = 16
Height = 16
Caption = 'B='
end
object lblResult: TLabel
Left = 604
Top = 72
Width = 184
Height = 16
Caption = 'Enter numbers, then Call Server'
end
object QuestionMemo: TMemo
Left = 16
Top = 72
Width = 257
Height = 193
TabOrder = 0
end
object NameEdit: TEdit
Left = 16
Top = 24
Width = 81
Height = 24
TabOrder = 1
end
object AddButton: TButton
Left = 16
Top = 280
Width = 145
Height = 25
Caption = 'Add the message'
TabOrder = 2
OnClick = AddButtonClick
end
object QuitButton: TButton
Left = 197
Top = 280
Width = 75
Height = 25
Caption = 'Quit'
TabOrder = 3
OnClick = QuitButtonClick
end
object FindButton: TButton
Left = 104
Top = 24
Width = 169
Height = 25
Caption = 'Find a previous message'
TabOrder = 4
OnClick = FindButtonClick
end
object edtA: TEdit
Left = 336
Top = 32
Width = 153
Height = 24
TabOrder = 5
Text = '2'
end
object edtB: TEdit
Left = 336
Top = 64
Width = 153
Height = 24
TabOrder = 6
Text = '3'
end
object btnCall: TButton
Left = 496
Top = 32
Width = 97
Height = 57
Caption = 'Call Server'
TabOrder = 7
OnClick = btnCallClick
end
object Button1: TButton
Left = 616
Top = 125
Width = 75
Height = 25
Caption = 'GetFileList'
TabOrder = 8
OnClick = Button1Click
end
object Edit1: TEdit
Left = 312
Top = 125
Width = 297
Height = 24
TabOrder = 9
Text = '.\'
end
object ListView1: TListView
Left = 312
Top = 155
Width = 625
Height = 151
Columns = <
item
Caption = 'Name'
Width = 200
end
item
Caption = 'Size'
Width = 90
end
item
Caption = 'Modified'
Width = 140
end
item
Caption = 'Version'
Width = 150
end>
TabOrder = 10
ViewStyle = vsReport
end
end
object Panel2: TPanel
Left = 8
Top = 8
Width = 361
Height = 41
BevelOuter = bvNone
Caption = 'Panel2'
TabOrder = 2
object txtUser: TEdit
Left = 8
Top = 9
Width = 121
Height = 24
TabOrder = 0
Text = 'User'
end
object txtPassword: TEdit
Left = 144
Top = 9
Width = 121
Height = 24
TabOrder = 1
Text = 'synopse'
end
object btnLogin: TButton
Left = 272
Top = 7
Width = 75
Height = 25
Caption = 'Login'
TabOrder = 2
OnClick = btnLoginClick
end
end
end

View File

@@ -0,0 +1,283 @@
unit ClientForm;
interface
uses
{$ifdef MSWINDOWS}
Windows,
Messages,
Graphics,
{$endif}
Classes, SysUtils, Forms, Controls, Dialogs, StdCtrls,
SynCommons,
SynLog,
mORMot,
mORMotHttpClient,
SampleData, ComCtrls, ExtCtrls;
type
{ TForm1 }
TForm1 = class(TForm)
StatusBar1: TStatusBar;
Panel1: TPanel;
Label1: TLabel;
Label2: TLabel;
lblA: TLabel;
lblB: TLabel;
lblResult: TLabel;
QuestionMemo: TMemo;
NameEdit: TEdit;
AddButton: TButton;
QuitButton: TButton;
FindButton: TButton;
edtA: TEdit;
edtB: TEdit;
btnCall: TButton;
Button1: TButton;
Edit1: TEdit;
ListView1: TListView;
Panel2: TPanel;
txtUser: TEdit;
txtPassword: TEdit;
btnLogin: TButton;
procedure AddButtonClick(Sender: TObject);
procedure FindButtonClick(Sender: TObject);
procedure btnLoginClick(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure QuitButtonClick(Sender: TObject);
procedure btnCallClick(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
procedure ClientFailed(Sender: TSQLRestClientURI; E: Exception;
Call: PSQLRestURIParams);
public
{ public declarations }
Port: String;
Client: TSQLRest;
Model: TSQLModel;
svcClient: TSQLRestClientURI;
svcModel: TSQLModel;
end;
var
Form1: TForm1;
implementation
{$ifdef FPC}
{$R *.lfm}
{$else}
{$R *.dfm}
{$endif}
{ TForm1 }
uses
CalcInterface, FileCollect;
var
startTime64, endTime64, frequency64: Int64;
elapsedSeconds: single;
procedure EnableControls(const ctrl: TWinControl; enbl: Boolean);
var
i: Integer;
begin
for i:=0 to ctrl.ControlCount -1 do
TWinControl(ctrl.Controls[i]).Enabled := enbl;
end;
procedure TForm1.btnLoginClick(Sender: TObject);
begin
EnableControls(Panel2, False);
try
if TSQLHttpClient(Client).SetUser(txtUser.Text, txtPassword.Text) then
begin
if svcClient.SetUser(txtUser.Text, txtPassword.Text) then
begin
svcClient.ServiceDefine([ICalculator],sicShared);
StatusBar1.SimpleText := 'Connected.';
EnableControls(Panel1, True);
end;
end;
except
on E: Exception do
begin
EnableControls(Panel2, True);
ShowMessage(E.Message);
end;
end;
end;
procedure TForm1.AddButtonClick(Sender: TObject);
var Rec: TSQLSampleRecord;
begin
Rec := TSQLSampleRecord.Create;
try
// we use explicit StringToUTF8() for conversion below
// a real application should use TLanguageFile.StringToUTF8() in mORMoti18n
Rec.Name := StringToUTF8(NameEdit.Text);
Rec.Question := StringToUTF8(QuestionMemo.Text);
if Client.Add(Rec,true)=0 then
ShowMessage('Error adding the data') else begin
NameEdit.Text := '';
QuestionMemo.Text := '';
NameEdit.SetFocus;
end;
finally
Rec.Free;
end;
end;
procedure TForm1.FindButtonClick(Sender: TObject);
var Rec: TSQLSampleRecord;
begin
QueryPerformanceCounter(startTime64);
Rec := TSQLSampleRecord.Create(Client,'Name=?',[StringToUTF8(NameEdit.Text)]);
try
if Rec.ID=0 then
QuestionMemo.Text := 'Not found'
else
QuestionMemo.Text := UTF8ToString(Rec.Question);
finally
Rec.Free;
end;
QueryPerformanceCounter(endTime64);
elapsedSeconds := (endTime64 - startTime64) / frequency64;
StatusBar1.SimpleText := FormatFloat('0.000 msec.', 1000*elapsedSeconds);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
Client.Free;
Model.Free;
svcClient.Free;
svcModel.Free;
end;
procedure TForm1.QuitButtonClick(Sender: TObject);
begin
Close;
end;
procedure TForm1.btnCallClick(Sender: TObject);
var a,b: integer;
err: integer;
I: ICalculator;
begin
QueryPerformanceCounter(startTime64);
val(edtA.Text,a,err);
if err<>0 then begin
edtA.SetFocus;
exit;
end;
val(edtB.Text,b,err);
if err<>0 then begin
edtB.SetFocus;
exit;
end;
if svcClient.Services['Calculator'].Get(I) then
lblResult.Caption := 'A + B = ' +IntToStr(I.Add(a,b));
QueryPerformanceCounter(endTime64);
elapsedSeconds := (endTime64 - startTime64) / frequency64;
StatusBar1.SimpleText := FormatFloat('0.000 msec.', 1000*elapsedSeconds);
end;
procedure TForm1.ClientFailed(Sender: TSQLRestClientURI; E: Exception;
Call: PSQLRestURIParams);
var
values: TPUtf8CharDynArray;
begin
if (E<>nil) then
StatusBar1.SimpleText := 'HTTP Error: '+e.Message
else if (Call<>nil) then
begin
JSONDecode(Call.OutBody,['errorCode', 'errorText'],@values,false);
StatusBar1.SimpleText := 'HTTP Error: '+values[0]+', '+values[1];
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
calc: ICalculator;
lst: TFlileCollection;
i: Integer;
begin
QueryPerformanceCounter(startTime64);
if svcClient.Services['Calculator'].Get(calc) then
begin
lst:= TFlileCollection.Create;
try
if calc.GetFileList(Edit1.Text, lst) then
begin
ListView1.Items.Clear;
for i:=0 to lst.Count-1 do
with ListView1.Items.Add do
begin
Caption := lst.Items[i].Name;
SubItems.Add(IntToStr(lst.Items[i].Size));
SubItems.Add(DateTimeToStr(lst.Items[i].ModificationDate));
SubItems.Add(lst.Items[i].Version);
end;
end
else
ShowMessage('Empty or invalid path!');
finally
lst.Free;
end;
end;
QueryPerformanceCounter(endTime64);
elapsedSeconds := (endTime64 - startTime64) / frequency64;
StatusBar1.SimpleText := FormatFloat('0.000 msec.', 1000*elapsedSeconds);
end;
procedure TForm1.FormCreate(Sender: TObject);
var
Server: AnsiString;
aHttps: Boolean;
begin
EnableControls(Panel1, False);
QueryPerformanceFrequency(frequency64);
{with TSQLLog.Family do
begin
Level := LOG_VERBOSE;
EchoToConsole := LOG_VERBOSE; // log all events to the console
end;
}
Caption := ' Sample HTTP Client';
Port := '8080';
if ParamCount=0 then
Server := 'localhost'
else
Server := AnsiString(Paramstr(1));
aHttps := (ParamCount>1) and (AnsiLowerCase(Paramstr(2))='ssl');
if aHttps then
begin
Caption := Caption + ' SSL';
Port := '8443';
end;
Model := CreateSampleModel; // from SampleData unit
Client := TSQLHttpsClient.Create(Server,Port,Model,aHttps);
TSQLHttpClient(Client).OnFailed := ClientFailed;
svcModel := TSQLModel.Create([],SERVICE_NAME);
svcClient := TSQLHttpsClient.Create(Server,Port,svcModel,aHttps);
svcClient.OnFailed := ClientFailed
end;
end.

View File

@@ -0,0 +1,73 @@
unit FileCollect;
interface
uses
Classes,
SynCommons,
mORMot;
type
TFileItem = class;
TFlileCollection = class(TInterfacedCollection)
private
function GetItem(aIndex: Integer): TFileItem;
protected
class function GetClass: TCollectionItemClass; override;
public
function Add: TFileItem;
property Items[aIndex: Integer]: TFileItem read GetItem; default;
end;
TFileItem = class(TCollectionItem)
private
FName:String;
FSize:Cardinal;
FModificationDate: TDateTime;
FVersion: String;
public
procedure Assign(Source:TPersistent);override;
published
property Name: String read FName write FName;
property Size: Cardinal read FSize write FSize;
property ModificationDate: TDateTime read FModificationDate write FModificationDate;
property Version: String read FVersion write FVersion;
end;
implementation
{ TFileItem }
procedure TFileItem.Assign(Source: TPersistent);
begin
if Source is TFileItem then
with Source as TFileItem do
begin
Self.FName:=Name;
Self.FSize:=Size;
Self.FModificationDate:=ModificationDate;
Self.FVersion:=Version;
end
else
inherited Assign(source);
end;
{ TFlileCollection }
function TFlileCollection.Add: TFileItem;
begin
Result := TFileItem(inherited Add);
end;
class function TFlileCollection.GetClass: TCollectionItemClass;
begin
Result := TFileItem;
end;
function TFlileCollection.GetItem(aIndex: Integer): TFileItem;
begin
Result := TFileItem(inherited GetItem(aIndex));
end;
end.

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<BorlandProject>
<PersonalityInfo>
<Option>
<Option Name="Personality">Default.Personality</Option>
<Option Name="ProjectType"></Option>
<Option Name="Version">1.0</Option>
<Option Name="GUID">{FC3C74B5-C087-4A9F-8F96-38AF9C207B93}</Option>
</Option>
</PersonalityInfo>
<Default.Personality>
<Projects>
<Projects Name="Project04ServerStatic.exe">Project04ServerStatic.bdsproj</Projects>
<Projects Name="Project04Client.exe">Project04Client.bdsproj</Projects>
<Projects Name="Targets">Project04ServerStatic.exe Project04Client.exe</Projects>
</Projects>
<Dependencies/>
</Default.Personality>
</BorlandProject>

View File

@@ -0,0 +1,23 @@
#------------------------------------------------------------------------------
VERSION = BWS.01
#------------------------------------------------------------------------------
!ifndef ROOT
ROOT = $(MAKEDIR)\..
!endif
#------------------------------------------------------------------------------
MAKE = $(ROOT)\bin\make.exe -$(MAKEFLAGS) -f$**
DCC = $(ROOT)\bin\dcc32.exe $**
BRCC = $(ROOT)\bin\brcc32.exe $**
#------------------------------------------------------------------------------
PROJECTS = Server.exe Client.exe
#------------------------------------------------------------------------------
default: $(PROJECTS)
#------------------------------------------------------------------------------
Server.exe: Server.dpr
$(DCC)
Client.exe: Client.dpr
$(DCC)

View File

@@ -0,0 +1,39 @@
/// it's a good practice to put all data definition into a stand-alone unit
// - this unit will be shared between client and server
unit SampleData;
interface
uses
SynCommons,
mORMot;
type
/// here we declare the class containing the data
// - it just has to inherits from TSQLRecord, and the published
// properties will be used for the ORM (and all SQL creation)
// - the beginning of the class name must be 'TSQL' for proper table naming
// in client/server environnment
TSQLSampleRecord = class(TSQLRecord)
private
fQuestion: RawUTF8;
fName: RawUTF8;
fTime: TModTime;
published
property Time: TModTime read fTime write fTime;
property Name: RawUTF8 read fName write fName;
property Question: RawUTF8 read fQuestion write fQuestion;
end;
/// an easy way to create a database model for client and server
function CreateSampleModel: TSQLModel;
implementation
function CreateSampleModel: TSQLModel;
begin
result := TSQLModel.Create([TSQLSampleRecord]);
end;
end.

View File

@@ -0,0 +1,36 @@
{
Synopse mORMot framework
Sample 04 - HTTP Client-Server
purpose of this sample is to show how to serve files in
addition to RESTful Client/Server of a SQLite3 database
This sample will serve as REST the data as defined in SampleData,
and serve 'www' sub-folder content within localhost:8080/static
It is IMHO preferred and less-error prone to define a method-based service,
then let the method return the file using Ctxt.ReturnFile() method.
See also https://synopse.info/forum/viewtopic.php?id=1896
Version 1.18
- added Project04ServerStatic.dpr program
}
program Server;
uses
FastMM4,
Forms,
ServerForm in 'ServerForm.pas' {Form1},
SampleData in 'SampleData.pas',
CalcInterface in 'CalcInterface.pas';
{$R *.res}
begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.

View File

@@ -0,0 +1,47 @@
object Form1: TForm1
Left = 272
Top = 122
Width = 434
Height = 220
Caption = ' 04 - HTTP Server'
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -13
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
OnDestroy = FormDestroy
PixelsPerInch = 96
TextHeight = 16
object Label1: TLabel
Left = 40
Top = 16
Width = 297
Height = 33
AutoSize = False
Font.Charset = DEFAULT_CHARSET
Font.Color = clTeal
Font.Height = -16
Font.Name = 'Tahoma'
Font.Style = [fsBold]
ParentFont = False
end
object Label2: TLabel
Left = 56
Top = 72
Width = 149
Height = 16
Caption = 'HTTP Server is starting ...'
end
object Button1: TButton
Left = 88
Top = 120
Width = 75
Height = 25
Caption = 'Quit'
TabOrder = 0
OnClick = Button1Click
end
end

View File

@@ -0,0 +1,191 @@
unit ServerForm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls,
SynLog, SynCommons, mORMot, mORMotSQLite3, SynSQLite3Static,
mORMotHttpServer, SynCrtSock,
SampleData;
type
TForm1 = class(TForm)
Label1: TLabel;
Button1: TButton;
Label2: TLabel;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
Port: String;
dbModel, svcModel: TSQLModel;
DB: TSQLRestServerDB;
SVC: TSQLRestServer;
public
Server: TSQLHttpServer;
end;
TCustomHttpServer = class(TSQLHttpServer)
protected
/// override the server response - must be thread-safe
function Request(Ctxt: THttpServerRequest): cardinal; override;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{$WARN SYMBOL_PLATFORM OFF}
uses CalcInterface, FileCollect;
type
TServiceCalculator = class(TInterfacedObject, ICalculator)
public
function Add(n1,n2: integer): integer;
function GetFileList(path: String; out lst: TFlileCollection): Boolean;
end;
function TServiceCalculator.Add(n1, n2: integer): integer;
begin
result := n1+n2;
end;
function FileVersion(const FileName: TFileName): String;
var
VerInfoSize: Cardinal;
VerValueSize: Cardinal;
Dummy: Cardinal;
PVerInfo: Pointer;
PVerValue: PVSFixedFileInfo;
begin
Result := '';
VerInfoSize := GetFileVersionInfoSize(PChar(FileName), Dummy);
GetMem(PVerInfo, VerInfoSize);
try
if GetFileVersionInfo(PChar(FileName), 0, VerInfoSize, PVerInfo) then
if VerQueryValue(PVerInfo, '\', Pointer(PVerValue), VerValueSize) then
with PVerValue^ do
Result := Format('v%d.%d.%d build %d', [
HiWord(dwFileVersionMS), //Major
LoWord(dwFileVersionMS), //Minor
HiWord(dwFileVersionLS), //Release
LoWord(dwFileVersionLS)]); //Build
finally
FreeMem(PVerInfo, VerInfoSize);
end;
end;
function TServiceCalculator.GetFileList(path: String; out lst: TFlileCollection): Boolean;
var
SR : TSearchRec;
ext: String;
begin
Result := (FindFirst(path + '*.*', faArchive, SR) = 0);
if Result then
begin
lst := TFlileCollection.Create;
repeat
with TFileItem(lst.Add()) do
begin
Name := SR.Name;
Size := Sr.Size;
ModificationDate := FileDateToDatetime(SR.Time);
ext := ExtractFileExt(SR.Name);
if AnsiSameText(ext, '.exe') or AnsiSameText(ext, '.dll') then
Version := FileVersion(path+SR.Name);
end;
until FindNext(SR) <> 0;
FindClose(SR);
end;
end;
{ TForm1 }
procedure TForm1.Button1Click(Sender: TObject);
begin
Close;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
aHttpServerSecurity: TSQLHttpServerSecurity;
begin
with TSQLLog.Family do begin
Level := LOG_VERBOSE;
EchoToConsole := LOG_VERBOSE; // log all events to the console
end;
TSQLLog.Add.Log(sllInfo,'Starting');
Port := PORT_HTTP;
aHttpServerSecurity := secNone;
Label1.Caption := 'HTTP Server';
if (ParamCount>0) then
begin
if (Paramstr(1)='ssl') then
aHttpServerSecurity := secSSL;
if (Paramstr(1)='sha') or (Paramstr(1)='aes') then
aHttpServerSecurity := secSynShaAes;
Label1.Caption := Label1.Caption +' '+AnsiUpperCase( Paramstr(1) );
end;
if aHttpServerSecurity <> secNone then
Port := PORT_HTTPS;
Label2.Caption := 'HTTP Server is running on port: '+Port;
dbModel := CreateSampleModel;
DB := TSQLRestServerDB.Create(dbModel,'server.db3',true);
DB.CreateMissingTables;
svcModel := TSQLModel.Create([],SERVICE_NAME);
SVC := TSQLRestServerFullMemory.Create(svcModel,'users.json',false,true);
SVC.ServiceDefine(TServiceCalculator,[ICalculator],sicShared);
Server := TCustomHttpServer.Create(Port,[DB,SVC],'+',useHttpApiRegisteringURI,32,aHttpServerSecurity,'static');
Server.AccessControlAllowOrigin := '*'; // allow cross-site AJAX queries
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
Server.Free;
SVC.Free;
DB.Free;
svcModel.Free;
dbModel.Free;
end;
{ TCustomHttpServer }
function TCustomHttpServer.Request(Ctxt: THttpServerRequest): Cardinal;
var
FileName: TFileName;
begin
if (Ctxt.Method='GET') and
IdemPChar(pointer(Ctxt.URL),'/STATIC/') and
(PosEx('..',Ctxt.URL)=0) then
begin
// http.sys will send the specified file from kernel mode
FileName := '..\www\'+UTF8ToString(Copy(Ctxt.URL,9,maxInt));
Ctxt.OutContent := StringToUTF8(FileName);
Ctxt.OutContentType := HTTP_RESP_STATICFILE;
Result := 200; // THttpApiServer.Execute will return 404 if not found
end
else
Result := inherited Request(Ctxt); // call the associated TSQLRestServer instance(s)
end;
end.

View File

@@ -0,0 +1,10 @@
[{"AuthGroup":[
{"RowID":1,"Ident":"Admin","SessionTimeout":10,"AccessRights":"47,1-256,0,1-256,0,1-256,0,1-256,0"},
{"RowID":2,"Ident":"Supervisor","SessionTimeout":60,"AccessRights":"46,1-256,0,3-256,0,3-256,0,3-256,0"},
{"RowID":3,"Ident":"User","SessionTimeout":60,"AccessRights":"14,3-256,0,3-256,0,3-256,0,3-256,0"},
{"RowID":4,"Ident":"Guest","SessionTimeout":60,"AccessRights":"0,3-256,0,0,0,0"}]
},{"AuthUser":[
{"RowID":1,"LogonName":"Admin","DisplayName":"Admin","PasswordHashHexa":"67aeea294e1cb515236fd7829c55ec820ef888e8e221814d24d83b3dc4d825dd","GroupRights":1,"Data":null},
{"RowID":2,"LogonName":"Supervisor","DisplayName":"Supervisor","PasswordHashHexa":"67aeea294e1cb515236fd7829c55ec820ef888e8e221814d24d83b3dc4d825dd","GroupRights":2,"Data":null},
{"RowID":3,"LogonName":"User","DisplayName":"User","PasswordHashHexa":"67aeea294e1cb515236fd7829c55ec820ef888e8e221814d24d83b3dc4d825dd","GroupRights":3,"Data":null}]
}]

View File

@@ -0,0 +1,6 @@
makecert -sv SignRoot.pvk -cy authority -r signroot.cer -a sha1 -n "CN=Dev Certification Authority" -ss my -sr localmachine
makecert -iv SignRoot.pvk -ic signroot.cer -cy end -pe -n CN="rest.server.com" -eku 1.3.6.1.5.5.7.3.1 -ss my -sr localmachine -sky exchange -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12
rem netsh http delete sslcert ipport=0.0.0.0:8443
netsh http add sslcert ipport=0.0.0.0:8443 1600ccafee284bdeec7622ad5534881310b2e0f0 appid={00112233-4455-6677-8899-AABBCCDDEEFF}

View File

@@ -0,0 +1,59 @@
public class CRC32 {
private static int[] table = {
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
};
private static long Crc32Add(long crc, int c) {
return table[(int)((crc^c) & 0xFF) ]^((crc>>8)&0xFFFFFF) ;
}
public static long calculate(String str, long crc){
crc ^= 0xFFFFFFFF;
if (crc < 0) {
crc += 4294967296L;
}
for (int n=0; n< str.length(); n++) {
crc= Crc32Add(crc, str.codePointAt(n));
}
crc ^= 0xFFFFFFFF;
if (crc < 0) {
crc += 4294967296L;
}
return crc;
}
}

View File

@@ -0,0 +1,47 @@
import okhttp3.*;
import org.json.JSONArray;
import org.json.JSONObject;
public class RESTClient {
public int statusCode;
private String server;
public static final MediaType JSON = MediaType.get("application/json");
private OkHttpClient client;
public RESTClient(String server) {
this.server = server;
this.client = new OkHttpClient();
}
public String doGet(String url) throws Exception {
Request request = new Request.Builder()
.url(server + url)
.build();
Response response = client.newCall(request).execute();
statusCode = response.code();
if (response.body() != null) {
return response.body().string();
}
return "";
}
public JSONObject doPost(String url, final JSONArray data) throws Exception {
//System.out.println("Post data: " + data.toString() );
RequestBody body = RequestBody.create(JSON, data.toString());
Request request = new Request.Builder()
.url(server + url)
.post(body)
.build();
Response response = client.newCall(request).execute();
statusCode = response.code();
if (response.body() != null) {
String json = response.body().string();
return new JSONObject(json);
}
return new JSONObject();
}
}

View File

@@ -0,0 +1,203 @@
import org.json.JSONArray;
import org.json.JSONObject;
import java.security.MessageDigest;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
class SynException extends Exception {
public SynException(int errorCode, String errorText) {
super(String.format("Server side error: %d %s", errorCode, errorText));
}
}
public class SynClient {
private String user;
private String root;
private RESTClient http;
private long userID;
private String logonDisplay;
private int timeout;
private String version;
private long sessionID = 0;
private String passwordHashHexa = "";
private String sessionIDHexa8 = "";
private long sessionPrivateKey = 0;
private long sessionTickCountOffset = 0;
private long serverTimeStampOffset;
private long lastSessionTickCount = 0;
public static int HTTP_OK = 200;
public static int HTTP_FORBIDDEN = 403;
public SynClient(String host, int port, String root, boolean ssl){
this.root = root;
String baseUrl = (ssl?"https":"http") + "://" + host + ":" + port + "/";
this.http = new RESTClient(baseUrl);
}
public long getTimestamp() {
return this.nowAsMormotTime() + this.serverTimeStampOffset;
}
public boolean login(String user, String password) throws Exception {
this.user = user;
this.passwordHashHexa = SHA256("salt" + password);
this.sessionTickCountOffset = System.currentTimeMillis();
return doAuth();
}
public JSONObject call(String function, JSONArray data) throws Exception {
JSONObject response = http.doPost(signUrl(root + "/" + function), data);
if (http.statusCode == HTTP_FORBIDDEN) {
if (doAuth()) {
response = http.doPost(signUrl(root + "/" + function), data);
}
}
if (http.statusCode != HTTP_OK)
throw new SynException(response.getInt("errorCode"),
response.getString("errorText") );
return response;
}
public void logout() throws Exception {
String url = signUrl(root + "/auth?UserName="+ this.user + "&Session=" + this.sessionID);
String response = http.doGet(url);
if (http.statusCode == HTTP_OK) {
sessionID = 0;
passwordHashHexa = "";
sessionIDHexa8 = "";
sessionPrivateKey = 0;
} else {
JSONObject json = new JSONObject(response);
throw new SynException(json.getInt("errorCode"),
json.getString("errorText"));
}
}
private String byteToHex(byte[] bts) {
StringBuilder des = new StringBuilder();
String tmp;
for (byte bt : bts) {
tmp = (Integer.toHexString(bt & 0xFF));
if (tmp.length() == 1) {
des.append("0");
}
des.append(tmp);
}
return des.toString();
}
private String SHA256(String nonce) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] bytes = nonce.getBytes();
md.update(bytes);
return byteToHex(md.digest());
}catch (java.security.NoSuchAlgorithmException err) {
err.printStackTrace();
}
return "";
}
private String padL(String s, int count) {
String result = String.format("%"+count+"s", s).replace(' ','0');
if (result.length() > count)
return result.substring(result.length() - count);
else
return result;
}
private String binaryString(int i, int digits) {
return padL(Integer.toBinaryString(i), digits);
}
private long nowAsMormotTime() {
Calendar cal = Calendar.getInstance();
String clientTime = binaryString( cal.get(Calendar.YEAR), 13);
clientTime += binaryString( cal.get(Calendar.MONTH), 4);
clientTime += binaryString( cal.get(Calendar.DAY_OF_MONTH)-1, 5);
clientTime += binaryString( cal.get(Calendar.HOUR_OF_DAY), 5);
clientTime += binaryString( cal.get(Calendar.MINUTE), 6);
clientTime += binaryString( cal.get(Calendar.SECOND), 6);
return Long.valueOf(clientTime, 2);
}
private void gotTimestamp(String timestamp) {
serverTimeStampOffset = Long.parseLong(timestamp) - nowAsMormotTime();
}
private String calcClientNonce () {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String clientNonce = df.format(new Date());
return SHA256(clientNonce);
}
private long gotSession(String sessionKey) {
int i = sessionKey.indexOf('+');
sessionID = Long.valueOf(sessionKey.substring(0, i));
sessionIDHexa8 = padL(Long.toHexString(sessionID), 8);
long r = CRC32.calculate(sessionKey, 0);
sessionPrivateKey = CRC32.calculate(passwordHashHexa, r);
return sessionPrivateKey;
}
private boolean doAuth() throws Exception {
String timeStamp = http.doGet(root + "/TimeStamp");
gotTimestamp(timeStamp);
String response = http.doGet(root + "/auth?UserName="+this.user);
JSONObject json = new JSONObject(response);
if (http.statusCode != HTTP_OK)
throw new SynException(json.getInt("errorCode"), json.getString("errorText") );
String serverNonce = json.getString("result");
String clientNonce = calcClientNonce();
response = http.doGet(root +"/auth"+
"?UserName="+ user +
"&Password="+ SHA256(root + serverNonce + clientNonce + user + passwordHashHexa)+
"&ClientNonce=" + clientNonce);
json = new JSONObject(response);
if (http.statusCode != HTTP_OK)
throw new SynException(json.getInt("errorCode"), json.getString("errorText") );
userID = json.getInt("logonid");
logonDisplay = json.getString("logondisplay");
version = json.getString("version");
timeout = json.getInt("timeout");
if (timeout == 0) timeout = 60;
return gotSession(json.getString("result")) > 0;
}
private String signUrl(String url) {
long ticks = System.currentTimeMillis() >> 8; // 256 ms resolution;
if (lastSessionTickCount == ticks)
lastSessionTickCount += 1;
else
lastSessionTickCount = ticks;
String nonce = padL(Long.toHexString(lastSessionTickCount), 8);
long signature = CRC32.calculate(url, CRC32.calculate(nonce, sessionPrivateKey));
String sessionSignature = sessionIDHexa8 + nonce +padL(Long.toHexString(signature), 8);
return url + (url.contains("?") ? "&session_signature=":
"?session_signature=") + sessionSignature;
}
}

View File

@@ -0,0 +1,28 @@
import org.json.JSONArray;
import org.json.JSONObject;
public class SynTest {
public static void main(String[] args) throws Exception {
JSONArray params;
JSONObject response;
SynClient client = new SynClient("127.0.0.1", 8080, "service", false);
if (client.login("User", "synopse")) {
System.out.println("Timestamp: " + client.getTimestamp());
//TEST 1
params = new JSONArray().put(2).put(3); // Add(2, 3)
response = client.call("Calculator.Add", params);
System.out.println("Summ: " + response);
//TEST 2
params = new JSONArray().put(".\\"); // GetFileList('.\')
response = client.call("Calculator.GetFileList", params);
System.out.println("List: " + response);
client.logout();
}
}
}