Wednesday, October 14, 2009

DataSnap: In-Process Server Method

DataSnap Server Method was introduced in Delphi 2009.  Most video or demo about DataSnap server method available only introduce socket based client server access communication. e.g.: TCP or HTTP protocol.
However, DataSnap was designed as a scalable data access solution that able to work with one, two, three or more tiers model.  All examples we see so far are suitable for 2 or 3 tiers design.  I can’t find any example talking about 1 tier or in-process design.
Indeed, it is very simple to work with in-process server method.  Most steps are similar to out-of-process server methods.

Define a Server Method

Define a well known EchoString() and a Sum() server method:
unit MyServerMethod;
interface
uses Classes, DBXCommon;
type
  {$MethodInfo On}
  TMyServerMethod = class(TPersistent)
  public
    function EchoString(Value: string): string;
    function Sum(const a, b: integer): integer; 
  end;
  {$MethodInfo Off}

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

function TMyServerMethod.Sum(const a, b: integer): integer;
begin
  Result := a + b;
end;

end.

Define a DataModule to access the server method

Drop a TDSServer and TDSServerClass as usual to the data module.  Define a OnGetClass event to TDSServerClass instance.  Please note that you don’t need to drop any transport components like TDSTCPServerTransport or TDSHTTPServer as we only want to consume the server method for in-process only.
object MyServerMethodDataModule1: TMyServerMethodDataModule
  OldCreateOrder = False
  Height = 293
  Width = 419
  object DSServer1: TDSServer
    AutoStart = True
    HideDSAdmin = False
    Left = 64
    Top = 40
  end
  object DSServerClass1: TDSServerClass
    OnGetClass = DSServerClass1GetClass
    Server = DSServer1
    LifeCycle = 'Server'
    Left = 64
    Top = 112
  end
end


unit MyServerMethodDataModule;
uses MyServerMethod;
procedure TMyServerMethodDataModule.DSServerClass1GetClass(DSServerClass: TDSServerClass;
    var PersistentClass: TPersistentClass);
begin
  PersistentClass := TMyServerMethod;
end;

Generate Server Method Client Classes

It is not easy to generate the server method client classes design for in-process server.  You may try any methods you are familiar with to hook up your server method to TCP or HTTP transport service, start the service and attempt to generate the client class by any means.
//
// Created by the DataSnap proxy generator.
//

unit DataSnapProxyClient;
interface
uses DBXCommon, DBXJSON, Classes, SysUtils, DB, SqlExpr, DBXDBReaders;
type
  TMyServerMethodClient = class
  private
    FDBXConnection: TDBXConnection;
    FInstanceOwner: Boolean;
    FEchoStringCommand: TDBXCommand;
  public
    constructor Create(ADBXConnection: TDBXConnection); overload;
    constructor Create(ADBXConnection: TDBXConnection; AInstanceOwner: Boolean); overload;
    destructor Destroy; override;
    function EchoString(Value: string): string;
    function Sum(const a, b: integer): integer;
  end;

implementation
function TMyServerMethodClient.EchoString(Value: string): string;
begin
  if FEchoStringCommand = nil then
  begin
    FEchoStringCommand := FDBXConnection.CreateCommand;
    FEchoStringCommand.CommandType := TDBXCommandTypes.DSServerMethod;
    FEchoStringCommand.Text := 'TMyServerMethod.EchoString';
    FEchoStringCommand.Prepare;
  end;
  FEchoStringCommand.Parameters[0].Value.SetWideString(Value);
  FEchoStringCommand.ExecuteUpdate;
  Result := FEchoStringCommand.Parameters[1].Value.GetWideString;
end;

function TMyServerMethodClient.Sum(a: Integer; b: Integer): Integer;
begin
  if FSumCommand = nil then
  begin
    FSumCommand := FDBXConnection.CreateCommand;
    FSumCommand.CommandType := TDBXCommandTypes.DSServerMethod;
    FSumCommand.Text := 'TMyServerMethod.Sum';
    FSumCommand.Prepare;
  end;
  FSumCommand.Parameters[0].Value.SetInt32(a);
  FSumCommand.Parameters[1].Value.SetInt32(b);
  FSumCommand.ExecuteUpdate;
  Result := FSumCommand.Parameters[2].Value.GetInt32;
end;

constructor TMyServerMethodClient.Create(ADBXConnection: TDBXConnection);
begin
  inherited Create;
  if ADBXConnection = nil then
    raise EInvalidOperation.Create('Connection cannot be nil.  Make sure the connection has been opened.');
  FDBXConnection := ADBXConnection;
  FInstanceOwner := True;
end;

constructor TMyServerMethodClient.Create(ADBXConnection: TDBXConnection; AInstanceOwner: Boolean);
begin
  inherited Create;
  if ADBXConnection = nil then
    raise EInvalidOperation.Create('Connection cannot be nil.  Make sure the connection has been opened.');
  FDBXConnection := ADBXConnection;
  FInstanceOwner := AInstanceOwner;
end;

destructor TMyServerMethodClient.Destroy;
begin
  FreeAndNil(FEchoStringCommand);
  inherited;
end;

end.

Invoke the server method via in-process

You may see from the following code that there is no different to access the server method for in-process and out-of-process design.
First, you create an instant of datasnap server.  This will register the DSServer to the TDBXDriverRegistry.  e.g. DSServer1 in this case.
You may then use TSQLConnection with DSServer1 as driver name instead of “DataSnap” that require socket connection to initiate in-process communication invoking the server method.
var o: TMyServerMethodDataModule;
    Q: TSQLConnection;
    c: TMyServerMethodClient;
begin
  o := TMyServerMethodDataModule.Create(Self);   Q := TSQLConnection.Create(Self);
  try
    Q.DriverName := 'DSServer1';     Q.LoginPrompt := False;
    Q.Open;

    c := TMyServerMethodClient.Create(Q.DBXConnection);
    try
      ShowMessage(c.EchoString('Hello'));
    finally
      c.Free;
    end;

  finally
    o.Free;
    Q.Free;
  end;
end;

Troubleshoot: Encounter Memory Leak after consume the in-process server methods

This happens in Delphi 2010 build 14.0.3513.24210.  It may have fixed in future release.  You may check QC#78696 for latest status.  Please note that you need to add “ReportMemoryLeaksOnShutdown := True;” in the code to show the leak report.
1
The memory leaks has no relation with in-process server methods.  It should be a problem in class TDSServerConnection where a property ServerConnectionHandler doesn’t free after consume.
Here is a fix for the problem:
unit DSServer.QC78696;
interface
implementation
uses SysUtils,
     DBXCommon, DSServer, DSCommonServer, DBXMessageHandlerCommon, DBXSqlScanner,
     DBXTransport,
     CodeRedirect;

type
  TDSServerConnectionHandlerAccess = class(TDBXConnectionHandler)
    FConProperties: TDBXProperties;
    FConHandle: Integer;
    FServer: TDSCustomServer;
    FDatabaseConnectionHandler: TObject;
    FHasServerConnection: Boolean;
    FInstanceProvider: TDSHashtableInstanceProvider;
    FCommandHandlers: TDBXCommandHandlerArray;
    FLastCommandHandler: Integer;
    FNextHandler: TDBXConnectionHandler;
    FErrorMessage: TDBXErrorMessage;
    FScanner: TDBXSqlScanner;
    FDbxConnection: TDBXConnection;
    FTransport: TDSServerTransport;
    FChannel: TDbxChannel;
    FCreateInstanceEventObject: TDSCreateInstanceEventObject;
    FDestroyInstanceEventObject: TDSDestroyInstanceEventObject;
    FPrepareEventObject: TDSPrepareEventObject;
    FConnectEventObject: TDSConnectEventObject;
    FErrorEventObject: TDSErrorEventObject;
    FServerCon: TDSServerConnection;
  end;

  TDSServerConnectionPatch = class(TDSServerConnection)
  public
    destructor Destroy; override;
  end;

  TDSServerDriverPatch = class(TDSServerDriver)
  protected
    function CreateConnectionPatch(ConnectionBuilder: TDBXConnectionBuilder): TDBXConnection;
  end;

destructor TDSServerConnectionPatch.Destroy;
var o: TDSServerConnectionHandlerAccess;
begin
  inherited Destroy;
  o := TDSServerConnectionHandlerAccess(ServerConnectionHandler);
  if o.FServerCon = Self then begin
    o.FServerCon := nil;
    ServerConnectionHandler.Free;
  end;
end;

function TDSServerDriverPatch.CreateConnectionPatch(
  ConnectionBuilder: TDBXConnectionBuilder): TDBXConnection;
begin
  Result := TDSServerConnectionPatch.Create(ConnectionBuilder);
end;

var QC78696: TCodeRedirect;
initialization
  QC78696 := TCodeRedirect.Create(@TDSServerDriverPatch.CreateConnection, @TDSServerDriverPatch.CreateConnectionPatch);
finalization
  QC78696.Free;
end.

Troubleshoot: Encounter "Invalid command handle" when consume more than one server method at runtime for in-process application

This happens in Delphi 2010 build 14.0.3513.24210.  It may have fixed in future release.  You may check QC#78698 for latest status.
To replay this problem, you may consume the server method as:
    c := TMyServerMethodClient.Create(Q.DBXConnection);
    try
      ShowMessage(c.EchoString('Hello'));
      ShowMessage(IntToStr(c.Sum(100, 200)));
    finally
      c.Free;
    end;

or this:
    c := TMyServerMethodClient.Create(Q.DBXConnection);
    try
      ShowMessage(c.EchoString('Hello'));
      ShowMessage(IntToStr(c.Sum(100, 200)));
      ShowMessage(c.EchoString('Hello'));
    finally
      c.Free;
    end;

Here is a fix for the problem
unit DSServer.QC78698;
interface
implementation
uses SysUtils, Classes,
     DBXCommon, DBXMessageHandlerCommon, DSCommonServer, DSServer,
     CodeRedirect;

type
  TDSServerCommandAccess = class(TDBXCommand)
  private
    FConHandler: TDSServerConnectionHandler;
    FServerCon: TDSServerConnection;
    FRowsAffected: Int64;
    FServerParameterList: TDBXParameterList;
  end;

  TDSServerCommandPatch = class(TDSServerCommand)
  private
    FCommandHandle: integer;
    function Accessor: TDSServerCommandAccess;
  private
    procedure ExecutePatch;
  protected
    procedure DerivedClose; override;
    function DerivedExecuteQuery: TDBXReader; override;
    procedure DerivedExecuteUpdate; override;
    function DerivedGetNextReader: TDBXReader; override;
    procedure DerivedPrepare; override;
  end;

  TDSServerConnectionPatch = class(TDSServerConnection)
  public
    function CreateCommand: TDBXCommand; override;
  end;

  TDSServerDriverPatch = class(TDSServerDriver)
  private
    function CreateServerCommandPatch(DbxContext: TDBXContext; Connection:
        TDBXConnection; MorphicCommand: TDBXCommand): TDBXCommand;
  public
    constructor Create(DBXDriverDef: TDBXDriverDef); override;
  end;

constructor TDSServerDriverPatch.Create(DBXDriverDef: TDBXDriverDef);
begin
  FCommandFactories := TStringList.Create;
  rpr;
  InitDriverProperties(TDBXProperties.Create);
  // '' makes this the default command factory.
  //
  AddCommandFactory('', CreateServerCommandPatch);
end;

function TDSServerDriverPatch.CreateServerCommandPatch(DbxContext: TDBXContext;
    Connection: TDBXConnection; MorphicCommand: TDBXCommand): TDBXCommand;
var
  ServerConnection: TDSServerConnection;
begin
  ServerConnection := Connection as TDSServerConnection;
  Result := TDSServerCommandPatch.Create(DbxContext, ServerConnection, TDSServerHelp.GetServerConnectionHandler(ServerConnection));
end;

function TDSServerCommandPatch.Accessor: TDSServerCommandAccess;
begin
  Result := TDSServerCommandAccess(Self);
end;

procedure TDSServerCommandPatch.DerivedClose;
var
  Message: TDBXCommandCloseMessage;
begin
  Message := Accessor.FServerCon.CommandCloseMessage;
  Message.CommandHandle := FCommandHandle;
  Message.HandleMessage(Accessor.FConHandler);
end;

function TDSServerCommandPatch.DerivedExecuteQuery: TDBXReader;
var
  List: TDBXParameterList;
  Parameter: TDBXParameter;
  Reader: TDBXReader;
begin
  ExecutePatch;
  List := Parameters;
  if (List <> nil) and (List.Count > 0) then
  begin
    Parameter := List.Parameter[List.Count - 1];
    if Parameter.DataType = TDBXDataTypes.TableType then
    begin
      Reader := Parameter.Value.GetDBXReader;
      Parameter.Value.SetNull;
      Exit(Reader);
    end;
  end;
  Result := nil;
end;

procedure TDSServerCommandPatch.DerivedExecuteUpdate;
begin
  ExecutePatch;
end;

function TDSServerCommandPatch.DerivedGetNextReader: TDBXReader;
var
  Message: TDBXNextResultMessage;
begin
  Message := Accessor.FServerCon.NextResultMessage;
  Message.CommandHandle := FCommandHandle;
  Message.HandleMessage(Accessor.FConHandler);
  Result := Message.NextResult;
end;

procedure TDSServerCommandPatch.DerivedPrepare;
begin
  inherited;
  FCommandHandle := Accessor.FServerCon.PrepareMessage.CommandHandle;
end;

procedure TDSServerCommandPatch.ExecutePatch;
var
  Count: Integer;
  Ordinal: Integer;
  Params: TDBXParameterList;
  CommandParams: TDBXParameterList;
  Message: TDBXExecuteMessage;
begin
  Message := Accessor.FServerCon.ExecuteMessage;
  if not IsPrepared then
    Prepare;
  for ordinal := 0 to Parameters.Count - 1 do
    Accessor.FServerParameterList.Parameter[Ordinal].Value.SetValue(Parameters.Parameter[Ordinal].Value);
  Message.Command := Text;
  Message.CommandType := CommandType;
  Message.CommandHandle := FCommandHandle;
  Message.Parameters := Parameters;
  Message.HandleMessage(Accessor.FConHandler);
  Params := Message.Parameters;
  CommandParams := Parameters;
  if Params <> nil then
  begin
    Count := Params.Count;
    if Count > 0 then
      for ordinal := 0 to Count - 1 do
      begin
        CommandParams.Parameter[Ordinal].Value.SetValue(Params.Parameter[Ordinal].Value);
        Params.Parameter[Ordinal].Value.SetNull;
      end;
  end;
  Accessor.FRowsAffected := Message.RowsAffected;
end;

function TDSServerConnectionPatch.CreateCommand: TDBXCommand;
var
  Command: TDSServerCommand;
begin
  Command := TDSServerCommandPatch.Create(FDbxContext, self, ServerConnectionHandler);
  Result := Command;
end;

var QC78698: TCodeRedirect;
initialization
  QC78698 := TCodeRedirect.Create(@TDSServerConnection.CreateCommand, @TDSServerConnectionPatch.CreateCommand);
finalization
  QC78698.Free;
end.

Reference:
  1. QC#78696: Memory Leak in TDSServerConnection for in-process connection
  2. QC#78698: Encounter "Invalid command handle" when consume more than one server method at runtime for in-process application

Wednesday, October 07, 2009

Linux: Configure a local repository from ISO image for YUM

yum is an interactive, rpm based, package manager. It can automatically perform system updates, including dependancy analysis and obsolete processing based on "repository" metadata. It can also perform installation of new packages, removal of old packages and perform queries on the installed and/or available packages among many other commands/services (see below). yum is similar to other high level package managers like apt-get and smart.

After install Fedora Core, some default YUM repository is stored in /etc/yum.repos.d.  These repositories are URLs located some where in the Internet. You need an internet connection in order to enjoy the convenient of yum to update your system.  It is rather inconvenient if you have a slow internet connection or you have many machines to update via yum.

A good approach to overcome these problems is setup a local repositories without to avoid grabbing RPM packages via Internet.

Setup a local repository for YUM

First, we need to disable default YUM repository configuration installed by Fedora:

[root@localhost yum.repos.d]# cd /etc/yum.repos.d/
[root@localhost yum.repos.d]# ls -al
total 28
drwxr-xr-x.  2 root root 4096 2009-10-07 09:16 .
drwxr-xr-x. 56 root root 4096 2009-10-07 09:17 ..
-rw-r--r--.  1 root root 1785 2009-05-12 06:45 fedora-rawhide.repo
-rw-r--r--.  1 root root 1144 2009-05-12 06:45 fedora.repo
-rw-r--r--.  1 root root 1105 2009-05-12 06:45 fedora-updates.repo
-rw-r--r--.  1 root root 1163 2009-05-12 06:45 fedora-updates-testing.repo

To disable these repositories, rename all files to *.repo.old:

[root@localhost yum.repos.d]# cd /etc/yum.repos.d/
[root@localhost yum.repos.d]# ls -al
total 28
drwxr-xr-x.  2 root root 4096 2009-10-07 09:16 .
drwxr-xr-x. 56 root root 4096 2009-10-07 09:17 ..
-rw-r--r--.  1 root root 1785 2009-05-12 06:45 fedora-rawhide.repo.old
-rw-r--r--.  1 root root 1144 2009-05-12 06:45 fedora.repo.old
-rw-r--r--.  1 root root 1105 2009-05-12 06:45 fedora-updates.repo.old
-rw-r--r--.  1 root root 1163 2009-05-12 06:45 fedora-updates-testing.repo.old

Create local repository

In this example, the local repository is an Fedora Core ISO image mount under /mnt/cdrom.

Create a local repository configuration file it and store in /etc/yum.repos.d:

[root@localhost yum.repos.d]# cat local.repo
[local]
name=ISO
baseurl=file:///mnt/cdrom

You may now start using YUM to install packages as lightning speed:

[root@localhost etc]# yum install samba

Troubleshoot: Public key for lzma-libs-4.32.7-2.fc11.i586.rpm is not installed

If you encounter an error message like “Public key for ***.rpm is not installed” when running “yum install”, you need to import the RPM public key for the local repository into RPM database first.

The RPM public key is usually reside in the root of ISO image:

[root@localhost cdrom]# ls /mnt/cdrom -al
total 453
drwxr-sr-x. 7 root  499   4096 2009-06-03 06:05 .
drwxr-xr-x. 3 root root   4096 2009-10-07 09:11 ..
-rw-r--r--. 1 root  499     37 2009-06-03 06:04 .discinfo
drwxr-xr-x. 3 root root   2048 2009-06-03 06:02 EFI
-rw-r--r--. 2 root root  18363 2007-07-04 06:06 GPL
drwxr-sr-x. 3 root  499   2048 2009-06-03 06:04 images
drwxr-sr-x. 2 root  499   2048 2009-06-03 06:02 isolinux
-rw-r--r--. 1 root  499     95 2009-06-03 06:05 media.repo
drwxr-sr-x. 2 root  499 405504 2009-06-03 05:48 Packages
-rw-r--r--. 2 root root  10581 2009-05-14 12:59 README-BURNING-ISOS-en_US.txt
drwxr-sr-x. 2 root  499   4096 2009-06-03 06:05 repodata
lrwxrwxrwx. 2 root root     29 2009-06-03 05:51 RPM-GPG-KEY-fedora -> RPM-GPG-KEY-fedora-11-primary
-rw-r--r--. 2 root root   1653 2009-05-12 06:45 RPM-GPG-KEY-fedora-11-primary
lrwxrwxrwx. 2 root root     29 2009-06-03 05:51 RPM-GPG-KEY-fedora-i386 -> RPM-GPG-KEY-fedora-11-primary
-rw-r--r--. 2 root root   1694 2009-05-12 06:45 RPM-GPG-KEY-fedora-ia64
lrwxrwxrwx. 2 root root     29 2009-06-03 05:51 RPM-GPG-KEY-fedora-ppc -> RPM-GPG-KEY-fedora-11-primary
lrwxrwxrwx. 2 root root     29 2009-06-03 05:51 RPM-GPG-KEY-fedora-ppc64 -> RPM-GPG-KEY-fedora-11-primary
lrwxrwxrwx. 2 root root     29 2009-06-03 05:51 RPM-GPG-KEY-fedora-x86_64 -> RPM-GPG-KEY-fedora-11-primary
-r--r--r--. 1 root root   4011 2009-06-03 06:05 TRANS.TBL
-rw-r--r--. 1 root  499   1437 2009-06-03 06:04 .treeinfo

To install the public key, try this:

[root@localhost cdrom]# rpm --import RPM-GPG-KEY-fedora

You may proceed to "yum install” as usual now.

Tuesday, October 06, 2009

Setup a VMWare machine to simulate low speed TCP/IP network

When we design a multi-tiers application in high speed network environment, we do not know if the data communicate performance is up to expectation if running on real network. The cost of develop the application in real network is high. There is a cost effective solution to simulate various kind of bandwidth and traffic conditions for WAN network.

Previously, I learned from DUMMYNET and NistNet has the solution. But it is very hard to deploy the solution for development. I finally found the TC (Traffic control) in iproute package of Linux distribution able to do the task. It is very easy to use.

Combine the TC and routing strategy in Linux network, we may design a masqueraded router that able to let us simulate low speed TCP/IP network in development stage.

We do not need a real machine to configure the router. Use VMWARE workstation to setup a virtual machine running a masqueraded router.

Some Theory

To realize the simulation:

  1. Configure 2 networks and make sure both networks may reach each others. 
  2. Bind a server application to one of the network
  3. Bind a client application to another network
  4. Make sure client may reach server and server response to client promptly
  5. We may then enforce a traffic control rule on one of the network
  6. Establish a connection from client to server and observe how the traffic control rule affect the connection quality.
  7. Tune the traffic control rule and continue monitor the connection quality.

Install and Configure VMWare Workstation

  1. Install and Start VMWare workstation.  The following illustration use VMware Workstation 6.5.3 as example.
  2. In VMWare Workstation window, click Edit | Virtual Network Editor…
  3. A “Virtual Network Editor” dialog prompt out, switch to page “Host Virtual Adapters”:

    2 
  4. There may be some VMWare network adapter created by default.  In this topic, I need 2 VMware network adapter connect as “Host only”.  For illustration purpose, I add 2 VMware network adapter connected to VMnet1 and VMnet2.
  5. Switch to page “Host Virtual Network Mapping” and make sure both VMnet1 and VMnet2 adapters are attached properly:

    3
  6. Click the row of VMNet1 “>” button to configure subnet of VMNet1.  Set subnet IP address to “192.168.101.0”:

    5
  7. Click the row of VMNet2 “>” button to configure subnet of VMNet2.  Set subnet IP address to “192.168.102.0”:

    6
  8. Click “Apply” button to commit changes.
  9. You may also need to check both VMNet1 and VMNet2 DHCP setting to see if the setting is correct:

    7 
  10. Switch to page “DHCP” and make sure both DHCP service for VMNet1 and VMNet2 are started:
     8
  11. Switch to “Summary” page to check the status of both VMNet1 and VMNet2:

    9 
  12. You may continue to next steps once done

Install a Fedora Core Virtual Machine


  1. Create a new virtual machine with the following hardware configuration:
    1. Memory: 1GB for installation.  You may change to 128MB after finish installation.
    2. Hard Disk: 1GB
    3. Network adapter 1 connect as “Custom” VMNet1 (Host-Only)
    4. Network adapter 2 connect as “Custom” VMNet2 (Host-Only)

      1
  2. Start the machine and install Fedora Core via any installation methods you prefer. (The following example use Fedora Core 11)
  3. The machine is only use for masqueraded network simulation, you may uncheck all software package to install a minimal bare-bone Fedora Core.

Configure Fedora Core Virtual Machine

  1. Configure network adapter to start after machine boot. 

    $ system-config-network
  2. In “Select Action” screen, select “Edit a device params” and press Enter to enter “Select A Device” screen.
  3. Select “eth0” and press Enter to enter “Network Configuration” screen. Press OK to commit changes.
  4. Select “eth1” and press Enter to enter “Network Configuration” screen. Press OK to commit changes and return to “Select A Device” screen.
  5. Press “Save” button to commit changes and return to “Select Action” screen.
  6. Press “Save&Quit” button to commit changes.
  7. Use “vi” editor to set both devices “ONBOOT” to “Yes”:
  8. $ cat /etc/sysconfig/networking/profiles/default/ifcfg-eth0
    DEVICE=eth0
    HWADDR=00:0c:29:2d:96:b6
    ONBOOT=yes
    BOOTPROTO=dhcp
    TYPE=Ethernet
    USERCTL=no
    PEERDNS=yes
    IPV6INIT=no


    $ cat /etc/sysconfig/networking/profiles/default/ifcfg-eth1
    DEVICE=eth1
    HWADDR=00:0c:29:2d:96:c0
    ONBOOT=yes
    BOOTPROTO=dhcp
    TYPE=Ethernet
    USERCTL=no
    PEERDNS=yes
    IPV6INIT=no

  9. Configure Network Service to make it get started after machine boot

    $ chkconfig –-list network
    network         0:off   1:off   2:off   3:off   4:off   5:off   6:off


    $ chkconfig network on

    $ chkconfig –-list network
    network         0:off   1:off   2:on    3:on    4:on    5:on    6:off

  10. Restart network service

  11. $ service network restart

  12. You may then use ifconfig to check both eth0 and eth1 device is up

    10 
  13. Change “net.ipv4.ip_forward” to 1 in file “/etc/sysctl.conf” to enable port forwarding:
  14. $ cat /etc/sysctl.conf
    # Kernel sysctl configuration file for Red Hat Linux
    #
    # For binary values, 0 is disabled, 1 is enabled.  See sysctl(8) and
    # sysctl.conf(5) for more details.

    # Controls IP packet forwarding
    net.ipv4.ip_forward = 1

    # Controls source route verification
    net.ipv4.conf.default.rp_filter = 1

  15. Run the following command to disable Firewall to reduce unnecessary troubles in later stage:

    $ chkconfig iptables off
  16. Restart the machine and double to make sure the following are working
    1. Both etc0 and et1 is up with assigned IP Address
    2. port forwarding is working

10

Troubleshoot: I encounter “Device eth0 does not seem to be present, delaying initialization.” after I clone the virtual machine

From time to time, you may encounter a situation where both the eth0 or eth1 doesn’t up.  Run the following command explicitly may yields:

11

The cause of this problem could be the device has been changed or swap.  The new device names for the network adapter could be eth2 or eth3.  A quick solution to this problem is:

$ rm /etc/udev/rules.d/70-persistent-net.rules
$ reboot

After restart the machine, the device names would restored back to eth0 and eth1.

Troubleshoot: I encounter “Device eth0 has different MAC address than expected, ignoring.” after I clone the virtual machine

This is most probably the HWAddr (Mac Address) of both network adapter (/etc/sysconfig/network-scripts/ifcfg-ethN) doesn’t match with VMWare  virtual machine configuration file (.vmx)

To solve the problem, remove a line “HWADDR” in

  1. /etc/sysconfig/network-scripts/ifcfg-eth0
  2. /etc/sysconfig/network-scripts/ifcfg-eth1

For example,

$ cat /etc/sysconfig/network-scripts/ifcfg-eth0
DEVICE=eth0
HWADDR=00:0c:29:2d:96:b6
ONBOOT=yes
BOOTPROTO=dhcp
TYPE=Ethernet
USERCTL=no
PEERDNS=yes
IPV6INIT=no

Restart network service start network devices:

$ service network restart

Quick Test Connection

You may now use ping command to test if both the VMNet 1 and VMNet 2 network are working in your own host PC (not Fedora in VMWare):

C:\>ping 192.168.101.128

Pinging 192.168.101.128 with 32 bytes of data:
Reply from 192.168.101.128: bytes=32 time<1ms TTL=64
Reply from 192.168.101.128: bytes=32 time<1ms TTL=64
Reply from 192.168.101.128: bytes=32 time<1ms TTL=64
Reply from 192.168.101.128: bytes=32 time<1ms TTL=64

Ping statistics for 192.168.101.128:
    Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
    Minimum = 0ms, Maximum = 0ms, Average = 0ms

C:\>ping 192.168.102.128

Pinging 192.168.102.128 with 32 bytes of data:
Reply from 192.168.102.128: bytes=32 time<1ms TTL=64
Reply from 192.168.102.128: bytes=32 time<1ms TTL=64
Reply from 192.168.102.128: bytes=32 time<1ms TTL=64
Reply from 192.168.102.128: bytes=32 time<1ms TTL=64

Ping statistics for 192.168.102.128:
    Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
    Minimum = 0ms, Maximum = 0ms, Average = 0ms

This test only ensure you may reach both networks from your host but it doesn’t confirm if you may reach VMNet1 from VMNet2 and vice versa.

Advance Test Connection

In your host PC, there should be 2 virtual network adapters (VMNet1 and VMNet2) configured:

C:\>ipconfig

Windows IP Configuration

Ethernet adapter VMware Network Adapter VMnet1:

   Connection-specific DNS Suffix  . :
   Link-local IPv6 Address . . . . . : fe80::f935:c046:af36:32c4%17
   IPv4 Address. . . . . . . . . . . : 192.168.101.1
   Subnet Mask . . . . . . . . . . . : 255.255.255.0
   Default Gateway . . . . . . . . . : 

Ethernet adapter VMware Network Adapter VMnet2:

   Connection-specific DNS Suffix  . :
   Link-local IPv6 Address . . . . . : fe80::d5a7:81c4:e2c9:de44%19
   IPv4 Address. . . . . . . . . . . : 192.168.102.1
   Subnet Mask . . . . . . . . . . . : 255.255.255.0
   Default Gateway . . . . . . . . . :

To make sure both network may reach each other, set the default gateway for both VMNet1 and VMNet2 connection to the Fedora VMware machine’s network adapter.  Please note that the Fedora machine’s network adapters obtained IP addresses via DHCP dynamically.  You may use ifconfig to check the IP addresses:

$ ifconfig eth0

$ ifconfig eth1

Once you get the IP addresses, set it to VMNet1 and VMNet2 adapter of your host PC respectively:

C:\>ipconfig

Windows IP Configuration

Ethernet adapter VMware Network Adapter VMnet1:

   Connection-specific DNS Suffix  . :
   Link-local IPv6 Address . . . . . : fe80::f935:c046:af36:32c4%17
   IPv4 Address. . . . . . . . . . . : 192.168.101.1
   Subnet Mask . . . . . . . . . . . : 255.255.255.0
   Default Gateway . . . . . . . . . : 192.168.101.128

Ethernet adapter VMware Network Adapter VMnet2:

   Connection-specific DNS Suffix  . :
   Link-local IPv6 Address . . . . . : fe80::d5a7:81c4:e2c9:de44%19
   IPv4 Address. . . . . . . . . . . : 192.168.102.1
   Subnet Mask . . . . . . . . . . . : 255.255.255.0
   Default Gateway . . . . . . . . . : 192.168.102.128

You may need to check if your Firewall allow ICMP Echo Request connection.  In Windows Vista or Windows 7, Start “Windows Firewall and with Advanced Security” in privilege account and enable a rule “File and Printer Sharing (Echo Request – ICMPv4-In)

12

You may use the following ping command to test if both networks may reach others:

C:\>ping -S 192.168.102.1 192.168.101.1

Pinging 192.168.101.1 from 192.168.102.1 with 32 bytes of data:
Reply from 192.168.101.1: bytes=32 time<1ms TTL=127
Reply from 192.168.101.1: bytes=32 time<1ms TTL=127
Reply from 192.168.101.1: bytes=32 time<1ms TTL=127
Reply from 192.168.101.1: bytes=32 time<1ms TTL=127

Ping statistics for 192.168.101.1:
    Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
    Minimum = 0ms, Maximum = 0ms, Average = 0ms

C:\>ping -S 192.168.101.1 192.168.102.1

Pinging 192.168.102.1 from 192.168.101.1 with 32 bytes of data:
Reply from 192.168.102.1: bytes=32 time<1ms TTL=127
Reply from 192.168.102.1: bytes=32 time<1ms TTL=127
Reply from 192.168.102.1: bytes=32 time<1ms TTL=127
Reply from 192.168.102.1: bytes=32 time<1ms TTL=127

Ping statistics for 192.168.102.1:
    Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
    Minimum = 0ms, Maximum = 0ms, Average = 0ms

Now it shows that both 192.168.101.0 and 192.168.102.0 networks may reach others.  We may proceed to traffic control.

Setup TC to simulate broadband connection

Example 1: Delay

# add the delay to device
$ tc qdisc add dev eth0 root netem delay 25ms 10ms

# change the delay of device
$ tc qdisc change dev eth0 root netem delay 35ms 10ms

# show the information of device
$ tc qdisc show dev eth0

# delete the device traffic control
$ tc qdisc delete dev eth0 root

Example 2: Rate 

# add rate control to eth1 
$ tc qdisc add dev eth1 root tbf rate 384kbit buffer 1600 limit 3000

# change the delay of device
$ tc qdisc change dev eth1 root tbf rate 5kbit buffer 1600 limit 3000

# show the information of device
$ tc qdisc show dev eth1

# delete the device traffic control
$ tc qdisc delete dev eth1 root

Reference:

  1. http://www.linuxfoundation.org/en/Net:Netem

Friday, October 02, 2009

Configure Windows 7 IIS7 for ISAPI DLL

Windows 7 IIS7 require some configurations to get ISAPI DLL works.  It is not that straight forward compare to IIS 5.

Install IIS 7

  1. Go to Control Panel | Programs and Features | Turn on Windows features on or off (require privilege mode).
  2. Check “Internet Information Services and make sure “ISAPI Extensions” and “ISAPI Filters” is checked as well.
  3. Click OK button to start installation.

1

After finish install IIS 7, open your favorite web browser and enter URL http://localhost/ to make sure the IIS is working and running.  You might need to check your firewall setting and add exception for port 80 TCP traffic if necessary.

Configure for ISAPI DLL

Add Virtual Directory

First, you may need to add a virtual directory to host your ISAPI DLL:

  1. Open Internet Information Service Manager (require privilege mode)
  2. Right click on “Default Web Site” node and click “Add Virtual Directory” of popup menu:

2

Enter “Alias” and “Physical Path” of the virtual directory:

3

Enable ISAPI for Virtual Directory

To enable ISAPI for the virtual directory:

  1. Select the virtual directory node (e.g.: “ISAPI” in this example). 
  2. Double click the “Handler Mappings” icon. 
  3. Click “Edit Feature Permissions…” in Actions panel
  4. A “Edit Feature Permission” dialog prompt out
  5. Check “Execute”.
  6. Click OK button to commit the changes.

4

Enable Directory Browsing for Virtual Directory

This is optional but is convenient.  To enable Directory Browsing for a virtual directory:

  1. Select the virtual directory node (e.g.: “ISAPI” in this example). 
  2. Double click the “Directory Browsing” icon.
  3. Click “Enable” in Actions panel.

5

Edit Anonymous Authentication Credentials

  1. Select the virtual directory node.
  2. Double click the “Authentication” icon.
  3. Click to select “Anonymous Authentication” item.
  4. Click “Edit…” in Actions panel.
  5. A dialog will prompt out.
  6. Checked “Application pool identity” and press OK button to commit changes.

1

Enable ISAPI modules

  1. Click on the root node.
  2. Double click the “ISAPI and CGI Restrictions” icon.
  3. Click ”Edit Feature Setting …” in Actions panel.
  4. Check “Allow unspecified ISAPI modules” option.  This option allow any ISAPI dll to be executed under IIS.  If you don’t use this option, you will need to specify a list of ISAPI DLLs explicitly.

6

Edit Permission for Virtual Directory

  1. Select the virtual directory node (e.g.: “ISAPI” in this example). 
  2. Right click on the node and click “Edit Permission” of popup menu.
  3. A Properties dialog prompt out.
  4. Switch to “Security” page
  5. Click Edit button to show Permission dialog.
  6. Add “IIS_IUSRS” into the permission list.

7

Enable 32 bits ISAPI DLL on IIS 7 x64

This is only require if you are using IIS7 x64 and would like to run 32 bits ISAPI DLL on the IIS.  If your ISAPI DLL and IIS7 is both x86 or both x64, you may skip this step.

  1. Click “Application Pools” node.
  2. Click “DefaultAppPool” item
  3. Click “Advanced Settings …” from Actions panel.
  4. A “Advanced Settings” dialog prompt out
  5. Set “Enable 32-bits Applications” to True
  6. Click OK button to commit changes

8

If you didn’t enable this options for 32 bits applications, you may encounter the following errors when execute the ISAPI from web browser:

HTTP Error 500.0 - Internal Server Error

The page cannot be displayed because an internal server error has occurred.

HTTP Error 500.0 - Internal Server Error
Module    IsapiModule
Notification    ExecuteRequestHandler
Handler    ISAPI-dll
Error Code    0x800700c1
Requested URL   
http://localhost:80/isapi/isapi.dll
Physical Path    C:\isapi\isapi.dll
Logon Method    Anonymous
Logon User    Anonymous
 

You may now deploy your ISAPI DLLs into the virtual directory and start execute the library from web browser.

DataSnap and ISAPI DLL

You may create Delphi DataSnap ISAPP DLL library and deploy on IIS.  From time to time, you may encounter compilation error during development or deployment time if you have consume the ISAPI DLL.  This is because the ISAPI DLL invoked will cache in the application pool.  You are not allow to overwrite the ISAPI DLL while it’s being cached.

To overcome this problem, you need to perform Recycle operation:

  1. Click “Application Pools” node.
  2. Right click on “DefaultAppPool” item and click “Recycle…” item.

Capture

Deploying as ISAPI DLL is encourage during deployment stage as IIS will cache the ISAPI DLL for performance consideration.

However, the caching might not feasible during development stage as recycling need to be performed while overwrite the ISAPI DLL either by frequent compiling or overwriting.  You may consider compile the server modules as CGI application in development time.  Each invocation of CGI is a separate OS process and won’t be cache by IIS application pool.

Install CGI on IIS

  1. Go to Control Panel | Programs and Features | Turn on Windows features on or off (require privilege mode).
  2. Check “Internet Information Services and make sure “CGI” is checked.
  3. Click OK button to start installation.

2

Enable CGI Module

  1. Click on the root node.
  2. Double click the “ISAPI and CGI Restrictions” icon.
  3. Click ”Edit Feature Setting …” in Actions panel.
  4. Check “Allow unspecified CGI modules” option.

3

Consume DataSnap Server Methods via URL

The DataSnap server methods are using JSON as data stream via REST protocol.  For example, a simple EchoString server method defined as:

type
  {$MethodInfo On}
  TMyServerMethod = class(TPersistent)
  public
    function EchoString(Value: string): string;
  end;
  {$MethodInfo Off}

implementation

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

To access this method compiled in ISAPI DLL via URL, the URL is something like

http://localhost/datasnap/MyISAPI.DLL/datasnap/rest/TMyServerMethod/EchoString/Hello

and the response text will be:

{"result":["Hello"]}

Likewise, a CGI URL is

http://localhost/datasnap/MyCGI.exe/datasnap/rest/TMyServerMethod/EchoString/Hello

 

Reference:

  1. DataSnap 2010 HTTP support with an ISAPI dll; Author: Tierney, Jim

Saturday, August 29, 2009

Input Director: Control machines with one set Keyboard and Mouse

In previous article (Title: Synergy: Control machines with one set Keyboard and Mouse), I have introduced a similar solution.  I found Input Director has much better performance and functionalities compare to Synergy.  Input Director also is under active development while Synergy seems cease operation since year 2006.

Installation

In summary, Input Director on the slave system needs to be configured to:

  • Authorise the nominated master system to control (send input) to it
  • Be enabled as a slave

Input Director on the master system needs to be configured to:

  • Add the slave and set an (optional) hotkey for it
  • Configure the position of the slave monitor.
  • Be enabled as the master

Once done, you can immediately see Input Director is working.  For more information, refer to Input Director installation guide.

Nice Features

  1. Ripples surround the cursor for a few seconds after transitioning to help the eye follow the cursor from one computer to another.

    features_ripples
  2. Can also set a key (or keys) that must be held down to permit transitions between systems.
  3. Shared Clipboard - copy and paste between computers (including files!).  I never try this yet.

Reference:

  1. Input Director. url: http://www.inputdirector.com/

Tuesday, August 04, 2009

dbExpress Driver for Firebird

dbExpress is data driver architecture developed by Embarcadero
Delphi / C++ Builder (Formerly known as Borland or CodeGear Delphi / C+
+ Builder).

Firebird is a relational database offering many ANSI SQL standard
features that runs on Linux, Windows, and a variety of Unix platforms.

dbExpress Driver for Firebird is a high quality driver providing
optimum performance for Firebird service data accessing operation.

dbExpress support both Delphi 2007 and Delphi 2009.

URL: http://sites.google.com/site/dbxfirebird/

Monday, July 27, 2009

Delphi 2007: Internal Error URW5517

I encounter the following error while trying to compile my application in Delphi 2007 (11.0.2902.10471):

F2084 Internal Error: URW5517

After a thorough testing, I found out it should be a Delphi compiler error in the version of Delphi 2007.

To replay the problem, create a new console project in Delphi 2007 and add the following unit into project:

unit Test_URW5517;

interface

implementation

procedure Test(const aObject: TObject);
var E: procedure(const aObject: TObject = nil);
begin

end;

end.

Then compile the project by pressing Ctrl-F9 for few times and you will encounter the error eventually.  If you build the project (Shift-F9) instead of compile, no error will be prompted.

In above unit, the Test procedure has a procedural variable E with the following declaration:

var E: procedure(const aObject: TObject = nil);

The procedure has a default parameter aObject.  This is the cause of the URW5517 internal error.

To avoid the internal error, try not to use default parameter in procedural variable declaration:

var E: procedure(const aObject: TObject);

This problem should have reported in Quality Central (QC#58068: Internal error in procedural type declaration with default parameter).  However, the resolution was “can’t reproduce” or it resolve in Delphi 2009.  There is no patch for Delphi 2007 to resolve this problem.

Thursday, July 23, 2009

Using Delphi 2007/2009 IDE in Windows x64

I use Windows 7 x64 for daily Delphi development task.  If I press F9 to run my application in IDE mode, there is a great chance to hit this error:

‘Assertion failure: “(!”SetThreadContext failed”)” in '..\win32src\thread32.cpp at line 412
Continue execution?

Capture

If Press No or ESC key will close the whole IDE.  Pressing Yes may prompt for the same dialog more.

There is a temporary solution for this problem.  Please note that this problem might gone in Windows 7 RTM release.

Delphi 2007

  1. Using any hex editor to open %ProgramFiles(x86)%\CodeGear\RAD Studio\5.0\bin\bordbk105N.dll
  2. The file version should be 105.11.1.12533
  3. Look for hex string in the file
    01 00 48 74 47 80 3d change to 
    01 00 48 EB 47 80 3d

Delphi 2009

  1. Using any hex editor to open %ProgramFiles(x86)%\CodeGear\RAD Studio\6.0\bin\bordbk120N.dll
  2. The file version should be 120.903.17.15115
  3. Look for hex string in the file
    01 00 48 74 47 80 3d
    change to 
    01 00 48 EB 47 80 3d

Reference:

  1. Temporary Solution: Delphi 2009 and Windows 7

Sunday, June 21, 2009

Unattended Windows XP installation

Unattended Windows XP installation will speed up the installation process without answering dialogs prompted by Windows XP installation service.  It save times waiting for the dialog prompt.  After the installation finish, you have a ready Windows XP instance.  You may walk away when installing.  All dialog prompts’ answer are provided by a unattended answer file.

The answer file in .ini text file.  However, there is a tool to help you generate the answer file:

  1. Extract setupmgr.exe from Windows XP installation CD in folder \support\tools\deploy.cab
  2. Run setupmgr.exe and follow the screen instruction to generate the answer file.

Once the answer file is generated, you may install Windows XP via command line installation:

d:\> i386\winnt32.exe /syspart:c: /makelocalsource /unattend:answer.txt

Saturday, June 20, 2009

Recover damage Windows Vista or Windows 7 installation

In most case, normal Windows users seldom face unbootable windows installation as most user usually install windows in a single partition utilize all available hard drive spaces.

For user who play with multi boot loader like GRUB or partition the hard drive into few and install more than one Windows in each partition, they may face the problem if mistake make during installation or configuration.

Some common errors are:

  1. When booting a windows instance, system prompt:

    BOOTMGR is missing
    Press Ctrl+Alt+Del to restart
  2. When booting a windows instance, system prompt:

    File: \Boot\BCD
    Status: 0xc0000034
    Info: The Windows Boot Configuration Data file is missing required information

  3. When booting a windows, the window start-up screen shown but never complete the booting

The cause of the problems might be:

  1. File BOOTMGR does not exist in root drive (e.g: C:\)
  2. \Boot\BCD is corrupted or missing due to improper configuration during multi boot setup.
  3. MBR has corrupted or invalid.

The most common solution is using Windows Recovery Environment to solve the problem.

Solution 1: Using Windows Recovery Environment with Windows DVD

  1. Put the Windows installation disc in the disc drive, and then start the computer.
  2. Press a key when you are prompted: “Press any key to boot from CD or DVD…”.  Windows installation service will start booting
  3. Select a language, a time, a currency, and a keyboard or another input method, and then click Next.
  4. Click Repair your computer.

    t1
  5. Click the operating system that you want to repair, and then click Next.
  6. In the System Recovery Options dialog box, click Command Prompt

    t2 
  7. In command prompt, type:

    X:\Sources> bootrec /fixmbr
    X:\Sources> bootrec /fixboot
    X:\Sources> bootrec /rebuildbcd

  8. Remove the Windows Installation disc and Restart the computer.
  9. Cross your finger and hope your Windows instance is back to normal.

Solution 2: Using Windows Recovery Environment in PXE environment

  1. Boot your computer into PXE service
  2. use “NET USE” to mount correct version of Windows installation share same as your damage Windows instance:

    e.g: net use k: \\server\windows.vista.share\
  3. Prepare to re-start your computer as Windows installation service
    1. copy k:\bootmgr c:\
    2. xcopy /s /e k:\boot c:\
    3. mkdir c:\sources
    4. copy k:\sources\boot.wim c:\sources
    5. k:\boot\bootsect.exe /nt60 c:
  4. Restart your computer
  5. Refer to step 3 of Solution 1 to continue the Windows Recovery Environment.
  6. If you able to recover the windows instance, you may do the following clean up task:
    1. Delete sources folder
    2. Start CMD in administrator mode and type

      attrib +s +h +r c:\bootmgr
      attrib +s +h –a c:\boot

Reference:

  1. Microsoft knowledgebase: Error message when you start Windows Vista: "The Windows Boot Configuration Data file is missing required information"; URL: http://support.microsoft.com/kb/927391
  2. How to Install Windows 7 or Windows Vista on Physical Machine Without DVD Media; URL: http://keznews.com/5185_How_to_Install_Windows_7_or_Windows_Vista_on_Physical_Machine_Without_DVD_Media

Friday, June 12, 2009

Synergy: Control machines with one set Keyboard and Mouse

Note: Please refer to Input Director for better solution.

If you have more than one machine (PC or notebook) on your desktop, you may use new set of keyboard and mouse for each machine on your desktop.  The Synergy application is here to help you reduce the devices on your desk by using just one set of keyboard and mouse to control all machines.  Synergy works on Windows XP, Windows Vista, Windows 7 or even Linux and Mac.

Example

This example show how to install and configure Synergy for 3 machines name as:

  1. Streamyx (OS: Windows XP)
  2. Optimus (OS: Windows Vista)
  3. Windows7 (OS: Windows 7)

Each machine attached with a LCD monitor individually.  The sequence of machines from Left to Right on desk are Streamyx, Optimus and Windows7.
First, install synergy on these 3 machines.  My keyboard and mouse attached to machine Optimus.  Thus, Optimus will be server.

Server Configuration: Optimus

Start synergy and click "Share this computer's keyboard and mouse (server)".

1

Click “Advance” button to set server’s parameters:

3

Press “Configure” button to configure:

  1. Add three machines’ screen name to “Screens” list box.
  2. A link is a pair of distinct adjacent screen.  Each adjacent pair must configure as 2 links.  For example, “Streamyx is left of Optimus” and “Optimus is right of Streamyx”

2

Click “Test” or “Start” button start synergy service.

Client Configuration: Streamyx and Windows7

Synergy configuration on client machines are simple:

  1. Click “Use another computer’s shared keyboard and mouse (client)”
  2. Enter the server host name (optimus)
  3. Click “Test” or “Start” button to establish connection

4

You may now enjoy using one set of mouse and keyboard to control three machines.

References:

  1. http://synergy2.sourceforge.net/

Friday, June 05, 2009

Firebird: Deal with Unavailable Database

“unavailble database” error prompt connecting to Firebird service is annoying and difficult to trace.  Sometime it work and sometime it doesn’t.  We don’t know when it work and when it doesn’t work.

“unavailable database” happen in TCP/IP connection

Use TCP/IP connection string to connect to a Firebird database is easy to trace.  The connection string looks like

  • 192.168.5.1:/data/db/test.fdb
  • localhost:/data/db/test.fdb

Please note that localhost is not using local protocol connection but it is a TCP/IP connection using loopback address.

If you encounter “unavailable database” error for TCP/IP connection, please make sure:

  1. You network connection on both end are working and the TCP/IP configuration is correct.  Try to run “PING” to the Firebird server will confirm the connection is working.
  2. Make sure Firebird Service is running
  3. Make sure Firewall doesn’t block Firebird listening port.  Default port number is 3050
  4. Make sure the connection string point to valid Firebird database file

Once you got above verified, the “unavailable database” error prompt should gone.

“unavailable database” happen in Local Protocol

The data transmission throughput for local protocol is few times better than TCP/IP connection.  However, it is easy to encounter “unavailable database” error while using Local Protocol when Firebird is running as service.  The connection string is simple:

  • c:\db\test1.fdb
  • c:\db\test2.fdb

The connection string is the database file path itself without any prefix.

Using Local Protocol with Firebird 1.5

  Run as Application Run as Service
Windows XP Yes Yes. Only work for first logon user
Windows Vista Yes No

Using Local Protocol with Firebird 2.X

  Run as Application Run as Service
Windows XP Yes Yes.  Work for any number of logon users
Windows Vista Yes No

The above tables explains why local protocol sometime work and sometime doesn’t work.

Friday, May 22, 2009

Apache HTTP Server: Configuration

Apache HTTP Server is a famous open source web server.  The default configuration file is located at “/etc/httpd/conf/httpd.conf”

Server Side Includes (SSI)

SSI (Server Side Includes) are directives that are placed in HTML pages, and evaluated on the server while the pages are being served. They let you add dynamically generated content to an existing HTML page, without having to serve the entire page via a CGI program, or other dynamic technology.

To enable SSI in Apache HTTP, add the following in conf file:

AddType text/html .shtml
AddHandler server-parsed .shtml

<Directory /data/www>
    Options +Includes
</Directory>

If the index file is a SSI file, you should add this line too:

DirectoryIndex index.html index.html.var index.shtml

Virtual Host Configuration

The term Virtual Host refers to the practice of maintaining more than one server on one machine, as differentiated by their apparent hostname. For example, it is often desirable for companies sharing a web server to have their own domains, with web servers accessible as www.company1.com and www.company2.com, without requiring the user to know any extra path information.

In order not to mess up the httpd.conf file, we may store the virtual host configuration in a separate file. (eg: /etc/httpd/conf/httpd-vhosts.conf)

/etc/httpd/conf/httpd-vhosts.conf

NameVirtualHost *:80

<Directory /data/www>
    Options +Includes
</Directory>

<VirtualHost *:80>
    DocumentRoot /data/www
    ServerName
www.company1.com
    ScriptAlias /cgi-bin/ /data/www/cgi-bin/ 
</VirtualHost>

We then use Include directive in httpd.conf by adding a line near the VirtualHost section:

Include conf/httpd-vhosts.conf

Restart the Apache HTTP Server

Remember to restart the Apache HTTP Server for changes you did:

# service httpd restart

Wednesday, April 15, 2009

Linux: Install a virtual PDF printer on CUPS

I have been always looking for a virtual PDF printer solution.  The only solution I know is windows PDF printer driver on Internet that need to pay.  I never know CUPS can do the job and it is free.

Service Installation

# yum install cups-pdf

After finish install, you may check if the PDF printer is installed and configured from CUPS web interface (e.g: http://cups-printer:631/)

You may configure where to keep PDF output in /etc/cups/cups-pdf.conf.  Look for “Out ${HOME}/Desktop” string in the file.

Windows Client Installation

  1. Make sure you have or know where is the printer driver files
  2. Click Start | Control Panel | Printers and Faxes
  3. Click Add a Printer
  4. Choose "A network printer, or a printer attached to another computer"
  5. Choose "Connect to a printer on the Internet or on a home or office network".  Type the printer URL (e.g: http://cups-printer:631/printers/cups-printer).
  6. Click Next and select appropriate PostScript (PS) printer driver (e.g: HP Color LaserJet 9500 PS)

Enjoy a new virtual PDF printer on your network.  You may start print document from windows workstation to this printer and collect the PDF output files from your Linux home account.

Reference:

  1. CUPS – PDF Printer

Wednesday, April 08, 2009

Delphi: Using TWebBrowser to display content from stream rather than URL

TWebBrowser is a wrapper for Internet Explorer COM interface.  The most common way to use TWebBrowser is using Navigate method that accept a URL string.  However, this is not convenient for user who already has a rendered HTML string or stream.  One workaround solution for this is save the string or stream to a temporary file and use supply a local file URL to TWebBrowser.Navigate.

Here is a solution for supplying a HTML stream to TWebBrowser:

uses ActiveX;

const IID_IPersistStreamInit : TGUID = '{7FD52380-4E07-101B-AE2D-08002B2EC713}';

procedure TForm19.Button1Click(Sender: TObject);
var S: TStream;
    o: IPersistStreamInit;
begin
  S := TStringStream.Create('<html><body>this is a test</body></html>');
  try
    WebBrowser1.Navigate('about:blank');
    while WebBrowser1.ReadyState <> Shdocvw.READYSTATE_COMPLETE do
      Application.ProcessMessages;

    if Supports(WebBrowser1.Document, IID_IPersistStreamInit, o) then
      o.Load(TStreamAdapter.Create(S));
  finally
    S.Free;
  end;
end;

Reference:

  1. Component to display HTML read from a stream rather than fetched from a URL. Author: Antonio Estevez. URL: https://forums.codegear.com/thread.jspa?threadID=15004&tstart=0

Friday, March 27, 2009

BITs operation in SQL

Introduction

The BIT we talk here is the smallest unit in binary: 0 and 1.

Using bit for attributes

If we want to assign a set of attributes to an item, a common practice is using mutually exclusive bit value to indicate each attribute and using “SUM” or “OR” operator to group assigned attributes as a single integer value.  This solution is neat and fast to be implement in programming.

For example, a file in a file system may have attributes of

  • READ
  • WRITE
  • EXECUTE

We may assign READ, READ and WRITE , READ and EXECUTE, or any combination of attributes to the file.  We may use only a single 32 bits (or 64 bits) integer to indicate these attributes.  This design allow us to expand the attribute set in future.  This is a more practical approach compare to using one field for each attribute design.

In the above example, we may define

  • 1 (0001) – READ
  • 2 (0010) – WRITE
  • 4 (0100) – EXECUTE
  • 8 (1000) - HIDDEN

Note that each attribute should occupy a bit position and it shouldn’t overlap with other attributes else we will not able to identify their actual attributes in later stage.

Thus,

  • 5 (0101) indicate READ + EXECUTE
  • 2 (0010) indicate WRITE
  • 7 (0111) indicate READ + WRITE + EXECUTE
  • 6 (0110) indicate EXECUTE + WRITE
  • 0 (0000) indicate No attributes defined

Using bits for states

In some situation we need a range of bits to indicate some kind of states.  The one bit one attribute design doesn’t fit well here.

For example, a document may have one of the following states but never possess more than one state at a time:

  • Draft
  • Waiting for approval
  • Approved
  • Rejected
  • Canceled

There are 5 states above, we may use 4 bits (3 bits is sufficient for above case) to present the state as

  • 1 (0001) – Draft
  • 2 (0010) – Waiting for approval
  • 3 (0011) – Approved
  • 4 (0100) – Rejected
  • 5 (0101) - Canceled

Programming attributes in programming language

We may use only a single variable of type INTEGER to indicate an attribute sets.  This design allow the attribute sets to expand in future without changing much in old codes.

Define attributes and states

Assume bit 0-3 (4 bits) is reserved for states and bit 4-7 (4 bits) is reserved for attributes.

const
  at_READ    = $10;
  at_WRITE   = $20;
  at_EXECUTE = $40;
  at_HIDDEN  = $80;

  st_Draft    = 1;
  st_Waiting  = 2;
  st_Approved = 3;
  st_Rejected = 4;
  st_Canceled = 5;

Set attributes

Use bit operator OR to set attribute:

  • B := at_READ OR at_WRITE;  // assign READ and WRITE
  • B := B OR at_READ;  // assign READ to B regardless of B READ attribute or not

Unset attribute

Use bit operator AND NOT to unset attribute:

  • B := B AND (NOT at_READ) // unset READ of B
  • B := B AND (NOT at_READ) AND (NOT at_WRITE) // unset READ and WRITE of B

Test an attribute is set

Use AND operator to check if READ attribute is set:

  • (B and at_READ) = at_READ

Test state

Bits 0-3 is reserve for states, to test if the item is canceled:

  • B and $0F = st_Canceled

Set State

Set state to st_Rejected (4):

  • B := B and $F0 + st_Rejected

Programming attributes in SQL

It wasn’t easy to perform the above operations in SQL unless the SQL service support bit operator or function.  The following solution is using normal mathematical operation to achieve the same bit operation as programming language without using special function.

Division operator in SQL

The division operator ( / ) in SQL has different behavior for integer and floating point division.  For example:

  • Integer division:
    • 1 / 2 is 0
    • 2 / 2 is 1
  • Floating point division:
    • 1 / 2.0 is 0.5
    • 2 / 2.0 is 1.0

An integer value divided by 2 is similar as performing a right shift operation for binary value.  For example,

  • 10 / 2 = 5
  • 9 / 2 = 4

Perform a right shift in binary for above numbers:

  • 1010 shr 1 = 101 (5 in decimal)
  • 1001 shr 1 = 100 (4 in decimal)

Likewise,

  • perform 2 right shifts is dividing the value by 4
  • perform 3 right shifts is dividing the value by 8
  • perform n right shifts is dividing the value by 2^n

Multiplication operator in SQL

The multiplication operator ( * ) for integer works similarly as performing a left shift operation for binary value.  For example,

  • 5 * 2 = 10
  • 7 * 2 = 14

Perform a left shift in binary for above numbers:

  • 101 shl 1 = 1010 (10 in decimal)
  • 111 shl 1 = 1110 (14 in decimal)

Likewise,

  • perform 2 left shifts is multiplying the value by 4
  • perform 3 left shifts is multiplying the value by 8
  • perform n left shifts is multiplying the value by 2^n

Test the value of least significant bit

The least significant bit of a number is the right most bit in binary presentation of the number.  For example, the least significant bit of 10 (1010 in binary) is 0 and 9 (1001 in binary) is 1.

We may use both integer or floating point division to check the least significant bit of a number:

i = (n / 2) – (n / 2.0)

i = 0 indicate least significant bit is off
i <> 0 indicate least significant bit is on

For example,

  • 10 / 2 – 10 / 2.0 = 5 – 5.0 = 0 (least significant bit is 0)
  • 9 / 2 – 9 / 2.0 = 4 – 4.5 = –0.5 (least significant bit is 1)

Test an attribute is set

To check if an at_EXECUTE ($40) attribute is set in SQL, we may use

WHERE ((Attribute / 64 / 2) - (Attribute / 64 / 2.0) <> 0

Set attribute

The following SQL set at_EXECUTE ($40) for rows that don’t have at_EXECUTE set:

UPDATE Table
   SET Attribute = Attribute + 64
WHERE ((Attribute / 64 / 2) - (Attribute / 64 / 2.0) = 0

Unset attribute

The following SQL unset at_EXECUTE ($40) for rows that have at_EXECUTE set:

UPDATE Table
   SET Attribute = Attribute – 64
WHERE ((Attribute / 64 / 2) - (Attribute / 64 / 2.0) <> 0

Test States

The following SQL retrieve all rows that has state of st_Canceled (5):

SELECT *
  FROM Table
WHERE Attribute – (Attribute / 16) * 16 = 5

Set States

The following SQL set all rows to state of st_Canceled (5):

UPDATE Table
   SET Attribute = (Attribute / 16) * 16 + 5

Wednesday, March 11, 2009

Using VNC to connect to X-Windows display

Introduction

Before VNC, we may use Xming or XWin32 to connect to a proper configured X-Windows Server.  This requires few X-Windows Server setup to get it done.

Since Fedora 7, there is a new service to connect to X Windows Server much easily.  This service is VNCServer.

Installation

Using yum to query and install for vnc-server rpm packages:

# yum list vnc-server
Installed Packages
vnc-server.i386                          4.1.2-23.fc8           installed

# yum install vnc-server.i386

VNC Server Configuration

  1. Define VNC server instances in /etc/sysconfig/vncservers:

    VNCSERVERS="1:myuser"
    VNCSERVERSARGS[1]="-geometry 1024x768 -nohttpd"

  2. Each VNC server instance listens on port 5900 plus the display number on which the server runs. In our case, myuser’s vnc server would listen on port 5901 (5900 + 1).
  3. You may setup multiple vnc instance as well:

    VNCSERVERS="1:myuser 2:user01 3:user02"
    VNCSERVERARGS[1]="-geometry 1024x768 -depth 16"
    VNCSERVERARGS[2]="-geometry 800x600 -depth 8"
    VNCSERVERARGS[3]="-geometry 1024x768 -depth 16"

  4. In this case, the vnc server would listen on port 5901, 5902 and 5903 respectively.
  5. In myuser's account, setup vnc password for the user:

    # vncpasswd
  6. Start VNC Server:

    # service vncserver restart
    # chkconfig vncserver on

  7. Define a new Firewall rule (/etc/sysconfig/iptables):

    -A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp --dport 5901 -j ACCEPT

More User Configuration

Once vncserver has startup for the first time, it will create a default xstartup config file.  You may define your own startup script:

# vi /home/myuser/.vnc/xstartup

#!/bin/sh

# Uncomment the following two lines for normal desktop:
unset SESSION_MANAGER
exec /etc/X11/xinit/xinitrc

[ -x /etc/vnc/xstartup ] && exec /etc/vnc/xstartup
[ -r $HOME/.Xresources ] && xrdb $HOME/.Xresources
# xsetroot -solid grey
# vncconfig -iconic &
# xterm -geometry 80x24+10+10 -ls -title "$VNCDESKTOP Desktop" &
# twm &

Connect to VNC Server

You may use any vesion of vncviewer to connect to the VNC Server.  For example, download TightVNC viewer for Windows.  The connection string is something like:

myhost:5901

Enter password and you may connect to your X windows service remotely.

Reference:

  1. Set up the VNC Server in Fedora