REST and IO redirection
Problem: I have a REST broker, and if I hit a code block, which does IO redirection, the REST reply becomes broken in one of the following ways:
- Binary output
- No output
- First 4096 characters of the reply are missing
Consider the following REST broker:
Class A.REST Extends %CSP.REST
{
XData UrlMap
{
<Routes>
<Route Url="/Test/:redirect" Method="GET" Call="Test"/>
</Routes>
}
ClassMethod Test(Redirect As %Boolean = {$$$YES}) As %Status
{
Set str = $TR($J("",4098)," ","1") // Get a string with the length of 4098 symbols
Do:Redirect ..OutputToStr($Classname(), "UnrelatedAction")
Write str
Return $$$OK
}
ClassMethod UnrelatedAction()
{
Set a=1
}
/// Executes actions and returns device output <br>
/// pObj - OREF or class<br>
/// pMethod - instance or class method to execute respectively<br>
/// pArgs - additional arguments
ClassMethod OutputToStr(pObj, pMethod, pArgs...) As %String [ ProcedureBlock = 0 ]
{
try {
set str=""
//Redirect IO to the current routine - makes use of the labels defined below
use $io::("^"_$ZNAME)
//Enable redirection
do ##class(%Device).ReDirectIO(1)
if $isobject(pObj) {
do $Method(pObj,pMethod,pArgs...)
} elseif $$$comClassDefined(pObj) {
do $ClassMethod(pObj,pMethod,pArgs...)
}
//Disable redirection
do ##class(%Device).ReDirectIO(0)
} catch ex {
if ##class(%Device).ReDirectIO() {
do ##class(%Device).ReDirectIO(0)
}
set str = ""
}
quit str
//Labels that allow for IO redirection
//Read Character - we don't care about reading
rchr(c) quit
//Read a string - we don't care about reading
rstr(sz,to) quit
//Write a character - call the output label
wchr(s) do output($char(s)) quit
//Write a form feed - call the output label
wff() do output($char(12)) quit
//Write a newline - call the output label
wnl() do output($char(13,10)) quit
//Write a string - call the output label
wstr(s) do output(s) quit
//Write a tab - call the output label
wtab(s) do output($char(9)) quit
//Output label - this is where you would handle what you actually want to do.
// in our case, we want to write to str
output(s) set str=str_s quit
}
}After I create a webapplication (name /test) with this broker, I expect that these two requests:
http://localhost:57772/test/Test/0 http://localhost:57772/test/Test/1
Would return the same result (namely, a string containing 4098 symbols "1").
But in reality the second call would return only 2 symbols "1". The only difference between calls, is that method OutputToStr, which contains IO redirection gets called.
How do I modify OutputToStr method, so that it does not affect REST output?
$zv Cache for Windows (x86-64) 2016.1 (Build 656U) Fri Mar 11 2016 17:42:42 EST
Comments
The problem is that REST uses IO redirection itself, and OutputToStr changes the mnemonic routine but doesn't change it back at the end.
For a great example of the general safe approach to cleaning up after IO redirection (restoring to the previous state of everything), see %WriteJSONStreamFromObject in %ZEN.Auxiliary.jsonProvider.
Here's a simple approach that works for me, in this case:
set tOldIORedirected = ##class(%Device).ReDirectIO()
set tOldMnemonic = ##class(%Device).GetMnemonicRoutine()
set tOldIO = $io
try {
set str=""
//Redirect IO to the current routine - makes use of the labels defined below
use $io::("^"_$ZNAME)
//Enable redirection
do ##class(%Device).ReDirectIO(1)
if $isobject(pObj) {
do $Method(pObj,pMethod,pArgs...)
} elseif $$$comClassDefined(pObj) {
do $ClassMethod(pObj,pMethod,pArgs...)
}
} catch ex {
set str = ""
}
//Return to original redirection/mnemonic routine settings
if (tOldMnemonic '= "") {
use tOldIO::("^"_tOldMnemonic)
} else {
use tOldIO
}
do ##class(%Device).ReDirectIO(tOldIORedirected)
quit str
It would be cool if something like this could work instead:
new $io
try {
set str=""
//Redirect IO to the current routine - makes use of the labels defined below
use $io::("^"_$ZNAME)
//Enable redirection
do ##class(%Device).ReDirectIO(1)
if $isobject(pObj) {
do $Method(pObj,pMethod,pArgs...)
} elseif $$$comClassDefined(pObj) {
do $ClassMethod(pObj,pMethod,pArgs...)
}
} catch ex {
set str = ""
}
quit str
But $io can't be new'd.
Thank you Timothy, that worked.
One important note on I/O redirection, from the documentation:
When a process performs I/O redirection, this redirection is performed using the user’s login $ROLES value, not the current $ROLES value.
In other words, if the I/O redirection is happening in a CSP application or privileged routine application context, in which matching or application roles are added that grant permissions on resources protecting assets the I/O functions need, you might get an unexpected <PROTECT> error, with roles seeming to disappear in wstr()/etc.
For the simple case of outputting to a string (as in this example) this is no big deal, but there may be issues with some types of streams, for example, if the stream implementation tries to set/kill globals. (This caught me by surprise the other day.)