Production Ready Macros for SAS Application Developers
https://github.com/sasjs/core
mm_createwebservice.sas
Go to the documentation of this file.
1/**
2 @file mm_createwebservice.sas
3 @brief Create a Web Ready Stored Process
4 @details This macro creates a Type 2 Stored Process with the mm_webout macro
5 included as pre-code.
6Usage:
7
8 %* compile macros ;
9 filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
10 %inc mc;
11
12 %* parmcards lets us write to a text file from open code ;
13 filename ft15f001 temp;
14 parmcards4;
15 %* do some sas, any inputs are now already WORK tables;
16 data example1 example2;
17 set sashelp.class;
18 run;
19 %* send data back;
20 %webout(OPEN)
21 %webout(ARR,example1) * Array format, fast, suitable for large tables ;
22 %webout(OBJ,example2) * Object format, easier to work with ;
23 %webout(CLOSE)
24 ;;;;
25 %mm_createwebservice(path=/Public/app/common,name=appInit,code=ft15f001)
26
27 <h4> SAS Macros </h4>
28 @li mm_createstp.sas
29 @li mf_getuser.sas
30 @li mm_createfolder.sas
31 @li mm_deletestp.sas
32
33 @param path= The full path (in SAS Metadata) where the service will be created
34 @param name= Stored Process name. Avoid spaces - testing has shown that
35 the check to avoid creating multiple STPs in the same folder with the same
36 name does not work when the name contains spaces.
37 @param desc= The description of the service (optional)
38 @param precode= Space separated list of filerefs, pointing to the code that
39 needs to be attached to the beginning of the service (optional)
40 @param code=(ft15f001) Space seperated fileref(s) of the actual code to be
41 added
42 @param server=(SASApp) The server which will run the STP. Server name or uri
43 is fine.
44 @param mDebug=(0) set to 1 to show debug messages in the log
45 @param replace=(YES) select NO to avoid replacing an existing service in that
46 location
47 @param adapter=(sasjs) the macro uses the sasjs adapter by default. To use
48 another adapter, add a (different) fileref here.
49
50 @version 9.2
51 @author Allan Bowe
52
53**/
54
55%macro mm_createwebservice(path=
56 ,name=initService
57 ,precode=
58 ,code=ft15f001
59 ,desc=This stp was created automagically by the mm_createwebservice macro
60 ,mDebug=0
61 ,server=SASApp
62 ,replace=YES
63 ,adapter=sasjs
64)/*/STORE SOURCE*/;
65
66%if &syscc ge 4 %then %do;
67 %put &=syscc - &sysmacroname will not execute in this state;
68 %return;
69%end;
70
71%local mD;
72%if &mDebug=1 %then %let mD=;
73%else %let mD=%str(*);
74%&mD.put Executing mm_createwebservice.sas;
75%&mD.put _local_;
76
77* remove any trailing slash ;
78%if "%substr(&path,%length(&path),1)" = "/" %then
79 %let path=%substr(&path,1,%length(&path)-1);
80
81/**
82 * Add webout macro
83 * These put statements are auto generated - to change the macro, change the
84 * source (mm_webout) and run `build.py`
85 */
86filename sasjs temp;
87data _null_;
88 file sasjs lrecl=3000 ;
89 put "/* Created on %sysfunc(datetime(),datetime19.) by %mf_getuser() */";
90/* WEBOUT BEGIN */
91 put ' ';
92 put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y,engine=DATASTEP,dbg=0 ';
93 put ')/*/STORE SOURCE*/; ';
94 put '%put output location=&jref; ';
95 put '%if &action=OPEN %then %do; ';
96 put ' options nobomfile; ';
97 put ' data _null_;file &jref encoding=''utf-8''; ';
98 put ' put ''{"START_DTTM" : "'' "%sysfunc(datetime(),datetime20.3)" ''"''; ';
99 put ' run; ';
100 put '%end; ';
101 put '%else %if (&action=ARR or &action=OBJ) %then %do; ';
102 put ' options validvarname=upcase; ';
103 put ' data _null_;file &jref mod encoding=''utf-8''; ';
104 put ' put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":"; ';
105 put ' ';
106 put ' %if &engine=PROCJSON %then %do; ';
107 put ' data;run;%let tempds=&syslast; ';
108 put ' proc sql;drop table &tempds; ';
109 put ' data &tempds /view=&tempds;set &ds; ';
110 put ' %if &fmt=N %then format _numeric_ best32.;; ';
111 put ' proc json out=&jref pretty ';
112 put ' %if &action=ARR %then nokeys ; ';
113 put ' ;export &tempds / nosastags fmtnumeric; ';
114 put ' run; ';
115 put ' proc sql;drop view &tempds; ';
116 put ' %end; ';
117 put ' %else %if &engine=DATASTEP %then %do; ';
118 put ' %local cols i tempds; ';
119 put ' %let cols=0; ';
120 put ' %if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1 %then %do; ';
121 put ' %put &sysmacroname: &ds NOT FOUND!!!; ';
122 put ' %return; ';
123 put ' %end; ';
124 put ' %if &fmt=Y %then %do; ';
125 put ' %put converting every variable to a formatted variable; ';
126 put ' /* see mp_ds2fmtds.sas for source */ ';
127 put ' proc contents noprint data=&ds ';
128 put ' out=_data_(keep=name type length format formatl formatd varnum); ';
129 put ' run; ';
130 put ' proc sort; ';
131 put ' by varnum; ';
132 put ' run; ';
133 put ' %local fmtds; ';
134 put ' %let fmtds=%scan(&syslast,2,.); ';
135 put ' /* prepare formats and varnames */ ';
136 put ' data _null_; ';
137 put ' if _n_=1 then call symputx(''nobs'',nobs,''l''); ';
138 put ' set &fmtds end=last nobs=nobs; ';
139 put ' name=upcase(name); ';
140 put ' /* fix formats */ ';
141 put ' if type=2 or type=6 then do; ';
142 put ' length fmt $49.; ';
143 put ' if format='''' then fmt=cats(''$'',length,''.''); ';
144 put ' else if formatl=0 then fmt=cats(format,''.''); ';
145 put ' else fmt=cats(format,formatl,''.''); ';
146 put ' newlen=max(formatl,length); ';
147 put ' end; ';
148 put ' else do; ';
149 put ' if format='''' then fmt=''best.''; ';
150 put ' else if formatl=0 then fmt=cats(format,''.''); ';
151 put ' else if formatd=0 then fmt=cats(format,formatl,''.''); ';
152 put ' else fmt=cats(format,formatl,''.'',formatd); ';
153 put ' /* needs to be wide, for datetimes etc */ ';
154 put ' newlen=max(length,formatl,24); ';
155 put ' end; ';
156 put ' /* 32 char unique name */ ';
157 put ' newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27); ';
158 put ' ';
159 put ' call symputx(cats(''name'',_n_),name,''l''); ';
160 put ' call symputx(cats(''newname'',_n_),newname,''l''); ';
161 put ' call symputx(cats(''len'',_n_),newlen,''l''); ';
162 put ' call symputx(cats(''fmt'',_n_),fmt,''l''); ';
163 put ' call symputx(cats(''type'',_n_),type,''l''); ';
164 put ' run; ';
165 put ' data &fmtds; ';
166 put ' /* rename on entry */ ';
167 put ' set &ds(rename=( ';
168 put ' %local i; ';
169 put ' %do i=1 %to &nobs; ';
170 put ' &&name&i=&&newname&i ';
171 put ' %end; ';
172 put ' )); ';
173 put ' %do i=1 %to &nobs; ';
174 put ' length &&name&i $&&len&i; ';
175 put ' &&name&i=left(put(&&newname&i,&&fmt&i)); ';
176 put ' drop &&newname&i; ';
177 put ' %end; ';
178 put ' if _error_ then call symputx(''syscc'',1012); ';
179 put ' run; ';
180 put ' %let ds=&fmtds; ';
181 put ' %end; /* &fmt=Y */ ';
182 put ' data _null_;file &jref mod encoding=''utf-8''; ';
183 put ' put "["; call symputx(''cols'',0,''l''); ';
184 put ' proc sort ';
185 put ' data=sashelp.vcolumn(where=(libname=''WORK'' & memname="%upcase(&ds)")) ';
186 put ' out=_data_; ';
187 put ' by varnum; ';
188 put ' ';
189 put ' data _null_; ';
190 put ' set _last_ end=last; ';
191 put ' call symputx(cats(''name'',_n_),name,''l''); ';
192 put ' call symputx(cats(''type'',_n_),type,''l''); ';
193 put ' call symputx(cats(''len'',_n_),length,''l''); ';
194 put ' if last then call symputx(''cols'',_n_,''l''); ';
195 put ' run; ';
196 put ' ';
197 put ' proc format; /* credit yabwon for special null removal */ ';
198 put ' value bart ._ - .z = null ';
199 put ' other = [best.]; ';
200 put ' ';
201 put ' data;run; %let tempds=&syslast; /* temp table for spesh char management */ ';
202 put ' proc sql; drop table &tempds; ';
203 put ' data &tempds/view=&tempds; ';
204 put ' attrib _all_ label=''''; ';
205 put ' %do i=1 %to &cols; ';
206 put ' %if &&type&i=char %then %do; ';
207 put ' length &&name&i $32767; ';
208 put ' format &&name&i $32767.; ';
209 put ' %end; ';
210 put ' %end; ';
211 put ' set &ds; ';
212 put ' format _numeric_ bart.; ';
213 put ' %do i=1 %to &cols; ';
214 put ' %if &&type&i=char %then %do; ';
215 put ' &&name&i=''"''!!trim(prxchange(''s/"/\"/'',-1, ';
216 put ' prxchange(''s/''!!''0A''x!!''/\n/'',-1, ';
217 put ' prxchange(''s/''!!''0D''x!!''/\r/'',-1, ';
218 put ' prxchange(''s/''!!''09''x!!''/\t/'',-1, ';
219 put ' prxchange(''s/\\/\\\\/'',-1,&&name&i) ';
220 put ' )))))!!''"''; ';
221 put ' %end; ';
222 put ' %end; ';
223 put ' run; ';
224 put ' /* write to temp loc to avoid _webout truncation ';
225 put ' - https://support.sas.com/kb/49/325.html */ ';
226 put ' filename _sjs temp lrecl=131068 encoding=''utf-8''; ';
227 put ' data _null_; file _sjs lrecl=131068 encoding=''utf-8'' mod; ';
228 put ' set &tempds; ';
229 put ' if _n_>1 then put "," @; put ';
230 put ' %if &action=ARR %then "[" ; %else "{" ; ';
231 put ' %do i=1 %to &cols; ';
232 put ' %if &i>1 %then "," ; ';
233 put ' %if &action=OBJ %then """&&name&i"":" ; ';
234 put ' &&name&i ';
235 put ' %end; ';
236 put ' %if &action=ARR %then "]" ; %else "}" ; ; ';
237 put ' proc sql; ';
238 put ' drop view &tempds; ';
239 put ' /* now write the long strings to _webout 1 byte at a time */ ';
240 put ' data _null_; ';
241 put ' length filein 8 fileid 8; ';
242 put ' filein = fopen("_sjs",''I'',1,''B''); ';
243 put ' fileid = fopen("&jref",''A'',1,''B''); ';
244 put ' rec = ''20''x; ';
245 put ' do while(fread(filein)=0); ';
246 put ' rc = fget(filein,rec,1); ';
247 put ' rc = fput(fileid, rec); ';
248 put ' rc =fwrite(fileid); ';
249 put ' end; ';
250 put ' rc = fclose(filein); ';
251 put ' rc = fclose(fileid); ';
252 put ' run; ';
253 put ' filename _sjs clear; ';
254 put ' data _null_; file &jref mod encoding=''utf-8''; ';
255 put ' put "]"; ';
256 put ' run; ';
257 put ' %end; ';
258 put '%end; ';
259 put ' ';
260 put '%else %if &action=CLOSE %then %do; ';
261 put ' data _null_;file &jref encoding=''utf-8'' mod; ';
262 put ' put "}"; ';
263 put ' run; ';
264 put '%end; ';
265 put '%mend mp_jsonout; ';
266 put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=Y); ';
267 put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug ';
268 put ' sasjs_tables; ';
269 put '%local i tempds jsonengine; ';
270 put ' ';
271 put '/* see https://github.com/sasjs/core/issues/41 */ ';
272 put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON; ';
273 put '%else %let jsonengine=DATASTEP; ';
274 put ' ';
275 put ' ';
276 put '%if &action=FETCH %then %do; ';
277 put ' %if %str(&_debug) ge 131 %then %do; ';
278 put ' options mprint notes mprintnest; ';
279 put ' %end; ';
280 put ' %let _webin_file_count=%eval(&_webin_file_count+0); ';
281 put ' /* now read in the data */ ';
282 put ' %do i=1 %to &_webin_file_count; ';
283 put ' %if &_webin_file_count=1 %then %do; ';
284 put ' %let _webin_fileref1=&_webin_fileref; ';
285 put ' %let _webin_name1=&_webin_name; ';
286 put ' %end; ';
287 put ' data _null_; ';
288 put ' infile &&_webin_fileref&i termstr=crlf; ';
289 put ' input; ';
290 put ' call symputx(''input_statement'',_infile_); ';
291 put ' putlog "&&_webin_name&i input statement: " _infile_; ';
292 put ' stop; ';
293 put ' data &&_webin_name&i; ';
294 put ' infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8''; ';
295 put ' input &input_statement; ';
296 put ' %if %str(&_debug) ge 131 %then %do; ';
297 put ' if _n_<20 then putlog _infile_; ';
298 put ' %end; ';
299 put ' run; ';
300 put ' %let sasjs_tables=&sasjs_tables &&_webin_name&i; ';
301 put ' %end; ';
302 put '%end; ';
303 put ' ';
304 put '%else %if &action=OPEN %then %do; ';
305 put ' /* fix encoding */ ';
306 put ' OPTIONS NOBOMFILE; ';
307 put ' ';
308 put ' /** ';
309 put ' * check xengine type to avoid the below err message: ';
310 put ' * > Function is only valid for filerefs using the CACHE access method. ';
311 put ' */ ';
312 put ' data _null_; ';
313 put ' set sashelp.vextfl(where=(fileref="_WEBOUT")); ';
314 put ' if xengine=''STREAM'' then do; ';
315 put ' rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8"); ';
316 put ' end; ';
317 put ' run; ';
318 put ' ';
319 put ' /* setup json */ ';
320 put ' data _null_;file &fref encoding=''utf-8''; ';
321 put ' %if %str(&_debug) ge 131 %then %do; ';
322 put ' put ''>>weboutBEGIN<<''; ';
323 put ' %end; ';
324 put ' put ''{"START_DTTM" : "'' "%sysfunc(datetime(),datetime20.3)" ''"''; ';
325 put ' run; ';
326 put ' ';
327 put '%end; ';
328 put ' ';
329 put '%else %if &action=ARR or &action=OBJ %then %do; ';
330 put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref ';
331 put ' ,engine=&jsonengine,dbg=%str(&_debug) ';
332 put ' ) ';
333 put '%end; ';
334 put '%else %if &action=CLOSE %then %do; ';
335 put ' %if %str(&_debug) ge 131 %then %do; ';
336 put ' /* if debug mode, send back first 10 records of each work table also */ ';
337 put ' options obs=10; ';
338 put ' data;run;%let tempds=%scan(&syslast,2,.); ';
339 put ' ods output Members=&tempds; ';
340 put ' proc datasets library=WORK memtype=data; ';
341 put ' %local wtcnt;%let wtcnt=0; ';
342 put ' data _null_; ';
343 put ' set &tempds; ';
344 put ' if not (upcase(name) =:"DATA"); /* ignore temp datasets */ ';
345 put ' i+1; ';
346 put ' call symputx(''wt''!!left(i),name,''l''); ';
347 put ' call symputx(''wtcnt'',i,''l''); ';
348 put ' data _null_; file &fref mod encoding=''utf-8''; ';
349 put ' put ",""WORK"":{"; ';
350 put ' %do i=1 %to &wtcnt; ';
351 put ' %let wt=&&wt&i; ';
352 put ' proc contents noprint data=&wt ';
353 put ' out=_data_ (keep=name type length format:); ';
354 put ' run;%let tempds=%scan(&syslast,2,.); ';
355 put ' data _null_; file &fref mod encoding=''utf-8''; ';
356 put ' dsid=open("WORK.&wt",''is''); ';
357 put ' nlobs=attrn(dsid,''NLOBS''); ';
358 put ' nvars=attrn(dsid,''NVARS''); ';
359 put ' rc=close(dsid); ';
360 put ' if &i>1 then put '',''@; ';
361 put ' put " ""&wt"" : {"; ';
362 put ' put ''"nlobs":'' nlobs; ';
363 put ' put '',"nvars":'' nvars; ';
364 put ' %mp_jsonout(OBJ,&tempds,jref=&fref,dslabel=colattrs,engine=&jsonengine) ';
365 put ' %mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,engine=&jsonengine) ';
366 put ' data _null_; file &fref mod encoding=''utf-8''; ';
367 put ' put "}"; ';
368 put ' %end; ';
369 put ' data _null_; file &fref mod encoding=''utf-8''; ';
370 put ' put "}"; ';
371 put ' run; ';
372 put ' %end; ';
373 put ' /* close off json */ ';
374 put ' data _null_;file &fref mod encoding=''utf-8''; ';
375 put ' _PROGRAM=quote(trim(resolve(symget(''_PROGRAM'')))); ';
376 put ' put ",""SYSUSERID"" : ""&sysuserid"" "; ';
377 put ' put ",""MF_GETUSER"" : ""%mf_getuser()"" "; ';
378 put ' put ",""_DEBUG"" : ""&_debug"" "; ';
379 put ' _METAUSER=quote(trim(symget(''_METAUSER''))); ';
380 put ' put ",""_METAUSER"": " _METAUSER; ';
381 put ' _METAPERSON=quote(trim(symget(''_METAPERSON''))); ';
382 put ' put '',"_METAPERSON": '' _METAPERSON; ';
383 put ' put '',"_PROGRAM" : '' _PROGRAM ; ';
384 put ' put ",""SYSCC"" : ""&syscc"" "; ';
385 put ' put ",""SYSERRORTEXT"" : ""&syserrortext"" "; ';
386 put ' put ",""SYSHOSTNAME"" : ""&syshostname"" "; ';
387 put ' put ",""SYSJOBID"" : ""&sysjobid"" "; ';
388 put ' put ",""SYSSCPL"" : ""&sysscpl"" "; ';
389 put ' put ",""SYSSITE"" : ""&syssite"" "; ';
390 put ' sysvlong=quote(trim(symget(''sysvlong''))); ';
391 put ' put '',"SYSVLONG" : '' sysvlong; ';
392 put ' put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" "; ';
393 put ' put '',"END_DTTM" : "'' "%sysfunc(datetime(),datetime20.3)" ''" ''; ';
394 put ' put "}" @; ';
395 put ' %if %str(&_debug) ge 131 %then %do; ';
396 put ' put ''>>weboutEND<<''; ';
397 put ' %end; ';
398 put ' run; ';
399 put '%end; ';
400 put ' ';
401 put '%mend mm_webout; ';
402 put ' ';
403 put '%macro mf_getuser(type=META ';
404 put ')/*/STORE SOURCE*/; ';
405 put ' %local user metavar; ';
406 put ' %if &type=OS %then %let metavar=_secureusername; ';
407 put ' %else %let metavar=_metaperson; ';
408 put ' ';
409 put ' %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %let user=&SYS_COMPUTE_SESSION_OWNER; ';
410 put ' %else %if %symexist(&metavar) %then %do; ';
411 put ' %if %length(&&&metavar)=0 %then %let user=&sysuserid; ';
412 put ' /* sometimes SAS will add @domain extension - remove for consistency */ ';
413 put ' %else %let user=%scan(&&&metavar,1,@); ';
414 put ' %end; ';
415 put ' %else %let user=&sysuserid; ';
416 put ' ';
417 put ' %quote(&user) ';
418 put ' ';
419 put '%mend mf_getuser; ';
420/* WEBOUT END */
421 put '%macro webout(action,ds,dslabel=,fmt=);';
422 put ' %mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt)';
423 put '%mend;';
424run;
425
426/* add precode and code */
427%local work tmpfile;
428%let work=%sysfunc(pathname(work));
429%let tmpfile=__mm_createwebservice.temp;
430%local x fref freflist mod;
431%let freflist= &adapter &precode &code ;
432%do x=1 %to %sysfunc(countw(&freflist));
433 %if &x>1 %then %let mod=mod;
434
435 %let fref=%scan(&freflist,&x);
436 %put &sysmacroname: adding &fref;
437 data _null_;
438 file "&work/&tmpfile" lrecl=3000 &mod;
439 infile &fref;
440 input;
441 put _infile_;
442 run;
443%end;
444
445/* create the metadata folder if not already there */
446%mm_createfolder(path=&path)
447%if &syscc ge 4 %then %return;
448
449%if %upcase(&replace)=YES %then %do;
450 %mm_deletestp(target=&path/&name)
451%end;
452
453/* create the web service */
454%mm_createstp(stpname=&name
455 ,filename=&tmpfile
456 ,directory=&work
457 ,tree=&path
458 ,stpdesc=&desc
459 ,mDebug=&mdebug
460 ,server=&server
461 ,stptype=2)
462
463/* find the web app url */
464%local url;
465%let url=localhost/SASStoredProcess;
466data _null_;
467 length url $128;
468 rc=METADATA_GETURI("Stored Process Web App",url);
469 if rc=0 then call symputx('url',url,'l');
470run;
471
472%put ;%put ;%put ;%put ;%put ;%put ;
473%put &sysmacroname: STP &name successfully created in &path;
474%put ;%put ;%put ;
475%put Check it out here:;
476%put ;%put ;%put ;
477%put &url?_PROGRAM=&path/&name;
478%put ;%put ;%put ;%put ;%put ;%put ;
479
480%mend mm_createwebservice;