source upload
This commit is contained in:
@@ -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>
|
@@ -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>
|
@@ -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 |
@@ -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.
|
@@ -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.
|
Binary file not shown.
@@ -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
|
@@ -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.
|
||||
|
@@ -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.
|
||||
|
@@ -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>
|
@@ -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)
|
||||
|
||||
|
@@ -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.
|
@@ -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.
|
Binary file not shown.
@@ -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
|
@@ -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.
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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}]
|
||||
}]
|
Binary file not shown.
Binary file not shown.
@@ -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}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user