Appserver Support¶
- Appserver Support
Configuration¶
Appserver configuration is done in FWD's directory. This includes the appserver details, the FWD process account under which the appserver Agent is executing, and more. This section will describe what configuration is required to setup an appserver in FWD's directory.
In the examples in this section:- the appserver is assumed to be named
app_server - the FWD process account is assumed to be named
appserver_process
Add the Process Account¶
Follow Batch Processes to add a FWD process account in directory.xml. The final definition should look like:
<node class="process" name="appserver_process">
<node-attribute name="appserver" value="app_server"/>
<node-attribute name="enabled" value="TRUE"/>
<node-attribute name="description" value="Process for appserver."/>
<node-attribute name="alias" value="appserver_process"/>
<node-attribute name="master" value="FALSE"/>
<node-attribute name="server" value="FALSE"/>
</node>
The certificate must be installed using Cryptography Setup Helper.
Configure the Scheduler¶
Follow Scheduler to schedule the automatic launch of the app_server at startup. The final configuration should look like this:
<node class="container" name="scheduler">
<node class="job" name="start_appserver">
<node-attribute name="type" value="process"/>
<node-attribute name="target" value="appserver_process"/>
<node-attribute name="enabled" value="TRUE"/>
<node-attribute name="mode" value="now"/>
</node>
</node>
Configure the Spawner¶
Follow Spawner setup to configure the clientConfig node for the FWD process associated with the appserver.
Configure the Appserver¶
Follow Configuring a Legacy Appserver to configure the appserver details in FWD. This configuration is for launching the appserver Agents, and not for appserver connections. The result should look like this:
<node class="container" name="appservers">
<node class="appserver" name="app_server">
<node-attribute name="operating_mode" value="state-free"/>
<node-attribute name="request_timeout" value="0"/>
<node-attribute name="auto_trim_timeout" value="30"/>
<node-attribute name="propath" value=".:appsrv/api:"/>
<node-attribute name="initial_agents" value="1"/>
<node-attribute name="min_agents" value="1"/>
<node-attribute name="max_agents" value="1"/>
<node-attribute name="activate" value=""/>
<node-attribute name="deactivate" value=""/>
<node-attribute name="connect" value=""/>
<node-attribute name="disconnect" value=""/>
<node-attribute name="startup" value=""/>
<node-attribute name="shutdown" value=""/>
<node-attribute name="startup_parameter" value=""/>
</node>
</node>
Add as many appservers configurations as required, with their configuration usually being extracted from the OpenEdge ubroker.properties files.
Configure the Appserver for Remote Connections¶
If the appserver is accessed from 4GL code (remote or local for the FWD server), app_services, follow Configuring an Appserver Client to specify the details. The result may look like this:
<node class="container" name="app_services">
<node class="container" name="app_server">
<node class="integer" name="p2j_port">
<node-attribute name="value" value="3333"/>
</node>
<node class="string" name="p2j_host">
<node-attribute name="value" value="localhost"/>
</node>
<node class="string" name="p2j_account">
<node-attribute name="value" value="appserver_agent"/>
</node>
<node class="boolean" name="sessionFree">
<node-attribute name="value" value="TRUE"/>
</node>
<node class="strings" name="aliases"/>
<node class="strings" name="hosts">
<node-attribute name="values" value="localhost"/>
</node>
<node class="integers" name="ports">
<node-attribute name="values" value="21200"/>
<node-attribute name="values" value="21300"/>
<node-attribute name="values" value="21500"/>
</node>
<node class="strings" name="services">
<node-attribute name="values" value="fwd1"/>
<node-attribute name="values" value="21200"/>
<node-attribute name="values" value="21300"/>
<node-attribute name="values" value="21500"/>
</node>
</node>
</node>
p2j_account needs to be specified only when connecting to a remote appserver; for only local 4GL SERVER:CONNECT method usage, this can be skipped.
Starting the Appserver¶
On FWD startup, all appserver configuration from the appservers node is read into AppServerDefinition. This will be used to determine the appserver details, when an Agent is started.
The appserver Agents are started automatically when the FWD server starts, assuming the Scheduler is configured for this. When the FWD client (process in this case) starts, StandardServer.standardEntry will check if AppServerManager.startAppServer() can handle an appserver Agent. A number of initial_agents appserver Agents (configured for an appserver) will be started automatically at FWD server startup.
If all agents started automatically at FWD server startup are busy, FWD will automatically launch an Agent for the target appserver, until max_agents is reached, when a appserver call is performed.
Appserver agents can also be manually started via ./server.sh -b <process> (assuming max_agents is not reached).
Agent trimming is done automatically, via the FWD's Scheduler support, which will schedule a AppServerLauncher.trimAgents task for each appserver, called every auto_trim_timeout seconds. When called, Agents will be terminated until min_agents is reached.
AgentPool.newPool(appServer). This creates either a AgentPool$BoundPool or AgentPool$UnboundPool, depending on the appserver's type:
- a
BoundPoolinstance forState-awareandState-resetappservers - a
UnboundPoolinstance forStatelessandState-freeappservers
Conversation, Reader and Writer thread, as for any other FWD client. The Agent's Conversation thread is not responsible for accepting directly an appserver request - the Conversation thread just waits for tasks to be posted. Instead, a remote request will be executed on the Dispatcher FWD server threads, which will call the proper AppServerManager API, to:
- identify an available Agent and retrieve it from the appserver pool (it will wait for a number of
request_timeoutseconds until an Agent is available). - post the task to the Agent's
Conversationthread. - wait for the task to complete.
- return the result back to the caller.
For this reason, if there are remote appserver requests (this includes OpenClient calls when the client is remote and not running in the same JVM as the FWD server), it is important to configure at the FWD Server a number Dispatcher threads which can handle the maximum number of simultaneous remote connections.
When the request is originating from the same JVM thread as the FWD server, the Dispatcher threads will not be used, and the caller will be linked with the AppServerManager directly, via a local proxy.
Connecting to AppServer¶
Appserver connections can be established via the SERVER:CONNECT method or via an FWD Open Client connection. The appserver connection approach differs in each case, FWD providing support of the 4GL connection options only for the SERVER:CONNECT method.
Supported SERVER:CONNECT method options¶
FWD supports both PASOE and Classic appserver connection options:
- for PASOE, the following options are supported:
-pf filename-sessionModel [ Session-managed | Session-free ]-URL Web-or-AppServer-path
- for Classic Appserver, these options are supported:
-AppService application-service-H [ host_name | IP-address ]-S [ service-name | port-number ]-DirectConnect- if set, then-Hand-Sspecify the appserver, otherwise the nameserver target.-URL Web-or-AppServer-path- when this is set, then host and port are extracted,-directConnectis assumed, and-Sis assumed the URI path-sessionModel [ Session-managed | Session-free ]
The following appserver connection options are not supported:
-ssl -nosessionreuse -nohostverify -connectionLifetime -initialConnections -maxConnections -nsClientMaxPort -nsClientMinPort -nsClientPicklistExpiration -nsClientPicklistSize -nsClientPortRetry -nsClientDelay -AppServerKeepaliveThe target appserver is being resolved using the Configuring an Appserver Client options, following these rules:
- if
-sessionModelis set, then:- when
Session-free,app_services/<appserver>/sessionFreemust be set totrue. - when
Session-managed,app_services/<appserver>/sessionFreemust be set tofalse.
- when
- either
-directConnectis set or, otherwise,app_services/<appserver>/aliasesmust contain the-AppServicename. app_services/<appserver>/hostscontains the host from-Hoptions or from the specified-URL; if neither is set, then it must containlocalhost.- if
-S portor-URLis set, thenapp_services/<appserver>/portsmust have this port - if
-S serviceis set, thenapp_services/<appserver>/servicesmust have the service name - if neither
-Sor-URLare set, the port is assumed to be5162, andapp_services/<appserver>/portsmust have this port - if neither
-Hor-URLis set, the host is assumed to belocalhost, andapp_services/<appserver>/hostsmust have this host - if
-AppServiceis not set and not in direct connnect mode, then-AppServiceis assumed to be the default service configured for that nameserver host (as the specified or implicit-His assumed to target a nameserver).
Connecting from OpenClient¶
Connecting from a remote client is done by using the FWD OpenClient APIs. This requires first to configure the connection to the remote server, and after that call LegacyJavaAppserverClient.connectRemote to establish the appserver connection. Details are specified at Proxygen_REST_SOAP_and_WebHandler.
Runtime Details¶
A named appserver in FWD is a pool of agents which all share the same configuration (of which the most important are the propath and session mode) and which can be targeted via options specified in the connect parameters for a 4GL <server_handle>:CONNECT() method call, or the equivalent connect API for an Open Client. The agents in a pool (for an appserver) all share the same appservers/<appserver> configuration.
-sessionModel is not explicitly used to resolve the target appserver - all the other connection options (like -AppService or -URL) are used to find the appserver. Once this is found, when -sessionModel is specified, it means that:
- if
Session-Free, the target appserver must be State-free. - if
Session-Managed, the target appserver must not be State-free.
- if the appserver is running in State-reset or State-aware mode, the Agent used to establish the connection becomes bound to the connection.
- if the appserver is running in Stateless mode, an Agent is bound to that connection only when a program is ran persistent.
- if the appserver is running in State-free mode, an Agent is not bound to the connection when a program is ran persistent, but any invocations targeting a persistent program will always route the request to the Agent which instantiated that persistent program.
Once the Agent is bound to the connection, it will not be released back to the agent pool, and is unavailable for work from other connections; only when it becomes unbound will be added back to the pool.
For an appserver, the work can be posted by the following callers:
| Caller Type | Description | Proxy | Proxy Setup Code | Proxy Tear-Down Code | RemoteObject Interface Used |
|---|---|---|---|---|---|
| local 4GL code | call to appserver from converted code in the same FWD server | local | AppServerHelper.connectLegacyMode |
lifetime of FWD server | AppServerEntry |
| remote 4GL code | call to appserver from converted code in a different FWD server, this will occur over a virtual session | RemoteObject | AppServerHelper.connectLegacyMode |
AppServerHelper.disconnect |
AppServerEntry |
| local Java Open Client replacement | call to appserver from hand coded Java in the same FWD server | local |
AppServerHelper.connect(BootstrapConfig config,
String account,
String appServerName,
boolean sessionFree)
|
Lifetime of FWD server. | AppServerEntry |
| remote Java Open Client replacement | call to appserver from hand coded Java in the a separate JVM, the call will come from a direct session through the dispatcher | RemoteObject |
AppServerHelper.connect(BootstrapConfig config,
String account,
String appServerName,
boolean sessionFree)
|
LegacyJavaAppserverClient.disconnect |
AppServerEntry |
| legacy REST | internal usage of appserver agents as a worker for processing requests from legacy REST services | local |
AppServerHelper.connect(BootstrapConfig config,
String account,
String appServerName,
boolean sessionFree)
|
Lifetime of FWD server. | AppServerEntry |
| legacy SOAP | internal usage of appserver agents as a worker for processing requests from legacy SOAP services | local |
AppServerHelper.connect(BootstrapConfig config,
String account,
String appServerName,
boolean sessionFree)
|
Lifetime of FWD server. | AppServerEntry |
| legacy WebHandler | internal usage of appserver agents as a worker for processing requests from legacy WebHandler services | local |
AppServerHelper.connect(BootstrapConfig config,
String account,
String appServerName,
boolean sessionFree)
|
Lifetime of FWD server. | AppServerEntry |
For legacy REST, SOAP or web handler support, FWD uses an embedded web server (Jetty) to handle the HTTP requests. But, the request to the appserver is not being processed on the thread handling the web request - instead, a pool of workers (a distinct pool for each REST, SOAP or web handler support) is used. This worker pool will use a queue on which a task (as calculated on the web thread for that service request) will be posted.
The worker pool was added because the number of active web threads trying to perform i.e. REST requests can be more than the available Agents on the target appserver - if each one of these web threads would try to post a task to the appserver at the same time, the Agent pool would be exhausted immediately, and many request would fail with 'no server available' errors. Having this intermediate pool allows to queue the requests, with each Jetty thread being able to post the work to the worker thread with the fewest tasks on its queue - it will just have to wait for the worker thread to finish all the tasks posted before.
Local 4GL Code¶

Remote 4GL Code¶

Local Open Java Client Replacement¶

Remote Open Java Client Replacement¶

Legacy REST¶

Legacy SOAP¶

Legacy WebHandler¶

PASOE v2 Implementation Details¶
The following implementation details apply to the current latest trunk version at r15845. The second version of PASOE in FWD:- is implemented as a single agent running without client process, completely inside the FWD server JVM;
- differs from the first version by applying the appserver configurations as they are used in the original OE version; (note: this differs from OE; in OE each multi-session agent is its own process)
- supports mixed connection mode, client connections can decide on the session model they want to use;
- supports multi-session processing;
- binds connections to sessions the same way as in OE: Session-Managed connections are not automatically bound, and persistent procedures create their own child connections in Session-Free that are bound, etc.;
- tasks are queued on the server-side and executed by an executor service;
- differs from the first version by implementing the client connection timeout configuration;
- doesn't support db shared connection for the agent like in OE, but each session creates it's own physical DB connection.
Debugging Details¶
All appserver invocations are exported via AppServerEntry proxy (at the client side), and implemented in AppServerManager, on FWD server-side.
AppServerManager will always acquire an Agent (the connection or procedure bound Agent, or one from the pool), and post the work to it. At the Agent FWD implementation, there are various APIs related to invoking an external program (persistent or not), an internal procedure, etc.
Conversation thread. The simplest way is to find a common API executed for all calls, and place a breakpoint in it - this is Agent.preProcessArguments, which converts the arguments to be compatible for being passed to the ControlFlowOps API call. Placing a breakpoint in this method allows you to exit just before the ControlFlowOps API call for the target. You can step through the code until this ControlFlowOps API is reached, and from that:
- if you want to debug the
ControlFlowOps, step further into through these APIs - otherwise, place a breakpoint in
Block.body()method, and 'step over' theControlFlowOpsAPI - this will execute the code until the targeted converted code will be reached - you can continuing stepping into thebodyMethod.body();call, and you will end up directly in the converted code.
Agent.preProcessArguments (which executes before ControlFlowOps) has a Agent.postProcessArguments paired method which always executes sometime after the ControlFlowOps API call finishes. Placing a breakpoint in postProcessArguments allows you to inspect the arguments as they are returned by the API call.
© 2004-2022 Golden Code Development Corporation. ALL RIGHTS RESERVED.