webApiGen version 1.4

Nick James

19-11-2011 - 13-08-2012

1 Introduction

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.

2 Prerequisites

This program doesn't do it for you. You need a reasonable understanding of:

3 Installation

Unzip webApiGen.zip, put webApiGen.exe and the gnu dlls somewhere on your path.

3.1 Building generated code

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++.

4 Synopsis

webApiGen -h? -v? filename?
-v

version. Writes version and exits.

-h

help. Writes usage text and exits.

filename

filename. Input filename. If omitted, stdin is read.

5 Action

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-11-23T15:54:04Z");
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

5.1 Example Notes

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...

6 Programmers notes

6.1 Grammar

The grammar is:

version:
VERSION QUOTEDSTRING
;

name:
NAME IDENTIFIER SEMI_COLON
;

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
| ε
;

emptyCodeblock:
CODE ENDCODE
;

filledCodeblock:
CODE codelines ENDCODE
;

codelines:
CODELINE
| codelines CODELINE
;

6.1.1 Details

6.1.1.1 Preamble

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.

6.1.1.2 PathList

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.

6.1.1.3 Function definition

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.

6.1.1.4 Function codeblock

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 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"]...

6.2 Compiling generated code

You need

6.3 Building webapigen

Use gcc\compile.bat. you will need g++, sed, yacc and flex on your path.

7 Bugs

8 Versions

8.1 v1.1

8.2 v1.2

8.3 v1.3

8.4 v1.4

9 Colophon

Produced with:

pp webApiGen.txt | pandoc --toc -N -c webApiGen.css -s -5 -o webApiGen.html

2012-11-23 15:54:05