Page 1 of 2

How do I tell HTTP Server where "parameters" begin in URI?

Posted: Wed Jul 10, 2024 3:32 am
by RodneyGaylor
I am making my first attempt at creating a web service based on the DIY section of the article "Providing RPG Web Services on IBM i".

So far I have:

1) Created a new HTTP Server called RESTFUL
2) Created a new IBM i library called RESTFUL
3) Created a SQLRPGLE program in the RESTFUL library called TSTCONNECT

I want to invoke the TSTCONNECT program from the web and pass filter parameters with a URI similar to:

http://address:port/tstconnect/{item}/{size}/{desc}/{hide}

NOTE: Any of the parameters {item}, {size}, {desc}, {hide} can be unnecessary to the request. I don't know how to indicate that in the URL. How do I indicate "no specific item selected" but still request a specific {size} in the URL? Do I need some kind of filler character(s) in the URL to indicate "no selection"?

In order to test the TSTCONNECT program with the Postman app, I have added the following to the configuration file for HTTP server RESTFUL:

ScriptAlias /tstconnect/ /qsys.lib/restful.lib/tstconnect.pgm
<Directory /qsys.lib/restful.lib>
Require all granted
</Directory>

With those pieces in place, I have used Postman to invoke the following URI:

http://address:port/tstconnect/13142/51/ASTER:SHOWMAKERS%20INDIGO%20ICE/hide=false

The response I am getting from Postman is:

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<HTML>
<HEAD>
<TITLE>403 Forbidden</TITLE>
</HEAD>
<BODY>
<H1>Forbidden</H1>
<p>You don't have permission to access this resource.</p>
</body>
</html>

The resulting entry in the access.log is:

... [09/Jul/2024:23:14:08 -0400] "GET /tstconnect/13142/51/ASTER:SHOWMAKERS%20INDIGO%20ICE/hide=false HTTP/1.1" 403 199 "-" "PostmanRuntime/7.40.0"

And the resulting entry in the error log is:

[Tue Jul 09 23:14:08.151624 2024] [core:error] [pid 58734:tid 000003B7] (3486)A path name is too long.: [client 10.151.151.14:56129] ZSRV_MSG064C: access to /tstconnect/13142/51/ASTER:SHOWMAKERS INDIGO ICE/hide=false failed (filesystem path 'Ìþ')

I am now officially in over my head. Help?

Re: How do I tell HTTP Server where "parameters" begin in URI?

Posted: Wed Jul 10, 2024 6:22 am
by Scott Klement
I'm guessing you wanted the URL to be as follows:

Code: Select all

http://address:port/tstconnect/13142/51/ASTER:SHOWMAKERS%20INDIGO%20ICE?hide=false
The ? character separates the path portion of the URL and the query-string portion. "hide=false" would be a query string

Re: How do I tell HTTP Server where "parameters" begin in URI?

Posted: Wed Jul 10, 2024 3:02 pm
by RodneyGaylor
EDIT: Would I be better off to forget about passing the parameters in the URI and instead put them in a JSON request file and process it as standard input? If so, which presentations can I peruse to get me down that path?
___
I am trying to pass four parameters to the program by following the example below from page 58 of the "Providing RPG Web Services on IBM i" presentation.

Code: Select all

http://ibmi.example.com/cust/495
• Browser connects to: ibmi.example.com
• Apache sees the /cust and calls RESTFUL/CUSTINFO
Our program can read the 495 (customer number) from the URL itself.

In my case I have added three additional parameters, all separated by a forward slash. I tested the following URI in Postman.

Code: Select all

GET http://address:port/tstconnect/13142/102/ASTER/false
Postman gets the following results:

Code: Select all

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<HTML>
<HEAD>
    <TITLE>403 Forbidden</TITLE>
</HEAD>
<BODY>
    <H1>Forbidden</H1>
    <p>You don't have permission to access this resource.</p>
</body>
</html>
The message in the error log on IFS tells me...

Code: Select all

[Wed Jul 10 10:05:17.063888 2024] [core:error] [pid 58734:tid 000003B7] (3486)A path name is too long.: [client **.***.***.**:*****] ZSRV_MSG064C: access to /tstconnect/13142/102/ASTER/false failed (filesystem path 'Ìþ')
It doesn't appear to like the parameters in the URL following "/tstconnect".

Re: How do I tell HTTP Server where "parameters" begin in URI?

Posted: Wed Jul 10, 2024 6:16 pm
by Scott Klement
For some reason, your scriptalias doesn't seem to be working. It is trying to access /tstconnect/13142/102/ASTER/false in the IFS, and it doesn't have permission to access that -- which is why you are getting a 403 error. (403 is an authority error.)

I don't know why that would be, it's not clear from what you've posted. I will try your ScriptAlias statements and see if I can reproduce it when I have time to do so.

Sending the data in a json document in the body of the request would not solve this problem.

The way you're doing this should work. That said, this does not comply with the REST architecture guidelines/recommendations. It'll work, but you can't really call it REST if done this way. The URI is supposed to identify the resource you're working with. The key word is IDENTIFY -- i.e. it should be the "unique key" that identifies whatever you are working with -- it should NOT contain all of the "fields of data" that goes with whatever you are working with. The data should be sent in the body, such as with a JSON document (recommended) or XML (today, typically only used for compatability with old software.) But the REST architecture doesn't care what the format of the body is -- just that it is where you transmit the details.

Re: How do I tell HTTP Server where "parameters" begin in URI?

Posted: Wed Jul 10, 2024 7:23 pm
by Scott Klement
In my test, it failed like yours did until I removed the slash from the end of the first part of the ScriptAlias.

so instead of doing this:

Code: Select all

ScriptAlias /tstconnect/ /qsys.lib/restful.lib/tstconnect.pgm
I did this:

Code: Select all

ScriptAlias /tstconnect /qsys.lib/restful.lib/tstconnect.pgm
Then it worked fine. Not sure why you had the extra / there -- were you trying to ensure that a URL of http://server:port/tstconnect did not call your program, and only a url of http://server:port/tstconnect/xxx did?

Re: How do I tell HTTP Server where "parameters" begin in URI?

Posted: Wed Jul 10, 2024 7:41 pm
by RodneyGaylor
One little slash! No, that was not intentional. Thanks for your help!

Re: How do I tell HTTP Server where "parameters" begin in URI?

Posted: Thu Jul 11, 2024 2:03 pm
by RodneyGaylor
I have changed my program to receive the filter parameters in a JSON payload rather than passing the filters in the URI.

Now I am having trouble with the STDOUT process. I am not receiving everything that was loaded by the program.

Here is my program:

Code: Select all

**free
/copy qfunctdefn,@copyright
//**************************************************************************************************
// Program    : getavlplug
// Description: Get Available Plug Inventory
// Author     : Rodney L Gaylor
// Create Date: 07-08-2024
// Notes      :
//**************************************************************************************************
// Modifications History
// Date        Programmer Name            Chg#  Description
// ----------  -------------------------  ----  ----------------------------------------------------
// 07-08-2024  Rodney L Gaylor            rg00  Program created
//**************************************************************************************************

// Set program control options
ctl-opt dftactgrp(*no)
        option(*nodebugio:*srcstmt)
        debug(*yes)
        bnddir('YAJL');

// Include YAJL procedures
/include yajl_h

// JSON Document data structure for request
dcl-ds jsonrequestdoc              qualified;
  dcl-ds filters;
    item                           varchar(13) inz('');
    size                           varchar(3) inz('');
    desc                           varchar(40) inz('');
    hide                           ind;
  end-ds;
end-ds;

// JSON Document data structure for results returned
dcl-ds jsonresultsdoc              qualified;
  num_weekdates                    int(10) inz(0);
  dcl-ds weekDates                 dim(6);
    weekdate                       like(ds_f53weeks.wk00);
  end-ds;
  num_items                        int (10) inz(0);
  dcl-ds items                     dim(32767);
    item                           varchar(13) inz('');
    size                           varchar(3) inz('');
    desc                           varchar(40) inz('');
    num_weeks                      int(10) inz(0);
    dcl-ds weeks                   dim(6);
      week                         varchar(10) inz('');
    end-ds;
  end-ds;
end-ds;

// Program data structures
dcl-ds ds_f53weeks                 extname('F53WEEKS')
                                   qualified
                                   alias
end-ds;

// Program work fields
dcl-s w_quote                      char(1) inz('''');
dcl-s w_item                       varchar(13);
dcl-s w_size                       varchar(3);
dcl-s w_desc                       varchar(40);
dcl-s w_wk                         packed(10);
dcl-s w_wk01                       packed(9:2);
dcl-s w_wk02                       like(w_wk);
dcl-s w_wk03                       like(w_wk);
dcl-s w_wk04                       like(w_wk);
dcl-s w_wk05                       like(w_wk);
dcl-s w_wk06                       like(w_wk);
dcl-s w_pos                        int(10) inz(0);
dcl-s w_jsonresponse               varchar(16000000);

// SQL work fields and constants
dcl-s sql_string                   varchar(5000);
dcl-c sqlsuccess                   '00000';

// Program indicators
dcl-s i_where                      ind inz(*off);

//**************************************************************************************************
// Main Procedure
//**************************************************************************************************

exec sql
  set option commit = *none;

// Load week dates for six display columns
exec sql
  select *
    into :ds_f53weeks
    from f53weeks
   fetch first row only;

jsonresultsdoc.weekdates(1).weekdate = ds_f53weeks.wk00;
jsonresultsdoc.weekdates(2).weekdate = ds_f53weeks.wk01;
jsonresultsdoc.weekdates(3).weekdate = ds_f53weeks.wk02;
jsonresultsdoc.weekdates(4).weekdate = ds_f53weeks.wk03;
jsonresultsdoc.weekdates(5).weekdate = ds_f53weeks.wk04;
jsonresultsdoc.weekdates(6).weekdate = ds_f53weeks.wk05;
jsonresultsdoc.num_weekdates = 6;

// Retrieve JSON request parameters from *STDIN
monitor;
data-into jsonrequestdoc
     %data( '*STDIN' :'case=convert allowmissing=yes')
   %parser('YAJLINTO');
on-error;
endmon;

// Build SQL select string from JSON request parameters
sql_string = 'select item, size, description, rb, wk1, wk2, wk3, wk4, wk5 from inv_p00002';

if jsonrequestdoc.filters.item <> *blank;
  sql_string += ' where item like '
             +  w_quote
             +  '%'
             +  %trim(jsonrequestdoc.filters.item)
             +  '%' + w_quote;
  i_where = *on;
endif;

if jsonrequestdoc.filters.size <> *blank;
  if not i_where;
    sql_string += ' where size like '
               +  w_quote
               +  '%'
               +  %trim(jsonrequestdoc.filters.size)
               +  '%'
               +  w_quote;
       i_where = *on;
  else;
    sql_string += ' and size like '
               +  w_quote
               +  '%'
               +  %trim(jsonrequestdoc.filters.size)
               +  '%' + w_quote;
  endif;
endif;

if jsonrequestdoc.filters.desc <> *blank;
  if not i_where;
    sql_string += ' where description like '
               +  w_quote + '%'
               +  %trim(jsonrequestdoc.filters.desc)
               +  '%'
               +  w_quote;
       i_where = *on;
  else;
    sql_string += ' and description like '
               +  w_quote
               +  '%'
               +  %trim(jsonrequestdoc.filters.desc)
               +  '%' + w_quote;
  endif;
endif;

if jsonrequestdoc.filters.hide = *on;
  if not i_where;
    sql_string += ' where item not like '
               +  w_quote
               + '%P%'
               +  w_quote;
       i_where = *on;
  else;
    sql_string += ' and item not like '
               +  w_quote
               + '%P%'
               +  w_quote;
  endif;
endif;

sql_string += ' order by sort, item, size';

// Prepare executable SQL statement p1 from sql_string
exec sql
  prepare sql_statement from :sql_string;

// Create sql cursor c1 for items selection
exec sql
  declare c1 cursor for sql_statement;

// Open sql cursor
exec sql
  open c1;

// Fetch requested item rows from inv_p00002
exec sql
  fetch from c1
   into :w_item, :w_size, :w_desc, :w_wk01, :w_wk02, :w_wk03, :w_wk04, :w_wk05, :w_wk06;

// Perform jsonResultsDoc data structure load until last item row selected
dow sqlstate = sqlsuccess;

jsonresultsdoc.num_items += 1;
jsonresultsdoc.items(jsonresultsdoc.num_items).item = w_item;
jsonresultsdoc.items(jsonresultsdoc.num_items).size = w_size;
jsonresultsdoc.items(jsonresultsdoc.num_items).desc = w_desc;
jsonresultsdoc.items(jsonresultsdoc.num_items).num_weeks = 0;

w_pos = %scan('P' : jsonresultsdoc.items(jsonresultsdoc.num_items).item);

select;
  when w_wk01 > 0
   and w_pos  > 0;
    jsonresultsdoc.items(jsonresultsdoc.num_items).weeks(1).week = %trim(%char(%editc(w_wk01:'3')));
    jsonresultsdoc.items(jsonresultsdoc.num_items).num_weeks = 1;
  when w_wk01 > 0;
    w_wk = w_wk01;
    jsonresultsdoc.items(jsonresultsdoc.num_items).weeks(1).week = %trim(%char(%editc(w_wk:'Z')));
    jsonresultsdoc.items(jsonresultsdoc.num_items).num_weeks = 1;
  other;
    jsonresultsdoc.items(jsonresultsdoc.num_items).weeks(1).week = *blank;
endsl;

if w_wk02 = 0;
  jsonresultsdoc.items(jsonresultsdoc.num_items).weeks(2).week = *blank;
else;
  jsonresultsdoc.items(jsonresultsdoc.num_items).weeks(2).week = %trim(%char(%editc(w_wk02:'Z')));
  jsonresultsdoc.items(jsonresultsdoc.num_items).num_weeks = 2;
endif;

if w_wk03 = 0;
  jsonresultsdoc.items(jsonresultsdoc.num_items).weeks(3).week = *blank;
else;
  jsonresultsdoc.items(jsonresultsdoc.num_items).weeks(3).week = %trim(%char(%editc(w_wk03:'Z')));
  jsonresultsdoc.items(jsonresultsdoc.num_items).num_weeks = 3;
endif;

if w_wk04 = 0;
  jsonresultsdoc.items(jsonresultsdoc.num_items).weeks(4).week = *blank;
else;
  jsonresultsdoc.items(jsonresultsdoc.num_items).weeks(4).week = %trim(%char(%editc(w_wk04:'Z')));
  jsonresultsdoc.items(jsonresultsdoc.num_items).num_weeks = 4;
endif;

if w_wk05 = 0;
  jsonresultsdoc.items(jsonresultsdoc.num_items).weeks(5).week = *blank;
else;
  jsonresultsdoc.items(jsonresultsdoc.num_items).weeks(5).week = %trim(%char(%editc(w_wk05:'Z')));
  jsonresultsdoc.items(jsonresultsdoc.num_items).num_weeks = 5;
endif;

if w_wk06 = 0;
  jsonresultsdoc.items(jsonresultsdoc.num_items).weeks(6).week = *blank;
else;
  jsonresultsdoc.items(jsonresultsdoc.num_items).weeks(6).week = %trim(%char(%editc(w_wk06:'Z')));
  jsonresultsdoc.items(jsonresultsdoc.num_items).num_weeks = 6;
endif;

exec sql
  fetch from c1
   into :w_item, :w_size, :w_desc, :w_wk01, :w_wk02, :w_wk03, :w_wk04, :w_wk05, :w_wk06;

enddo;

// Close the c1 sql cursor
exec sql
  close c1;

// WHEN I USE THIS CODE I GET THE EXPECTED RESULTS IN THE DOC FILE
//data-gen jsonresultsdoc %data( '/rodneyg/getavlplugresults.json'
//                             : 'doc=file countprefix=num_'
//                             ) %gen('YAJLDTAGEN');

// WHEN I USE THIS CODE I DO NOT GET ITEM DATA IN THE DOC FILE OR IN THE STDOUT TO THE BROWSER
// Return requested rows to caller
data-gen jsonresultsdoc %data( '/rodneyg/getavlplugresults.json'
                             : 'doc=file countprefix=num_'
                             ) %gen('YAJLDTAGEN'
                             : '{"http status": 200, "write to stdout": true}'
                             );

*inlr = *on;
return;
WHen I run the program in DEBUG and manually set a filter for Item 2692, this is how the DS is loaded.

Code: Select all

EVAL jsonresultsdoc
JSONRESULTSDOC.NUM_WEEKDATES = 6
JSONRESULTSDOC.WEEKDATES.WEEKDATE(1) = '2024-07-15'
JSONRESULTSDOC.WEEKDATES.WEEKDATE(2) = '2024-07-22'
JSONRESULTSDOC.WEEKDATES.WEEKDATE(3) = '2024-07-29'
JSONRESULTSDOC.WEEKDATES.WEEKDATE(4) = '2024-08-05'
JSONRESULTSDOC.WEEKDATES.WEEKDATE(5) = '2024-08-12'
JSONRESULTSDOC.WEEKDATES.WEEKDATE(6) = '2024-08-19'
JSONRESULTSDOC.NUM_ITEMS = 1
JSONRESULTSDOC.ITEMS.ITEM(1) = '2692         '
JSONRESULTSDOC.ITEMS.SIZE(1) = '50 '
JSONRESULTSDOC.ITEMS.DESC(1) = 'MONTAUK DAISY RC                        '
JSONRESULTSDOC.ITEMS.NUM_WEEKS(1) = 1
JSONRESULTSDOC.ITEMS.WEEKS.WEEK(1,1) = '3         '
JSONRESULTSDOC.ITEMS.WEEKS.WEEK(1,2) = '          '
JSONRESULTSDOC.ITEMS.WEEKS.WEEK(1,3) = '          '
JSONRESULTSDOC.ITEMS.WEEKS.WEEK(1,4) = '          '
JSONRESULTSDOC.ITEMS.WEEKS.WEEK(1,5) = '          '
JSONRESULTSDOC.ITEMS.WEEKS.WEEK(1,6) = '          '
JSONRESULTSDOC.ITEMS.ITEM(2) = '             '
JSONRESULTSDOC.ITEMS.SIZE(2) = '   '
JSONRESULTSDOC.ITEMS.DESC(2) = '                                        '
JSONRESULTSDOC.ITEMS.NUM_WEEKS(2) = 0
JSONRESULTSDOC.ITEMS.WEEKS.WEEK(2,1) = '          '
JSONRESULTSDOC.ITEMS.WEEKS.WEEK(2,2) = '          '
JSONRESULTSDOC.ITEMS.WEEKS.WEEK(2,3) = '          '
JSONRESULTSDOC.ITEMS.WEEKS.WEEK(2,4) = '          '
JSONRESULTSDOC.ITEMS.WEEKS.WEEK(2,5) = '          '
JSONRESULTSDOC.ITEMS.WEEKS.WEEK(2,6) = '          '
When I run the program with this DATA-GEN setting...

Code: Select all

data-gen jsonresultsdoc %data( '/rodneyg/getavlplugresults.json'
                             : 'doc=file countprefix=num_'
                             ) %gen('YAJLDTAGEN');
... this is what the document looks like, which is what I expected.

Code: Select all

{
	"weekDates": [
		{
			"weekdate": "2024-07-15"
		},
		{
			"weekdate": "2024-07-22"
		},
		{
			"weekdate": "2024-07-29"
		},
		{
			"weekdate": "2024-08-05"
		},
		{
			"weekdate": "2024-08-12"
		},
		{
			"weekdate": "2024-08-19"
		}
	],
	"items": [
		{
			"item": "2692",
			"size": "50",
			"desc": "MONTAUK DAISY RC",
			"weeks": [
				{
					"week": "3"
				}
			]
		}
	]
}
But when I run the DATA-GEN code to return the results via STDOUT like this...

Code: Select all

data-gen jsonresultsdoc %data( '/rodneyg/getavlplugresults.json'
                             : 'doc=file countprefix=num_'
                             ) %gen('YAJLDTAGEN'
                             : '{"http status": 200, "write to stdout": true}'
                             );
My resulting doc file only includes these entries.

Code: Select all

{
	"weekDates": [
		{
			"weekdate": "2024-07-15"
		},
		{
			"weekdate": "2024-07-22"
		},
		{
			"weekdate": "2024-07-29"
		},
		{
			"weekdate": "2024-08-05"
		},
		{
			"weekdate": "2024-08-12"
		},
		{
			"weekdate": "2024-08-19"
		}
	]
}
And this is what is returned in a browser.

Code: Select all

{"weekDates":[{"weekdate":"2024-07-15"},{"weekdate":"2024-07-22"},{"weekdate":"2024-07-29"},{"weekdate":"2024-08-05"},{"weekdate":"2024-08-12"},{"weekdate":"2024-08-19"}]}
Why am I losing the selected Item data?

Re: How do I tell HTTP Server where "parameters" begin in URI?

Posted: Thu Jul 11, 2024 8:13 pm
by Scott Klement
I don't see any reason why these would be different. They are running the exact same code under the covers.

Check your num_items field. The results you are reporting are what I would expect if num_items is set to 0.

If you can't figure it out, you'll need to post code that I can use to reproduce the problem. My brain is not a compiler, so posting code and expecting me to figure it out by reading your code isn't really going to work. I can't run the code you've provided, since 99% of it is complex database operations on a database table hat I don't have.

Re: How do I tell HTTP Server where "parameters" begin in URI?

Posted: Thu Jul 18, 2024 8:44 am
by RodneyGaylor
Hi Scott,

I finally got my program working, but now I am having another challenge.

I know this is not a YAJL or HTTPAPI issue, but I have created an HTTP Server, following your guidance in the DIY section of the "Providing Web Services on IBM i" presentation, to run this program as an API. This is my current configuration file for the new HTTP Server. My modifications begin on line 32.

Code: Select all

  1	   # Configuration originally created by Create HTTP Server wizard on Wed Jul 17 12:50:58 EDT 2024
  2	   Listen *:10020
  3	   DocumentRoot /www/restserver/htdocs
  4	   TraceEnable Off
  5	   Options -FollowSymLinks
  6	   LogFormat "%h %T %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
  7	   LogFormat "%{Cookie}n \"%r\" %t" cookie
  8	   LogFormat "%{User-agent}i" agent
  9	   LogFormat "%{Referer}i -> %U" referer
  10	   LogFormat "%h %l %u %t \"%r\" %>s %b" common
  11	   CustomLog logs/access_log combined
  12	   LogMaint logs/access_log 7 0
  13	   LogMaint logs/error_log 7 0
  14	   SetEnvIf "User-Agent" "Mozilla/2" nokeepalive
  15	   SetEnvIf "User-Agent" "JDK/1\.0" force-response-1.0
  16	   SetEnvIf "User-Agent" "Java/1\.0" force-response-1.0
  17	   SetEnvIf "User-Agent" "RealPlayer 4\.0" force-response-1.0
  18	   SetEnvIf "User-Agent" "MSIE 4\.0b2;" nokeepalive
  19	   SetEnvIf "User-Agent" "MSIE 4\.0b2;" force-response-1.0
  20	   ServerUserID QTMHHTTP
  21	   <Directory />
  22	        Require all denied
  23	   </Directory>
  24	   <Directory /www/restserver/htdocs>
  25	        Require all granted
  26	   </Directory>
  27	   HTTPSubsystemDesc QHTTPSVR/QHTTPSVR
  28	   HTTPStartJobDesc QHTTPSVR/QZHBHTTP
  29	   HTTPStartJobQueue *JOBD
  30	   HTTPRoutingData *JOBD
  31	   
  32	   #RESTful web services
  33	   SetEnv QIBM_CGI_LIBRARY_LIST "RESTFUL;PHPDATA;YAJL"
  34	   ScriptAliasMatch /rest/([a-z0-9]*) /qsys.lib/restful.lib/$1.pgm
  35	   <Directory /qsys.lib/restful.lib>
  36	        Require all granted
  37	   </Directory>
  42	   #End RESTful web services
This allows me to execute my program (getavlplug) using the following URI when I am logged in to our network.

Code: Select all

http://10.151.151.1:10020/rest/getavlplug
I need to make this program available outside our local network to be used on our website. I've been trying to figure out how to do that for three days now, but for the life of me I cannot figure out how to make it available.

Can you help me understand what I need to do to make this server/program available from our public facing domain?

Code: Select all

http://network.kubepak.com (http://96.56.221.220)
Thanks for any help you can offer!

Re: How do I tell HTTP Server where "parameters" begin in URI?

Posted: Thu Jul 18, 2024 2:56 pm
by Scott Klement
In order to help you with that, I'd need to be trained in how your network is set up, which type/model of equipment you have and how to use it, and so forth. So this isn't really something I can help with.

I can tell you what the most common setup is...
  • Your ISP has a line run to your building.
  • There is a firewall. It may be built-in to the router (next step) or it might be a separate device. The firewall blocks all traffic unless it is on a list of "open" ports. You'll need to configure it to open a port that goes to your IBM i.
  • The line hooks up to a device called a "router". A router's job is to determine what data is going to your network, or from your network, and move it onward to the appropriate place within your network. It will need to be configured so that traffic on a particular port goes to your IBM i. (Or all traffic to a given IP address.)
  • Most routers these days use something called Network Address Translation (NAT). You'll know this is involved if you have a separate "internal" and "external" IP address. If you are running NAT, it also needs to be configured to forward the particular port to your IBM i.
  • You need to make sure that there are no port retrictions on the IBM i as well.
  • You may (and this is a good idea) have a DMZ. This is a setup where there are two firewalls, and the DMZ sits between them (before traffic gets to your internal router). The idea is that the DMZ receives connections to your network and creates a proxy to the IBM i. This way if a hacker breaks in, they only get into the DMZ.
Typically a company has an employee or consultant who set all of this stuff up and knows how it works. This "network guy" is the person you should be talking to to understand how to make it available publicly.