|
| 1 | +Class Frontier.DevTools.FakeAgent Extends %RegisteredObject |
| 2 | +{ |
| 3 | + |
| 4 | +Property Cache As %String; |
| 5 | + |
| 6 | +Property Namespace As %String; |
| 7 | + |
| 8 | +Method %OnNew() As %Status |
| 9 | +{ |
| 10 | + set ..Namespace = $namespace |
| 11 | + set ..Cache = "^|"""_..Namespace_"""|Frontier.DevTools.FakeAgent" |
| 12 | + return $$$OK |
| 13 | +} |
| 14 | + |
| 15 | +Method %OnClose() As %Status |
| 16 | +{ |
| 17 | + kill @i%Cache |
| 18 | + return $$$OK |
| 19 | +} |
| 20 | + |
| 21 | +ClassMethod EnsureRequestExists(Output request As Frontier.UnitTest.FakeRequest, url As %String, method As %String, payload As %DynamicObject, auth As %String = "Basic Zm9vOmJhcg==") As %Status [ Internal, Private ] |
| 22 | +{ |
| 23 | + do ##class(%Net.URLParser).Parse(url, .components) |
| 24 | + do ParseQueryString(components("query"), .data) |
| 25 | + |
| 26 | + if '$data(request) || ($data(request) && '$isobject(request)) { |
| 27 | + set request = ##class(Frontier.DevTools.FakeRequest).%New() |
| 28 | + } |
| 29 | + |
| 30 | + set request.URL = components("path") |
| 31 | + set request.CgiEnvs("CONTENT_LENGTH") = 0 |
| 32 | + set request.CgiEnvs("CONTENT_TYPE") = "application/json; charset=utf-8" |
| 33 | + set request.CgiEnvs("HTTP_ACCEPT") = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8" |
| 34 | + set request.CgiEnvs("HTTP_ACCEPT_ENCODING") = "gzip, deflate" |
| 35 | + set request.CgiEnvs("HTTP_ACCEPT_LANGUAGE") = "en-US;q=0.8,en;q=0.7" |
| 36 | + set request.CgiEnvs("HTTP_AUTHORIZATION") = auth |
| 37 | + set request.CgiEnvs("HTTP_HOST") = "localhost:57772" |
| 38 | + set request.CgiEnvs("HTTP_USER_AGENT") = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36 FakeAgent/1.0" |
| 39 | + set request.CgiEnvs("QUERY_STRING") = $get(components("query")) |
| 40 | + set request.CgiEnvs("REQUEST_METHOD") = $$$ucase(method) |
| 41 | + set request.CgiEnvs("REQUEST_SCHEME") = "http" |
| 42 | + set request.CgiEnvs("REQUEST_URI") = components("path") |
| 43 | + set request.CgiEnvs("SERVER_NAME") = "localhost" |
| 44 | + set request.CgiEnvs("SERVER_PORT") = 57772 |
| 45 | + set request.CgiEnvs("SERVER_PROTOCOL") = "HTTP/1.1" |
| 46 | + set request.Content = ##class(%Stream.GlobalCharacter).%New() |
| 47 | + if $isobject(payload) do request.Content.Write(payload.%ToJSON()) |
| 48 | + if $order(data("")) '= "" merge request.Data = data |
| 49 | + |
| 50 | + return $$$OK |
| 51 | + |
| 52 | +ParseQueryString(qs, data) |
| 53 | + if qs = "" quit |
| 54 | + |
| 55 | + set qp = $lfs(qs, "&") |
| 56 | + |
| 57 | + for i=1:1:$ll(qp) { |
| 58 | + set key = $piece($lg(qp, i), "=", 1) |
| 59 | + set value = $piece($lg(qp, i), "=", 2) |
| 60 | + if key '= "" && (value '= "") set data(key, 1) = value |
| 61 | + } |
| 62 | + quit |
| 63 | +} |
| 64 | + |
| 65 | +ClassMethod DispatchRequestAndRespondToStream(dispatcherClass As %String, url As %String, httpMethod As %String, Output str As %Stream.Object) As %Status [ Internal, Private, ProcedureBlock = 0 ] |
| 66 | +{ |
| 67 | + |
| 68 | + new %frontier |
| 69 | + set %frontier = ##class(Frontier.Context).%New(%session, %request, %response, 1) |
| 70 | + |
| 71 | + new oldMnemonic, alreadyRedirected, sc |
| 72 | + |
| 73 | + set sc = $$$OK |
| 74 | + set isRedirected = 0 |
| 75 | + |
| 76 | + set str = ##class(%Stream.GlobalCharacter).%New() |
| 77 | + set alreadyRedirected = ##class(%Device).ReDirectIO() |
| 78 | + set oldMnemonic = "^"_##class(%Device).GetMnemonicRoutine() |
| 79 | + set initIO = $io |
| 80 | + |
| 81 | + try { |
| 82 | + use $io::("^"_$zname) |
| 83 | + |
| 84 | + do ##class(%Device).ReDirectIO(1) |
| 85 | + set isRedirected = 1 |
| 86 | + set sc = $classmethod(dispatcherClass, "DispatchRequest", url, httpMethod) |
| 87 | + do str.Rewind() |
| 88 | + } catch ex { |
| 89 | + set str = "" |
| 90 | + set sc = ex.AsStatus() |
| 91 | + } |
| 92 | + |
| 93 | + |
| 94 | + if oldMnemonic '= "" { |
| 95 | + use initIO::(oldMnemonic) |
| 96 | + } else { |
| 97 | + use oldMnemonic |
| 98 | + } |
| 99 | + |
| 100 | + do ##class(%Device).ReDirectIO(alreadyRedirected) |
| 101 | + |
| 102 | + return sc |
| 103 | + |
| 104 | +wstr(s) Do str.Write(s) Quit |
| 105 | +wchr(a) Do str.Write($char(a)) Quit |
| 106 | +wnl Do str.Write($char(13,10)) Quit |
| 107 | +wff Do str.Write($char(13,10,13,10)) Quit |
| 108 | +wtab(n) Do str.Write($c(9)) Quit |
| 109 | +rstr(len,time) Quit "" |
| 110 | +rchr(time) Quit "" |
| 111 | +} |
| 112 | + |
| 113 | +Method ForgeRequest(url As %String, method As %String = "GET", payload As %DynamicAbstractObject = {{}}, auth As %String = "", Output sc As %Status = {$$$OK}) As %Stream.GlobalBinary [ PublicList = (%session, %response) ] |
| 114 | +{ |
| 115 | + |
| 116 | + new %request, %session, %response |
| 117 | + |
| 118 | + // Makes sure that any attempts to change the namespace internally ends up in the original one. |
| 119 | + set fromNamespace = ..Namespace |
| 120 | + set str = "" |
| 121 | + |
| 122 | + set %session = ##class(%CSP.Session).%New(-1, 0) |
| 123 | + set %response = ##class(%CSP.Response).%New() |
| 124 | + |
| 125 | + try { |
| 126 | + set urlWithInitialSlash = $select($extract(url) '= "/" : "/"_url, 1: url) |
| 127 | + set appInfo = ..GetApplicationInfoFromUrl(urlWithInitialSlash) |
| 128 | + do ..EnsureRequestExists(.%request, url, method, payload, auth) |
| 129 | + set %request.Application = appInfo.Name |
| 130 | + $$$ThrowOnError(..DispatchRequestAndRespondToStream(appInfo.DispatchClass, %request.URL, method, .str)) |
| 131 | + } catch ex { |
| 132 | + set sc = ex.AsStatus() |
| 133 | + set ^mtempFrontier("err", $i(i)) = sc |
| 134 | + if '$isobject(str) set str = ##class(%Stream.GlobalBinary).%New() |
| 135 | + } |
| 136 | + kill %request, %session, %response |
| 137 | + |
| 138 | + set $namespace = fromNamespace |
| 139 | + |
| 140 | + return str |
| 141 | +} |
| 142 | + |
| 143 | +Method GetApplicationInfoFromUrl(url As %String) As %DynamicObject |
| 144 | +{ |
| 145 | + |
| 146 | + #define APPCACHE @i%Cache |
| 147 | + |
| 148 | + // Cache matches to prevent roundtrips to %SYS. |
| 149 | + if $data($$$APPCACHE) { |
| 150 | + set index = $lf($$$APPCACHE, url) |
| 151 | + if index > 0 return $$ListToJSON(index) |
| 152 | + } |
| 153 | + |
| 154 | + set $namespace = "%SYS" |
| 155 | + |
| 156 | + set result = {} |
| 157 | + |
| 158 | + // Revert the ordering so that longer are considered first, note that the longer the path is higher is similarity with the url. |
| 159 | + set rows = ##class(%SQL.Statement).%ExecDirect(, "SELECT TOP 1 Name, DispatchClass FROM SECURITY.APPLICATIONS WHERE ? %STARTSWITH Name ORDER BY LEN(Name) DESC", url) |
| 160 | + if rows.%Next() { |
| 161 | + set $list($$$APPCACHE, *+1) = url |
| 162 | + set index = $ll($$$APPCACHE) |
| 163 | + set $list($$$APPCACHE, *+1) = rows.%Get("Name") |
| 164 | + set $list($$$APPCACHE, *+1) = rows.%Get("DispatchClass") |
| 165 | + set result = $$ListToJSON(index) |
| 166 | + } |
| 167 | + |
| 168 | + set $namespace = ..Namespace |
| 169 | + |
| 170 | + return result |
| 171 | + |
| 172 | +ListToJSON(urlIndex) |
| 173 | + return { |
| 174 | + "Name": ($lg($$$APPCACHE, urlIndex + 1)), |
| 175 | + "DispatchClass": ($lg($$$APPCACHE, urlIndex + 2)) |
| 176 | + } |
| 177 | +} |
| 178 | + |
| 179 | +ClassMethod Request(url As %String, method As %String = "GET", payload As %DynamicAbstractObject = {{}}, auth As %String = "", outputToDevice As %Boolean = 0, Output sc As %Status = {$$$OK}) As %Stream.GlobalBinary |
| 180 | +{ |
| 181 | + set agent = ..%New() |
| 182 | + if outputToDevice = 1 { |
| 183 | + set str = agent.ForgeRequest(url, method, payload, auth, .sc) |
| 184 | + do str.OutputToDevice() |
| 185 | + } else { |
| 186 | + return agent.ForgeRequest(url, method, payload, auth, .sc) |
| 187 | + } |
| 188 | + return str |
| 189 | +} |
| 190 | + |
| 191 | +} |
| 192 | + |
0 commit comments