1- Class Grongier .Service .WSGI Extends ( %RegisteredObject , % CSP .REST ) [ ServerOnly = 1 ]
1+ Class Grongier .Service .WSGI Extends % CSP .REST [ ServerOnly = 1 ]
22{
33
44Parameter CLASSPATHS ;
@@ -7,99 +7,304 @@ Parameter MODULENAME;
77
88Parameter APPNAME ;
99
10- /// Helper method to write data to the output stream
11- ClassMethod write (data )
12- {
13- w data
14- }
10+ Parameter DEVMODE = 1 ;
1511
16- /// Helper to build the environ
17- ClassMethod GetEnviron ( ) As %SYS . Python
12+ /// This method matches the request and method and calls the dispatcher
13+ ClassMethod Page ( skipheader As %Boolean = 0 ) As %Status [ ProcedureBlock = 1 ]
1814{
19- //set builtins
20- set builtins = ##class (%SYS.Python ).Import (" builtins" )
15+ #dim tSC As %Status = $$$OK
16+ #dim e As %Exception.AbstractException
17+
18+ #dim tAuthorized ,tRedirected As %Boolean
19+ #dim tRedirectRoutine ,tURL As %String = " "
20+ #dim %response As %CSP.Response
21+
22+ Try {
2123
22- //import dict to create environ
23- set dict = builtins . dict ()
24+ #; Ensure that we honor the requested charset
25+ Set %response . CharSet =..#CHARSET
2426
25- #dim %request As %CSP.Request
27+ #; Ensure that we honor the requested CONTENTTYPE
28+ If ..#CONTENTTYPE'=" " Set %response .ContentType =..#CONTENTTYPE
2629
27- //set environ
28- do dict ." __setitem__" (" SERVER_NAME" , $System .INetInfo .LocalHostName ())
29- do dict ." __setitem__" (" SERVER_PORT" , " " )
30- do dict ." __setitem__" (" SERVER_PROTOCOL" , " HTTP/1.1" )
31- do dict ." __setitem__" (" SERVER_SOFTWARE" , " IRIS" )
32- do dict ." __setitem__" (" SCRIPT_NAME" , ..#APPNAME)
33- do dict ." __setitem__" (" REQUEST_METHOD" , %request .Method )
34- do dict ." __setitem__" (" CONTENT_TYPE" , %request .ContentType )
35- do dict ." __setitem__" (" CHARSET" , %request .CharSet )
36- Set app =$$getapp ^%SYS .cspServer (%request .URL ,.path ,.match ,.updatedurl )
37- do dict ." __setitem__" (" PATH_INFO" , $extract (updatedurl ,$length (path ),*))
30+ #; Ensure that we honor the requested HTTP_ACCEPT_LANGUAGE
31+ Set %response .Domain = ..#DOMAIN
32+ Do %response .MatchLanguage ()
33+
34+ #; Record if device re-direction is already active
35+ Set tRedirected =##class (%Library.Device ).ReDirectIO ()
36+
37+ #; Record the redirect routine
38+ Set tRedirectRoutine =$System .Device .GetMnemonicRoutine ()
39+
40+ if ..#DEVMODE {
41+ #; Not so pretty but help to for reload the WSGI application
42+ do outputError ^%SYS .cspServer2 (" " ," " ," " ," " )
43+ }
44+ else {
45+ #; Now switch to using THIS routine for device redirection
46+ Use $io ::(" ^%SYS.cspServer2" )
3847
39- // to extract the query string
48+ #; Switch device redirection on (may already be on but thats ok)
49+ Do ##class (%Library.Device ).ReDirectIO (1 )
50+ }
4051
41- return dict
42- }
52+ #; Ensure that the application is defined (security check)
53+ If $$$GetSecurityApplicationsDispatchClass(%request .AppData )=" " {
54+
55+ #; Report not authorized
56+ Set tSC =..Http403 ()
57+
58+ #; Done
59+ Quit
60+ }
61+
62+ #; GgiEnvs are not defined in the CSP shell
63+ Set tURL =$Get (%request .CgiEnvs (" CSPLIB" ))
64+ If tURL =" " Set tURL =%request .URL
65+
66+ #; Do an access check
67+ Set tSC =..AccessCheck (.tAuthorized )
68+ If $$$ISERR(tSC ) Quit
4369
44- /// Implement a singleton pattern to get the python app
45- ClassMethod GetPyhonApp () As %SYS .Python
46- {
47- if ..#CLASSPATHS '=" " {
48- set sys = ##class (%SYS.Python ).Import (" sys" )
49- set delimiter = $s ($system .Version .GetOS ()=" Windows" :" ;" ,1 :" :" )
50- set extraClasspaths = $tr (..#CLASSPATHS,delimiter ," |" )
51- for i =1 :1 :$l (extraClasspaths ," |" ) {
52- set onePath = $p (extraClasspaths ," |" ,i )
53- set onePath = ##class (%File ).NormalizeDirectory (onePath )
54- if onePath ?1 " $$IRISHOME" 1 P .E set onePath = $e ($system .Util .InstallDirectory (),1 ,*-1 )_$e (onePath ,11 ,*)
55- if onePath '=" " do sys .path .append (onePath )
70+ If tAuthorized =0 {
71+
72+ #; Don't want the session token
73+ Set %response .OutputSessionToken =0
74+
75+ #; Set the Http Status
76+ Set %response .Status =..#HTTP403FORBIDDEN
77+
78+ #; Done
79+ Quit
5680 }
81+
82+ #; Extract the match url from the application name
83+ Set tMatchUrl = " /" _$Extract (tURL ,$Length (%request .Application )+1 ,*)
84+
85+ #; Dispatch the request
86+ Set tSC =..DispatchRequest (tMatchUrl ,%request .Method )
87+
88+ } Catch (e ) {
89+ Set tSC =e .AsStatus ()
90+ }
91+
92+ If $$$ISERR(tSC ) {
93+
94+ #; Don't want the session token
95+ Set %response .OutputSessionToken =0
96+
97+ Do ..Http500 (##class (%Exception.StatusException ).CreateFromStatus (tSC ))
98+ }
99+
100+ #; Ensure that at least something is written out as the body
101+ #; This will trigger the device redirect capture and force headers to be written
102+ #; (if not already done)
103+ Write " "
104+
105+ #; Reset redirect device if necessary
106+ If tRedirected {
107+
108+ #; Use the original redirected routine
109+ Use $io ::(" ^" _tRedirectRoutine )
110+
111+ #; Switch device redirection on
112+ Do ##class (%Library.Device ).ReDirectIO (1 )
57113 }
114+
115+ #; Any errors should have been caught and reported
116+ Quit $$$OK
117+ }
58118
59- //import module
60- set module = ##class (%SYS.Python ).Import (..#MODULENAME)
119+ ClassMethod OnPreDispatch (
120+ pUrl As %String ,
121+ pMethod As %String ,
122+ ByRef pContinue As %Boolean ) As %Status
123+ {
124+ Set path = ..#CLASSPATHS
125+ Set appName = ..#APPNAME
126+ Set module = ..#MODULENAME
127+ Set devmode = ..#DEVMODE
128+ Set pContinue = 1
129+ Do ..DispatchREST (pUrl , path , appName , module , devmode )
130+ Quit $$$OK
131+ }
61132
62- //set builtins
63- set builtins = ##class (%SYS.Python ).Import (" builtins" )
133+ ClassMethod DispatchREST (
134+ PathInfo As %String ,
135+ appPath As %String ,
136+ appName As %String ,
137+ module As %String ,
138+ devmode As %Boolean = 1 ) As %Status
139+ {
140+ Set builtins = ##CLASS (%SYS.Python ).Builtins ()
141+ Set interface = ##CLASS (%SYS.Python ).Import (" grongier.pex.wsgi.handlers" )
142+ Set rawformdata = " "
143+ Set environ = builtins .dict ()
144+ Set key = %request .NextCgiEnv (" " )
64145
65- //set app
66- set application = builtins .getattr (module , ..#APPNAME)
146+ // Let's check if the WSGI application has been loaded or not for this session.
147+
148+ If (($DATA (%session .Data (" application" )) && $ISOBJECT (%session .Data (" application" ))) && 'devmode ) {
149+ Set application = %session .Data (" Application" )
150+ }
151+ Else {
67152
68- Return application
69- }
153+ Set application = ..GetPythonClass (appName , module , appPath )
154+ If application = " " {
155+ throw ##class (%Exception.General ).%New (" Error loading WSGI application: " _module _" ." _appName _" from " _appPath )
156+ }
157+ Else {
158+ Set %session .Data (" Application" ) = application
159+ }
160+ }
70161
71- ClassMethod Page (skipheader As %Boolean = 1 ) As %Status [ Internal , ServerOnly = 1 ]
72- {
73- Try {
74-
75- //set environ
76- set environ = ..GetEnviron ()
162+ // Editing some CGI variables that may be incorrect in %request
163+ // Also filling in environ with as many CGI variables as possible from %request
164+ // WSGI states that all CGI variables are valid and should be included if possible
165+ While (key '=" " ) {
166+ Set value = %request .GetCgiEnv (key )
167+ If key = " PATH_INFO" {
168+ Set app =$$getapp ^%SYS .cspServer (%request .URL ,.path ,.match ,.updatedurl )
169+ Set value = $EXTRACT (updatedurl ,$LENGTH (path ),*)
170+ }
171+ If key = " SCRIPT_NAME" {
172+ //%request will sometimes have Script_name include Path_info
173+ Set value = $PIECE (%request .Application , " /" ,1 ,*-1 )
174+ }
175+ Do environ ." __setitem__" (key ,value )
176+ Set key = %request .NextCgiEnv (key )
177+ }
77178
78- //import sys
79- set sys = ##class (%SYS.Python ).Import (" sys" )
179+ //Have to set up a correct wsgi.input stream from %request
180+ Set stream = %request .Content
181+
182+ Set contentType = %request .ContentType
183+ Set contentLength = 0
80184
81- //set stdin
82- set builtins = ##class (%SYS.Python ).Import (" builtins" )
83- set ba = builtins .bytearray ()
84-
85- while %request .Content .AtEnd = 0 {
86- do ba .extend (##class (%SYS.Python ).Bytes (%request .Content .Read ()))
87- }
88- //set handler
89- set handler = ##class (%SYS.Python ).Import (" grongier.pex.wsgi.handlers" ).IrisHandler (ba , sys .stdout , sys .stderr ,environ )
185+ If contentType = " application/x-www-form-urlencoded" {
186+ Set formdict = builtins .dict ()
187+ Set key = $ORDER (%request .Data (" " ))
188+ While (key '=" " ) {
189+ Set value = $GET (%request .Data (key ,1 ))
190+ Do formdict ." __setitem__" (key ,value )
191+ Set key = $ORDER (%request .Data (key ))
192+ }
193+ Do environ ." __setitem__" (" formdata" , formdict )
194+ }
195+ ElseIf contentType = " multipart/form-data" {
196+ Set boundary = $PIECE (%request .GetCgiEnv (" CONTENT_TYPE" ), " =" ,2 )
197+ Set stream = ##CLASS (%CSP.BinaryStream ).%New ()
90198
91- // get a singleton app
92- set application = ..GetPyhonApp ()
199+ Do stream .Write ($CHAR (13 ,10 ))
93200
94- //run app
95- do handler .run (application )
201+ //Get the Form Data values
96202
97- }
98- Catch ex {
99- return ex .AsStatus ()
100- }
203+ Set key = $ORDER (%request .Data (" " ))
204+ While (key '=" " ) {
205+ Do stream .Write (" --" )
206+ Do stream .Write (boundary )
207+ Do stream .Write ($CHAR (13 ,10 ))
208+ Set value = $GET (%request .Data (key ,1 ))
209+ Do stream .Write (" Content-Disposition: form-data; name=" )
210+ Do stream .Write (" " " " _key _" " " " )
211+ Do stream .Write ($CHAR (13 ,10 ,13 ,10 ))
212+ Do stream .Write (value )
213+ Do stream .Write ($CHAR (13 ,10 ))
214+ Set key = $ORDER (%request .Data (key ))
215+ }
216+
217+ //Now get the possible MIME data streams
218+ Set key = %request .NextMimeData (" " )
219+ While key '=" " {
220+ Set numMimeStreams = %request .CountMimeData (key )
221+ Set index = %request .NextMimeDataIndex (key , " " )
222+ Do stream .Write (" --" )
223+ Do stream .Write (boundary )
224+ Do stream .Write ($CHAR (13 ,10 ))
225+ If numMimeStreams > 1 {
226+ //I need to create a boundary for a nested multipart content type
227+ Set internalboundary = " --"
228+ For i = 1 : 1 : 7 {
229+ Set internalboundary = internalboundary _ $RANDOM (10 )
230+ }
231+ While index '= " " {
232+ Set mimestream = %request .GetMimeData (key , index )
233+ Set headers = mimestream .Headers
234+ Do stream .Write (" --" )
235+ Do stream .Write (internalboundary )
236+ Do stream .Write ($CHAR (13 ,10 ))
237+ Do stream .Write (headers )
238+ Do stream .Write ($CHAR (13 ,10 ,13 ,10 ))
239+ Set sc = stream .CopyFrom (mimestream )
240+ //TODO error handling
241+ Do stream .Write ($CHAR (13 ,10 ))
242+ Set index = %request .NextMimeDataIndex (key , index )
243+ }
244+ Do stream .Write (" --" )
245+ Do stream .Write (internalboundary )
246+ Do stream .Write (" --" )
247+ }
248+ Else {
249+ Set mimestream = %request .GetMimeData (key , index )
250+ Set headers = mimestream .Headers
251+ Do stream .Write (headers )
252+ Do stream .Write ($CHAR (13 ,10 ,13 ,10 ))
253+ Set sc = stream .CopyFrom (mimestream )
254+ //TODO error handling
255+ Do stream .Write ($CHAR (13 ,10 ))
256+ }
257+ Set key = %request .NextMimeData (key )
258+ }
259+ Do stream .Write (" --" )
260+ Do stream .Write (boundary )
261+ Do stream .Write (" --" )
262+ Do stream .Rewind ()
263+ }
264+
265+
266+ Try {
267+ Do interface ." make_request" (environ , stream , application , appPath )
268+ }
269+ Catch exception {
270+ throw exception
271+ }
272+ Quit $$$OK
273+ }
274+
275+ ClassMethod GetPythonClass (
276+ pClassname As %String ,
277+ pModule As %String ,
278+ pClasspath As %String ) As %SYS .Python
279+ {
280+ Try {
281+ If pClasspath '=" " {
282+ set sys = ##class (%SYS.Python ).Import (" sys" )
283+
284+ for i =0 :1 :(sys .path ." __len__" ()-1 ) {
285+ Try {
286+ if sys .path ." __getitem__" (i ) = pClasspath {
287+ do sys .path ." __delitem__" (i )
288+ }
289+ }
290+ Catch ex {
291+ // do nothing
292+ }
293+
294+ }
295+ do sys .path .insert (0 , pClasspath )
296+ }
101297
102- quit $$$OK
298+ Set importlib = ##class (%SYS.Python ).Import (" importlib" )
299+ Set builtins = ##class (%SYS.Python ).Import (" builtins" )
300+ Set module = importlib ." import_module" (pModule )
301+ Set class = builtins .getattr (module , pClassname )
302+ }
303+ Catch ex {
304+ throw ##class (%Exception.General ).%New (" Error loading WSGI application: " _pModule _" ." _pClassname _" from " _pClasspath )
305+ }
306+
307+ Quit class
103308}
104309
105310}
0 commit comments