Jump to content

Run WebService in local machine from webapplication running on browser


mazluta

Recommended Posts

Hi

I write little web service (REST) work on port 8989.

this web service opens outlook and get all the address books (regular and suggestion) + the first 1000 Emails detail from InBox (2-3 seconds)

i write Web App with UniGui work on port 8077

there i have some form look like outlook, for sending message with attachments.

i want the user to get the list of emails coming from the WebService.

when i run the WebApp in my pc the web service is found and gives back the list i want.

when i run the web app in the cloud and open with chrome or whatever i get error message

image.png.838bf988aba0c17a7fce322b618e962c.png

 i read about it in google and it seems i need to build a tunnel to my localhost...

there are tools like ngRok, TunnelLocal.....

but this is not good practice, it creates public IP so the"site" can call the web service in the local host by external IP.

i dont think any client will agree on such solution....

does anyone did some connection like that from the web app to local host?

if this will be solved, we could digitally sign with a token, talk to scanner, pad... and so on...

 

a walk around is to put any flag (as file existing) in the file area and let the web service or http application write the data to some DB table and read the data on the web app

i dont like this solution too.

any one have idea?

 

 

 

 

 

Link to comment
Share on other sites

I have an app on the cloud which is communicating with a local webserver, using CORS, cross-origin resource sharing, which requires that the local webserver is configured to accept calls from a client on the same IP. I inject JS code which does an http call to the local webserver, using ajaxRequest to return the reply to the cloud app.

In theory this is straight-forward, but I had to make three different ways to talk to the local server, all using CORS, to be able to make it work with different customers and their hardware/software setups:

1. Calling a windows service running an http server, and I prefer this as it is the fastest, but I had to make it call console EXEs to do stuff, to be 100% stable
2. Calling Apache using PHP, which calls console EXEs
3. Calling Apache using CGI, which are console apps

Check your CORS config, as it may block external hosts.

Link to comment
Share on other sites

Hi Ron

thanks for quick responce.

please look at this.

image.png.202666163de09ad05143165348d9bf4f.png

when i try to call 

  NetHTTPRequest1.MethodString := 'Get';
  NetHTTPRequest1.URL := 'http://localhost:8989/GetMI';
  NetHTTPRequest1.Execute();

the browser can not call that webservice in the locan host.

if a client work with my site )in is on Cloud), i want to run some service in each of there pc's

do you use the same strategy

dose CORS will help me to do this?
 

Link to comment
Share on other sites

In my code, I see that I use 127.0.0.1 as local client IP, and the jQuery http client. Three options where the first is a Delphi created Windows service. In all cases the returned data is routed to the server via ajaxRequest. 

case uniMainModule.comType of
    2: UniSession.AddJS('$.get("http://127.0.0.1:'+uniMainModule.regPort+'?event='+inttostr(EventID)+'&op='+uniMainModule.initials+'", 
    function( data ){' +
	 ' ajaxRequest(TransForm.form, ["regEvent"], { response : data });  '+
   ' }); ');
    4: UniSession.AddJS('$.get("http://127.0.0.1:'+uniMainModule.regPort+'/regevent.php?event='+inttostr(EventID)+'&op='+uniMainModule.initials+'",   
    function( data ){' +
	 ' ajaxRequest(TransForm.form, ["regEvent"], { response : data });  '+
   ' }); ');
    5: UniSession.AddJS('$.get("http://127.0.0.1:'+uniMainModule.regPort+'/cgi-bin/regevent.cgi?event='+inttostr(EventID)+'&op='+uniMainModule.initials+'", 
    function( data ){' +
	 ' ajaxRequest(TransForm.form, ["regEvent"], { response : data });  '+
   ' }); ');
  end;

For the local server to respond I had to configure it accepting cross-reference calls to a specific folder where the apps called through the server are located:

<VirtualHost *:80>
    DocumentRoot "c:\apache22\htdocs"
    Header set Access-Control-Allow-Origin "*"
</VirtualHost>

 

Link to comment
Share on other sites

Hi Ron,

i have problems working with Delphi + JS + Ajax + not pure Delphi 🥵

i now understand (or maybe not) that i have to use JS to call the WebService, because it must work in the client area

and then move back the result data to the server to complete the task...

i work on this since Friday and didnt get what i want...

reading a lot of posts in unigui forms.

 

when i create simple HTML :

<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script>
$(document).ready(function(){
  $("button").click(function(){
    $.ajax({url: "http://localhost:8989/GetMi", success: function(result){
      $("#div1").html(result);

    }});
  });
});
</script>
</head>
<body>

<div id="div1"><h2>Let jQuery AJAX Change This Text</h2></div>

<button>Get External Content</button>

</body>
</html>

and run it + click the button the #div1 caption get the result i need (really work).

i can't figure out how to convert it to Delphi.

in that code the function is done inside the JS and don't give back result.

can you, please, help me understand this magic?

 

Main.dfm Main.pas

Link to comment
Share on other sites

Sure, this should work, and you can check the returning data in the event handler before displaying it.

procedure TMainForm.UniButton1Click(Sender: TObject);
begin
  UniSession.AddJS('$.get("http://127.0.0.1:8989/GetMi",  function( data ){' +
	 ' ajaxRequest(MainForm.form, ["test"], { response : data });  '+
   ' }); ');
end;

procedure TMainForm.UniFormAjaxEvent(Sender: TComponent; EventName: string;
  Params: TUniStrings);
begin
  if sameText(EventName, 'test') then
    uniEdit1.Text:=Params.Values['response'];
end;

 

test1.zip

Link to comment
Share on other sites

Hi Ron

i did has you suggested.

procedure TSendMailFrm.btnAddFilesFromListClick(Sender: TObject);
Var
  ThisName : String;
begin
// UniSession.AddJS('$.get("http://127.0.0.1:8989/GetMi",  function( data ){' +
//     ' ajaxRequest(SendMailFrm.form, ["test"], { response : data });  '+
//   ' }); ');

  ThisName := Self.Name;
  JustWriteToLog('btnAddFilesFromListClick - Self.Name = ' + ThisName);

  JustWriteToLog('Before UniSession.AddJS($.get("http://127.0.0.1:8989/GetMi"...');
  UniSession.AddJS('$.get("http://127.0.0.1:8989/GetMi",  function( data ){' +
       //' ajaxRequest(SendMailFrm.form, ["test"], { response : data });  '+
       ' ajaxRequest(' + ThisName + '.form, ["test"], { response : data });  '+
    ' }); ');

  JustWriteToLog('After UniSession.AddJS($.get("http://127.0.0.1:8989/GetMi"...');
end;

procedure TSendMailFrm.UniFormAjaxEvent(Sender: TComponent; EventName: string;
  Params: TUniStrings);
begin
  JustWriteToLog('EventName : ' + EventName);
  JustWriteToLog('Params : ' + Params.Text);
  if sameText(EventName, 'test') then
    edToList.Text:=Params.Values['response'];
end;
 

++++++++++++++++++++++++++++

it work if the site (8077) and the server (8989) are in the same machine.

if i run the site from the internet (before my router) and the serve is in my machine,

the call to the function - done.

but the ajax event on the form ajax event is not fire... so i can't get the response

Any suggestion ?

Link to comment
Share on other sites

Hi All

If we manage to run this service from the local computer, it will be possible to easily take control of external components, such as smart cards, scanners, special printers (plotters), run programs in local computer and receive an answer file in all kinds of matters and more (like all contacts from local outlook).
I would be happy if someone has an answer for running a Web Service in local computer from a form screen in a browser that is running from a remote server

like this diagram :

image.png.202666163de09ad05143165348d9bf4f.png

Link to comment
Share on other sites

Quote

If we manage to run this service from the local computer, it will be possible to easily take control of external components

My customers use this function every day, and it is called CORS as mentioned above.
It is used for triggering receipt printing and cash drawer ejection, and has been working for years.

The local webserver will only respond if it is opened up for CORS, so make sure it is configured correctly. Then it should work.

This issue is not something that we are trying to solve, because it is already working in production.
What I mean is that we know that it works, it only takes the right configuration of the webserver.

Link to comment
Share on other sites

Hi Ron

i send you private message.

No matter what I try, it doesn't work and always reports a problem with the CORS

image.png.3c482b5a8ae07fc40d672e5e78ff0179.png

//// this is the server side

procedure TWebModule1.WebModuleBeforeDispatch(Sender: TObject;
  Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
  JustWriteToLog('On WebModuleBeforeDispatch');
  Response.SetCustomHeader('Access-Control-Allow-Origin','*');
  Response.SetCustomHeader('Access-Control-Allow-Credentials','*');
  Response.SetCustomHeader('Access-Control-Allow-Methods','*');
  Response.SetCustomHeader('Access-Control-Allow-Private-Network','*');
  Response.SetCustomHeader('Access-Control-Allow-Headers', '*');
  //Response.SetCustomHeader('Access-Control-Allow-Headers', 'x-requested-with');

  //Response.CustomHeaders.Values['Access-Control-Allow-Origin']      := '*';
  //Response.CustomHeaders.Values['Access-Control-Allow-Credentials'] := 'true';
  //Response.CustomHeaders.Values['Access-Control-Allow-Methods']     := 'GET, POST, PUT, DELETE, OPTIONS';
  //Response.CustomHeaders.Values['Access-Control-Allow-Origin']      := '*';
  //Response.CustomHeaders.Values['Access-Control-Allow-Credentials'] := '*';
  //Response.CustomHeaders.Values['Access-Control-Allow-Methods']     := '*';

  if Trim(Request.GetFieldByName('Access-Control-Request-Headers')) <> '' then
  begin
    JustWriteToLog('On WebModuleBeforeDispatch - Trim(Request.GetFieldByName(Access-Control-Request-Headers))');
    Response.SetCustomHeader('Access-Control-Allow-Headers', Request.GetFieldByName('Access-Control-Request-Headers'));
    Handled := True;
  end;

  if SameText(Request.Method, 'Option') then
  begin
    JustWriteToLog('On WebModuleBeforeDispatch - if SameText(Request.Method, Option)');
    Handled := True;
  end;

  if FServerFunctionInvokerAction <> nil then
  begin
    JustWriteToLog('On WebModuleBeforeDispatch - if FServerFunctionInvokerAction <> nil');
    FServerFunctionInvokerAction.Enabled := AllowServerFunctionInvoker;
  end;
end;
 

// in the client side - i add header to request

procedure TSendMailFrm.btnAddFilesFromListClick(Sender: TObject);
Var
  ThisName : String;
begin
// UniSession.AddJS('$.get("http://127.0.0.1:8989/GetMi",  function( data ){' +
//     ' ajaxRequest(SendMailFrm.form, ["test"], { response : data });  '+
//   ' }); ');

  ThisName := Self.Name;
  JustWriteToLog('btnAddFilesFromListClick - Self.Name = ' + ThisName);

  JustWriteToLog('Before UniSession.AddJS($.get("http://127.0.0.1:8989/GetMi"...');
  //UniSession.AddJS('$.get("http://127.0.0.1:8989/GetMi",  function( data ){' +
  //                   ' ajaxRequest(' + ThisName + '.form, ["test"], { response : data });}); ');

  //UniSession.AddJS('$.ajaxSetup({headers: { "Access-Control-Allow-Origin": "*" }});');

  UniSession.AddJS('$.get("http://127.0.0.1:8989/GetMi", function( data ){' +
                     ' ajaxRequest(' + ThisName + '.form, ["test"],{headers: { "Access-Control-Allow-Origin": "*" }}, { response : data });}); ');

  JustWriteToLog('After UniSession.AddJS($.get("http://127.0.0.1:8989/GetMi"...');
end;
 

+++++++++++++++++++++++++++++++++++++++++++++++++++++

the server is in the MzDataSnap-WebModule.zip

the client is SendMail.zip

 

i hope you can help by providing  example for SERVER (rest) that allowing this CORS

 

 

MzDataSnap-WebModule.zip SendMail.zip

Link to comment
Share on other sites

I noticed that someone suggested this change on the server side, i.e. replacing hyphen with underscore - not sure if that works:

if Trim(Request.GetFieldByName('Access_Control_Request_Headers')) <> '' then
  begin
    JustWriteToLog('On WebModuleBeforeDispatch - Trim(Request.GetFieldByName(Access_Control_Request_Headers))');
    Response.SetCustomHeader('Access_Control_Allow_Headers', Request.GetFieldByName('Access_Control_Request_Headers'));
    Handled := True;
  end;

 

Link to comment
Share on other sites

ok.

first - thanks to RON.

i finally get the answer.

in one of Thousands of posts i read, i found that :

 

Accessing localhost

If your website needs to issue requests to localhost, then you just need to upgrade your website to HTTPS.

this was in : https://developer.chrome.com/blog/private-network-access-update/

since my test site was HTTP://HanibaalDemos:8077

chrome did not allow CORS.

i bought new SSL certificate, add set SSL capability to the Site - and Vuala - it work.

 

to run the WebService From The client side, Just

Var
  ThisName : String;
begin
  ThisName := Self.Name;
  JustWriteToLog('btnAddFilesFromListClick - Self.Name = ' + ThisName);

  JustWriteToLog('Before UniSession.AddJS($.get("http://127.0.0.1:8989/GetMi"...');

  UniSession.AddJS('$.ajaxSetup({headers: { "Access-Control-Allow-Origin": "*" }});');
  UniSession.AddJS('$.get("http://127.0.0.1:8989/GetMi", function( data ){' +
                     ' ajaxRequest(' + ThisName + '.form, ["test"],{headers: { "Access-Control-Allow-Origin": "*" }}, { response : data });}); ');

  JustWriteToLog('After UniSession.AddJS($.get("http://127.0.0.1:8989/GetMi"...');
end;

 

and Add Ajax Event For "Test" :

procedure TSendMailFrm.UniFormAjaxEvent(Sender: TComponent; EventName: string;
  Params: TUniStrings);
begin
  JustWriteToLog('EventName : ' + EventName);
  JustWriteToLog('Params : ' + Params.Text);
  if sameText(EventName, 'test') then
    edToList.Text:=Params.Values['response'];
end;

 

at the server side :

in the WebModuleBeforeDispatch Event - 

procedure TWebModule1.WebModuleBeforeDispatch(Sender: TObject;
  Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
  JustWriteToLog('On WebModuleBeforeDispatch');

  Response.CustomHeaders.Values['Access-Control-Allow-Origin']      := '*';
  Response.CustomHeaders.Values['Access-Control-Allow-Headers']     := '*';
  Response.CustomHeaders.Values['Access-Control-Allow-Methods']     := 'GET, POST, PUT, DELETE, OPTIONS';
  Response.CustomHeaders.Values['Access-Control-Allow-Credentials'] := 'true';
  Response.CustomHeaders.Values['Access-Control-Allow-Private-Network'] := '*';
  Response.CustomHeaders.Values['Access-Control-Expose-Headers']        := '';

  if Trim(Request.GetFieldByName('Access-Control-Request-Headers')) <> '' then
  begin
    JustWriteToLog('On WebModuleBeforeDispatch - Trim(Request.GetFieldByName(Access-Control-Request-Headers))');
    //Response.SetCustomHeader('Access-Control-Allow-Headers', Request.GetFieldByName('Access-Control-Request-Headers'));
    Response.CustomHeaders.Values['Access-Control-Allow-Headers'] := Request.GetFieldByName('Access-Control-Request-Headers');
    Handled := True;
  end;

  if SameText(Request.Method, 'OPTIONS') then
  begin
    JustWriteToLog('On WebModuleBeforeDispatch - if SameText(Request.Method, Option)');
    Handled := True;
  end;

  if FServerFunctionInvokerAction <> nil then
  begin
    JustWriteToLog('On WebModuleBeforeDispatch - if FServerFunctionInvokerAction <> nil');
    FServerFunctionInvokerAction.Enabled := AllowServerFunctionInvoker;
  end;
end;

 

now - we gat the backend door  to control the local PC components/data from our cloud site

  • Thanks 1
Link to comment
Share on other sites

the right way to call to ajaxrequest is :

UniSession.AddJS('$.get("http://127.0.0.1:8989/GetMi", function( data ){' +
                     ' ajaxRequest(' + ThisName + '.form, ["test"], { response : data });}); ');

 

not sending the HEADER in the request {headers: { "Access-Control-Allow-Origin": "*" }}

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...