Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
Stefan-Schnell
Active Contributor
835
In a few cases it is necessary to mockup different application designs and with real data. Here is one easy example how to realize this kind of requirement with web scenario in a very flexible approach.



To communicate with the SAP application server I use two different web application server. The first web application server is a normal standard server with PHP. The second web application server, which is used by the first, is a PowerShell script which uses the dotNET Connector (NCo) to communicate with the SAP application server. If it is necessary you can use only the PowerShell WAS, but with the standard in-between you have a lot of other possibilties easily, e.g. to use UI frameworks or something else.

The clou of this method is on the one hand, that you can absolut easily manipulate the communication to the SAP application server. All you have to do is to modify the PowerShell script. And on the other hand you can also easily manipulate the display of the result via the standard web application server.

Here an example how to do that. I will start with the viewing of the system information. Therefore I use the URI. I call the localhost with the correct file and the query string systeminfo.



The PHP file, which runs on the standard server, receives the requests and call the local PowerShell web application server.
<?php
$QueryString = preg_replace("/&/", "?", $_SERVER['QUERY_STRING'], 1);
$File = fopen("http://localhost:8888/$QueryString", "r");
if($File <> false) {
while (!feof($File)) {
$Line = fread($File, 4096);
echo $Line;
}
fclose($File);
}
?>

 

The PowerShell web application server, which works on another port, gets this request and executes its Get-Systeminfo function. This calls via NCo the RFC-enabled function module RFC_SYSTEM_INFO.

The PowerShell script is very easy to understand. The main routine loads NCo and starts the application server. This is not more than a HTTP listener on the port 8888. If the listener receives a request, it calls a RFC function, in dependence to the HTTP method and URL.

 
#-Begin-----------------------------------------------------------------

#-Global variables----------------------------------------------------
$url = "http://localhost:8888/" #Listening URL
$CrLf = "`r`n"

#-Here-Strings--------------------------------------------------------
$error_htm = @"
<!DOCTYPE HTML>
<html>
<head>
<title>Error site</title>
</head>
<body>
An error occured
</body>
</html>
"@

$index_htm = @"
<!DOCTYPE HTML>
<html>
<head>
<title>Index site</title>
</head>
<body>
WebServer example
</body>
</html>
"@

#-Sub Load-NCo--------------------------------------------------------
Function Load-NCo() {

$ScriptDir = $PSScriptRoot

If ([Environment]::Is64BitProcess) {
$Path = $ScriptDir + "\x64\"
}
Else {
$Path = $ScriptDir + "\x86\"
}

$File = $Path + "sapnco.dll"; Add-Type -Path $File
$File = $Path + "sapnco_utils.dll"; Add-Type -Path $File

}

#-Function Get-Destination--------------------------------------------
Function Get-Destination() {

#-Connection parameters-------------------------------------------
$cfgParams = New-Object SAP.Middleware.Connector.RfcConfigParameters
$cfgParams.Add("NAME", "TEST")
$cfgParams.Add("ASHOST", "ABAP702")
$cfgParams.Add("SYSNR", "00")
$cfgParams.Add("CLIENT", "001")
$cfgParams.Add("USER", "BCUSER")
$cfgParams.Add("PASSWD", "minisap")

Return [SAP.Middleware.Connector.RfcDestinationManager]::GetDestination($cfgParams)

}

#-Function Get-SystemInfo---------------------------------------------
Function Get-SystemInfo() {

$destination = Get-Destination

#-Metadata--------------------------------------------------------
$rfcFunction = `
$destination.Repository.CreateFunction("RFC_SYSTEM_INFO")

#-Call function module--------------------------------------------
Try {
$rfcFunction.Invoke($destination)

$Export = $rfcFunction.GetStructure("RFCSI_EXPORT")

#-Get information---------------------------------------------
$strRet = $CrLf + "Host: " + `
$Export.GetValue("RFCHOST") + "<br />"
$strRet = $strRet + $CrLf + "SysID: " + `
$Export.GetValue("RFCSYSID") + "<br />"
$strRet = $strRet + $CrLf + "DBHost: " + `
$Export.GetValue("RFCDBHOST") + "<br />"
$strRet = $strRet + $CrLf + "DBSys: " + `
$Export.GetValue("RFCDBSYS") + "<br />"
$strRet = $strRet + $CrLf

}
Catch {
Write-Host "Exception occured:`r`n" $_.Exception.Message
Return "<strong>Exception occured</strong><br />" + `
$_.Exception.Message
}

Return $strRet

}

#-Function Read-Table-------------------------------------------------
Function Read-Table([String]$TableName, [Int]$RowCount) {

$destination = Get-Destination

#-Metadata--------------------------------------------------------
$rfcFunction = `
$destination.Repository.CreateFunction("RFC_READ_TABLE")

#-Call function module--------------------------------------------
Try {
$rfcFunction.SetValue("QUERY_TABLE", $TableName)
$rfcFunction.SetValue("DELIMITER", "~")
$rfcFunction.SetValue("ROWCOUNT", $RowCount)

$rfcFunction.Invoke($destination)

$Header = New-Object System.Collections.ArrayList

#-Get field names for the CSV header--------------------------
[SAP.Middleware.Connector.IRfcTable]$Fields = `
$rfcFunction.GetTable("FIELDS")
ForEach ($Field In $Fields) {
[Void]$Header.Add($Field.GetValue("FIELDNAME"))
}

#-Get table data line by line for CSV-------------------------
[SAP.Middleware.Connector.IRfcTable]$Table = `
$rfcFunction.GetTable("DATA")
ForEach ($Line In $Table) {
$CSV = $CSV + $Line.GetValue("WA") + "`r`n"
}

#-Convert CSV to JSON-----------------------------------------
$JSON = $CSV | ConvertFrom-Csv -Delimiter "~" `
-Header $Header | ConvertTo-Json

Return $JSON

}
Catch {
Write-Host "Exception occured:`r`n" $_.Exception.Message
Return "<strong>Exception occured</strong><br />" + `
$_.Exception.Message
}

}

#-Function Head-------------------------------------------------------
Function Head([String] $Title) {
Return "<!DOCTYPE HTML>$($CrLf)<html>$($CrLf) <head>$($CrLf)" + `
" <title>" + $Title + "</title>$($CrLf) </head>$($CrLf)" + `
" <body>"
}

#-Function Tail-------------------------------------------------------
Function Tail() {
Return " </body>$($CrLf)</html>"
}

#-Sub Main------------------------------------------------------------
Function Start-WebServer() {

$listener = new-object system.net.httplistener
$listener.prefixes.add($url)
$listener.start()

if ($Host.Name -eq "ConsoleHost") {
[console]::TreatControlCAsInput = $true
write-host "Press any key to end after the next incoming request"
}

$Continue = $True
while ($Continue) {

if ($Host.Name -eq "ConsoleHost") {
if ([console]::KeyAvailable) {
$Continue = $False
}
}

$context = $listener.getcontext()
$request = $context.request
$response = $context.response

switch ($request.httpmethod.toUpper()) {

#-Create------------------------------------------------------
"POST" {
switch ($request.url.localpath) {

Default {
$content = $error_htm
$response.StatusCode = 404
}

}
}

#-Read--------------------------------------------------------
"GET" {

switch ($request.url.localpath) {

#---------------------------------------------------------
#-
#- Call
#- http://localhost/001sap.php?systeminfo
#- or
#- http://localhost:8888/systeminfo
#-
#---------------------------------------------------------
"/systeminfo" {
$content = $(Head("System Information")) + `
$(Get-SystemInfo) + $(Tail)
$response.StatusCode = 200
}

#---------------------------------------------------------
#-
#- Call
#- http://localhost/001sap.php?readtable&query_table=USR01&rowcount=3
#- or
#- http://localhost:8888/readtable?query_table=usr05&rowcount=2
#- The first parameter is the table name and the second
#- parameter is the number of rowcount
#-
#---------------------------------------------------------
"/readtable" {
$QueryTable = $request.querystring["query_table"].ToUpper()
$RowCount = $request.querystring["rowcount"]
if ($RowCount -notmatch "^[\d\.]+$") {
$RowCount = 0
}
$content = $(Read-Table $QueryTable $RowCount)
$response.StatusCode = 200
}

Default {
$content = $index_htm
$response.StatusCode = 200
}

}
}

#-Update (Replace/Modify)-------------------------------------
{"PUT", "PATCH" -Contains $_} {

switch ($request.url.localpath) {
Default {
$content = $error_htm
$response.StatusCode = 404
}
}

}

#-Delete------------------------------------------------------
"DELETE" {

switch ($request.url.localpath) {
Default {
$content = $error_htm
$response.StatusCode = 404
}
}

}

#-Other methods are not supported-----------------------------
Default {
$content = $error_htm
$response.StatusCode = 404
}

}

$buffer = [System.Text.Encoding]::UTF8.GetBytes($content)
$response.contentlength64 = $buffer.length
$response.outputstream.write($buffer, 0, $buffer.length)

$response.close()
}
$listener.stop()
}

#-Sub Main------------------------------------------------------------
Function Main() {
If ($PSVersionTable.PSVersion.Major -ge 5) {
Load-NCo
Start-WebServer
}
}

#-Main----------------------------------------------------------------
Main

#-End-------------------------------------------------------------------

 

With the URL readtable I call the function module RFC_READ_TABLE. A few parameters in the query string to set the table name and the max. count of rows. So I get the content of a table back, in this case USR01 in JSON format.

 



 

Now you could ask, for what I need that? Why should I use this stuff, if I can do that all well in the SAP backend? Well, one perspective is e.g. if you work with an older release of SAP, like 7.0x, as a sandbox system and you want to test a web connection, it is tough to get the data in JSON format. It is possible but you have something to do. In PowerShell it is one line. Do you need another format, e.g. XML, it is also one line. It is not necessary to do something in the backend, if you use the standard RFC function modules. It is absolut fast to do this kind of changings in those environment.

 




 

The second example is a little bit more complex. Here you can set the host, client, user name and password. Therefore I defined a tiny form section in the PHP file. Also you can select the operation and set the parameters.

 



 

It is easy, the PHP code passes the parameter from the form into a query string, that's it.

 
<!DOCTYPE html>

<html>

<head>

<title>
SAP.php
</title>

</head>

<body>

<?php

$Result = "";

if (isset($_POST["ashost"])) { $ashost = $_POST["ashost"]; }
if (isset($_POST["sysnr"])) { $sysnr = $_POST["sysnr"]; }
if (isset($_POST["client"])) { $client = $_POST["client"]; }
if (isset($_POST["user"])) { $user = $_POST["user"]; }
if (isset($_POST["passwd"])) { $passwd = $_POST["passwd"]; }
if (isset($ashost) && isset($sysnr) && isset($client) &&
isset($user) && isset($passwd) ) {

if($_POST["operation"] == "systeminfo") {
$QueryString = "systeminfo";
} elseif ($_POST["operation"] == "readtable") {
$QueryString = "readtable";
}
$QueryString = $QueryString . "?ASHOST=" . $ashost .
"&SYSNR=" . $sysnr . "&CLIENT=" . $client .
"&USER=" . $user . "&PASSWD=" . $passwd;
if ($_POST["operation"] == "readtable"){
$QueryString = $QueryString . "&QUERY_TABLE=" .
$_POST["tablename"] . "&ROWCOUNT=" . $_POST["rowcount"];
}

$File = fopen("http://localhost:8888/$QueryString", "r");
if($File <> false) {
while (!feof($File)) {
$Line = fread($File, 4096);
$Result = $Result . $Line;
}
fclose($File);
}
}

?>

<form method="post" action="002sap.php">
ASHOST <input type="text" name="ashost" value="ABAP702" /><br />
SYSNR <input type="text" name="sysnr" value="00" /><br />
CLIENT <input type="text" name="client" value="001" /><br />
USER <input type="text" name="user" value="BCUSER" /><br />
PASSWD <input type="password" name="passwd" value="minisap" /><br />
<br />
<input type="radio" name="operation" value="systeminfo" checked />systeminfo<br />
<input type="radio" name="operation" value="readtable" />readtable<br />
&nbsp;&nbsp;&nbsp;TableName <input type="text" name="tablename" value="SFLIGHT" /><br />
&nbsp;&nbsp;&nbsp;RowCount <input type="number" name="rowcount" value="3" /><br />
<br />
<input type="submit" name="submit" value="Execute" />
</form>
<br />
<textarea name="result" cols="80" rows="25" wrap="off"
style="overflow:scroll;"> <?php echo $Result; ?> </textarea>

</body>

</html>

 

The PowerShell WAS decomposes the query string and passes the parameters to the Get-Destination function. This is the only difference between the PowerShell script below and above.

 
#-Begin-----------------------------------------------------------------

#-Global variables----------------------------------------------------
$url = "http://localhost:8888/" #Listening URL
$CrLf = "`r`n"

#-Here-Strings--------------------------------------------------------
$error_htm = @"
<!DOCTYPE HTML>
<html>
<head>
<title>Error site</title>
</head>
<body>
An error occured
</body>
</html>
"@

$index_htm = @"
<!DOCTYPE HTML>
<html>
<head>
<title>Index site</title>
</head>
<body>
WebServer example
</body>
</html>
"@

#-Sub Load-NCo--------------------------------------------------------
Function Load-NCo() {

$ScriptDir = $PSScriptRoot

If ([Environment]::Is64BitProcess) {
$Path = $ScriptDir + "\x64\"
}
Else {
$Path = $ScriptDir + "\x86\"
}

$File = $Path + "sapnco.dll"; Add-Type -Path $File
$File = $Path + "sapnco_utils.dll"; Add-Type -Path $File

}

#-Function Get-Destination--------------------------------------------
Function Get-Destination([String]$ASHOST, [String]$SYSNR,
[String]$CLIENT, [String]$USER, [String]$PASSWD) {

#-Connection parameters-------------------------------------------
$cfgParams = New-Object SAP.Middleware.Connector.RfcConfigParameters
$cfgParams.Add("NAME", "TEST")
$cfgParams.Add("ASHOST", $ASHOST)
$cfgParams.Add("SYSNR", $SYSNR)
$cfgParams.Add("CLIENT", $CLIENT)
$cfgParams.Add("USER", $USER)
$cfgParams.Add("PASSWD", $PASSWD)

Return [SAP.Middleware.Connector.RfcDestinationManager]::GetDestination($cfgParams)

}

#-Function Get-SystemInfo---------------------------------------------
Function Get-SystemInfo([String]$ASHOST, [String]$SYSNR,
[String]$CLIENT, [String]$USER, [String]$PASSWD) {

$destination = Get-Destination $ASHOST $SYSNR $CLIENT $USER $PASSWD

#-Metadata--------------------------------------------------------
$rfcFunction = `
$destination.Repository.CreateFunction("RFC_SYSTEM_INFO")

#-Call function module--------------------------------------------
Try {
$rfcFunction.Invoke($destination)

$Export = $rfcFunction.GetStructure("RFCSI_EXPORT")

#-Get information---------------------------------------------
$strRet = $CrLf + "Host: " + `
$Export.GetValue("RFCHOST")
$strRet = $strRet + $CrLf + "SysID: " + `
$Export.GetValue("RFCSYSID")
$strRet = $strRet + $CrLf + "DBHost: " + `
$Export.GetValue("RFCDBHOST")
$strRet = $strRet + $CrLf + "DBSys: " + `
$Export.GetValue("RFCDBSYS")
$strRet = $strRet + $CrLf

}
Catch {
Write-Host "Exception occured:`r`n" $_.Exception.Message
Return "<strong>Exception occured</strong><br />" + `
$_.Exception.Message
}

Return $strRet

}

#-Function Read-Table-------------------------------------------------
Function Read-Table([String]$ASHOST, [String]$SYSNR, [String]$CLIENT,
[String]$USER, [String]$PASSWD, [String]$TableName, [Int]$RowCount) {

$destination = Get-Destination $ASHOST $SYSNR $CLIENT $USER $PASSWD

#-Metadata--------------------------------------------------------
$rfcFunction = `
$destination.Repository.CreateFunction("RFC_READ_TABLE")

#-Call function module--------------------------------------------
Try {
$rfcFunction.SetValue("QUERY_TABLE", $TableName)
$rfcFunction.SetValue("DELIMITER", "~")
$rfcFunction.SetValue("ROWCOUNT", $RowCount)

$rfcFunction.Invoke($destination)

$Header = New-Object System.Collections.ArrayList

#-Get field names for the CSV header--------------------------
[SAP.Middleware.Connector.IRfcTable]$Fields = `
$rfcFunction.GetTable("FIELDS")
ForEach ($Field In $Fields) {
[Void]$Header.Add($Field.GetValue("FIELDNAME"))
}

#-Get table data line by line for CSV-------------------------
[SAP.Middleware.Connector.IRfcTable]$Table = `
$rfcFunction.GetTable("DATA")
ForEach ($Line In $Table) {
$CSV = $CSV + $Line.GetValue("WA") + "`r`n"
}

#-Convert CSV to JSON-----------------------------------------
$JSON = $CSV | ConvertFrom-Csv -Delimiter "~" `
-Header $Header | ConvertTo-Json

Return $JSON

}
Catch {
Write-Host "Exception occured:`r`n" $_.Exception.Message
Return "<strong>Exception occured</strong><br />" + `
$_.Exception.Message
}

}

#-Sub Main------------------------------------------------------------
Function Start-WebServer() {

$listener = new-object system.net.httplistener
$listener.prefixes.add($url)
$listener.start()

if ($Host.Name -eq "ConsoleHost") {
[console]::TreatControlCAsInput = $true
write-host "Press any key to end after the next incoming request"
}

$Continue = $True
while ($Continue) {

if ($Host.Name -eq "ConsoleHost") {
if ([console]::KeyAvailable) {
$Continue = $False
}
}

$context = $listener.getcontext()
$request = $context.request
$response = $context.response

$ASHOST = $request.querystring["ASHOST"]
$SYSNR = $request.querystring["SYSNR"]
$CLIENT = $request.querystring["CLIENT"]
$USER = $request.querystring["USER"]
$PASSWD = $request.querystring["PASSWD"]

switch ($request.httpmethod.toUpper()) {

#-Create------------------------------------------------------
"POST" {

switch ($request.url.localpath) {

Default {
$content = $error_htm
$response.StatusCode = 404
}

}
}

#-Read--------------------------------------------------------
"GET" {

switch ($request.url.localpath) {

"/systeminfo" {
$content = $(Get-SystemInfo $ASHOST $SYSNR $CLIENT `
$USER $PASSWD)
$response.StatusCode = 200
}

"/readtable" {
$QueryTable = $request.querystring["QUERY_TABLE"].ToUpper()
$RowCount = $request.querystring["ROWCOUNT"]
if ($RowCount -notmatch "^[\d\.]+$") {
$RowCount = 0
}
$content = $(Read-Table $ASHOST $SYSNR $CLIENT $USER `
$PASSWD $QueryTable $RowCount)
$response.StatusCode = 200
}

Default {
$content = $index_htm
$response.StatusCode = 200
}

}
}

#-Update (Replace/Modify)-------------------------------------
{"PUT", "PATCH" -Contains $_} {

switch ($request.url.localpath) {
Default {
$content = $error_htm
$response.StatusCode = 404
}
}

}

#-Delete------------------------------------------------------
"DELETE" {

switch ($request.url.localpath) {
Default {
$content = $error_htm
$response.StatusCode = 404
}
}

}

#-Other methods are not supported-----------------------------
Default {
$content = $error_htm
$response.StatusCode = 404
}

}

$buffer = [System.Text.Encoding]::UTF8.GetBytes($content)
$response.contentlength64 = $buffer.length
$response.outputstream.write($buffer, 0, $buffer.length)

$response.close()
}
$listener.stop()
}

#-Sub Main------------------------------------------------------------
Function Main() {
If ($PSVersionTable.PSVersion.Major -ge 5) {
Load-NCo
Start-WebServer
}
}

#-Main----------------------------------------------------------------
Main

#-End-------------------------------------------------------------------

 

It makes no difference if you use the x86 or x64 version or a mix from both.

Here an example how to use the readtable operation with the table USR05 and the result in JSON format.

 



 

I think this is a nice little playground to test different perspectives outside from an SAP system, but with the connect to an SAP system. Also it is very interesting to combine different technics like PHP, PowerShell and SAP via NCo. If you think this is an exotic approach, take a look here - Pascal Rodé describes the CIS mobile solution from Synactive, it uses nearly the same method - and much more, e.g. it integrates GuiXT and SAP GUI Scripting. This is also possible with this approach.
Labels in this area