webApiGen generates a cgiMain function to be called by the cgic library. The idea is to provide a collection of functions for an AJAX (or AJA JSON) environment, although it can be used to emit straight html. The program takes a specification of an api and generates cgiMain() along with some helper functions.
Whether it's worth messing about with a mini language to come up with a relatively simple program which most people would probably do in some scripting language is for you to decide. I enjoy concocting small languages and prefer c++ to such scripting languages (php, python, lua, javascript) as I have had passing contact with: if this is you, then you may like this effort.
You are not intended to put any webApiGen generated programs on the public internet. There is code to stop the program doing very much if invoked from anywhere but localhost; remove it at your peril. In addition, it's probably a lousy idea to run webApiGen generated programs whilst connected to the snake pit the internet has become.
This program doesn't do it for you. You need a reasonable understanding of:
yacc grammars
sadly the error messages are pretty unhelpful, you'll need to be able to understand the grammar to write code that parses. You could try this.
c++
you need a rudimentary understanding of the stl. These people know a bit.
html
Unzip webApiGen.zip, put webApiGen.exe and the gnu dlls somewhere on your path.
You will need the cgic library, otherwise the generated code will never get called. You will need to have WagAboutPage.h and WagErrorPage.h on your include path when compiling generated code: these files are supplied in the runTimeSource directory. A batch file, example\compile.bat, is supplied for building the example: as it stands this uses gcc and g++.
webApiGen -h? -v? filename?
version. Writes version and exits.
help. Writes usage text and exits.
filename. Input filename. If omitted, stdin is read.
webApiGen is used to specify and generate code for a cgi program. The only methods we support are those used by cgic, GET and POST.
To specify inputs we need a path name and query names; code is generated to deal with GETs or POSTs to path, with (name, value) pairs from the query part of the URI sent to the cgi script.
For instance if example.wag looks like:
version "1.4"
api-name exampleDB;
/hello get (str)
code
/* visit, for example, localhost/wagExample.exe/hello?str=world */
if ($1.size() == 1)
{
fprintf (cgiOut,
"<html><head><title>hello</title></head>"
"<body>hello %s</body></html>", $1.c_str());
}
endcode
return html
doing webApiGen example.wag gives us a cgiMain, in file exampleDBMain.cpp, like so:
boilerplate
...
int cgiMain()
{
std::string host = cgiRemoteHost;
std::string addr = cgiRemoteAddr ;
std::vector<std::string> funcSigs;
funcSigs.push_back("/hello (str) [get] return html");
std::sort(funcSigs.begin(), funcSigs.end());
/*
Stop remote invocation of this program
*/
if (!(host == "localhost" || addr == "127.0.0.1"))
{
cgiHeaderContentType("text/html");
fprintf (cgiOut,
"<html><head><title>access denied</title></head>"
"<body>attempt to connect from %s %s denied</body>"
"</html>",
cgiRemoteAddr,
cgiRemoteHost);
return 1;
}
std::string path = cgiPathInfo;
if (path == "/webapigen") // webapigen has dibs on webapigen
{
WagAboutPage about("1.4", "exampleDB", "2012-09-16T22:38:32Z");
cgiHeaderContentType("text/html");
about.writeHtml(cgiOut, funcSigs);
}
else if (path == "/hello")
{
/* hello (str) [get] return html */;
std::map<std::string, std::vector<std::string> > userArrays;
std::map<std::string, std::string> userVars;
getParams(userArrays, userVars);
cgiHeaderContentType("text/html");
/* visit, for example, localhost/wagExample.exe/hello?str=world */
if (userVars["str"].size() == 1)
{
fprintf (cgiOut,
"<html><head><title>hello</title></head>"
"<body>hello %s</body></html>", userVars["str"].c_str());
}
}
else
{
WagErrorPage err;
cgiHeaderContentType("text/html");
err.writeHtml(cgiOut, path, funcSigs);
}
return 0;
}
...
boilerplate
Note that two variables are supplied: std::map<std::string, std::vector<std::string> > userArrays and std::map<std::string, std::string> userVars. The maps are keyed on the control names; if the parameter was declared to be an array (using []), it's values will be in userArrays["control-name"] as a std::vector<std::string> otherwise it will be in userVars["control-name"] as a std::string.
Note also that the user code delimiters code and endcode must be the only words appearing on the line.
Compile this as follows (you may need to adjust for compiler etc):
rem file ..\example\compile.bat
@echo off
set MIN_GW_BIN=c:\bin\MinGWNew\bin
set OLDPATH=%PATH%
set PATH=%MIN_GW_BIN%;%OLDPATH%
set GPPPATH="%MIN_GW_BIN%\g++.exe"
set GCCPATH="%MIN_GW_BIN%\gcc.exe"
set WAGPATH=..\gcc\webApiGen
echo ************************************
echo ****** MAKING WAGEXAMPLE.EXE *******
echo ************************************
%WAGPATH% example.wag
%GCCPATH% -c cgic.c
%GPPPATH% -o wagExample.exe -I../runTimeSource exampleDBMain.cpp cgic.o
del cgic.o
set MIN_GW_BIN=
set GGPPPATH=
set GCCPATH=
set PATH=%OLDPATH%
set OLDPATH=
and you have a cgi program to put on a server.
If you put wagExample.exe on a server and visit localhost/path/to/wagExample.exe/webapigen, you get a full fat web page with some server data on it; localhost/path/to/wagExample.exe/hello?str=world gets you a "hello world" page.
You can put a return statement at the end of the function: possible values for this are html, plain, void and, the default, json. return json gets you cgiHeaderContentType("application/json"); and so on; return void generates cgiHeaderStatus(204, "No Content");. The programmer needs to ensure the output is well formed...
The grammar is:
apiSpec:
preamble pathList
|error
;
preamble:
version name codeblock
|version name
;
version:
VERSION QUOTEDSTRING
;
name:
NAME IDENTIFIER SEMI_COLON
;
pathList:
path
|pathList path
;
path:
SLASH IDENTIFIER function
|SLASH IDENTIFIER function RETURN returnType
;
returnType:
HTML
|JSON
|PLAIN
|VCARD
|VEVENT
|VOID
;
function:
METHOD OPEN_BRACKET paramList CLOSE_BRACKET codeblock
;
paramList:
IDENTIFIER
|IDENTIFIER ARRAY_DECL
|paramList COMMA IDENTIFIER
|paramList COMMA IDENTIFIER ARRAY_DECL
|ε
;
codeblock:
emptyCodeblock
|filledCodeblock
;
emptyCodeblock:
CODE ENDCODE
;
filledCodeblock:
CODE codelines ENDCODE
;
codelines:
CODELINE
|codelines CODELINE
;
This looks like
version "1.3"
api-name SomeRandomName;
This (i) stops us compiling outdated source files and (ii) says our output file is going to be called SomeRandomNameMain.cpp. An optional code block, like
code
...
endcode
can be used to #include files, declare variables etc etc near the head of the output file.
Our cgi program, let's call it SomeRandomName.exe, will be at http://localhost/SomeRandomName.exe. The various functions we define are invoked by visting a URI (or making an XMLHTTPRequest) like http://localhost/SomeRandomName.exe/functionName?parameter=value. The webapigen definition of this would look like:
/functionName get (parameter)
code
// some c++ goes here
endcode
return plain
We can specify a return type (the default is json) of html, json, plain or void.
After /functionName we need to define the function. This looks like method (get or post), (paramList) followed by a codeblock.
A paramList is a comma separated list of identifiers. The identifiers may have a [] after them (an array_decl) to show that we're expecting a lot of values for this parameter name.
There is a special parameter identifier $pageState. The idea here is that the client page is throwing everything at us and we pick the bones out of it in the code block. This may be redundant.
Finally, we do some real work. The cgi script has been invoked (perhaps by XMLHTTPRequest or plain old html) by a client which has supplied (name, value) pairs as parameters. One of two things happens:
the parameter list is empty.
you write code to emit whatever type you've said you're going to emit.
otherwise.
in this case a variable called userParams (of type std::map<std::string, std::vector<std::string> >) has been declared and initialized for you. userParams["parameterName"] yields the value(s), in a std::vectorparameterName.
Alternatively you can use positional parameters to reference values: a parameter list like (i, j, k) could be referenced with $1, $2 and $3 in the source file's codeblock; webapigen would substitute userParams["i"] for $1 and so on. A positional parameter has type std::map<std::string, std::vector<std::string> >. The example demonstrates this function.
You need to ensure that your output is in accord with the the return type.
the code block is wrapped like so:
code
// c++ goes here
endcode
the words code and endcode must be the only letters on the line.
A potential problem: you may get confused (I did!) if the client provides the parameter values under a different parameter name to the one you're expecting. For example, if the client page has
<input value="value" name="param" type="checkbox">
on a form and your cgi spec has a path like
/function get (parameter)
a reference to userParams["parameter"] won't get you anything, it'll be in userParams["param"]...
You need
to compile the webapigen output file.
to compile cgic.c (as C code, not C++)
to have WagAboutPage.h and WagErrorPage.h on your include path
to compile any files #included, plus any supporting bits and bobs, eg for database access.
For what it's worth I tend to do it a la Java, a constructor for a helper class that gets the ducks in a row and then call a function on the class like writeHtml() or writeJson() as appropriate.
webapigen
Use gcc\compile.bat. you will need g++, sed, yacc and flex on your path.
The supplied binary may have dll dependencies that mean you have to recompile the source on your machine.
Input that ends "endcode<EOF>" fails to parse, "endcode\n<EOF>" will work.
userVars decl written when no userVars - fixed 1.1
cgi-url etc are redundant - fixed 1.1
/ get func() should be valid
apiUrl is redundant and has been removed
consequently apigenParameters is also redundant
unecessary userVars decl no longer written
/webapigen now gets you a summary of the api as well as server variables.
/webapigen tweak: WagAboutPage now uses api spec id in constructor
$1, $2 etc in code blocks for path definitions are expanded to userVars["..."] in generated code. This expression has type std::string.
the banner now has a timestamp and a function list.
return statement introduced, call to cgiHeaderContentType is now generated.
$1, $2 etc in code blocks for path definitions are expanded to userParams["..."] in generated code. This expression has type std::vector<std::string>.
Produced with:
pp webApiGen.txt | pandoc --toc -N -c webApiGen.css -s -5 -o webApiGen.html
2012-09-16 23:38:35