Kevin's Research Blog

ObjectPascal: Creating and Using RPC

Building on yesterday's HTML Fragment introduction post, this post will build onto that to add an RPC Server and Client. If you are following along, you should have the the HTML Fragments project ready to add onto it. However, before we can use an RPC Client in our HTML Fragment program, we'll first need to create one! This post will begin with creating a basic server-side RPC server for use in these examples. Let's begin!

Let's get Serving some RPC!

The RPC Server we'll be building today is a JSON-RPC. It will only take about 15 minutes to build, if you can even believe that! There really is nothing to it in Lazarus. The main requirement for this project is that you have the fpWeb package installed into your IDE, this can be found where Pas2JS packages were found, **Packages->Install/Uninstall Packages. Just locate *fpWeb, and rebuild your IDE.

If the package is successfully installed, you should see a new tab in your component palette aptly called fpWeb. Let's start a new project now! Go to Projects->New Project..., from here choose the project type of FastCGI Application.

There is one thing we need to configure in the main program file, this is the .LPR file. You can open it through your Project Inspector, and on a new project it should have the filename of fcgiproject1.lpr, go ahead and open that in the source editor. Uncomment the line for Application.Port:=2015, you can change this port if you'd like.

Our next task is to Data Module here a proper name, click into the new form-like window that appeared alongside the project, and in the left properties window, locate Name, and choose something. Once you have chosen a name, edit the module's source code, near the end there should be a link like this:

RegisterHTTPModule('TFPWebModule1', TNewName);

Change the string in quotes there to be api. This is the base URL for our Data Module where the RPC Server lives, and the URL we will be using throughout this example.

Let's Produce some RPC

Our next step is to begin populating our RPC Data Module with our components. The first component that we'll be needing is the TJSONRPCContentProducer, it can be located in the fpWeb tab. It's the icon with JSON RPC on it, and has three dashes on it. Click onto the component, then click into an empty space on your form.

The next component we'll need is the TJSONRPCDispatcher, which again is an icon with the text JSON RPC on it, but this icon has a down arrow on it. Click onto it, and then once again, click onto an empty area on your form to place it.

Now that we have our two main components for the actual RPC Server to work, let's glue it all together. First, go ahead and give both of these components a name. After you've named then, click onto the ContentProducer one, the first one we placed. In the properties, there should be one called Dispatcher, open it's dropdown, and you should see the other component you placed, the RPCDispatcher one. Select it.

Next, we'll need to create a new action and connect it to our Content Producer. Click onto an empty place in the form, and the properties window should now reflect the properties of the Data Module. From here, you should see a property called Actions, which should currently have 0. If you click onto the field, you should then notice a button appear in the far-right of the field with several dots on it, click it. This should open a new dialog box with no actions lists. Click on the Add button to create a new action. This should change the properties window to display the properties for this newly created action.

Set this newly created action's name to RPC, check the Default checkbox, and in the ContentProducer property, open the drop-down, and you should see your RPC Content Producer we added and configured earlier, select it.

Creating an RPC Handler

There we go, we now have a fully working JSON-RPC Server! That's all that is needed, not even a single line of code yet, besides updating two variables, a port, and a URL base path. Let's move onto creating a handler that we can actually call from our pas2js code.

Locate the TJSONRPCHandler component from the fpWeb tab, it should have the text JSON RPC, and the icon will have two arrows, one going left, and the other going right with some lines like an equal sign. Click onto the component, then click onto an empty place in your form.

Click onto your new component and be sure to select a proper Name for it, as this is what we will be using from our client-side code to actually call this method. For this first example, we are not going to be concerned with the rest of these properties, however, we do need to set up an Event, so go into that tab and locate OnExecute, click on the far-right button within the field to have the IDE generate the event handler in the code editor.

This will be a very simple Hello World RPC handler, nothing too special, and here is the full handler procedure code:

procedure TRPCModule.TestHandlerExecute(Sender: TObject;
  const Params: TJSONData; out Res: TJSONData);
begin
  Res:=TJSONObject.Create;
  TJSONObject(Res).Strings['hello']:='This is the TestHandler!';
end;

I named my handler for this example TestHandler, so the name of your procedure may differ here. You'll notice that the event expects a variable coming back from it, Res, as it is a TJSONData, it is compatible with the much easier to use TJSONObject, so our first line creates the new object. The second line of our event, sets a return string in a hello property, and gives it a value.

Okay, now that we have a example RPC Server to test out our client with, let's go back to our HTML Fragment example program from yesterday and add the client-side code for this.

Pas2JS RPC Client Code

Open the HTML Fragments projects from yesterday, and let's begin by adding a new component to our form. The component we are looking for is the TPas2jsRPCClient which is in the Pas2js tab, and on my installation it is located as the last one with the text RPC on it. Click onto this component, then click onto an empty space on your HTML Fragment Data Module form window. There's isn't much we need to configure for this component, click onto it, and in the Properties window, give it a Name, and then in the URL, enter in the same one we choose in our server, it should appear like /api/ in the edit box.

Before we can perform an actual RPC request, we'll first need to define several new event callbacks for it, one for Success and another for Failure. These are the event signatures below:

  private
    Procedure RPCSuccess(Sender: TObject; const aResult: JSValue);
    Procedure RPCFailed(Sender: TObject; const aError: TRPCError);

Add those to your HTML Fragment Data Module. We'll also need to add some new units to the uses clause at the top, you should add the following: JS, fpjson, fpjsonjs if they aren't already there. If you press Ctrl-Space while the cursor is after one of those newly created procedures, it should auto-create the procedures below in the Implementations section. Here is what you should type into them for this example:

procedure TTest1.RPCSuccess(Sender: TObject; const aResult: JSValue);
var
  json: TJSONObject;
begin
  json:=JSValueToJSONData(aResult) as TJSONObject;
  acttmsg.Value:='Your Data has been submitted, '+acttname.Value+'!';
  actttitle.Value:=json.Strings['hello'];
end;

procedure TTest1.RPCFailed(Sender: TObject; const aError: TRPCError);
begin
  acttmsg.Value:=aError.Message;
end;

As you can see, in the success callback, we use the hello property we set on the RPC Server. Next, let's update our button to actually call this code, here is an updated actT1BtnExecute:

procedure TTest1.actT1BtnExecute(Sender: TObject; Event: TJSEvent);
begin
  if acttname.Value = '' then
    acttmsg.Value:='Name Please!'
  else
  begin
    if actttitle.Value = '' then
      actttitle.Value:='Untitled';
    RPCClient.ExecuteRequest('', 'TestHandler', TJSArray.new, @RPCSuccess, @RPCFailed);
  end;
end;

This is where the actual RPC Request happens in your code, as you can see, we use the RPCClient component we created in the form, so do update RPCClient here with the same name you choose for that component. The first parameter is for the ClassName, and in this example, we do not use that. The next parameter is the Method we want to call in the RPC Server, as you can see, this is the handler at least I named TestHandler. Change this to whatever name you may have chosen for your handler. The next parameter is the parameters we wish to send to the server, in case, our example method doesn't expect any parameters, so we merely just pass it an empty array. The next two parameters are for the Success and Failure event callbacks.

That's all there really is to it, but wait! This isn't very useful if we cannot send data to server, so let's create a new handler on the RPC Server to demonstrate how we can create a handler that can accept parameters.

Sending RPC Handler Parameters

Go back to the RPC Server again, and add a new TJSONRPCHandler to the Data Module form. Again, set a name which we will be using on the client. However, this time, we'll actually be setting some additional properties, so don't go creating that OnExecute event just yet, and even if you did jump ahead, it's fine, but we'll be setting the properties first in this example.

In the Properties for the new Handler, expand the Options, and here you will find several interesting options. The ones we are interested in for this example are jroCheckParams and jroObjectParams. Check the boxes for those two options.

Next, we will need to create our actual parameters this RPC Handler is going to expect, click on the ParamDefs property, and a button on the far-right of the field with several dots should appear. Click onto it. This will open a new dialog box where we can add our new parameters. For this example, we will be adding one, so click on the Add button, and the properties for our new parameter should show in the properties window. You can leave the DataType as jtString, and leave Required checked. In the Name property, enter in name in all lowercase like that.

With our parameter created, back in the Data Module window, click back onto the RPC Handler itself, in the properties for it, go into the Events tab, and now create a new event for OnExecute.

procedure TRPCModule.SayHelloExecute(Sender: TObject; const Params: TJSONData;
  out Res: TJSONData);
begin
  Res:=TJSONObject.Create;
  TJSONObject(Res).Strings['hello']:='Hello from RPC there, '+TJSONObject(Params).Strings['name']+'!';
end;

There is the example for this handler that we will be using for this example. As with the previous handler, we initialize our Res variable, and then as you can see, we are now referencing our parameter. Also note, that we aren't even checking if the parameter exists in this routine, as it is always assured to exist if it made it to this routine, as the RPC Server code will ensure that all the correct parameters are passed before even running out code. The RPC Server will send out generic JSON-RPC error messages if there is an error, and most standard RPC clients can perfectly handle those, so we don't need to check the parameters ourselves. This makes all of our handlers much easier to manage and write, as we don't need to ever think of the data being invalid within our own code.

Sending Parameters

Okay, so now back to our HTML Fragment project, let's update the code for our button to the following:

procedure TTest1.actT1BtnExecute(Sender: TObject; Event: TJSEvent);
var
  params: TJSObject;
begin
  if acttname.Value = '' then
    acttmsg.Value:='Name Please!'
  else
  begin
    if actttitle.Value = '' then
      actttitle.Value:='Untitled';
    params:=TJSObject.new;
    params.Properties['name']:=acttname.Value;
    RPCClient.ExecuteRequest('', 'SayHello', params, @RPCSuccess, @RPCFailed);
  end;
end;

Notice that we are using a TJSObject here, and not a TJSONObject, they are not the same. The RPC client expects a TJSObject for it's parameter here, and this is how that object is created in code.

That's all there is to it to send parameters over to our RPC Server.

Conclusion

Hopefully this post has helped you learn more about how to create both a functioning RPC Server and use pas2js as an RPC Client for it. There is one last piece of code I will leave you with, as it will make trying out these example projects on your own much easier. Below is a working Caddyfile, and if placed in the same directory as your project, it should serve both the HTML and JavaScript application with ease. There is also an end-point for the FastCGI Server, which will need to be started either from the IDE, or manually from the command-line to test:

:8000 {
  encode

  handle /api/* {
    reverse_proxy localhost:2015 {
      transport fastcgi
    }
  }

  handle {
    file_server
    header /index.html Cache-Control "public, max-age=0, must-revalidate"
  }

}

Hopefully this post was helpful, and look forward to a future post where we will tackle the creation and using of a fully functional REST Server.

#current #pas2js #pascal