VMS web applications

Content:

  1. Introduction
  2. Approch
  3. Examples
  4. Mid 90's
    1. CGI (C, Pascal, Basic, Cobol, DCL, Python)
  5. Around 2000
    1. PHP
    2. JSP
  6. Early 00's
    1. PHP + PHP
    2. JSP + Java
    3. JSP + Groovy
    4. Struts (JSP + Java)
  7. Late 00's
    1. Zend Framework (PHP)
    2. Spring MVC with JSP view (Java)
    3. JSF with JSP view (Java)
  8. 10's
    1. Laravel (PHP) [not working yet]
    2. Grails (GSP + Groovy)
    3. Ktor with Thymeleaf view (Kotlin)
    4. Spring MVC with Thymeleaf view (Java)
    5. JSF with Facelet view (Java)
  9. Current
    1. Slim (PHP)
    2. Flask (Python)
    3. JAX-RS via Jersey (Java)
    4. Spring MVC (Java)
  10. Performance
  11. Conclusion

Introduction:

User interfaces to applications has changed over the last 30 years.

The traditional text/console/VT user interface are mostly gone.

The traditional client GUI application became popular (VB6, Delphi etc.) but is today most common on phones and tablets - not desktop computers.

By far the most common user interface today is web/browser based.

The average age of applications on VMS is way higher than on Windows/Linux and many VMS applications predate web and browsers. But still a web/browser based user interface is relevant if the application need to be modernized.

Approch:

The article will present a number of technologies for creating web applications.

The technologies will be presented in approximate chronological order. That makes sense because a new technology is often designed to avoid problems found in the previous technology, so understanding the previous technology gives a better understanding of current technology.

Note that as a general rule then the newer more advanced more complex web frameworks are better for big web applications. But for small web applications, then an older but simpler technology may be just fine.

Newer web applications typical use one of:

ASP.NET and node.js are currently not available on VMS.

Java, PHP and Python are available on VMS. Python is not yet available for VMS x86-64 though.

This article will show examples in older technology (CGI in traditional VMS languages), Java and PHP.

There are an overrepresentation of Java based solutions as I simply know those better.

All of these frameworks are huge and this article is not a tutorial on all of them. This article just provide an appetizer to show what is available on VMS and to give the reader an overview that makes it possible to pick the right technology. After chosing a technology then studying a tutorial is recommended (unless the reader already knows the technology from other platforms.

Examples:

All examples will follow the simple model.

Data as rows of T1 with fields F1 integer and F2 string length 32.

Index-sequential file create:

$ create/fdl=sys$input db.isq
FILE
    ORGANIZATION            indexed

RECORD
    FORMAT                  fixed
    SIZE                    36

KEY 0
    SEG0_LENGTH             4
    SEG0_POSITION           0
    TYPE                    int4
$

Since DCL cannot use integer keys the DCL examples will use:

isq2.com:

$ create/fdl=sys$input db2.isq
FILE
    ORGANIZATION            indexed

RECORD
    FORMAT                  fixed
    SIZE                    36

KEY 0
    SEG0_LENGTH             4
    SEG0_POSITION           0
    TYPE                    string
$

MySQL create:

CREATE TABLE t1 (
    f1 INTEGER NOT NULL,
    f2 VARCHAR(50),
    PRIMARY KEY (f1)
);

The flow will be:

Example flow

Mid 90's:

When the web was new then no web-specific languages or frameworks existed.

All that existed was the web server and the CGI protocol for communication between the web server and external scripts.

So web applications was done in the same languages as other types of applications. Perl, C, DCL, Cobol, Fortran etc..

And because the languages was not web specific then web applications outputted HTML markup similar to how previous applications had outputted console output (possible with VT escape sequences for formatting).

CGI:

We will see examples using an index-sequential file and examples using a MySQL database for data.

Fundamentally CGI is a standard and a CGI script should work identical across web servers - in this case Apache and OSU.

In reality there are a few differences:

Data in index-sequential file:

We will see examples in Pascal, Basic and Cobol.

Apache:

isq.pas:

type
   pstr = varying [255] of char;
   char32 = packed array [1..32] of char;
   t1 = packed record
           f1 : integer;
           f2 : char32;
        end;
   t1file = file of t1;

function trim(s : char32) : pstr;

var
   i : integer;

begin
   i := length(s);
   while (i > 0) and (s[i] = ' ') do i := i - 1;
   trim := substr(s, 1, i);
end;

isqshow.pas:

program isqshow(input, output);

%include 'isq.pas'

var
   db : t1file;
   f1 : integer;
   f2 : pstr;

begin
   writeln('content-type: text/html');
   writeln('');
   writeln('<html>');
   writeln('<head>');
   writeln('<title>CGI - Pascal</title>');
   writeln('</head>');
   writeln('<body>');
   writeln('<h1>CGI - Pascal</body>');
   writeln('<h2>Show:</h2>');
   writeln('<table border="1">');
   writeln('<tr>');
   writeln('<th>F1</th>');
   writeln('<th>F2</th>');
   writeln('</tr>');
   open(db, 'disk5:[cgi.apache]db.isq', old, organization := indexed, access_method := keyed);
   reset(db);
   while not(eof(db)) do begin
      f1 := db^.f1;
      f2 := trim(db^.f2);
      writeln('<tr>');
      writeln('<td>', f1:1, '</td>');
      writeln('<td>', f2, '</td>');
      writeln('<td><a href="/cgi-bin/isqdel?f1=', f1:1, '">Delete</a></td>');
      writeln('</tr>');
      get(db);
   end;
   close(db);
   writeln('</table>');
   writeln('<h2>Add:</h2>');
   writeln('<form method="post" action="/cgi-bin/isqadd">');
   writeln('F1: <input type="text" name="f1">');
   writeln('<br>');
   writeln('F2: <input type="text" name="f2">');
   writeln('<br>');
   writeln('<input type="submit" value="Add">');
   writeln('</form>');
   writeln('</body>');
   writeln('</html>');
end.

isqadd.pas:

program isqadd(input, output);

%include 'isq.pas'

const
   EXTRA = chr(13) + chr(10);

var
   db : t1file;
   f : text;
   line : varying [1000] of char;
   ix1, ix2 : integer;
   form_f1, form_f2 : varying [255] of char;
   f1 : integer;
   f2 : pstr;

begin
   open(f, 'apache$input', old);
   reset(f);
   readln(f, line);
   close(f);
   ix1 := index(line, 'f1=') + 3;
   ix2 := index(line + '&', '&');
   form_f1 := substr(line, ix1, ix2 - ix1);
   readv(form_f1, f1);
   line := substr(line, ix2 + 1, line.length - ix2);
   ix1 := index(line, 'f2=') + 3;
   ix2 := index(line + '&', '&');
   form_f2 := substr(line, ix1, ix2 - ix1);
   f2 := form_f2;
   open(db, 'disk5:[cgi.apache]db.isq', old, organization := indexed, access_method := keyed);
   reset(db);
   db^.f1 := f1;
   db^.f2 := f2;
   put(db);
   close(db);
   writeln('Location: http://arne1.vajhoej.dk/cgi-bin/isqshow', EXTRA);
   writeln(EXTRA);
end.

isqdel.pas:

[inherit('sys$library:pascal$lib_routines')]
program isqdel(input, output);

%include 'isq.pas'

const
   EXTRA = chr(13) + chr(10);

var
   db : t1file;
   q, q_f1 : varying [255] of char;
   f1 : integer;

begin
   lib$get_symbol('QUERY_STRING', q.body, q.length);
   q_f1 := substr(q, 4, q.length - 3);
   readv(q_f1, f1);
   open(db, 'disk5:[cgi.apache]db.isq', old, organization := indexed, access_method := keyed);
   reset(db);
   findk(db, 0, f1);
   delete(db);
   close(db);
   writeln('Location: http://arne1.vajhoej.dk/cgi-bin/isqshow', EXTRA);
   writeln(EXTRA);
end.

Start URL: http://nodename:port/cgi-bin/isqshow

isq.bas:

record t1
    integer f1
    string f2 = 32
end record

isqshow.bas:

program isqshow

option type = explicit

%include "isq.bas"

map (t1buf) t1 buf
declare integer f1
declare string f2

print "content-type: text/html"
print ""
print "<html>"
print "<head>"
print "<title>CGI - Basic</title>"
print "</head>"
print "<body>"
print "<h1>CGI - Basic</body>"
print "<h2>Show:</h2>"
print "<table border='1'>"
print "<tr>"
print "<th>F1</th>"
print "<th>F2</th>"
print "</tr>"
open "disk5:[cgi.apache]db.isq" as file #1, indexed fixed, map t1buf, primary key buf::f1
handler eof_handler
end handler
when error use eof_handler
    while 1 = 1
        get #1
        print "<tr>"
        print using "<td>#</td>", buf::f1
        print using "<td>'E</td>", buf::f2
        print using "<td><a href='E/cgi-bin/isqdel?f1=#'E>Delete</a></td>", "'", buf::f1, "'"
        print "</tr>"
    next
end when
close #1
print "</table>"
print "<h2>Add:</h2>"
print "<form method='post' action='/cgi-bin/isqadd'>"
print "F1: <input type='text' name='f1'>"
print "<br>"
print "F2: <input type='text' name='f2'>"
print "<br>"
print "<input type='submit' value='Add'>"
print "</form>"
print "</body>"
print "</html>"

end program

isqadd.bas:

program isqadd

option type = explicit

%include "isq.bas"

map (t1buf) t1 buf
declare integer f1, ix1, ix2
declare string f2, form_f1, form_f2
map (buf) string lin = 1000

open "apache$input" for input as file #1, map buf
get #1
close #1
ix1 = instr(1, lin, "f1=") + 3
ix2 = instr(1, lin + "&", "&")
form_f1 = mid(lin, ix1, ix2 - ix1)
f1 = integer(form_f1)
lin = mid(lin, ix2 + 1, len(lin) - ix2)
ix1 = instr(1, lin, "f2=") + 3
ix2 = instr(1, lin + "&", "&")
form_f2 = mid(lin, ix1, ix2 - ix1)
f2 = form_f2
open "disk5:[cgi.apache]db.isq" as file #1, indexed fixed, map t1buf, primary key buf::f1
buf::f1 = f1
buf::f2 = f2
put #1
close #1
print "Location: http://arne1.vajhoej.dk/cgi-bin/isqshow" + chr$(13)
print chr$(10) + ""

end program

isqdel.bas:

program isqdel

option type = explicit

%include "isq.bas"
external sub lib$get_symbol(string, string)

map (t1buf) t1 buf
declare integer f1
declare string q, q_f1

call  lib$get_symbol('QUERY_STRING', q)
q_f1 = mid(q, 4, len(q) - 3)
f1 = integer(q_f1)
open "disk5:[cgi.apache]db.isq" as file #1, indexed fixed, map t1buf, primary key buf::f1
find #1, key #0 eq f1
delete #1
close #1
print "Location: http://arne1.vajhoej.dk/cgi-bin/isqshow" + chr$(13)
print chr$(10) + ""

end program

Start URL: http://nodename:port/cgi-bin/isqshow

isq.cob:

fd db-file.
01 t1.
   03 f1 pic s9(8) comp.
   03 f2 pic x(32).

isqshow.cob:

identification division.
program-id.isqshow.
*
environment division.
input-output section.
file-control.
    select db-file assign to "disk5:[cgi.apache]db.isq" organization is indexed access mode is dynamic record key is f1.
*
data division.
file section.
copy "isq.cob".
working-storage section.
01 eof-flag pic x.
01 f1-print pic 9(8) display.
*
procedure division.
main-paragraph.
    display "content-type: text/html"
    display ""
    display "<html>"
    display "<head>"
    display "<title>CGI - Cobol</title>"
    display "</head>"
    display "<body>"
    display "<h1>CGI - Cobol</body>"
    display "<h2>Show:</h2>"
    display "<table border='1'>"
    display "<tr>"
    display "<th>F1</th>"
    display "<th>F2</th>"
    display "</tr>"
    open i-o db-file
    move 0 to f1
    start db-file key is greater than f1
        invalid key display "Error rewinding"
        not invalid key continue
    end-start
    move 'N' to eof-flag
    perform until eof-flag = 'Y'
        read db-file next
            at end move 'Y' to eof-flag
            not at end perform display-paragraph
        end-read
    end-perform
    close db-file
    display "</table>"
    display "<h2>Add:</h2>"
    display "<form method='post' action='/cgi-bin/isqadd'>"
    display "F1: <input type='text' name='f1'>"
    display "<br>"
    display "F2: <input type='text' name='f2'>"
    display "<br>"
    display "<input type='submit' value='Add'>"
    display "</form>"
    display "</body>"
    display "</html>"
    stop run.
display-paragraph.
    move f1 to f1-print
    display "<tr>"
    display "<td>" f1-print "</td>"
    display "<td>" f2 "</td>"
    display "<td><a href='/cgi-bin/isqdel?f1=" f1-print "'>Delete</a></td>"
    display "</tr>".

isqadd.cob:

identification division.
program-id.isqadd.
*
environment division.
input-output section.
file-control.
    select db-file assign to "disk5:[cgi.apache]db.isq" organization is indexed access mode is dynamic record key is f1.
    select in-file assign to "apache$input" organization is line sequential.
*
data division.
file section.
fd in-file.
01 in-record.
    03 lin pic x(1000).
copy "isq.cob".
working-storage section.
01 i pic s9(8) comp.
01 f1-tag pic x(3) value is "f1=".
01 f2-tag pic x(3) value is "f2=".
01 delim-tag pic x(1) value is "&".
01 ix1 pic s9(8) comp.
01 ix2 pic s9(8) comp.
01 form-f1 pic s9(8) comp.
01 form-f2 pic x(32).
*
procedure division.
main-paragraph.
    open input in-file
    read in-file
        at end display "No form data"
        not at end continue
    end-read
    close in-file
    perform varying i from 1 by 1 until lin(i:1) = " "
        continue
    end-perform
    move "&" to lin(i:1)
    call "str$position"
        using
            by descriptor lin
            by descriptor f1-tag
        giving
            ix1
    end-call
    compute ix1 = ix1 + 3
    call "str$position"
        using
            by descriptor lin
            by descriptor delim-tag
        giving
            ix2
    end-call
    move lin(ix1:ix2 - ix1) to form-f1
    move lin(ix2 + 1:1000 - ix2 - 1) to lin
    call "str$position"
        using
            by descriptor lin
            by descriptor f2-tag
        giving
            ix1
    end-call
    compute ix1 = ix1 + 3
    call "str$position"
        using
            by descriptor lin
            by descriptor delim-tag
        giving
            ix2
    end-call
    move lin(ix1:ix2 - ix1) to form-f2
    open i-o db-file
    move form-f1 to f1
    move form-f2 to f2
    write t1
        invalid key display "Error writing"
        not invalid key continue
    end-write.
    close db-file
    display "Location: http://arne1.vajhoej.dk/cgi-bin/isqshow" function char(14) function char(11)
    display ""
    stop run.

isqdel.cob:

identification division.
program-id.isqdel.
*
environment division.
input-output section.
file-control.
    select db-file assign to "disk5:[cgi.apache]db.isq" organization is indexed access mode is dynamic record key is f1.
*
data division.
file section.
copy "isq.cob".
working-storage section.
01 q-sym pic x(12) value is "QUERY_STRING".
01 q pic x(12).
01 q-len pic s9(8) comp.
01 q-f1 pic x(9).
*
procedure division.
main-paragraph.
    call "lib$get_symbol"
        using
           by descriptor q-sym
           by descriptor q
           by reference q-len
    move q(4:q-len - 3) to q-f1
    compute q-len = q-len - 3
    move q-f1(1:q-len) to f1
    open i-o db-file
    delete db-file
        invalid key display "Error deleting"
        not invalid key continue
    end-delete
    close db-file
    display "Location: http://arne1.vajhoej.dk/cgi-bin/isqshow" function char(14) function char(11)
    display ""
    stop run.

Start URL: http://nodename:port/cgi-bin/isqshow

isqshow2.com:

$ write sys$output "content-type: text/html"
$ write sys$output ""
$ write sys$output "<html>"
$ write sys$output "<head>"
$ write sys$output "<title>CGI - DCL</title>"
$ write sys$output "</head>"
$ write sys$output "<body>"
$ write sys$output "<h1>CGI - DCL</body>"
$ write sys$output "<h2>Show:</h2>"
$ write sys$output "<table border='1'>"
$ write sys$output "<tr>"
$ write sys$output "<th>F1</th>"
$ write sys$output "<th>F2</th>"
$ write sys$output "</tr>"
$ open db disk5:[cgi.apache]db2.isq
$ loop:
$    read/end=endloop db rec
$    write sys$output "<tr>"
$    f1 = f$edit(f$extract(0, 4, rec), "trim")
$    f2 = f$edit(f$extract(4, f$length(rec) - 4, rec), "trim")
$    write sys$output "<td>" + f1 + "</td>"
$    write sys$output "<td>" + f2 + "</td>"
$    write sys$output "<td><a href='/cgi-bin/isqdel2?f1=" + f1 + "'>Delete</a></td>"
$    write sys$output "</tr>"
$    goto loop
$ endloop:
$ close db
$ write sys$output "</table>"
$ write sys$output "<h2>Add:</h2>"
$ write sys$output "<form method='post' action='/cgi-bin/isqadd2'>"
$ write sys$output "F1: <input type='text' name='f1'>"
$ write sys$output "<br>"
$ write sys$output "F2: <input type='text' name='f2'>"
$ write sys$output "<br>"
$ write sys$output "<input type='submit' value='Add'>"
$ write sys$output "</form>"
$ write sys$output "</body>"
$ write sys$output "</html>"
$ exit

isqadd2.com:

$ open f apache$input
$ read f line
$ close f
$ temp = f$extract(3, f$length(line) - 3, line)
$ ix = f$locate("&", temp)
$ f1 = f$extract(0, ix, temp)
$ temp = f$extract(ix + 1, f$length(temp) - ix, temp)
$ f2 = f$extract(3, f$length(temp) - 3, temp)
$ rec = f$extract(0, 4 - f$length(f1), "    ") + f1 + f2 + f$extract(0, 32 - f$length(f2), "                                ")
$ open/read/write db disk5:[cgi.apache]db2.isq
$ write db rec
$ close db
$ crlf[0,8] = 13
$ crlf[8,8] = 10
$ write sys$output "Location: http://arne1.vajhoej.dk/cgi-bin/isqshow2" + crlf
$ write sys$output "" + crlf
$ exit

isqdel2.com:

$ q = "''QUERY_STRING'"
$ f1 = f$extract(3, f$length(q) - 3, q)
$ realf1 = f$extract(0, 4 - f$length(f1), "    ") + f1
$ open/read/write db disk5:[cgi.apache]db2.isq
$ read/index=0/key="''realf1'"/delete db rec
$ close db
$ crlf[0,8] = 13
$ crlf[8,8] = 10
$ write sys$output "Location: http://arne1.vajhoej.dk/cgi-bin/isqshow2" + crlf
$ write sys$output "" + crlf
$ exit

Start URL: http://nodename:port/cgi-bin/isqshow2

OSU:

isq.pas:

type
   pstr = varying [255] of char;
   char32 = packed array [1..32] of char;
   t1 = packed record
           f1 : integer;
           f2 : char32;
        end;
   t1file = file of t1;

function trim(s : char32) : pstr;

var
   i : integer;

begin
   i := length(s);
   while (i > 0) and (s[i] = ' ') do i := i - 1;
   trim := substr(s, 1, i);
end;

isqshow.com:

$ write net_link "<DNETRECMODE>"
$ mcr 'f$parse("cgi_symbols.exe;",f$environment("procedure"))'
$ define sys$output net_link
$ mcr 'f$parse("isqshow.exe;",f$environment("procedure"))'
$ exit

isqshow.pas:

program isqshow(input, output);

%include 'isq.pas'

var
   db : t1file;
   f1 : integer;
   f2 : pstr;

begin
   writeln('content-type: text/html');
   writeln('');
   writeln('<html>');
   writeln('<head>');
   writeln('<title>CGI - Pascal</title>');
   writeln('</head>');
   writeln('<body>');
   writeln('<h1>CGI - Pascal</body>');
   writeln('<h2>Show:</h2>');
   writeln('<table border="1">');
   writeln('<tr>');
   writeln('<th>F1</th>');
   writeln('<th>F2</th>');
   writeln('</tr>');
   open(db, 'disk5:[cgi.osu]db.isq', old, organization := indexed, access_method := keyed);
   reset(db);
   while not(eof(db)) do begin
      f1 := db^.f1;
      f2 := trim(db^.f2);
      writeln('<tr>');
      writeln('<td>', f1:1, '</td>');
      writeln('<td>', f2, '</td>');
      writeln('<td><a href="/htbin/isqdel?f1=', f1:1, '">Delete</a></td>');
      writeln('</tr>');
      get(db);
   end;
   close(db);
   writeln('</table>');
   writeln('<h2>Add:</h2>');
   writeln('<form method="post" action="/htbin/isqadd">');
   writeln('F1: <input type="text" name="f1">');
   writeln('<br>');
   writeln('F2: <input type="text" name="f2">');
   writeln('<br>');
   writeln('<input type="submit" value="Add">');
   writeln('</form>');
   writeln('</body>');
   writeln('</html>');
end.

isqadd.com:

$ write net_link "<DNETRECMODE>"
$ mcr 'f$parse("cgi_symbols.exe;",f$environment("procedure"))' www_ form_
$ define sys$output net_link
$ mcr 'f$parse("isqadd.exe;",f$environment("procedure"))'
$ exit

isqadd.pas:

[inherit('sys$library:pascal$lib_routines')]
program isqadd(input, output);

%include 'isq.pas'

var
   db : t1file;
   form_f1, form_f2 : varying [255] of char;
   f1 : integer;
   f2 : pstr;

begin
   lib$get_symbol('FORM_FLD_F1', form_f1.body, form_f1.length);
   readv(form_f1, f1);
   lib$get_symbol('FORM_FLD_F2', form_f2.body, form_f2.length);
   f2 := form_f2;
   open(db, 'disk5:[cgi.osu]db.isq', old, organization := indexed, access_method := keyed);
   reset(db);
   db^.f1 := f1;
   db^.f2 := f2;
   put(db);
   close(db);
   writeln('Location: http://arne1.vajhoej.dk:81/htbin/isqshow');
   writeln;
end.

isqdel.com:

$ write net_link "<DNETRECMODE>"
$ mcr 'f$parse("cgi_symbols.exe;",f$environment("procedure"))'
$ define sys$output net_link
$ mcr 'f$parse("isqdel.exe;",f$environment("procedure"))'
$ exit

isqdel.pas:

[inherit('sys$library:pascal$lib_routines')]
program isqdel(input, output);

%include 'isq.pas'

var
   db : t1file;
   q, q_f1 : varying [255] of char;
   f1 : integer;

begin
   lib$get_symbol('WWW_QUERY_STRING', q.body, q.length);
   q_f1 := substr(q, 4, q.length - 3);
   readv(q_f1, f1);
   open(db, 'disk5:[cgi.osu]db.isq', old, organization := indexed, access_method := keyed);
   reset(db);
   findk(db, 0, f1);
   delete(db);
   close(db);
   writeln('Location: http://arne1.vajhoej.dk:81/htbin/isqshow');
   writeln;
end.

Start URL: http://nodename:port/htbin/isqshow

isq.bas:

record t1
    integer f1
    string f2 = 32
end record

isqshow.com:

$ write net_link "<DNETRECMODE>"
$ mcr 'f$parse("cgi_symbols.exe;",f$environment("procedure"))'
$ define sys$output net_link
$ mcr 'f$parse("isqshow.exe;",f$environment("procedure"))'
$ exit

isqshow.bas:

program isqshow

option type = explicit

%include "isq.bas"

map (t1buf) t1 buf
declare integer f1
declare string f2

print "content-type: text/html"
print ""
print "<html>"
print "<head>"
print "<title>CGI - Basic</title>"
print "</head>"
print "<body>"
print "<h1>CGI - Basic</body>"
print "<h2>Show:</h2>"
print "<table border='1'>"
print "<tr>"
print "<th>F1</th>"
print "<th>F2</th>"
print "</tr>"
open "disk5:[cgi.osu]db.isq" as file #1, indexed fixed, map t1buf, primary key buf::f1
handler eof_handler
end handler
when error use eof_handler
    while 1 = 1
        get #1
        print "<tr>"
        print using "<td>#</td>", buf::f1
        print using "<td>'E</td>", buf::f2
        print using "<td><a href='E/htbin/isqdel?f1=#'E>Delete</a></td>", "'", buf::f1, "'"
        print "</tr>"
    next
end when
close #1
print "</table>"
print "<h2>Add:</h2>"
print "<form method='post' action='/htbin/isqadd'>"
print "F1: <input type='text' name='f1'>"
print "<br>"
print "F2: <input type='text' name='f2'>"
print "<br>"
print "<input type='submit' value='Add'>"
print "</form>"
print "</body>"
print "</html>"

end program

isqadd.com:

$ write net_link "<DNETRECMODE>"
$ mcr 'f$parse("cgi_symbols.exe;",f$environment("procedure"))' www_ form_
$ define sys$output net_link
$ mcr 'f$parse("isqadd.exe;",f$environment("procedure"))'
$ exit

isqadd.bas:

program isqadd

option type = explicit

%include "isq.bas"
external sub lib$get_symbol(string, string)

map (t1buf) t1 buf
declare integer f1
declare string f2, form_f1, form_f2

call lib$get_symbol('FORM_FLD_F1', form_f1)
f1 = integer(form_f1)
call lib$get_symbol('FORM_FLD_F2', form_f2)
f2 = form_f2
open "disk5:[cgi.osu]db.isq" as file #1, indexed fixed, map t1buf, primary key buf::f1
buf::f1 = f1
buf::f2 = f2
put #1
close #1
print "Location: http://arne1.vajhoej.dk:81/htbin/isqshow"
print ""

end program

isqdel.com:

$ write net_link "<DNETRECMODE>"
$ mcr 'f$parse("cgi_symbols.exe;",f$environment("procedure"))'
$ define sys$output net_link
$ mcr 'f$parse("isqdel.exe;",f$environment("procedure"))'
$ exit

isqdel.bas:

program isqdel

option type = explicit

%include "isq.bas"
external sub lib$get_symbol(string, string)

map (t1buf) t1 buf
declare integer f1
declare string q, q_f1

call  lib$get_symbol('WWW_QUERY_STRING', q)
q_f1 = mid(q, 4, len(q) - 3)
f1 = integer(q_f1)
open "disk5:[cgi.osu]db.isq" as file #1, indexed fixed, map t1buf, primary key buf::f1
find #1, key #0 eq f1
delete #1
close #1
print "Location: http://arne1.vajhoej.dk:81/htbin/isqshow"
print ""

end program

Start URL: http://nodename:port/htbin/isqshow

isq.cob:

fd db-file.
01 t1.
   03 f1 pic s9(8) comp.
   03 f2 pic x(32).

isqshow.com:

$ write net_link "<DNETRECMODE>"
$ mcr 'f$parse("cgi_symbols.exe;",f$environment("procedure"))'
$ define sys$output net_link
$ mcr 'f$parse("isqshow.exe;",f$environment("procedure"))'
$ exit

isqshow.cob:

identification division.
program-id.isqshow.
*
environment division.
input-output section.
file-control.
    select db-file assign to "disk5:[cgi.osu]db.isq" organization is indexed access mode is dynamic record key is f1.
*
data division.
file section.
copy "isq.cob".
working-storage section.
01 eof-flag pic x.
01 f1-print pic 9(8) display.
*
procedure division.
main-paragraph.
    display "content-type: text/html"
    display ""
    display "<html>"
    display "<head>"
    display "<title>CGI - Cobol</title>"
    display "</head>"
    display "<body>"
    display "<h1>CGI - Cobol</body>"
    display "<h2>Show:</h2>"
    display "<table border='1'>"
    display "<tr>"
    display "<th>F1</th>"
    display "<th>F2</th>"
    display "</tr>"
    open i-o db-file
    move 0 to f1
    start db-file key is greater than f1
        invalid key display "Error rewinding"
        not invalid key continue
    end-start
    move 'N' to eof-flag
    perform until eof-flag = 'Y'
        read db-file next
            at end move 'Y' to eof-flag
            not at end perform display-paragraph
        end-read
    end-perform
    close db-file
    display "</table>"
    display "<h2>Add:</h2>"
    display "<form method='post' action='/htbin/isqadd'>"
    display "F1: <input type='text' name='f1'>"
    display "<br>"
    display "F2: <input type='text' name='f2'>"
    display "<br>"
    display "<input type='submit' value='Add'>"
    display "</form>"
    display "</body>"
    display "</html>"
    stop run.
display-paragraph.
    move f1 to f1-print
    display "<tr>"
    display "<td>" f1-print "</td>"
    display "<td>" f2 "</td>"
    display "<td><a href='/htbin/isqdel?f1=" f1-print "'>Delete</a></td>"
    display "</tr>".

isqadd.com:

$ write net_link "<DNETRECMODE>"
$ mcr 'f$parse("cgi_symbols.exe;",f$environment("procedure"))' www_ form_
$ define sys$output net_link
$ mcr 'f$parse("isqadd.exe;",f$environment("procedure"))'
$ exit

isqadd.cob:

identification division.
program-id.isqadd.
*
environment division.
input-output section.
file-control.
    select db-file assign to "disk5:[cgi.osu]db.isq" organization is indexed access mode is dynamic record key is f1.
    select in-file assign to "apache$input" organization is line sequential.
*
data division.
file section.
fd in-file.
01 in-record.
    03 lin pic x(1000).
copy "isq.cob".
working-storage section.
01 f1-sym pic x(11) value is "FORM_FLD_F1".
01 f2-sym pic x(11) value is "FORM_FLD_F2".
01 form-f1 pic x(9).
01 form-f2 pic x(32).
01 f1-len pic s9(8) comp.
01 f2-len pic s9(8) comp.
*
procedure division.
main-paragraph.
    call "lib$get_symbol"
        using
           by descriptor f1-sym
           by descriptor form-f1
           by reference f1-len
    end-call
    call "lib$get_symbol"
        using
           by descriptor f2-sym
           by descriptor form-f2
           by reference f2-len
    end-call
    open i-o db-file
    move form-f1(1:f1-len) to f1
    move form-f2(1:f2-len) to f2
    write t1
        invalid key display "Error writing"
        not invalid key continue
    end-write.
    close db-file
    display "Location: http://arne1.vajhoej.dk:81/htbin/isqshow"
    display ""
    stop run.

isqdel.com:

$ write net_link "<DNETRECMODE>"
$ mcr 'f$parse("cgi_symbols.exe;",f$environment("procedure"))'
$ define sys$output net_link
$ mcr 'f$parse("isqdel.exe;",f$environment("procedure"))'
$ exit

isqdel.cob:

identification division.
program-id.isqdel.
*
environment division.
input-output section.
file-control.
    select db-file assign to "disk5:[cgi.osu]db.isq" organization is indexed access mode is dynamic record key is f1.
*
data division.
file section.
copy "isq.cob".
working-storage section.
01 q-sym pic x(16) value is "WWW_QUERY_STRING".
01 q pic x(12).
01 q-len pic s9(8) comp.
01 q-f1 pic x(9).
*
procedure division.
main-paragraph.
    call "lib$get_symbol"
        using
           by descriptor q-sym
           by descriptor q
           by reference q-len
    move q(4:q-len - 3) to q-f1
    compute q-len = q-len - 3
    move q-f1(1:q-len) to f1
    open i-o db-file
    delete db-file
        invalid key display "Error deleting"
        not invalid key continue
    end-delete
    close db-file
    display "Location: http://arne1.vajhoej.dk:81/htbin/isqshow"
    display ""
    stop run.

Start URL: http://nodename:port/htbin/isqshow

isqshow2.com:

$ write net_link "<DNETRECMODE>"
$ mcr 'f$parse("cgi_symbols.exe;",f$environment("procedure"))'
$ define sys$output net_link
$ write sys$output "content-type: text/html"
$ write sys$output ""
$ write sys$output "<html>"
$ write sys$output "<head>"
$ write sys$output "<title>CGI - DCL</title>"
$ write sys$output "</head>"
$ write sys$output "<body>"
$ write sys$output "<h1>CGI - DCL</body>"
$ write sys$output "<h2>Show:</h2>"
$ write sys$output "<table border='1'>"
$ write sys$output "<tr>"
$ write sys$output "<th>F1</th>"
$ write sys$output "<th>F2</th>"
$ write sys$output "</tr>"
$ open db disk5:[cgi.osu]db2.isq
$ loop:
$    read/end=endloop db rec
$    write sys$output "<tr>"
$    f1 = f$edit(f$extract(0, 4, rec), "trim")
$    f2 = f$edit(f$extract(4, f$length(rec) - 4, rec), "trim")
$    write sys$output "<td>" + f1 + "</td>"
$    write sys$output "<td>" + f2 + "</td>"
$    write sys$output "<td><a href='/htbin/isqdel2?f1=" + f1 + "'>Delete</a></td>"
$    write sys$output "</tr>"
$    goto loop
$ endloop:
$ close db
$ write sys$output "</table>"
$ write sys$output "<h2>Add:</h2>"
$ write sys$output "<form method='post' action='/htbin/isqadd2'>"
$ write sys$output "F1: <input type='text' name='f1'>"
$ write sys$output "<br>"
$ write sys$output "F2: <input type='text' name='f2'>"
$ write sys$output "<br>"
$ write sys$output "<input type='submit' value='Add'>"
$ write sys$output "</form>"
$ write sys$output "</body>"
$ write sys$output "</html>"
$ exit

isqadd2.com:

$ write net_link "<DNETRECMODE>"
$ mcr 'f$parse("cgi_symbols.exe;",f$environment("procedure"))' www_ form_
$ define sys$output net_link
$ f1 = "''FORM_FLD_F1'"
$ f2 = "''FORM_FLD_F2'"
$ rec = f$extract(0, 4 - f$length(f1), "    ") + f1 + f2 + f$extract(0, 32 - f$length(f2), "                                ")
$ open/read/write db disk5:[cgi.osu]db2.isq
$ write db rec
$ close db
$ write sys$output "Location: http://arne1.vajhoej.dk:81/htbin/isqshow2"
$ write sys$output ""
$ exit

isqdel2.com:

$ write net_link "<DNETRECMODE>"
$ mcr 'f$parse("cgi_symbols.exe;",f$environment("procedure"))'
$ define sys$output net_link
$ q = "''WWW_QUERY_STRING'"
$ f1 = f$extract(3, f$length(q) - 3, q)
$ realf1 = f$extract(0, 4 - f$length(f1), "    ") + f1
$ open/read/write db disk5:[cgi.osu]db2.isq
$ read/index=0/key="''realf1'"/delete db rec
$ close db
$ write sys$output "Location: http://arne1.vajhoej.dk:81/htbin/isqshow2"
$ write sys$output ""
$ exit

Start URL: http://nodename:port/htbin/isqshow2

Data in MySQL:

We will see examples in C, Pascal and Basic.

Apache:

show.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SOCKET int
#include <mysql.h>

#define F2_MAX_LEN 50

int main()
{
    printf("content-type: text/html\n");
    printf("\n");
    printf("<html>\n");
    printf("<head>\n");
    printf("<title>CGI - C</title>\n");
    printf("</head>\n");
    printf("<body>\n");
    printf("<h1>CGI - C</body>\n");
    printf("<h2>Show:</h2>\n");
    printf("<table border='1'>\n");
    printf("<tr>\n");
    printf("<th>F1</th>\n");
    printf("<th>F2</th>\n");
    printf("</tr>\n");
    int stat;
    MYSQL *con = mysql_init(NULL);
    con = mysql_real_connect(con, "localhost", "root", "", "test", 3306, NULL, 0);
    MYSQL_STMT *stmt = mysql_stmt_init(con);
    char *sqlstr = "SELECT f1,f2 FROM t1";
    stat = mysql_stmt_prepare(stmt, sqlstr, strlen(sqlstr));
    stat = mysql_stmt_execute(stmt);
    MYSQL_BIND out[2];
    int f1;
    char f2[F2_MAX_LEN + 1];
    unsigned long int lenf2;
    memset(out, 0, sizeof(out));
    out[0].buffer_type = MYSQL_TYPE_LONG;
    out[0].buffer = &f1;
    out[0].buffer_length = sizeof(f1);
    out[1].buffer_type = MYSQL_TYPE_STRING;
    out[1].buffer = f2;
    out[1].buffer_length = sizeof(f2);
    out[1].length = &lenf2;
    stat = mysql_stmt_bind_result(stmt, out);
    stat = mysql_stmt_store_result(stmt);
    while(!mysql_stmt_fetch(stmt))
    {
        f2[lenf2] = 0;
        printf("<tr>\n");
        printf("<td>%d</td>\n", f1);
        printf("<td>%s</td>\n", f2);
        printf("<td><a href='/cgi-bin/del?f1=%d'>Delete</a></td>\n", f1);
        printf("</tr>\n");
    }
    mysql_stmt_free_result(stmt);
    mysql_close(con);
    printf("</table>\n");
    printf("<h2>Add:</h2>\n");
    printf("<form method='post' action='/cgi-bin/add'>\n");
    printf("F1: <input type='text' name='f1'>\n");
    printf("<br>\n");
    printf("F2: <input type='text' name='f2'>\n");
    printf("<br>\n");
    printf("<input type='submit' value='Add'>\n");
    printf("</form>\n");
    printf("</body>\n");
    printf("</html>\n");
    return 0;
}

add.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SOCKET int
#include <mysql.h>

static char *parse(char *line, char *marker)
{
    char *p = strstr(line, marker);
    if(p == NULL)
    {
        return NULL;
    }
    else
    {
        char *res = malloc(strlen(line) + 1);
        p = p + strlen(marker);
        char *p2 = p;
        while(*p2 && *p2 != '&') p2++;
        memcpy(res, p, p2 - p);
        res[p2 - p] = 0;
        for(int i = 0; i < strlen(res); i++) if(res[i] == '+') res[i] = ' ';
        return res;
    }
}

int main()
{
    FILE *fp = fopen("apache$input", "r");
    char line[1000];
    fgets(line, sizeof(line), fp);
    fclose(fp);
    char *form_f1 = parse(line, "f1=");
    char *form_f2 = parse(line, "f2=");
    int stat;
    MYSQL *con = mysql_init(NULL);
    con = mysql_real_connect(con, "localhost", "root", "", "test", 3306, NULL, 0);
    MYSQL_STMT *stmt = mysql_stmt_init(con);
    char *sqlstr = "INSERT INTO t1 VALUES(?, ?)";
    stat = mysql_stmt_prepare(stmt, sqlstr, strlen(sqlstr));
    MYSQL_BIND in[2];
    int f1 = atoi(form_f1);
    char *f2 = form_f2;
    memset(in, 0, sizeof(in));
    in[0].buffer_type = MYSQL_TYPE_LONG;
    in[0].buffer = &f1;
    in[0].buffer_length = sizeof(f1);
    in[0].is_null= 0;
    in[1].buffer_type = MYSQL_TYPE_STRING;
    in[1].buffer = f2;
    in[1].buffer_length = strlen(f2);
    stat = mysql_stmt_bind_param(stmt, in);
    stat = mysql_stmt_execute(stmt);
    mysql_stmt_free_result(stmt);
    mysql_close(con);
    char *EXTRA = "\r\n";
    printf("Location: http://arne1.vajhoej.dk/cgi-bin/show%s\n", EXTRA);
    printf("%s\n", EXTRA);
    return 0;
}

del.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SOCKET int
#include <mysql.h>

static char *parse(char *line, char *marker)
{
    char *p = strstr(line, marker);
    if(p == NULL)
    {
        return NULL;
    }
    else
    {
        char *res = malloc(strlen(line) + 1);
        p = p + strlen(marker);
        char *p2 = p;
        while(*p2 && *p2 != '&') p2++;
        memcpy(res, p, p2 - p);
        res[p2 - p] = 0;
        for(int i = 0; i < strlen(res); i++) if(res[i] == '+') res[i] = ' ';
        return res;
    }
}

int main()
{
    char *q = getenv("QUERY_STRING");
    char *q_f1 = parse(q, "f1=");
    int stat;
    MYSQL *con = mysql_init(NULL);
    con = mysql_real_connect(con, "localhost", "root", "", "test", 3306, NULL, 0);
    MYSQL_STMT *stmt = mysql_stmt_init(con);
    char *sqlstr = "DELETE FROM t1 WHERE f1 = ?";
    stat = mysql_stmt_prepare(stmt, sqlstr, strlen(sqlstr));
    MYSQL_BIND in[1];
    int f1 = atoi(q_f1);
    memset(in, 0, sizeof(in));
    in[0].buffer_type = MYSQL_TYPE_LONG;
    in[0].buffer = &f1;
    in[0].buffer_length = sizeof(f1);
    in[0].is_null= 0;
    stat = mysql_stmt_bind_param(stmt, in);
    stat = mysql_stmt_execute(stmt);
    mysql_stmt_free_result(stmt);
    mysql_close(con);
    char *EXTRA = "\r\n";
    printf("Location: http://arne1.vajhoej.dk/cgi-bin/show%s\n", EXTRA);
    printf("%s\n", EXTRA);
    return 0;
}

Start URL: http://nodename:port/cgi-bin/show

show.pas:

[inherit('pmysqldir:pmysql', 'pmysqldir:common', 'pmysqldir:mysql')]
program show(input, output);

var
   con : mysql_ptr;
   stmt : mysql_stmt_ptr;
   stat : integer;
   outparam : array[1..2] of mysql_bind;
   f1 : integer;
   f2 : longpstr(255);

begin
   writeln('content-type: text/html');
   writeln('');
   writeln('<html>');
   writeln('<head>');
   writeln('<title>CGI - Pascal</title>');
   writeln('</head>');
   writeln('<body>');
   writeln('<h1>CGI - Pascal</body>');
   writeln('<h2>Show:</h2>');
   writeln('<table border="1">');
   writeln('<tr>');
   writeln('<th>F1</th>');
   writeln('<th>F2</th>');
   writeln('</tr>');
   con := pmysql_init;
   con := pmysql_real_connect(con, 'localhost', 'root', '', 'test');
   stmt := pmysql_stmt_init(con);
   stat := pmysql_stmt_prepare(stmt, 'SELECT f1,f2 FROM t1');
   stat := pmysql_stmt_execute(stmt);
   pmysql_init_bind_long(outparam[1], f1);
   pmysql_init_bind_string_out(outparam[2], f2);
   stat := pmysql_stmt_bind_result(stmt, outparam);
   stat := pmysql_stmt_store_result(stmt);
   while pmysql_stmt_fetch(stmt) = 0 do begin
      writeln('<tr>');
      writeln('<td>', f1:1, '</td>');
      writeln('<td>', stdstr(f2), '</td>');
      writeln('<td><a href="/cgi-bin/del?f1=', f1:1, '">Delete</a></td>');
      writeln('</tr>');
   end;
   pmysql_stmt_free_result(stmt);
   pmysql_close(con);
   writeln('</table>');
   writeln('<h2>Add:</h2>');
   writeln('<form method="post" action="/cgi-bin/add">');
   writeln('F1: <input type="text" name="f1">');
   writeln('<br>');
   writeln('F2: <input type="text" name="f2">');
   writeln('<br>');
   writeln('<input type="submit" value="Add">');
   writeln('</form>');
   writeln('</body>');
   writeln('</html>');
end.

add.pas:

[inherit('pmysqldir:pmysql', 'pmysqldir:common', 'pmysqldir:mysql')]
program add(input, output);

const
   EXTRA = chr(13) + chr(10);

var
   con : mysql_ptr;
   stmt : mysql_stmt_ptr;
   stat : integer;
   inparam : array[1..2] of mysql_bind;
   f : text;
   line : varying [1000] of char;
   ix1, ix2 : integer;
   form_f1, form_f2 : varying [255] of char;
   f1 : integer;
   f2 : pstr;

begin
   open(f, 'apache$input', old);
   reset(f);
   readln(f, line);
   close(f);
   ix1 := index(line, 'f1=') + 3;
   ix2 := index(line + '&', '&');
   form_f1 := substr(line, ix1, ix2 - ix1);
   readv(form_f1, f1);
   line := substr(line, ix2 + 1, line.length - ix2);
   ix1 := index(line, 'f2=') + 3;
   ix2 := index(line + '&', '&');
   form_f2 := substr(line, ix1, ix2 - ix1);
   f2 := form_f2;
   con := pmysql_init;
   con := pmysql_real_connect(con, 'localhost', 'root', '', 'test');
   stmt := pmysql_stmt_init(con);
   stat := pmysql_stmt_prepare(stmt, 'INSERT INTO t1 VALUES(?, ?)');
   pmysql_init_bind_long(inparam[1], f1);
   pmysql_init_bind_string_in(inparam[2], f2);
   stat := pmysql_stmt_bind_param(stmt, inparam);
   stat := pmysql_stmt_execute(stmt);
   pmysql_stmt_free_result(stmt);
   pmysql_close(con);
   writeln('Location: http://arne1.vajhoej.dk/cgi-bin/show', EXTRA);
   writeln(EXTRA);
end.

del.pas:

[inherit('sys$library:pascal$lib_routines', 'pmysqldir:pmysql', 'pmysqldir:common', 'pmysqldir:mysql')]
program del(input, output);

const
   EXTRA = chr(13) + chr(10);

var
   con : mysql_ptr;
   stmt : mysql_stmt_ptr;
   stat : integer;
   inparam : array[1..1] of mysql_bind;
   q, q_f1 : varying [255] of char;
   f1 : integer;

begin
   lib$get_symbol('QUERY_STRING', q.body, q.length);
   q_f1 := substr(q, 4, q.length - 3);
   readv(q_f1, f1);
   con := pmysql_init;
   con := pmysql_real_connect(con, 'localhost', 'root', '', 'test');
   stmt := pmysql_stmt_init(con);
   stat := pmysql_stmt_prepare(stmt, 'DELETE FROM t1 WHERE f1 = ?');
   pmysql_init_bind_long(inparam[1], f1);
   stat := pmysql_stmt_bind_param(stmt, inparam);
   stat := pmysql_stmt_execute(stmt);
   pmysql_stmt_free_result(stmt);
   pmysql_close(con);
   writeln('Location: http://arne1.vajhoej.dk/cgi-bin/show', EXTRA);
   writeln(EXTRA);
end.

Start URL: http://nodename:port/cgi-bin/show

show.bas:

program show

option type = explicit

%include "bmysqldir:b.bas"

declare integer dbcon, stmt, outparam
declare integer stat
declare integer f1
declare string f2

print "content-type: text/html"
print ""
print "<html>"
print "<head>"
print "<title>CGI - Basic</title>"
print "</head>"
print "<body>"
print "<h1>CGI - Basic</body>"
print "<h2>Show:</h2>"
print "<table border='1'>"
print "<tr>"
print "<th>F1</th>"
print "<th>F2</th>"
print "</tr>"
dbcon = bmysql_open("localhost", 3306, "root", "", "test")
stmt = bmysql_stmt_prepare(dbcon, "SELECT f1,f2 FROM t1")
outparam = bmysql_outparam_init(2)
call bmysql_outparam_long(outparam, 0, f1)
call bmysql_outparam_string(outparam, 1, f2)
stat = bmysql_stmt_execute(stmt, 0, outparam)
while bmysql_stmt_fetch(stmt, outparam) = 0
   print "<tr>"
   print using "<td>#</td>", f1
   print using "<td>'E</td>", f2
   print using "<td><a href='E/cgi-bin/del?f1=#'E>Delete</a></td>", "'", f1, "'"
   print "</tr>"
next
call bmysql_outparam_free(outparam)
call bmysql_stmt_free(stmt)
call bmysql_close(dbcon)
print "</table>"
print "<h2>Add:</h2>"
print "<form method='post' action='/cgi-bin/add'>"
print "F1: <input type='text' name='f1'>"
print "<br>"
print "F2: <input type='text' name='f2'>"
print "<br>"
print "<input type='submit' value='Add'>"
print "</form>"
print "</body>"
print "</html>"

end program

add.bas:

program add

option type = explicit

%include "bmysqldir:b.bas"

declare integer dbcon, stmt, inparam
declare integer stat
declare integer f1, ix1, ix2
declare string f2, form_f1, form_f2
map (buf) string lin = 1000

open "apache$input" for input as file #1, map buf
get #1
close #1
ix1 = instr(1, lin, "f1=") + 3
ix2 = instr(1, lin + "&", "&")
form_f1 = mid(lin, ix1, ix2 - ix1)
f1 = integer(form_f1)
lin = mid(lin, ix2 + 1, len(lin) - ix2)
ix1 = instr(1, lin, "f2=") + 3
ix2 = instr(1, lin + "&", "&")
form_f2 = mid(lin, ix1, ix2 - ix1)
f2 = form_f2
dbcon = bmysql_open("localhost", 3306, "root", "", "test")
stmt = bmysql_stmt_prepare(dbcon, "INSERT INTO t1 VALUES(?,?)")
inparam = bmysql_inparam_init(2)
call bmysql_inparam_long(inparam, 0, f1)
call bmysql_inparam_string(inparam, 1, f2)
stat = bmysql_stmt_execute(stmt, inparam, 0)
call bmysql_inparam_free(inparam)
call bmysql_stmt_free(stmt)
call bmysql_close(dbcon)
print "Location: http://arne1.vajhoej.dk/cgi-bin/show" + chr$(13)
print chr$(10) + ""

end program

del.bas:

program xdel

option type = explicit

%include "bmysqldir:b.bas"
external sub lib$get_symbol(string, string)

declare integer dbcon, stmt, inparam
declare integer stat
declare integer f1
declare string q, q_f1

call  lib$get_symbol('QUERY_STRING', q)
q_f1 = mid(q, 4, len(q) - 3)
f1 = integer(q_f1)
dbcon = bmysql_open("localhost", 3306, "root", "", "test")
stmt = bmysql_stmt_prepare(dbcon, "DELETE FROM t1 WHERE f1 = ?")
inparam = bmysql_inparam_init(1)
call bmysql_inparam_long(inparam, 0, f1)
stat = bmysql_stmt_execute(stmt, inparam, 0)
call bmysql_inparam_free(inparam)
call bmysql_stmt_free(stmt)
call bmysql_close(dbcon)
print "Location: http://arne1.vajhoej.dk/cgi-bin/show" + chr$(13)
print chr$(10) + ""

end program

Start URL: http://nodename:port/cgi-bin/show

showpy.com:

$ set def disk5:[cgi.apache]
$ python show.py
$ exit

show.py:

import pymysql

print('content-type: text/html')
print('')
print('<html>')
print('<head>')
print('<title>CGI - Python</title>')
print('</head>')
print('<body>')
print('<h1>CGI - Python</body>')
print('<h2>Show:</h2>')
print('<table border="1">')
print('<tr>')
print('<th>F1</th>')
print('<th>F2</th>')
print('</tr>')
con = pymysql.connect(host='localhost',user='root',password='',db='test')
c = con.cursor()
c.execute('SELECT f1,f2 FROM t1')
res = c.fetchall()
for o in res:
   f1 = o[0]
   f2 = o[1]
   print('<tr>')
   print('<td>%d</td>' % (f1))
   print('<td>%s</td>' % (f2))
   print('<td><a href="/cgi-bin/delpy?f1=%s">Delete</a></td>' % (f1))
   print('</tr>')
c.close()
con.close()
print('</table>')
print('<h2>Add:</h2>')
print('<form method="post" action="/cgi-bin/addpy">')
print('F1: <input type="text" name="f1">')
print('<br>')
print('F2: <input type="text" name="f2">')
print('<br>')
print('<input type="submit" value="Add">')
print('</form>')
print('</body>')
print('</html>')

addpy.com:

$ set def disk5:[cgi.apache]
$ define/nolog sys$input apache$input
$ python add.py
$ exit

add.py:

import cgi
import pymysql

form = cgi.FieldStorage()
f1 = form['f1'].value
f2 = form['f2'].value
con = pymysql.connect(host='localhost',user='root',password='',db='test')
c = con.cursor()
c.execute('INSERT INTO t1(f1,f2) VALUES(%s,%s)', (f1,f2))
c.close()
con.commit()
con.close()
print('Location: http://arne1.vajhoej.dk/cgi-bin/showpy')
print('')

delpy.com:

$ set def disk5:[cgi.apache]
$ python del.py
$ exit

del.py:

import cgi
import pymysql

q = cgi.parse()
f1 = q['f1'][0]
con = pymysql.connect(host='localhost',user='root',password='',db='test')
c = con.cursor()
c.execute('DELETE FROM t1 WHERE f1 = %s', (f1,))
c.close()
con.commit()
con.close()
print('Location: http://arne1.vajhoej.dk/cgi-bin/showpy')
print('')

Start URL: http://nodename:port/cgi-bin/showpy

OSU:

show.com:

$ write net_link "<DNETRECMODE>"
$ mcr 'f$parse("cgi_symbols.exe;",f$environment("procedure"))'
$ define sys$output net_link
$ mcr 'f$parse("show.exe;",f$environment("procedure"))'
$ exit

show.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SOCKET int
#include <mysql.h>

#define F2_MAX_LEN 50

int main()
{
    printf("content-type: text/html\n");
    printf("\n");
    printf("<html>\n");
    printf("<head>\n");
    printf("<title>CGI - C</title>\n");
    printf("</head>\n");
    printf("<body>\n");
    printf("<h1>CGI - C</body>\n");
    printf("<h2>Show:</h2>\n");
    printf("<table border='1'>\n");
    printf("<tr>\n");
    printf("<th>F1</th>\n");
    printf("<th>F2</th>\n");
    printf("</tr>\n");
    int stat;
    MYSQL *con = mysql_init(NULL);
    con = mysql_real_connect(con, "localhost", "root", "", "test", 3306, NULL, 0);
    MYSQL_STMT *stmt = mysql_stmt_init(con);
    char *sqlstr = "SELECT f1,f2 FROM t1";
    stat = mysql_stmt_prepare(stmt, sqlstr, strlen(sqlstr));
    stat = mysql_stmt_execute(stmt);
    MYSQL_BIND out[2];
    int f1;
    char f2[F2_MAX_LEN + 1];
    unsigned long int lenf2;
    memset(out, 0, sizeof(out));
    out[0].buffer_type = MYSQL_TYPE_LONG;
    out[0].buffer = &f1;
    out[0].buffer_length = sizeof(f1);
    out[1].buffer_type = MYSQL_TYPE_STRING;
    out[1].buffer = f2;
    out[1].buffer_length = sizeof(f2);
    out[1].length = &lenf2;
    stat = mysql_stmt_bind_result(stmt, out);
    stat = mysql_stmt_store_result(stmt);
    while(!mysql_stmt_fetch(stmt))
    {
        f2[lenf2] = 0;
        printf("<tr>\n");
        printf("<td>%d</td>\n", f1);
        printf("<td>%s</td>\n", f2);
        printf("<td><a href='/htbin/del?f1=%d'>Delete</a></td>\n", f1);
        printf("</tr>\n");
    }
    mysql_stmt_free_result(stmt);
    mysql_close(con);
    printf("</table>\n");
    printf("<h2>Add:</h2>\n");
    printf("<form method='post' action='/htbin/add'>\n");
    printf("F1: <input type='text' name='f1'>\n");
    printf("<br>\n");
    printf("F2: <input type='text' name='f2'>\n");
    printf("<br>\n");
    printf("<input type='submit' value='Add'>\n");
    printf("</form>\n");
    printf("</body>\n");
    printf("</html>\n");
    return 0;
}

add.com:

$ write net_link "<DNETRECMODE>"
$ mcr 'f$parse("cgi_symbols.exe;",f$environment("procedure"))' www_ form_
$ define sys$output net_link
$ mcr 'f$parse("add.exe;",f$environment("procedure"))'
$ exit

add.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SOCKET int
#include <mysql.h>

int main()
{
    FILE *fp = fopen("apache$input", "r");
    char line[1000];
    fgets(line, sizeof(line), fp);
    fclose(fp);
    char *form_f1 = getenv("FORM_FLD_F1");
    char *form_f2 = getenv("FORM_FLD_F2");
    int stat;
    MYSQL *con = mysql_init(NULL);
    con = mysql_real_connect(con, "localhost", "root", "", "test", 3306, NULL, 0);
    MYSQL_STMT *stmt = mysql_stmt_init(con);
    char *sqlstr = "INSERT INTO t1 VALUES(?, ?)";
    stat = mysql_stmt_prepare(stmt, sqlstr, strlen(sqlstr));
    MYSQL_BIND in[2];
    int f1 = atoi(form_f1);
    char *f2 = form_f2;
    memset(in, 0, sizeof(in));
    in[0].buffer_type = MYSQL_TYPE_LONG;
    in[0].buffer = &f1;
    in[0].buffer_length = sizeof(f1);
    in[0].is_null= 0;
    in[1].buffer_type = MYSQL_TYPE_STRING;
    in[1].buffer = f2;
    in[1].buffer_length = strlen(f2);
    stat = mysql_stmt_bind_param(stmt, in);
    stat = mysql_stmt_execute(stmt);
    mysql_stmt_free_result(stmt);
    mysql_close(con);
    printf("Location: http://arne1.vajhoej.dk:81/htbin/show\n");
    printf("\n");
    return 0;
}

del.com:

$ write net_link "<DNETRECMODE>"
$ mcr 'f$parse("cgi_symbols.exe;",f$environment("procedure"))'
$ define sys$output net_link
$ mcr 'f$parse("del.exe;",f$environment("procedure"))'
$ exit

del.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SOCKET int
#include <mysql.h>

static char *parse(char *line, char *marker)
{
    char *p = strstr(line, marker);
    if(p == NULL)
    {
        return NULL;
    }
    else
    {
        char *res = malloc(strlen(line) + 1);
        p = p + strlen(marker);
        char *p2 = p;
        while(*p2 && *p2 != '&') p2++;
        memcpy(res, p, p2 - p);
        res[p2 - p] = 0;
        for(int i = 0; i < strlen(res); i++) if(res[i] == '+') res[i] = ' ';
        return res;
    }
}

int main()
{
    char *q = getenv("WWW_QUERY_STRING");
    char *q_f1 = parse(q, "f1=");
    int stat;
    MYSQL *con = mysql_init(NULL);
    con = mysql_real_connect(con, "localhost", "root", "", "test", 3306, NULL, 0);
    MYSQL_STMT *stmt = mysql_stmt_init(con);
    char *sqlstr = "DELETE FROM t1 WHERE f1 = ?";
    stat = mysql_stmt_prepare(stmt, sqlstr, strlen(sqlstr));
    MYSQL_BIND in[1];
    int f1 = atoi(q_f1);
    memset(in, 0, sizeof(in));
    in[0].buffer_type = MYSQL_TYPE_LONG;
    in[0].buffer = &f1;
    in[0].buffer_length = sizeof(f1);
    in[0].is_null= 0;
    stat = mysql_stmt_bind_param(stmt, in);
    stat = mysql_stmt_execute(stmt);
    mysql_stmt_free_result(stmt);
    mysql_close(con);
    printf("Location: http://arne1.vajhoej.dk:81/htbin/show\n");
    printf("\n");
    return 0;
}

Start URL: http://nodename:port/htbin/show

show.com:

$ write net_link "<DNETRECMODE>"
$ mcr 'f$parse("cgi_symbols.exe;",f$environment("procedure"))'
$ define sys$output net_link
$ mcr 'f$parse("show.exe;",f$environment("procedure"))'
$ exit

show.pas:

[inherit('pmysqldir:pmysql', 'pmysqldir:common', 'pmysqldir:mysql')]
program show(input, output);

var
   con : mysql_ptr;
   stmt : mysql_stmt_ptr;
   stat : integer;
   outparam : array[1..2] of mysql_bind;
   f1 : integer;
   f2 : longpstr(255);

begin
   writeln('content-type: text/html');
   writeln('');
   writeln('<html>');
   writeln('<head>');
   writeln('<title>CGI - Pascal</title>');
   writeln('</head>');
   writeln('<body>');
   writeln('<h1>CGI - Pascal</body>');
   writeln('<h2>Show:</h2>');
   writeln('<table border="1">');
   writeln('<tr>');
   writeln('<th>F1</th>');
   writeln('<th>F2</th>');
   writeln('</tr>');
   con := pmysql_init;
   con := pmysql_real_connect(con, 'localhost', 'root', '', 'test');
   stmt := pmysql_stmt_init(con);
   stat := pmysql_stmt_prepare(stmt, 'SELECT f1,f2 FROM t1');
   stat := pmysql_stmt_execute(stmt);
   pmysql_init_bind_long(outparam[1], f1);
   pmysql_init_bind_string_out(outparam[2], f2);
   stat := pmysql_stmt_bind_result(stmt, outparam);
   stat := pmysql_stmt_store_result(stmt);
   while pmysql_stmt_fetch(stmt) = 0 do begin
      writeln('<tr>');
      writeln('<td>', f1:1, '</td>');
      writeln('<td>', stdstr(f2), '</td>');
      writeln('<td><a href="/htbin/del?f1=', f1:1, '">Delete</a></td>');
      writeln('</tr>');
   end;
   pmysql_stmt_free_result(stmt);
   writeln('</table>');
   writeln('<h2>Add:</h2>');
   writeln('<form method="post" action="/htbin/add">');
   writeln('F1: <input type="text" name="f1">');
   writeln('<br>');
   writeln('F2: <input type="text" name="f2">');
   writeln('<br>');
   writeln('<input type="submit" value="Add">');
   writeln('</form>');
   writeln('</body>');
   writeln('</html>');
   pmysql_close(con);
end.

add.com:

$ write net_link "<DNETRECMODE>"
$ mcr 'f$parse("cgi_symbols.exe;",f$environment("procedure"))' www_ form_
$ define sys$output net_link
$ mcr 'f$parse("add.exe;",f$environment("procedure"))'
$ exit

add.pas:

[inherit('sys$library:pascal$lib_routines', 'pmysqldir:pmysql', 'pmysqldir:common', 'pmysqldir:mysql')]
program add(input, output);

var
   con : mysql_ptr;
   stmt : mysql_stmt_ptr;
   stat : integer;
   inparam : array[1..2] of mysql_bind;
   form_f1, form_f2 : varying [255] of char;
   f1 : integer;
   f2 : pstr;

begin
   lib$get_symbol('FORM_FLD_F1', form_f1.body, form_f1.length);   
   readv(form_f1, f1);
   lib$get_symbol('FORM_FLD_F2', form_f2.body, form_f2.length);   
   f2 := form_f2;
   con := pmysql_init;
   con := pmysql_real_connect(con, 'localhost', 'root', '', 'test');
   stmt := pmysql_stmt_init(con);
   stat := pmysql_stmt_prepare(stmt, 'INSERT INTO t1 VALUES(?, ?)');
   pmysql_init_bind_long(inparam[1], f1);
   pmysql_init_bind_string_in(inparam[2], f2);
   stat := pmysql_stmt_bind_param(stmt, inparam);
   stat := pmysql_stmt_execute(stmt);
   pmysql_stmt_free_result(stmt);
   pmysql_close(con);
   writeln('Location: http://arne1.vajhoej.dk:81/htbin/show');
   writeln;
end.

del.com:

$ write net_link "<DNETRECMODE>"
$ mcr 'f$parse("cgi_symbols.exe;",f$environment("procedure"))'
$ define sys$output net_link
$ mcr 'f$parse("del.exe;",f$environment("procedure"))'
$ exit

del.pas:

[inherit('sys$library:pascal$lib_routines', 'pmysqldir:pmysql', 'pmysqldir:common', 'pmysqldir:mysql')]
program del(input, output);

var
   con : mysql_ptr;
   stmt : mysql_stmt_ptr;
   stat : integer;
   inparam : array[1..1] of mysql_bind;
   ix1, ix2 : integer;
   q, q_f1 : varying [255] of char;
   f1 : integer;

begin
   lib$get_symbol('WWW_QUERY_STRING', q.body, q.length);
   q_f1 := substr(q, 4, q.length - 3);
   readv(q_f1, f1);
   con := pmysql_init;
   con := pmysql_real_connect(con, 'localhost', 'root', '', 'test');
   stmt := pmysql_stmt_init(con);
   stat := pmysql_stmt_prepare(stmt, 'DELETE FROM t1 WHERE f1 = ?');
   pmysql_init_bind_long(inparam[1], f1);
   stat := pmysql_stmt_bind_param(stmt, inparam);
   stat := pmysql_stmt_execute(stmt);
   pmysql_stmt_free_result(stmt);
   pmysql_close(con);
   writeln('Location: http://arne1.vajhoej.dk:81/htbin/show');
   writeln;
end.

Start URL: http://nodename:port/htbin/show

show.com:

$ write net_link "<DNETRECMODE>"
$ mcr 'f$parse("cgi_symbols.exe;",f$environment("procedure"))'
$ define sys$output net_link
$ mcr 'f$parse("show.exe;",f$environment("procedure"))'
$ exit

show.bas:

program show

option type = explicit

%include "bmysqldir:b.bas"

declare integer dbcon, stmt, outparam
declare integer stat
declare integer f1
declare string f2

print "content-type: text/html"
print ""
print "<html>"
print "<head>"
print "<title>CGI - Basic</title>"
print "</head>"
print "<body>"
print "<h1>CGI - Basic</body>"
print "<h2>Show:</h2>"
print "<table border='1'>"
print "<tr>"
print "<th>F1</th>"
print "<th>F2</th>"
print "</tr>"
dbcon = bmysql_open("localhost", 3306, "root", "", "test")
stmt = bmysql_stmt_prepare(dbcon, "SELECT f1,f2 FROM t1")
outparam = bmysql_outparam_init(2)
call bmysql_outparam_long(outparam, 0, f1)
call bmysql_outparam_string(outparam, 1, f2)
stat = bmysql_stmt_execute(stmt, 0, outparam)
while bmysql_stmt_fetch(stmt, outparam) = 0
   print "<tr>"
   print using "<td>#</td>", f1
   print using "<td>'E</td>", f2
   print using "<td><a href='E/htbin/del?f1=#'E>Delete</a></td>", "'", f1, "'"
   print "</tr>"
next
call bmysql_outparam_free(outparam)
call bmysql_stmt_free(stmt)
call bmysql_close(dbcon)
print "</table>"
print "<h2>Add:</h2>"
print "<form method='post' action='/htbin/add'>"
print "F1: <input type='text' name='f1'>"
print "<br>"
print "F2: <input type='text' name='f2'>"
print "<br>"
print "<input type='submit' value='Add'>"
print "</form>"
print "</body>"
print "</html>"

end program

add.com:

$ write net_link "<DNETRECMODE>"
$ mcr 'f$parse("cgi_symbols.exe;",f$environment("procedure"))' www_ form_
$ define sys$output net_link
$ mcr 'f$parse("add.exe;",f$environment("procedure"))'
$ exit

add.bas:

program add

option type = explicit

%include "bmysqldir:b.bas"
external sub lib$get_symbol(string, string)

declare integer dbcon, stmt, inparam
declare integer stat
declare integer f1
declare string f2, form_f1, form_f2

call lib$get_symbol('FORM_FLD_F1', form_f1)
f1 = integer(form_f1)
call lib$get_symbol('FORM_FLD_F2', form_f2)
f2 = form_f2
dbcon = bmysql_open("localhost", 3306, "root", "", "test")
stmt = bmysql_stmt_prepare(dbcon, "INSERT INTO t1 VALUES(?,?)")
inparam = bmysql_inparam_init(2)
call bmysql_inparam_long(inparam, 0, f1)
call bmysql_inparam_string(inparam, 1, f2)
stat = bmysql_stmt_execute(stmt, inparam, 0)
call bmysql_inparam_free(inparam)
call bmysql_stmt_free(stmt)
call bmysql_close(dbcon)
print "Location: http://arne1.vajhoej.dk:81/htbin/show"
print ""

end program

del.com:

$ write net_link "<DNETRECMODE>"
$ mcr 'f$parse("cgi_symbols.exe;",f$environment("procedure"))'
$ define sys$output net_link
$ mcr 'f$parse("del.exe;",f$environment("procedure"))'
$ exit

del.bas:

program xdel

option type = explicit

%include "bmysqldir:b.bas"
external sub lib$get_symbol(string, string)

declare integer dbcon, stmt, inparam
declare integer stat
declare integer f1
declare string q, q_f1

call lib$get_symbol('WWW_QUERY_STRING', q)
q_f1 = mid(q, 4, len(q) - 3)
f1 = integer(q_f1)
dbcon = bmysql_open("localhost", 3306, "root", "", "test")
stmt = bmysql_stmt_prepare(dbcon, "DELETE FROM t1 WHERE f1 = ?")
inparam = bmysql_inparam_init(1)
call bmysql_inparam_long(inparam, 0, f1)
stat = bmysql_stmt_execute(stmt, inparam, 0)
call bmysql_inparam_free(inparam)
call bmysql_stmt_free(stmt)
call bmysql_close(dbcon)
print "Location: http://arne1.vajhoej.dk:81/htbin/show"
print ""

end program

Start URL: http://nodename:port/htbin/show

showpy.com:

$ write net_link "<DNETRECMODE>"
$ mcr 'f$parse("cgi_symbols.exe;",f$environment("procedure"))'
$ define sys$output net_link
$ set def disk5:[cgi.osu]
$ python show.py
$ exit

show.py:

import pymysql

print('content-type: text/html')
print('')
print('<html>')
print('<head>')
print('<title>CGI - Python</title>')
print('</head>')
print('<body>')
print('<h1>CGI - Python</body>')
print('<h2>Show:</h2>')
print('<table border="1">')
print('<tr>')
print('<th>F1</th>')
print('<th>F2</th>')
print('</tr>')
con = pymysql.connect(host='localhost',user='root',password='',db='test')
c = con.cursor()
c.execute('SELECT f1,f2 FROM t1')
res = c.fetchall()
for o in res:
   f1 = o[0]
   f2 = o[1]
   print('<tr>')
   print('<td>%d</td>' % (f1))
   print('<td>%s</td>' % (f2))
   print('<td><a href="/htbin/delpy?f1=%s">Delete</a></td>' % (f1))
   print('</tr>')
c.close()
con.close()
print('</table>')
print('<h2>Add:</h2>')
print('<form method="post" action="/htbin/addpy">')
print('F1: <input type="text" name="f1">')
print('<br>')
print('F2: <input type="text" name="f2">')
print('<br>')
print('<input type="submit" value="Add">')
print('</form>')
print('</body>')
print('</html>')

addpy.com:

$ write net_link "<DNETRECMODE>"
$ mcr 'f$parse("cgi_symbols.exe;",f$environment("procedure"))' www_ form_
$ define sys$output net_link
$ set def disk5:[cgi.osu]
$ python add.py
$ exit

add.py:

import os
import pymysql

f1 = os.getenv('FORM_FLD_F1')
f2 = os.getenv('FORM_FLD_F2')
con = pymysql.connect(host='localhost',user='root',password='',db='test')
c = con.cursor()
c.execute('INSERT INTO t1(f1,f2) VALUES(%s,%s)', (f1,f2))
c.close()
con.commit()
con.close()
print('Location: http://arne1.vajhoej.dk:81/htbin/showpy')
print('')

delpy.com:

$ write net_link "<DNETRECMODE>"
$ mcr 'f$parse("cgi_symbols.exe;",f$environment("procedure"))'
$ define sys$output net_link
$ set def disk5:[cgi.osu]
$ python del.py
$ exit

del.py:

import os
import pymysql

q = os.getenv('WWW_QUERY_STRING')
f1 = q[3:]
con = pymysql.connect(host='localhost',user='root',password='',db='test')
c = con.cursor()
c.execute('DELETE FROM t1 WHERE f1 = %s', (f1,))
c.close()
con.commit()
con.close()
print('Location: http://arne1.vajhoej.dk:81/htbin/showpy')
print('')

Start URL: http://nodename:port/htbin/showpy

oshow.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SOCKET int
#include <mysql.h>

#include "cgilib.h"

#define F2_MAX_LEN 50

int main(int argc, char *argv[])
{
    cgi_init(argc, argv);
    cgi_printf("content-type: text/html\n");
    cgi_printf("\n");
    cgi_printf("<html>\n");
    cgi_printf("<head>\n");
    cgi_printf("<title>CGI (OSU) - C</title>\n");
    cgi_printf("</head>\n");
    cgi_printf("<body>\n");
    cgi_printf("<h1>CGI (OSU) - C</body>\n");
    cgi_printf("<h2>Show:</h2>\n");
    cgi_printf("<table border='1'>\n");
    cgi_printf("<tr>\n");
    cgi_printf("<th>F1</th>\n");
    cgi_printf("<th>F2</th>\n");
    cgi_printf("</tr>\n");
    int stat;
    MYSQL *con = mysql_init(NULL);
    con = mysql_real_connect(con, "localhost", "root", "", "test", 3306, NULL, 0);
    MYSQL_STMT *stmt = mysql_stmt_init(con);
    char *sqlstr = "SELECT f1,f2 FROM t1";
    stat = mysql_stmt_prepare(stmt, sqlstr, strlen(sqlstr));
    stat = mysql_stmt_execute(stmt);
    MYSQL_BIND out[2];
    int f1;
    char f2[F2_MAX_LEN + 1];
    unsigned long int lenf2;
    memset(out, 0, sizeof(out));
    out[0].buffer_type = MYSQL_TYPE_LONG;
    out[0].buffer = &f1;
    out[0].buffer_length = sizeof(f1);
    out[1].buffer_type = MYSQL_TYPE_STRING;
    out[1].buffer = f2;
    out[1].buffer_length = sizeof(f2);
    out[1].length = &lenf2;
    stat = mysql_stmt_bind_result(stmt, out);
    stat = mysql_stmt_store_result(stmt);
    while(!mysql_stmt_fetch(stmt))
    {
        f2[lenf2] = 0;
        cgi_printf("<tr>\n");
        cgi_printf("<td>%d</td>\n", f1);
        cgi_printf("<td>%s</td>\n", f2);
        cgi_printf("<td><a href='/htbin/odel?f1=%d'>Delete</a></td>\n", f1);
        cgi_printf("</tr>\n");
    }
    mysql_stmt_free_result(stmt);
    mysql_close(con);
    cgi_printf("</table>\n");
    cgi_printf("<h2>Add:</h2>\n");
    cgi_printf("<form method='post' action='/htbin/oadd'>\n");
    cgi_printf("F1: <input type='text' name='f1'>\n");
    cgi_printf("<br>\n");
    cgi_printf("F2: <input type='text' name='f2'>\n");
    cgi_printf("<br>\n");
    cgi_printf("<input type='submit' value='Add'>\n");
    cgi_printf("</form>\n");
    cgi_printf("</body>\n");
    cgi_printf("</html>\n");
    return 0;
}

oadd.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SOCKET int
#include <mysql.h>

#include "cgilib.h"

static char *parse(char *line, char *marker)
{
    char *p = strstr(line, marker);
    if(p == NULL)
    {
        return NULL;
    }
    else
    {
        char *res = malloc(strlen(line) + 1);
        p = p + strlen(marker);
        char *p2 = p;
        while(*p2 && *p2 != '&') p2++;
        memcpy(res, p, p2 - p);
        res[p2 - p] = 0;
        for(int i = 0; i < strlen(res); i++) if(res[i] == '+') res[i] = ' ';
        return res;
    }
}

int main(int argc, char *argv[])
{
    cgi_init(argc, argv);
    int linelen = atoi(cgi_info("CONTENT_LENGTH"));
    char line[1000];
    cgi_read(line, linelen);
    line[linelen] = 0;
    char *form_f1 = parse(line, "f1=");
    char *form_f2 = parse(line, "f2=");
    int stat;
    MYSQL *con = mysql_init(NULL);
    con = mysql_real_connect(con, "localhost", "root", "", "test", 3306, NULL, 0);
    MYSQL_STMT *stmt = mysql_stmt_init(con);
    char *sqlstr = "INSERT INTO t1 VALUES(?, ?)";
    stat = mysql_stmt_prepare(stmt, sqlstr, strlen(sqlstr));
    MYSQL_BIND in[2];
    int f1 = atoi(form_f1);
    char *f2 = form_f2;
    memset(in, 0, sizeof(in));
    in[0].buffer_type = MYSQL_TYPE_LONG;
    in[0].buffer = &f1;
    in[0].buffer_length = sizeof(f1);
    in[0].is_null= 0;
    in[1].buffer_type = MYSQL_TYPE_STRING;
    in[1].buffer = f2;
    in[1].buffer_length = strlen(f2);
    stat = mysql_stmt_bind_param(stmt, in);
    stat = mysql_stmt_execute(stmt);
    mysql_stmt_free_result(stmt);
    mysql_close(con);
    cgi_printf("Location: http://arne1.vajhoej.dk:81/htbin/oshow\n");
    cgi_printf("\n");
    return 0;
}

odel.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SOCKET int
#include <mysql.h>

#include "cgilib.h"

static char *parse(char *line, char *marker)
{
    char *p = strstr(line, marker);
    if(p == NULL)
    {
        return NULL;
    }
    else
    {
        char *res = malloc(strlen(line) + 1);
        p = p + strlen(marker);
        char *p2 = p;
        while(*p2 && *p2 != '&') p2++;
        memcpy(res, p, p2 - p);
        res[p2 - p] = 0;
        for(int i = 0; i < strlen(res); i++) if(res[i] == '+') res[i] = ' ';
        return res;
    }
}

int main(int argc, char *argv[])
{
    cgi_init(argc, argv);
    char *q = cgi_info("QUERY_STRING");
    char *q_f1 = parse(q, "f1=");
    int stat;
    MYSQL *con = mysql_init(NULL);
    con = mysql_real_connect(con, "localhost", "root", "", "test", 3306, NULL, 0);
    MYSQL_STMT *stmt = mysql_stmt_init(con);
    char *sqlstr = "DELETE FROM t1 WHERE f1 = ?";
    stat = mysql_stmt_prepare(stmt, sqlstr, strlen(sqlstr));
    MYSQL_BIND in[1];
    int f1 = atoi(q_f1);
    memset(in, 0, sizeof(in));
    in[0].buffer_type = MYSQL_TYPE_LONG;
    in[0].buffer = &f1;
    in[0].buffer_length = sizeof(f1);
    in[0].is_null= 0;
    stat = mysql_stmt_bind_param(stmt, in);
    stat = mysql_stmt_execute(stmt);
    mysql_stmt_free_result(stmt);
    mysql_close(con);
    cgi_printf("Location: http://arne1.vajhoej.dk:81/htbin/oshow\n");
    cgi_printf("\n");
    return 0;
}

Start URL: http://nodename:port/htbin/oshow

Around 2000:

Around 2000 new web pages languages was invented that allows mixing HTML markup and code in same file.

Languages include ASP, PHP, JSP, CFM etc..

PHP and JSP are available on VMS.

PHP:

show.php:

<html>
<head>
<title>Spagetti PHP</title>
</head>
<body>
<h1>Spagetti PHP</body>
<h2>Show:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
<?php
$con = mysqli_connect('localhost', 'root', '', 'test');
$stmt = mysqli_prepare($con, 'SELECT f1,f2 FROM t1');
mysqli_stmt_execute($stmt);
$rs = mysqli_stmt_get_result($stmt);
while($row = mysqli_fetch_array($rs, MYSQLI_ASSOC)) {
    $f1 = $row['f1'];
    $f2 = $row['f2'];
    echo "<tr>\n<td>$f1</td>\n<td>$f2</td>\n<td><a href='del.php?f1=$f1'>Delete</a></td></tr>\n";
}
mysqli_stmt_close($stmt);
mysqli_close($con);
?>
</table>
<h2>Add:</h2>
<form method="post" action="add.php">
F1: <input type="text" name="f1">
<br>
F2: <input type="text" name="f2">
<br>
<input type="submit" value="Add">
</form>
</body>
</html>

add.php:

<?php
$f1 = $_POST['f1'];
$f2 = $_POST['f2'];

$con = mysqli_connect('localhost', 'root', '', 'test');
$stmt = mysqli_prepare($con, 'INSERT INTO t1 VALUES(?,?)');
mysqli_stmt_bind_param($stmt, 'is', $f1, $f2);
mysqli_stmt_execute($stmt);
mysqli_stmt_close($stmt);
mysqli_close($con);

header('Location: show.php');
?>

del.php:

<?php
$q = $_SERVER['QUERY_STRING'];
$f1 = (int)substr($q, 3);

$con = mysqli_connect('localhost', 'root', '', 'test');
$stmt = mysqli_prepare($con, 'DELETE FROM t1 WHERE f1 = ?');
mysqli_stmt_bind_param($stmt, 'i', $f1);
mysqli_stmt_execute($stmt);
mysqli_stmt_close($stmt);
mysqli_close($con);

header('Location: show.php');
?>

Start URL: http://nodename:port/application/show.php

showx.php:

<html>
<head>
<title>Spagetti PHP (pool)</title>
</head>
<body>
<h1>Spagetti PHP (pool)</body>
<h2>Show:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
<?php
$con = mysqli_connect('p:localhost', 'root', '', 'test');
$stmt = mysqli_prepare($con, 'SELECT f1,f2 FROM t1');
mysqli_stmt_execute($stmt);
$rs = mysqli_stmt_get_result($stmt);
while($row = mysqli_fetch_array($rs, MYSQLI_ASSOC)) {
    $f1 = $row['f1'];
    $f2 = $row['f2'];
    echo "<tr>\n<td>$f1</td>\n<td>$f2</td>\n<td><a href='delx.php?f1=$f1'>Delete</a></td></tr>\n";
}
mysqli_stmt_close($stmt);
mysqli_close($con);
?>
</table>
<h2>Add:</h2>
<form method="post" action="addx.php">
F1: <input type="text" name="f1">
<br>
F2: <input type="text" name="f2">
<br>
<input type="submit" value="Add">
</form>
</body>
</html>

addx.php:

<?php
$f1 = $_POST['f1'];
$f2 = $_POST['f2'];

$con = mysqli_connect('p:localhost', 'root', '', 'test');
$stmt = mysqli_prepare($con, 'INSERT INTO t1 VALUES(?,?)');
mysqli_stmt_bind_param($stmt, 'is', $f1, $f2);
mysqli_stmt_execute($stmt);
mysqli_stmt_close($stmt);
mysqli_close($con);

header('Location: showx.php');
?>

delx.php:

<?php
$q = $_SERVER['QUERY_STRING'];
$f1 = (int)substr($q, 3);

$con = mysqli_connect('p:localhost', 'root', '', 'test');
$stmt = mysqli_prepare($con, 'DELETE FROM t1 WHERE f1 = ?');
mysqli_stmt_bind_param($stmt, 'i', $f1);
mysqli_stmt_execute($stmt);
mysqli_stmt_close($stmt);
mysqli_close($con);

header('Location: showx.php');
?>

Start URL: http://nodename:port/application/showx.php

JSP:

JSP (Java Server Pages) allow for mixing of HTML markup and Java code.

show.jsp:

<%@ page import="java.sql.*" %>
<html>
<head>
<title>Spagetti JSP</title>
</head>
<body>
<h1>Spagetti JSP</body>
<h2>Show:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
<%
Class.forName("com.mysql.jdbc.Driver");
Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "");
PreparedStatement pstmt = con.prepareStatement("SELECT f1,f2 FROM t1");
ResultSet rs = pstmt.executeQuery();
while(rs.next()) {
    int f1 = rs.getInt(1);
    String f2 = rs.getString(2);
%>
    <tr>
        <td><%=f1%></td>
        <td><%=f2%></td>
        <td><a href='del.jsp?f1=<%=f1%>'>Delete</a></td>
    </tr>
<%
}
rs.close();
pstmt.close();
con.close();
%>
</table>
<h2>Add:</h2>
<form method="post" action="add.jsp">
F1: <input type="text" name="f1">
<br>
F2: <input type="text" name="f2">
<br>
<input type="submit" value="Add">
</form>
</body>
</html>

add.jsp:

<%@ page import="java.sql.*" %>
<%
int f1 = Integer.parseInt(request.getParameter("f1"));
String f2 = request.getParameter("f2");
Class.forName("com.mysql.jdbc.Driver");
Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "");
PreparedStatement pstmt = con.prepareStatement("INSERT INTO t1 VALUES(?,?)");
pstmt.setInt(1,  f1);
pstmt.setString(2, f2);
pstmt.executeUpdate();
pstmt.close();
con.close();
response.sendRedirect("show.jsp");
%>

del.jsp:

<%@ page import="java.sql.*" %>
<%
int f1 = Integer.parseInt(request.getParameter("f1"));
Class.forName("com.mysql.jdbc.Driver");
Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "");
PreparedStatement pstmt = con.prepareStatement("DELETE FROM t1 WHERE f1 = ?");
pstmt.setInt(1,  f1);
pstmt.executeUpdate();
pstmt.close();
con.close();
response.sendRedirect("show.jsp");
%>

Start URL: http://nodename:port/application/show.jsp

showx.jsp:

<%@ page import="java.sql.*,javax.naming.*,javax.sql.*" %>
<html>
<head>
<title>Spagetti JSP (pool)</title>
</head>
<body>
<h1>Spagetti JSP (pool)</body>
<h2>Show:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
<%
Context initctx = new InitialContext();
Context envctx  = (Context)initctx.lookup("java:/comp/env");
DataSource ds = (DataSource)envctx.lookup("jdbc/TestDB");
Connection con = ds.getConnection();
PreparedStatement pstmt = con.prepareStatement("SELECT f1,f2 FROM t1");
ResultSet rs = pstmt.executeQuery();
while(rs.next()) {
    int f1 = rs.getInt(1);
    String f2 = rs.getString(2);
%>
    <tr>
        <td><%=f1%></td>
        <td><%=f2%></td>
        <td><a href='delx.jsp?f1=<%=f1%>'>Delete</a></td>
    </tr>
<%
}
rs.close();
pstmt.close();
con.close();
%>
</table>
<h2>Add:</h2>
<form method="post" action="addx.jsp">
F1: <input type="text" name="f1">
<br>
F2: <input type="text" name="f2">
<br>
<input type="submit" value="Add">
</form>
</body>
</html>

addx.jsp:

<%@ page import="java.sql.*,javax.naming.*,javax.sql.*" %>
<%
int f1 = Integer.parseInt(request.getParameter("f1"));
String f2 = request.getParameter("f2");
Context initctx = new InitialContext();
Context envctx  = (Context)initctx.lookup("java:/comp/env");
DataSource ds = (DataSource)envctx.lookup("jdbc/TestDB");
Connection con = ds.getConnection();
PreparedStatement pstmt = con.prepareStatement("INSERT INTO t1 VALUES(?,?)");
pstmt.setInt(1,  f1);
pstmt.setString(2, f2);
pstmt.executeUpdate();
pstmt.close();
con.close();
response.sendRedirect("showx.jsp");
%>

delx.jsp:

<%@ page import="java.sql.*,javax.naming.*,javax.sql.*" %>
<%
int f1 = Integer.parseInt(request.getParameter("f1"));
Context initctx = new InitialContext();
Context envctx  = (Context)initctx.lookup("java:/comp/env");
DataSource ds = (DataSource)envctx.lookup("jdbc/TestDB");
Connection con = ds.getConnection();
PreparedStatement pstmt = con.prepareStatement("DELETE FROM t1 WHERE f1 = ?");
pstmt.setInt(1,  f1);
pstmt.executeUpdate();
pstmt.close();
con.close();
response.sendRedirect("showx.jsp");
%>

Start URL: http://nodename:port/application/showx.jsp

[.WEB-INF]web.xml:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
</web-app>

Directory structure:

Directory DISK5:[WWW_arne1.Tomcat.spagetti]

add.jsp;1           addx.jsp;1          del.jsp;1           delx.jsp;1
show.jsp;1          showx.jsp;1         WEB-INF.DIR;1

Total of 7 files.

Directory DISK5:[WWW_arne1.Tomcat.spagetti.WEB-INF]

lib.DIR;1           web.xml;1

Total of 2 files.

Directory DISK5:[WWW_arne1.Tomcat.spagetti.WEB-INF.lib]

mysql-connector-java-5_1_36-bin.jar;1

Total of 1 file.

Grand total of 3 directories, 10 files.

Early 00's:

Early ASP/PHP/JSP often turned into spagetti code, so the next step was to try and separate markup and code.

A common design was MVC:

M - Model
code interfacing business logic and data
V - View
presentation layout responsible for generating the HTML
C - Controller
code handling user actions

We will see some examples with DIY MVC with multiple controllers and an example of a real MVC framework: Struts.

PHP + PHP:

display.php:

<html>
<head>
<title>PHP MVC</title>
</head>
<body>
<h1>PHP MVC</body>
<h2>Show:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
<?php
foreach($data as $o) {
    $f1 = $o->f1;
    $f2 = $o->f2;
    echo "<tr>\n<td>$f1</td>\n<td>$f2</td>\n<td><a href='del.php?f1=$f1'>Delete</a></td></tr>\n";
}
?>
</table>
<h2>Add:</h2>
<form method="post" action="add.php">
F1: <input type="text" name="f1">
<br>
F2: <input type="text" name="f2">
<br>
<input type="submit" value="Add">
</form>
</body>
</html>

show.php:

<?php

include 'data.php';

$data = selectAllT1();

include 'display.php';

?>

add.php:

<?php

include 'data.php';

$f1 = $_POST['f1'];
$f2 = $_POST['f2'];
$o = new T1($f1, $f2);
insertOneT1($o);

header('Location: show.php');
?>

del.php:

<?php

include 'data.php';

$q = $_SERVER['QUERY_STRING'];
$f1 = (int)substr($q, 3);

deleteOneT1($f1);

header('Location: show.php');
?>

data.php:

<?php

class T1 {
    public $f1;
    public $f2;
    public function __construct($f1, $f2) {
        $this->f1 = $f1;
        $this->f2 = $f2;
    }
}

function getConnection() {
    $con = mysqli_connect('localhost', 'root', '', 'test');
    return $con;
}

function selectAllT1() {
    $con = getConnection();
    $stmt = mysqli_prepare($con, 'SELECT f1,f2 FROM t1');
    mysqli_stmt_execute($stmt);
    $rs = mysqli_stmt_get_result($stmt);
    $res = array();
    while($row = mysqli_fetch_array($rs, MYSQLI_ASSOC)) {
        $f1 = $row['f1'];
        $f2 = $row['f2'];
        $res[] = new T1($f1, $f2);
    }
    mysqli_stmt_close($stmt);
    mysqli_close($con);
    return $res;
}

function insertOneT1($o) {
    $con = getConnection();
    $stmt = mysqli_prepare($con, 'INSERT INTO t1 VALUES(?,?)');
    mysqli_stmt_bind_param($stmt, 'is', $o->f1, $o->f2);
    mysqli_stmt_execute($stmt);
    mysqli_stmt_close($stmt);
    mysqli_close($con);
}

function deleteOneT1($f1) {
    $con = getConnection();
    $stmt = mysqli_prepare($con, 'DELETE FROM t1 WHERE f1 = ?');
    mysqli_stmt_bind_param($stmt, 'i', $f1);
    mysqli_stmt_execute($stmt);
    mysqli_stmt_close($stmt);
    mysqli_close($con);
}

?>

Start URL: http://nodename:port/application/show.php

displayx.php:

<html>
<head>
<title>PHP MVC (pool)</title>
</head>
<body>
<h1>PHP MVC (pool)</body>
<h2>Show:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
<?php
foreach($data as $o) {
    $f1 = $o->f1;
    $f2 = $o->f2;
    echo "<tr>\n<td>$f1</td>\n<td>$f2</td>\n<td><a href='delx.php?f1=$f1'>Delete</a></td></tr>\n";
}
?>
</table>
<h2>Add:</h2>
<form method="post" action="addx.php">
F1: <input type="text" name="f1">
<br>
F2: <input type="text" name="f2">
<br>
<input type="submit" value="Add">
</form>
</body>
</html>

showx.php:

<?php

include 'datax.php';

$data = selectAllT1();

include 'displayx.php';

?>

addx.php:

<?php

include 'datax.php';

$f1 = $_POST['f1'];
$f2 = $_POST['f2'];
$o = new T1($f1, $f2);
insertOneT1($o);

header('Location: showx.php');
?>

delx.php:

<?php

include 'datax.php';

$q = $_SERVER['QUERY_STRING'];
$f1 = (int)substr($q, 3);

deleteOneT1($f1);

header('Location: showx.php');
?>

datax.php:

<?php

class T1 {
    public $f1;
    public $f2;
    public function __construct($f1, $f2) {
        $this->f1 = $f1;
        $this->f2 = $f2;
    }
}

function getConnection() {
    $con = mysqli_connect('localhost', 'root', '', 'test');
    return $con;
}

function selectAllT1() {
    $con = getConnection();
    $stmt = mysqli_prepare($con, 'SELECT f1,f2 FROM t1');
    mysqli_stmt_execute($stmt);
    $rs = mysqli_stmt_get_result($stmt);
    $res = array();
    while($row = mysqli_fetch_array($rs, MYSQLI_ASSOC)) {
        $f1 = $row['f1'];
        $f2 = $row['f2'];
        $res[] = new T1($f1, $f2);
    }
    mysqli_stmt_close($stmt);
    mysqli_close($con);
    return $res;
}

function insertOneT1($o) {
    $con = getConnection();
    $stmt = mysqli_prepare($con, 'INSERT INTO t1 VALUES(?,?)');
    mysqli_stmt_bind_param($stmt, 'is', $o->f1, $o->f2);
    mysqli_stmt_execute($stmt);
    mysqli_stmt_close($stmt);
    mysqli_close($con);
}

function deleteOneT1($f1) {
    $con = getConnection();
    $stmt = mysqli_prepare($con, 'DELETE FROM t1 WHERE f1 = ?');
    mysqli_stmt_bind_param($stmt, 'i', $f1);
    mysqli_stmt_execute($stmt);
    mysqli_stmt_close($stmt);
    mysqli_close($con);
}

?>

Start URL: http://nodename:port/application/showx.php

JSP + Java:

Besides the MVC split then the JSP can be further made code free by replacing Java code fragments with special tags - JSTL (Java Standard Tag Library) - providing common functionality.

display.jsp:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>JSP + Java MVC</title>
</head>
<body>
<h1>JSP + Java MVC</body>
<h2>Show:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
<c:forEach items="${data}" var="o">
    <tr>
        <td><c:out value="${o.f1}"/></td>
        <td><c:out value="${o.f2}"/></td>
        <td><a href='del?f1=<c:out value="${o.f1}"/>'>Delete</a></td>
    </tr>
</c:forEach>
</table>
<h2>Add:</h2>
<form method="post" action="add">
F1: <input type="text" name="f1">
<br>
F2: <input type="text" name="f2">
<br>
<input type="submit" value="Add">
</form>
</body>
</html>

Show.java:

package mvcj;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Show extends HttpServlet {
    private Data data = new Data();
    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setAttribute("data", data.selectAllT1());
        req.getRequestDispatcher("display.jsp").forward(req,  resp);
    }
}

Add.java:

package mvcj;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Add extends HttpServlet {
    private Data data = new Data();
    @Override
    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        int f1 = Integer.parseInt(req.getParameter("f1"));
        String f2 = req.getParameter("f2");
        T1 o = new T1(f1, f2);
        data.insertOneT1(o);
        resp.sendRedirect("show");
    }
}

Del.java:

package mvcj;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Del extends HttpServlet {
    private Data data = new Data();
    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        int f1 = Integer.parseInt(req.getParameter("f1"));
        data.deleteOneT1(f1);
        resp.sendRedirect("show");
    }
}

T1.java:

package mvcj;

public class T1 {
    private int f1;
    private String f2;
    public T1() {
        this(0, "");
    }
    public T1(int f1, String f2) {
        this.f1 = f1;
        this.f2 = f2;
    }
    public int getF1() {
        return f1;
    }
    public void setF1(int f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
}

Data.java:

package mvcj;

import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class Data {
    public List<T1> selectAllT1() {
        List<T1> res = new ArrayList<T1>();
        try {
            Class.forName("com.mysql.jdbc.Driver");
            Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "");
            PreparedStatement pstmt = con.prepareStatement("SELECT f1,f2 FROM t1");
            ResultSet rs = pstmt.executeQuery();
            while(rs.next()) {
                int f1 = rs.getInt(1);
                String f2 = rs.getString(2);
                T1 o = new T1(f1, f2);
                res.add(o);
            }
            rs.close();
            pstmt.close();
            con.close();
        } catch(ClassNotFoundException ex) {
            ex.printStackTrace();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
        return res;
    }
    public void insertOneT1(T1 o) {
        try {
            Class.forName("com.mysql.jdbc.Driver");
            Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "");
            PreparedStatement pstmt = con.prepareStatement("INSERT INTO t1 VALUES(?,?)");
            pstmt.setInt(1, o.getF1());
            pstmt.setString(2, o.getF2());
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(ClassNotFoundException ex) {
            ex.printStackTrace();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
    public void deleteOneT1(int f1) {
        try {
            Class.forName("com.mysql.jdbc.Driver");
            Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "");
            PreparedStatement pstmt = con.prepareStatement("DELETE FROM t1 WHERE f1 = ?");
            pstmt.setInt(1,  f1);
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(ClassNotFoundException ex) {
            ex.printStackTrace();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
}

Start URL: http://nodename:port/application/show

displayx.jsp:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>JSP + Java MVC (pool)</title>
</head>
<body>
<h1>JSP + Java MVC (pool)</body>
<h2>Show:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
<c:forEach items="${data}" var="o">
    <tr>
        <td><c:out value="${o.f1}"/></td>
        <td><c:out value="${o.f2}"/></td>
        <td><a href='delx?f1=<c:out value="${o.f1}"/>'>Delete</a></td>
    </tr>
</c:forEach>
</table>
<h2>Add:</h2>
<form method="post" action="addx">
F1: <input type="text" name="f1">
<br>
F2: <input type="text" name="f2">
<br>
<input type="submit" value="Add">
</form>
</body>
</html>

ShowX.java:

package mvcj;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ShowX extends HttpServlet {
    private DataX data = new DataX();
    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setAttribute("data", data.selectAllT1());
        req.getRequestDispatcher("displayx.jsp").forward(req,  resp);
    }
}

AddX.java:

package mvcj;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class AddX extends HttpServlet {
    private DataX data = new DataX();
    @Override
    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        int f1 = Integer.parseInt(req.getParameter("f1"));
        String f2 = req.getParameter("f2");
        T1 o = new T1(f1, f2);
        data.insertOneT1(o);
        resp.sendRedirect("showx");
    }
}

DelX.java:

package mvcj;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class DelX extends HttpServlet {
    private DataX data = new DataX();
    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        int f1 = Integer.parseInt(req.getParameter("f1"));
        data.deleteOneT1(f1);
        resp.sendRedirect("showx");
    }
}

T1.java:

package mvcj;

public class T1 {
    private int f1;
    private String f2;
    public T1() {
        this(0, "");
    }
    public T1(int f1, String f2) {
        this.f1 = f1;
        this.f2 = f2;
    }
    public int getF1() {
        return f1;
    }
    public void setF1(int f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
}

DataX.java:

package mvcj;

import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

public class DataX {
    private static DataSource ds;
    static {
        try {
            Context initctx = new InitialContext();
            Context envctx  = (Context)initctx.lookup("java:/comp/env");
            ds = (DataSource)envctx.lookup("jdbc/TestDB");
        } catch(NamingException ex) {
            ex.printStackTrace();
        }
    }
    public List<T1> selectAllT1() {
        List<T1> res = new ArrayList<T1>();
        try {
            Connection con = ds.getConnection();
            PreparedStatement pstmt = con.prepareStatement("SELECT f1,f2 FROM t1");
            ResultSet rs = pstmt.executeQuery();
            while(rs.next()) {
                int f1 = rs.getInt(1);
                String f2 = rs.getString(2);
                T1 o = new T1(f1, f2);
                res.add(o);
            }
            rs.close();
            pstmt.close();
            con.close();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
        return res;
    }
    public void insertOneT1(T1 o) {
        try {
            Connection con = ds.getConnection();
            PreparedStatement pstmt = con.prepareStatement("INSERT INTO t1 VALUES(?,?)");
            pstmt.setInt(1, o.getF1());
            pstmt.setString(2, o.getF2());
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
    public void deleteOneT1(int f1) {
        try {
            Connection con = ds.getConnection();
            PreparedStatement pstmt = con.prepareStatement("DELETE FROM t1 WHERE f1 = ?");
            pstmt.setInt(1,  f1);
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
}

Start URL: http://nodename:port/application/showx

[.WEB-INF]web.xml:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
    <servlet>
        <servlet-name>ShowServlet</servlet-name>
        <servlet-class>mvcj.Show</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>ShowServlet</servlet-name>
        <url-pattern>/show</url-pattern>
    </servlet-mapping>
    <servlet>
        <servlet-name>AddServlet</servlet-name>
        <servlet-class>mvcj.Add</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>AddServlet</servlet-name>
        <url-pattern>/add</url-pattern>
    </servlet-mapping>
    <servlet>
        <servlet-name>DelServlet</servlet-name>
        <servlet-class>mvcj.Del</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>DelServlet</servlet-name>
        <url-pattern>/del</url-pattern>
    </servlet-mapping>
    <servlet>
        <servlet-name>ShowXServlet</servlet-name>
        <servlet-class>mvcj.ShowX</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>ShowXServlet</servlet-name>
        <url-pattern>/showx</url-pattern>
    </servlet-mapping>
    <servlet>
        <servlet-name>AddXServlet</servlet-name>
        <servlet-class>mvcj.AddX</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>AddXServlet</servlet-name>
        <url-pattern>/addx</url-pattern>
    </servlet-mapping>
    <servlet>
        <servlet-name>DelXServlet</servlet-name>
        <servlet-class>mvcj.DelX</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>DelXServlet</servlet-name>
        <url-pattern>/delx</url-pattern>
    </servlet-mapping>
</web-app>

Directory structure:

Directory DISK5:[WWW_arne1.Tomcat.mvcj]

display.jsp;1       displayx.jsp;1      WEB-INF.DIR;1

Total of 3 files.

Directory DISK5:[WWW_arne1.Tomcat.mvcj.WEB-INF]

classes.DIR;1       lib.DIR;1           web.xml;1

Total of 3 files.

Directory DISK5:[WWW_arne1.Tomcat.mvcj.WEB-INF.classes]

mvcj.DIR;1

Total of 1 file.

Directory DISK5:[WWW_arne1.Tomcat.mvcj.WEB-INF.classes.mvcj]

ADD.CLASS;1         Add.java;1          AddX.class;1        AddX.java;1
DATA.CLASS;1        Data.java;1         DataX.class;1       DataX.java;1
DEL.CLASS;1         Del.java;1          DelX.class;1        DelX.java;1
gen.com;1           SHOW.CLASS;1        Show.java;1         ShowX.class;1
ShowX.java;1        T1.CLASS;1          T1.java;1

Total of 19 files.

Directory DISK5:[WWW_arne1.Tomcat.mvcj.WEB-INF.lib]

jstl.jar;1          mysql-connector-java-5_1_36-bin.jar;1   standard.jar;1

Total of 3 files.

Grand total of 5 directories, 29 files.

JSP + Groovy:

The Java code in controllers and models can be replaced by Groovy code utilizing socalled groovelets. This can reduce code size.

display.jsp:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>JSP + Groovy MVC</title>
</head>
<body>
<h1>JSP + Groovy MVC</body>
<h2>Show:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
<c:forEach items="${data}" var="o">
    <tr>
        <td><c:out value="${o.f1}"/></td>
        <td><c:out value="${o.f2}"/></td>
        <td><a href='del.groovy?f1=<c:out value="${o.f1}"/>'>Delete</a></td>
    </tr>
</c:forEach>
</table>
<h2>Add:</h2>
<form method="post" action="add.groovy">
F1: <input type="text" name="f1">
<br>
F2: <input type="text" name="f2">
<br>
<input type="submit" value="Add">
</form>
</body>
</html>

show.groovy:

import mvcg.*

data = new Data()
request.setAttribute("data", data.selectAllT1())
request.getRequestDispatcher("display.jsp").forward(request, response)

add.groovy:

import mvcg.*

data = new Data()
f1 = Integer.parseInt(request.getParameter("f1"))
f2 = request.getParameter("f2")
o = new T1(f1: f1, f2: f2)
data.insertOneT1(o)
response.sendRedirect("show.groovy")

del.groovy:

import mvcg.*

data = new Data()
f1 = Integer.parseInt(request.getParameter("f1"))
data.deleteOneT1(f1)
response.sendRedirect("show.groovy")

Data.groovy:

package mvcg

import java.sql.*

class T1 {
    int f1
    String f2
}

class Data {
    List<T1> selectAllT1() {
        List<T1> res = new ArrayList<T1>()
        Class.forName("com.mysql.jdbc.Driver")
        def con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "")
        def pstmt = con.prepareStatement("SELECT f1,f2 FROM t1")
        def rs = pstmt.executeQuery()
        while(rs.next()) {
            def f1 = rs.getInt(1)
            def f2 = rs.getString(2)
            def o = new T1(f1: f1, f2: f2)
            res.add(o)
        }
        rs.close()
        pstmt.close()
        con.close()
        return res
    }
    void insertOneT1(T1 o) {
        Class.forName("com.mysql.jdbc.Driver")
        def con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "")
        def pstmt = con.prepareStatement("INSERT INTO t1 VALUES(?,?)")
        pstmt.setInt(1, o.f1)
        pstmt.setString(2, o.f2)
        pstmt.executeUpdate()
        pstmt.close()
        con.close()
    }
    void deleteOneT1(int f1) {
        Class.forName("com.mysql.jdbc.Driver")
        def con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "")
        def pstmt = con.prepareStatement("DELETE FROM t1 WHERE f1 = ?")
        pstmt.setInt(1, f1)
        pstmt.executeUpdate()
        pstmt.close()
        con.close()
    }
}

Start URL: http://nodename:port/application/show.groovy

displayx.jsp:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>JSP + Groovy MVC (pool)</title>
</head>
<body>
<h1>JSP + Groovy MVC (pool)</body>
<h2>Show:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
<c:forEach items="${data}" var="o">
    <tr>
        <td><c:out value="${o.f1}"/></td>
        <td><c:out value="${o.f2}"/></td>
        <td><a href='delx.groovy?f1=<c:out value="${o.f1}"/>'>Delete</a></td>
    </tr>
</c:forEach>
</table>
<h2>Add:</h2>
<form method="post" action="addx.groovy">
F1: <input type="text" name="f1">
<br>
F2: <input type="text" name="f2">
<br>
<input type="submit" value="Add">
</form>
</body>
</html>

showx.groovy:

import mvcg.*

data = new DataX()
request.setAttribute("data", data.selectAllT1())
request.getRequestDispatcher("displayx.jsp").forward(request, response)

addx.groovy:

import mvcg.*

data = new DataX()
f1 = Integer.parseInt(request.getParameter("f1"))
f2 = request.getParameter("f2")
o = new T1(f1: f1, f2: f2)
data.insertOneT1(o)
response.sendRedirect("showx.groovy")

delx.groovy:

import mvcg.*

data = new DataX()
f1 = Integer.parseInt(request.getParameter("f1"))
data.deleteOneT1(f1)
response.sendRedirect("showx.groovy")

DataX.groovy:

package mvcg

import java.sql.*
import javax.naming.*
import javax.sql.*

class T1 {
    int f1
    String f2
}

class DataX {
    static DataSource ds
    static {
        def initctx = new InitialContext()
        def envctx  = initctx.lookup("java:/comp/env")
        ds = envctx.lookup("jdbc/TestDB")
    }
    List<T1> selectAllT1() {
        List<T1> res = new ArrayList<T1>()
        def con = ds.getConnection()
        def pstmt = con.prepareStatement("SELECT f1,f2 FROM t1")
        def rs = pstmt.executeQuery()
        while(rs.next()) {
            def f1 = rs.getInt(1)
            def f2 = rs.getString(2)
            def o = new T1(f1: f1, f2: f2)
            res.add(o)
        }
        rs.close()
        pstmt.close()
        con.close()
        return res
    }
    void insertOneT1(T1 o) {
        def con = ds.getConnection()
        def pstmt = con.prepareStatement("INSERT INTO t1 VALUES(?,?)")
        pstmt.setInt(1, o.f1)
        pstmt.setString(2, o.f2)
        pstmt.executeUpdate()
        pstmt.close()
        con.close()
    }
    void deleteOneT1(int f1) {
        def con = ds.getConnection()
        def pstmt = con.prepareStatement("DELETE FROM t1 WHERE f1 = ?")
        pstmt.setInt(1, f1)
        pstmt.executeUpdate()
        pstmt.close()
        con.close()
    }
}

Start URL: http://nodename:port/application/showx.groovy

[.WEB-INF]web.xml:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
    <servlet>
        <servlet-name>GroovyServlet</servlet-name>
        <servlet-class>groovy.servlet.GroovyServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>GroovyServlet</servlet-name>
        <url-pattern>*.groovy</url-pattern>
    </servlet-mapping>
</web-app>

Directory structure:

Directory DISK5:[WWW_arne1.Tomcat.mvcg]

add.groovy;1        addx.groovy;1       del.groovy;1        delx.groovy;1
display.jsp;1       displayx.jsp;1      show.groovy;1       showx.groovy;1
WEB-INF.DIR;1

Total of 9 files.

Directory DISK5:[WWW_arne1.Tomcat.mvcg.WEB-INF]

classes.DIR;1       lib.DIR;1           web.xml;1

Total of 3 files.

Directory DISK5:[WWW_arne1.Tomcat.mvcg.WEB-INF.classes]

Data.groovy;1       DataX.groovy;1      gen.com;1           mvcg.DIR;1

Total of 4 files.

Directory DISK5:[WWW_arne1.Tomcat.mvcg.WEB-INF.classes.mvcg]

Data.class;1        DataX.class;1       T1.class;1

Total of 3 files.

Directory DISK5:[WWW_arne1.Tomcat.mvcg.WEB-INF.lib]

groovy-all-2_2_2.jar;1                  jstl.jar;1          mysql-connector-java-5_1_36-bin.jar;1
standard.jar;1

Total of 4 files.

Grand total of 5 directories, 23 files.

Struts (JSP + Java):

Struts 1.x was the first popular web MVC framework.

It uses a fixed controller servlet with specific action classes.

display.jsp:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Struts 1.x</title>
</head>
<body>
<h1>Struts 1.x</body>
<h2>Show:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
<c:forEach items="${data}" var="o">
    <tr>
        <td><c:out value="${o.f1}"/></td>
        <td><c:out value="${o.f2}"/></td>
        <td><a href='del.do?f1=<c:out value="${o.f1}"/>'>Delete</a></td>
    </tr>
</c:forEach>
</table>
<h2>Add:</h2>
<form method="post" action="add.do">
F1: <input type="text" name="f1">
<br>
F2: <input type="text" name="f2">
<br>
<input type="submit" value="Add">
</form>
</body>
</html>

ShowAction.java:

package struts;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

public class ShowAction extends Action {
    public ActionForward execute(ActionMapping mapping,
                                 ActionForm form,
                                 HttpServletRequest request,
                                 HttpServletResponse response) throws IOException, ServletException {
        Data data = new Data();
        request.setAttribute("data", data.selectAllT1());
        return mapping.findForward("show.ok");
    }
}

AddAction.java:

package struts;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.DynaActionForm;

public class AddAction extends Action {
    public ActionForward execute(ActionMapping mapping,
                                 ActionForm form,
                                 HttpServletRequest request,
                                 HttpServletResponse response) throws IOException, ServletException {
        DynaActionForm daf = (DynaActionForm)form;
        int f1 = (Integer)daf.get("f1");
        String f2 = (String)daf.get("f2");
        T1 o = new T1(f1, f2);
        Data data = new Data();
        data.insertOneT1(o);
        return mapping.findForward("add.ok");
    }
}

DelAction.java:

package struts;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.DynaActionForm;

public class DelAction extends Action {
    public ActionForward execute(ActionMapping mapping,
                                 ActionForm form,
                                 HttpServletRequest request,
                                 HttpServletResponse response) throws IOException, ServletException {
        DynaActionForm daf = (DynaActionForm)form;
        int f1 = Integer.parseInt(request.getParameter("f1"));
        Data data = new Data();
        data.deleteOneT1(f1);
        return mapping.findForward("del.ok");
    }
}

T1.java:

package struts;

public class T1 {
    private int f1;
    private String f2;
    public T1() {
        this(0, "");
    }
    public T1(int f1, String f2) {
        this.f1 = f1;
        this.f2 = f2;
    }
    public int getF1() {
        return f1;
    }
    public void setF1(int f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
}

Data.java:

package struts;

import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class Data {
    public List<T1> selectAllT1() {
        List<T1> res = new ArrayList<T1>();
        try {
            Class.forName("com.mysql.jdbc.Driver");
            Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "");
            PreparedStatement pstmt = con.prepareStatement("SELECT f1,f2 FROM t1");
            ResultSet rs = pstmt.executeQuery();
            while(rs.next()) {
                int f1 = rs.getInt(1);
                String f2 = rs.getString(2);
                T1 o = new T1(f1, f2);
                res.add(o);
            }
            rs.close();
            pstmt.close();
            con.close();
        } catch(ClassNotFoundException ex) {
            ex.printStackTrace();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
        return res;
    }
    public void insertOneT1(T1 o) {
        try {
            Class.forName("com.mysql.jdbc.Driver");
            Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "");
            PreparedStatement pstmt = con.prepareStatement("INSERT INTO t1 VALUES(?,?)");
            pstmt.setInt(1, o.getF1());
            pstmt.setString(2, o.getF2());
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(ClassNotFoundException ex) {
            ex.printStackTrace();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
    public void deleteOneT1(int f1) {
        try {
            Class.forName("com.mysql.jdbc.Driver");
            Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "");
            PreparedStatement pstmt = con.prepareStatement("DELETE FROM t1 WHERE f1 = ?");
            pstmt.setInt(1,  f1);
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(ClassNotFoundException ex) {
            ex.printStackTrace();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
}

Start URL: http://nodename:port/application/show.do

displayx.jsp:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Struts 1.x (pool)</title>
</head>
<body>
<h1>Struts 1.x (pool)</body>
<h2>Show:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
<c:forEach items="${data}" var="o">
    <tr>
        <td><c:out value="${o.f1}"/></td>
        <td><c:out value="${o.f2}"/></td>
        <td><a href='delx.do?f1=<c:out value="${o.f1}"/>'>Delete</a></td>
    </tr>
</c:forEach>
</table>
<h2>Add:</h2>
<form method="post" action="addx.do">
F1: <input type="text" name="f1">
<br>
F2: <input type="text" name="f2">
<br>
<input type="submit" value="Add">
</form>
</body>
</html>

ShowXAction.java:

package struts;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

public class ShowXAction extends Action {
    public ActionForward execute(ActionMapping mapping,
                                 ActionForm form,
                                 HttpServletRequest request,
                                 HttpServletResponse response) throws IOException, ServletException {
        DataX data = new DataX();
        request.setAttribute("data", data.selectAllT1());
        return mapping.findForward("showx.ok");
    }
}

AddXAction.java:

package struts;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.DynaActionForm;

public class AddXAction extends Action {
    public ActionForward execute(ActionMapping mapping,
                                 ActionForm form,
                                 HttpServletRequest request,
                                 HttpServletResponse response) throws IOException, ServletException {
        DynaActionForm daf = (DynaActionForm)form;
        int f1 = (Integer)daf.get("f1");
        String f2 = (String)daf.get("f2");
        T1 o = new T1(f1, f2);
        DataX data = new DataX();
        data.insertOneT1(o);
        return mapping.findForward("addx.ok");
    }
}

DelXAction.java:

package struts;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.DynaActionForm;

public class DelXAction extends Action {
    public ActionForward execute(ActionMapping mapping,
                                 ActionForm form,
                                 HttpServletRequest request,
                                 HttpServletResponse response) throws IOException, ServletException {
        DynaActionForm daf = (DynaActionForm)form;
        int f1 = Integer.parseInt(request.getParameter("f1"));
        DataX data = new DataX();
        data.deleteOneT1(f1);
        return mapping.findForward("delx.ok");
    }
}

T1.java:

package struts;

public class T1 {
    private int f1;
    private String f2;
    public T1() {
        this(0, "");
    }
    public T1(int f1, String f2) {
        this.f1 = f1;
        this.f2 = f2;
    }
    public int getF1() {
        return f1;
    }
    public void setF1(int f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
}

DataX.java:

package struts;

import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

public class DataX {
    private static DataSource ds;
    static {
        try {
            Context initctx = new InitialContext();
            Context envctx  = (Context)initctx.lookup("java:/comp/env");
            ds = (DataSource)envctx.lookup("jdbc/TestDB");
        } catch(NamingException ex) {
            ex.printStackTrace();
        }
    }
    public List<T1> selectAllT1() {
        List<T1> res = new ArrayList<T1>();
        try {
            Connection con = ds.getConnection();
            PreparedStatement pstmt = con.prepareStatement("SELECT f1,f2 FROM t1");
            ResultSet rs = pstmt.executeQuery();
            while(rs.next()) {
                int f1 = rs.getInt(1);
                String f2 = rs.getString(2);
                T1 o = new T1(f1, f2);
                res.add(o);
            }
            rs.close();
            pstmt.close();
            con.close();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
        return res;
    }
    public void insertOneT1(T1 o) {
        try {
            Connection con = ds.getConnection();
            PreparedStatement pstmt = con.prepareStatement("INSERT INTO t1 VALUES(?,?)");
            pstmt.setInt(1, o.getF1());
            pstmt.setString(2, o.getF2());
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
    public void deleteOneT1(int f1) {
        try {
            Connection con = ds.getConnection();
            PreparedStatement pstmt = con.prepareStatement("DELETE FROM t1 WHERE f1 = ?");
            pstmt.setInt(1,  f1);
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
}

Start URL: http://nodename:port/application/showx.do

[.WEB-INF]web.xml:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
    <servlet>
        <servlet-name>action</servlet-name>
        <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
        <init-param>
            <param-name>config</param-name>
            <param-value>/WEB-INF/struts-config.xml</param-value>
        </init-param>
        <load-on-startup>5</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>action</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
 </web-app>

[.WEB-INF]struts-config.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd" >
<struts-config>
    <form-beans>
        <form-bean name="addForm" type="org.apache.struts.action.DynaActionForm">
            <form-property name="f1" type="java.lang.Integer"/>
            <form-property name="f2" type="java.lang.String"/>
        </form-bean>
    </form-beans>
    <action-mappings>
        <action path="/show" type="struts.ShowAction">
            <forward name="show.ok" path="/display.jsp"/>
        </action>
        <action path="/add" type="struts.AddAction" name="addForm" input="/display.jsp">
            <forward name="add.ok" path="/show.do" redirect="true"/>
        </action>
        <action path="/del" type="struts.DelAction">
            <forward name="del.ok" path="/show.do" redirect="true"/>
        </action>
        <action path="/showx" type="struts.ShowXAction">
            <forward name="showx.ok" path="/displayx.jsp"/>
        </action>
        <action path="/addx" type="struts.AddXAction" name="addForm" input="/displayx.jsp">
            <forward name="addx.ok" path="/showx.do" redirect="true"/>
        </action>
        <action path="/delx" type="struts.DelXAction">
            <forward name="delx.ok" path="/showx.do" redirect="true"/>
        </action>
    </action-mappings>
</struts-config>

Directory structure:

Directory DISK5:[WWW_arne1.Tomcat.struts]

display.jsp;1       displayx.jsp;1      WEB-INF.DIR;1

Total of 3 files.

Directory DISK5:[WWW_arne1.Tomcat.struts.WEB-INF]

classes.DIR;1       lib.DIR;1           struts-config.xml;1 web.xml;1

Total of 4 files.

Directory DISK5:[WWW_arne1.Tomcat.struts.WEB-INF.classes]

struts.DIR;1

Total of 1 file.

Directory DISK5:[WWW_arne1.Tomcat.struts.WEB-INF.classes.struts]

AddAction.class;1   AddAction.java;1    ADDXACTION.CLASS;1  AddXAction.java;1
Data.class;1        Data.java;1         DataX.class;1       DataX.java;1
DelAction.class;1   DelAction.java;1    DELXACTION.CLASS;1  DelXAction.java;1
gen.com;1           ShowAction.class;1  ShowAction.java;1   SHOWXACTION.CLASS;1
ShowXAction.java;1  T1.class;1          T1.java;1

Total of 19 files.

Directory DISK5:[WWW_arne1.Tomcat.struts.WEB-INF.lib]

antlr-2_7_2.jar;1   commons-beanutils-1_8_0.jar;1           commons-chain-1_2.jar;1
commons-digester-1_8.jar;1              commons-fileupload-1_1_1.jar;1
commons-logging-1_0_4.jar;1             commons-validator-1_3_1.jar;1
jstl.jar;1          mysql-connector-java-5_1_36-bin.jar;1   oro-2_0_8.jar;1
standard.jar;1      struts-core-1_3_10.jar;1

Total of 12 files.

Grand total of 5 directories, 39 files.

Late 00's:

Zend Framework (PHP):

Zend Framework was one of the first PHP MVC frameworks. And it has inspired many other PHP MVC frameworks.

As I cannot get composer working on VMS, then I created the project on PC, zipped, uploaded to VMS and unzipped.

Project creation:

php composer.phar create-project zendframework/skeleton-application zf

module/Application/view/application/t1/show.phtml:

<html>
<head>
<title>Zend Framework</title>
</head>
<body>
<h1>Zend Framework</h1>
<h2>Show:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
<?php
foreach($data as $o) {
?>
<tr>
<td><?= $o->f1 ?></td>
<td><?= $o->f2 ?></td>
<td><a href="<?php echo $this->url('t1', array('action' => 'del'), array('query' => array('f1' => $o->f1))); ?>">Delete</a></td>
</tr>
<?php
}
?>
</table>
<h2>Add:</h2>
<form method="post" action="<?php echo $this->url('t1', array('action' => 'add')); ?>">
F1: <input type="text" name="f1">
<br>
F2: <input type="text" name="f2">
<br>
<input type="submit" value="Add">
</form>
</body>
</html>

module/Application/src/Controller/T1Controller.php:

<?php

namespace Application\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;

use Application\Model\Data;
use Application\Model\T1;

class T1Controller extends AbstractActionController
{
    public function showAction() {
        $data = new Data();
        return new ViewModel(array('data' => $data->selectAllT1()));
    }
    public function addAction() {
        $formdata = $this->params()->fromPost();
        $f1 = $formdata['f1'];
        $f2 = $formdata['f2'];
        $data = new Data();
        $data->insertOneT1(new T1($f1, $f2));
        $this->redirect()->toRoute('t1', [ 'action' => 'show' ]);
    }
    public function delAction() {
        $querydata = $this->params()->fromQuery();
        $f1 = $querydata['f1'];
        $data = new Data();
        $data->deleteOneT1($f1);
        $this->redirect()->toRoute('t1', [ 'action' => 'show' ]);
    }
}

module/Application/src/Model/T1.php:

<?php

namespace Application\Model;

class T1 {
    public $f1;
    public $f2;
    public function __construct($f1, $f2) {
        $this->f1 = $f1;
        $this->f2 = $f2;
    }
}

?>

module/Application/src/Model/Data.php:

<?php

namespace Application\Model;

class Data {
    private function getConnection() {
        $con = mysqli_connect('arnepc5', 'arne', 'hemmeligt', 'test');
        return $con;
    }
    public function selectAllT1() {
        $con = $this->getConnection();
        $stmt = mysqli_prepare($con, 'SELECT f1,f2 FROM t1');
        mysqli_stmt_execute($stmt);
        $rs = mysqli_stmt_get_result($stmt);
        $res = array();
        while($row = mysqli_fetch_array($rs, MYSQLI_ASSOC)) {
            $f1 = $row['f1'];
            $f2 = $row['f2'];
            $res[] = new T1($f1, $f2);
        }
        mysqli_stmt_close($stmt);
        mysqli_close($con);
        return $res;
    }
    public function insertOneT1($o) {
        $con = $this->getConnection();
        $stmt = mysqli_prepare($con, 'INSERT INTO t1 VALUES(?,?)');
        mysqli_stmt_bind_param($stmt, 'is', $o->f1, $o->f2);
        mysqli_stmt_execute($stmt);
        mysqli_stmt_close($stmt);
        mysqli_close($con);
    }
    public function deleteOneT1($f1) {
        $con = $this->getConnection();
        $stmt = mysqli_prepare($con, 'DELETE FROM t1 WHERE f1 = ?');
        mysqli_stmt_bind_param($stmt, 'i', $f1);
        mysqli_stmt_execute($stmt);
        mysqli_stmt_close($stmt);
        mysqli_close($con);
    }
}

?>

Start URL: http://nodename:port/t1/show

module/Application/view/application/t1/showx.phtml:

<html>
<head>
<title>Zend Framework (pool)</title>
</head>
<body>
<h1>Zend Framework (pool)</h1>
<h2>Show:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
<?php
foreach($data as $o) {
?>
<tr>
<td><?= $o->f1 ?></td>
<td><?= $o->f2 ?></td>
<td><a href="<?php echo $this->url('t1x', array('action' => 'delx'), array('query' => array('f1' => $o->f1))); ?>">Delete</a></td>
</tr>
<?php
}
?>
</table>
<h2>Add:</h2>
<form method="post" action="<?php echo $this->url('t1x', array('action' => 'addx')); ?>">
F1: <input type="text" name="f1">
<br>
F2: <input type="text" name="f2">
<br>
<input type="submit" value="Add">
</form>
</body>
</html>

module/Application/src/Controller/T1xController.php:

<?php

namespace Application\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;

use Application\Model\DataX;
use Application\Model\T1;

class T1xController extends AbstractActionController // it hs to be T1xController as T1XController cause a hyphen to be inserted
{
    public function showxAction() {
        $data = new DataX();
        return new ViewModel(array('data' => $data->selectAllT1()));
    }
    public function addxAction() {
        $formdata = $this->params()->fromPost();
        $f1 = $formdata['f1'];
        $f2 = $formdata['f2'];
        $data = new DataX();
        $data->insertOneT1(new T1($f1, $f2));
        $this->redirect()->toRoute('t1x', [ 'action' => 'showx' ]);
    }
    public function delxAction() {
        $querydata = $this->params()->fromQuery();
        $f1 = $querydata['f1'];
        $data = new DataX();
        $data->deleteOneT1($f1);
        $this->redirect()->toRoute('t1x', [ 'action' => 'showx' ]);
    }
}

module/Application/src/Model/T1.php:

<?php

namespace Application\Model;

class T1 {
    public $f1;
    public $f2;
    public function __construct($f1, $f2) {
        $this->f1 = $f1;
        $this->f2 = $f2;
    }
}

?>

module/Application/src/Model/DataX.php:

<?php

namespace Application\Model;

class DataX {
    private function getConnection() {
        $con = mysqli_connect('p:arnepc5', 'arne', 'hemmeligt', 'test');
        return $con;
    }
    public function selectAllT1() {
        $con = $this->getConnection();
        $stmt = mysqli_prepare($con, 'SELECT f1,f2 FROM t1');
        mysqli_stmt_execute($stmt);
        $rs = mysqli_stmt_get_result($stmt);
        $res = array();
        while($row = mysqli_fetch_array($rs, MYSQLI_ASSOC)) {
            $f1 = $row['f1'];
            $f2 = $row['f2'];
            $res[] = new T1($f1, $f2);
        }
        mysqli_stmt_close($stmt);
        mysqli_close($con);
        return $res;
    }
    public function insertOneT1($o) {
        $con = $this->getConnection();
        $stmt = mysqli_prepare($con, 'INSERT INTO t1 VALUES(?,?)');
        mysqli_stmt_bind_param($stmt, 'is', $o->f1, $o->f2);
        mysqli_stmt_execute($stmt);
        mysqli_stmt_close($stmt);
        mysqli_close($con);
    }
    public function deleteOneT1($f1) {
        $con = $this->getConnection();
        $stmt = mysqli_prepare($con, 'DELETE FROM t1 WHERE f1 = ?');
        mysqli_stmt_bind_param($stmt, 'i', $f1);
        mysqli_stmt_execute($stmt);
        mysqli_stmt_close($stmt);
        mysqli_close($con);
    }
}

?>

Start URL: http://nodename:port/t1x/showx

module/Application/config/module.config.php fragment:

't1' => [
                'type'    => Segment::class,
                'options' => [
                    'route'    => '/t1[/:action]',
                    'defaults' => [
                        'controller' => Controller\T1Controller::class,
                        'action'     => 'show',
                    ],
                ],
            ],
            't1x' => [
                'type'    => Segment::class,
                'options' => [
                    'route'    => '/t1x[/:action]',
                    'defaults' => [
                        'controller' => Controller\T1xController::class,
                        'action'     => 'showx',
                    ],
                ],
            ],
        ],
    ],
    ...
    'controllers' => [
        'factories' => [
            ...
            Controller\T1Controller::class => InvokableFactory::class,
            Controller\T1xController::class => InvokableFactory::class,
        ],
    ],

Note that this way to do database access is not the ZF way. But I have done it this way to keep the code more comparable to to the rest of the code.

Spring MVC with JSP view (Java):

Struts 1.x faded away and it was not replaced by Struts 2.x but with Spring MVC.

Spring MVC build on the popular Spring DI framework. But are otherwise a rather traditional MVC framework.

display.jsp:

<%@ page language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Spring with JSP view</title>
</head>
<body>
<h1>Spring with JSP view</h1>
<h2>Show:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
<c:forEach items="${data}" var="o">
<tr>
<td><c:out value="${o.f1}"/></td>
<td><c:out value="${o.f2}"/></td>
<td><a href='del?f1=<c:out value="${o.f1}"/>'>Delete</a></td>
</tr>
</c:forEach>
</table>
<h2>Add:</h2>
<form method="post" action="add">
F1: <input type="text" name="f1">
<br>
F2: <input type="text" name="f2">
<br>
<input type="submit" value="Add"/>
</form>
</body>
</html>

Manager.java:

package springjsp;

import org.springframework.stereotype.Controller;  
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.view.RedirectView;
import org.springframework.web.servlet.View;

@Controller
public class Manager {
    @RequestMapping(value="/show", method=RequestMethod.GET)
    public String show(Model m) {
        Data data = new Data();
        m.addAttribute("data", data.selectAllT1());
        return "display";
    }
    @RequestMapping(value="/add", method=RequestMethod.POST)
    public View add(@RequestParam("f1") int f1, @RequestParam("f2") String f2, Model m) {
        T1 o = new T1(f1, f2);
        Data data = new Data();
        data.insertOneT1(o);
        return new RedirectView("show");
    }
    @RequestMapping(value="/del", method=RequestMethod.GET)
    public View del(@RequestParam("f1") int f1, Model m) {
        Data data = new Data();
        data.deleteOneT1(f1);
        return new RedirectView("show");
    }
}

T1.java:

package springjsp;

import java.io.Serializable;

public class T1 implements Serializable {
    private int f1;
    private String f2;
    public T1() {
        this(0, "");
    }
    public T1(int f1, String f2) {
        this.f1 = f1;
        this.f2 = f2;
    }
    public int getF1() {
        return f1;
    }
    public void setF1(int f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
}

Data.java:

package springjsp;

import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class Data {
    public List<T1> selectAllT1() {
        List<T1> res = new ArrayList<T1>();
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            Connection con = DriverManager.getConnection("jdbc:mysql://arnepc5:3306/test", "arne", "hemmeligt");
            PreparedStatement pstmt = con.prepareStatement("SELECT f1,f2 FROM t1");
            ResultSet rs = pstmt.executeQuery();
            while(rs.next()) {
                int f1 = rs.getInt(1);
                String f2 = rs.getString(2);
                T1 o = new T1(f1, f2);
                res.add(o);
            }
            rs.close();
            pstmt.close();
            con.close();
        } catch(ClassNotFoundException ex) {
            ex.printStackTrace();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
        return res;
    }
    public void insertOneT1(T1 o) {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            Connection con = DriverManager.getConnection("jdbc:mysql://arnepc5:3306/test", "arne", "hemmeligt");
            PreparedStatement pstmt = con.prepareStatement("INSERT INTO t1 VALUES(?,?)");
            pstmt.setInt(1, o.getF1());
            pstmt.setString(2, o.getF2());
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(ClassNotFoundException ex) {
            ex.printStackTrace();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
    public void deleteOneT1(int f1) {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            Connection con = DriverManager.getConnection("jdbc:mysql://arnepc5:3306/test", "arne", "hemmeligt");
            PreparedStatement pstmt = con.prepareStatement("DELETE FROM t1 WHERE f1 = ?");
            pstmt.setInt(1,  f1);
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(ClassNotFoundException ex) {
            ex.printStackTrace();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
}

Start URL: http://nodename:port/application/show

displayx.jsp:

<%@ page language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Spring with JSP view (pool)</title>
</head>
<body>
<h1>Spring with JSP view (pool)</h1>
<h2>Show:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
<c:forEach items="${data}" var="o">
<tr>
<td><c:out value="${o.f1}"/></td>
<td><c:out value="${o.f2}"/></td>
<td><a href='delx?f1=<c:out value="${o.f1}"/>'>Delete</a></td>
</tr>
</c:forEach>
</table>
<h2>Add:</h2>
<form method="post" action="addx">
F1: <input type="text" name="f1">
<br>
F2: <input type="text" name="f2">
<br>
<input type="submit" value="Add"/>
</form>
</body>
</html>

ManagerX.java:

package springjsp;

import org.springframework.stereotype.Controller;  
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.view.RedirectView;
import org.springframework.web.servlet.View;

@Controller
public class ManagerX {
    @RequestMapping(value="/showx", method=RequestMethod.GET)
    public String showx(Model m) {
        DataX data = new DataX();
        m.addAttribute("data", data.selectAllT1());
        return "displayx";
    }
    @RequestMapping(value="/addx", method=RequestMethod.POST)
    public View addx(@RequestParam("f1") int f1, @RequestParam("f2") String f2, Model m) {
        T1 o = new T1(f1, f2);
        DataX data = new DataX();
        data.insertOneT1(o);
        return new RedirectView("showx");
    }
    @RequestMapping(value="/delx", method=RequestMethod.GET)
    public View delx(@RequestParam("f1") int f1, Model m) {
        DataX data = new DataX();
        data.deleteOneT1(f1);
        return new RedirectView("showx");
    }
}

T1.java:

package springjsp;

import java.io.Serializable;

public class T1 implements Serializable {
    private int f1;
    private String f2;
    public T1() {
        this(0, "");
    }
    public T1(int f1, String f2) {
        this.f1 = f1;
        this.f2 = f2;
    }
    public int getF1() {
        return f1;
    }
    public void setF1(int f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
}

DataX.java:

package springjsp;

import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

public class DataX {
    private static DataSource ds;
    static {
        try {
            Context initctx = new InitialContext();
            Context envctx  = (Context)initctx.lookup("java:/comp/env");
            ds = (DataSource)envctx.lookup("jdbc/TestDB");
        } catch(NamingException ex) {
            ex.printStackTrace();
        }
    }
    public List<T1> selectAllT1() {
        List<T1> res = new ArrayList<T1>();
        try {
            Connection con = ds.getConnection();
            PreparedStatement pstmt = con.prepareStatement("SELECT f1,f2 FROM t1");
            ResultSet rs = pstmt.executeQuery();
            while(rs.next()) {
                int f1 = rs.getInt(1);
                String f2 = rs.getString(2);
                T1 o = new T1(f1, f2);
                res.add(o);
            }
            rs.close();
            pstmt.close();
            con.close();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
        return res;
    }
    public void insertOneT1(T1 o) {
        try {
            Connection con = ds.getConnection();
            PreparedStatement pstmt = con.prepareStatement("INSERT INTO t1 VALUES(?,?)");
            pstmt.setInt(1, o.getF1());
            pstmt.setString(2, o.getF2());
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
    public void deleteOneT1(int f1) {
        try {
            Connection con = ds.getConnection();
            PreparedStatement pstmt = con.prepareStatement("DELETE FROM t1 WHERE f1 = ?");
            pstmt.setInt(1,  f1);
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
}

Start URL: http://nodename:port/application/showx

[.WEB-INF]web.xml:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
    <servlet>
        <servlet-name>spring</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>spring</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

[.WEB-INF]spring-servlet.xml:

<beans xmlns = "http://www.springframework.org/schema/beans"
       xmlns:context = "http://www.springframework.org/schema/context"
       xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation = "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    <context:component-scan base-package="springjsp" />
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/" />
        <property name="suffix" value=".jsp" />
    </bean>
</beans>

Directory structure:

Directory TOMCAT$ROOT:[webapps.springjsp]

WEB-INF.DIR;1

Total of 1 file.

Directory TOMCAT$ROOT:[webapps.springjsp.WEB-INF]

classes.DIR;1       jsp.DIR;1           lib.DIR;1           spring-servlet.xml;1
web.xml;1

Total of 5 files.

Directory TOMCAT$ROOT:[webapps.springjsp.WEB-INF.classes]

springjsp.DIR;1

Total of 1 file.

Directory TOMCAT$ROOT:[webapps.springjsp.WEB-INF.classes.springjsp]

Data.class;1        Data.java;1         DataX.class;1       DataX.java;1
gen.com;1           Manager.class;1     Manager.java;1      ManagerX.class;1
ManagerX.java;1     T1.class;1          T1.java;1

Total of 11 files.

Directory TOMCAT$ROOT:[webapps.springjsp.WEB-INF.jsp]

display.jsp;1       displayx.jsp;1

Total of 2 files.

Directory TOMCAT$ROOT:[webapps.springjsp.WEB-INF.lib]

commons-logging-1_1.jar;1               mysql-connector-j-8_0_33.jar;1
spring-aop-5_1_2_RELEASE.jar;1          spring-aspects-5_1_2_RELEASE.jar;1
spring-beans-5_1_2_RELEASE.jar;1        spring-context-5_1_2_RELEASE.jar;1
spring-core-5_1_2_RELEASE.jar;1         spring-expression-5_1_2_RELEASE.jar;1
spring-web-5_1_2_RELEASE.jar;1          spring-webmvc-5_1_2_RELEASE.jar;1

Total of 10 files.

Grand total of 6 directories, 30 files.

JSF with JSP view (Java):

JSF (Java Server Faces) is not a traditional MVC framework - instead it attempts to make a web application look similar to a desktop GUI application.

JSF is very much inspired by ASP.NET web forms.

For more details about JSF see here.

show.jsp:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<html>
<head>
<title>JSF with JSP view</title>
</head>
<body>
<f:view>
<h1>JSF with JSP view</h1>
<h2>Show:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
<c:forEach items="#{manager.data}" var="o">
<tr>
<td><h:outputText value="#{o.f1}"/></td>
<td><h:outputText value="#{o.f2}"/></td>
<td><h:form><h:commandButton value="Delete" action="#{manager.del(o.f1)}"/></h:form></td>
</tr>
</c:forEach>
</table>
<h2>Add:</h2>
<h:form>
F1: <h:inputText value="#{manager.f1}"/>
<br>
F2: <h:inputText value="#{manager.f2}"/>
<br>
<h:commandButton value="Add" action="#{manager.add}"/>
</h:form>
</f:view>
</body>
</html>

Manager.java:

package jsf1;

import java.io.Serializable;
import java.util.List;

public class Manager implements Serializable {
    // show
    public List<T1> getData() {
        Data data = new Data();
        return data.selectAllT1();
    }
    // add
    private Integer f1;
    private String f2;
    public Integer getF1() {
        return f1;
    }
    public void setF1(Integer f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
    public String add() {
        T1 o = new T1((int)f1, f2);
        Data data = new Data();
        data.insertOneT1(o);
        f1 = null;
        f2 = null;
        return "display";
    }
    // del
    public String del(int f1) {
        Data data = new Data();
        data.deleteOneT1(f1);
        return "display";
    }
}

T1.java:

package jsf1;

import java.io.Serializable;

public class T1 implements Serializable {
    private int f1;
    private String f2;
    public T1() {
        this(0, "");
    }
    public T1(int f1, String f2) {
        this.f1 = f1;
        this.f2 = f2;
    }
    public int getF1() {
        return f1;
    }
    public void setF1(int f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
}

Data.java:

package jsf1;

import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class Data {
    public List<T1> selectAllT1() {
        List<T1> res = new ArrayList<T1>();
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            Connection con = DriverManager.getConnection("jdbc:mysql://arnepc5:3306/test", "arne", "hemmeligt");
            PreparedStatement pstmt = con.prepareStatement("SELECT f1,f2 FROM t1");
            ResultSet rs = pstmt.executeQuery();
            while(rs.next()) {
                int f1 = rs.getInt(1);
                String f2 = rs.getString(2);
                T1 o = new T1(f1, f2);
                res.add(o);
            }
            rs.close();
            pstmt.close();
            con.close();
        } catch(ClassNotFoundException ex) {
            ex.printStackTrace();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
        return res;
    }
    public void insertOneT1(T1 o) {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            Connection con = DriverManager.getConnection("jdbc:mysql://arnepc5:3306/test", "arne", "hemmeligt");
            PreparedStatement pstmt = con.prepareStatement("INSERT INTO t1 VALUES(?,?)");
            pstmt.setInt(1, o.getF1());
            pstmt.setString(2, o.getF2());
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(ClassNotFoundException ex) {
            ex.printStackTrace();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
    public void deleteOneT1(int f1) {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            Connection con = DriverManager.getConnection("jdbc:mysql://arnepc5:3306/test", "arne", "hemmeligt");
            PreparedStatement pstmt = con.prepareStatement("DELETE FROM t1 WHERE f1 = ?");
            pstmt.setInt(1,  f1);
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(ClassNotFoundException ex) {
            ex.printStackTrace();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
}

Start URL: http://nodename:port/application/show.jsf

showx.jsp:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<html>
<head>
<title>JSF with JSP view (pool)</title>
</head>
<body>
<f:view>
<h1>JSF with JSP view (pool)</h1>
<h2>Show:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
<c:forEach items="#{managerx.data}" var="o">
<tr>
<td><h:outputText value="#{o.f1}"/></td>
<td><h:outputText value="#{o.f2}"/></td>
<td><h:form><h:commandButton value="Delete" action="#{managerx.del(o.f1)}"/></h:form></td>
</tr>
</c:forEach>
</table>
<h2>Add:</h2>
<h:form>
F1: <h:inputText value="#{managerx.f1}"/>
<br>
F2: <h:inputText value="#{managerx.f2}"/>
<br>
<h:commandButton value="Add" action="#{managerx.add}"/>
</h:form>
</f:view>
</body>
</html>

ManagerX.java:

package jsf1;

import java.io.Serializable;
import java.util.List;

public class ManagerX implements Serializable {
    // show
    public List<T1> getData() {
        DataX data = new DataX();
        return data.selectAllT1();
    }
    // add
    private Integer f1;
    private String f2;
    public Integer getF1() {
        return f1;
    }
    public void setF1(Integer f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
    public String add() {
        DataX data = new DataX();
        T1 o = new T1((int)f1, f2);
        data.insertOneT1(o);
        f1 = null;
        f2 = null;
        return "display";
    }
    // del
    public String del(int f1) {
        DataX data = new DataX();
        data.deleteOneT1(f1);
        return "display";
    }
}

T1.java:

package jsf1;

import java.io.Serializable;

public class T1 implements Serializable {
    private int f1;
    private String f2;
    public T1() {
        this(0, "");
    }
    public T1(int f1, String f2) {
        this.f1 = f1;
        this.f2 = f2;
    }
    public int getF1() {
        return f1;
    }
    public void setF1(int f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
}

DataX.java:

package jsf1;

import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

public class DataX {
    private static DataSource ds;
    static {
        try {
            Context initctx = new InitialContext();
            Context envctx  = (Context)initctx.lookup("java:/comp/env");
            ds = (DataSource)envctx.lookup("jdbc/TestDB");
        } catch(NamingException ex) {
            ex.printStackTrace();
        }
    }
    public List<T1> selectAllT1() {
        List<T1> res = new ArrayList<T1>();
        try {
            Connection con = ds.getConnection();
            PreparedStatement pstmt = con.prepareStatement("SELECT f1,f2 FROM t1");
            ResultSet rs = pstmt.executeQuery();
            while(rs.next()) {
                int f1 = rs.getInt(1);
                String f2 = rs.getString(2);
                T1 o = new T1(f1, f2);
                res.add(o);
            }
            rs.close();
            pstmt.close();
            con.close();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
        return res;
    }
    public void insertOneT1(T1 o) {
        try {
            Connection con = ds.getConnection();
            PreparedStatement pstmt = con.prepareStatement("INSERT INTO t1 VALUES(?,?)");
            pstmt.setInt(1, o.getF1());
            pstmt.setString(2, o.getF2());
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
    public void deleteOneT1(int f1) {
        try {
            Connection con = ds.getConnection();
            PreparedStatement pstmt = con.prepareStatement("DELETE FROM t1 WHERE f1 = ?");
            pstmt.setInt(1,  f1);
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
}

Start URL: http://nodename:port/application/showx.jsf

[.WEB-INF]web.xml:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.jsf</url-pattern>
    </servlet-mapping>
    <context-param>
        <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
        <param-value>server</param-value>
    </context-param>
</web-app>

[.WEB-INF]faces-config.xml:

<?xml version="1.0"?>
<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd"
              version="1.2">
    <navigation-rule>
        <from-view-id>/show.jsp</from-view-id>
        <navigation-case>
            <from-outcome>display</from-outcome>
            <to-view-id>/show.jsp</to-view-id>
            <redirect/>
        </navigation-case>
    </navigation-rule> 
    <navigation-rule>
        <from-view-id>/showx.jsp</from-view-id>
        <navigation-case>
            <from-outcome>display</from-outcome>
            <to-view-id>/showx.jsp</to-view-id>
            <redirect/>
        </navigation-case>
    </navigation-rule>
    <managed-bean>
        <managed-bean-name>manager</managed-bean-name>
        <managed-bean-class>jsf1.Manager</managed-bean-class>
        <managed-bean-scope>session</managed-bean-scope>
    </managed-bean>
    <managed-bean>
        <managed-bean-name>managerx</managed-bean-name>
        <managed-bean-class>jsf1.ManagerX</managed-bean-class>
        <managed-bean-scope>session</managed-bean-scope>
    </managed-bean>
</faces-config>

Directory structure:

Directory TOMCAT$ROOT:[webapps.jsf1]

show.jsp;1          showx.jsp;1         WEB-INF.DIR;1

Total of 3 files.

Directory TOMCAT$ROOT:[webapps.jsf1.WEB-INF]

classes.DIR;1       faces-config.xml;1  lib.DIR;1           web.xml;1

Total of 4 files.

Directory TOMCAT$ROOT:[webapps.jsf1.WEB-INF.classes]

jsf1.DIR;1

Total of 1 file.

Directory TOMCAT$ROOT:[webapps.jsf1.WEB-INF.classes.jsf1]

Data.class;1        Data.java;1         DataX.class;1       DataX.java;1
gen.com;1           Manager.class;1     Manager.java;1      ManagerX.class;1
ManagerX.java;1     T1.class;1          T1.java;1

Total of 11 files.

Directory TOMCAT$ROOT:[webapps.jsf1.WEB-INF.lib]

commons-beanutils-1_9_2.jar;1           commons-collections-3_2_2.jar;1
commons-digester-1_8.jar;1              commons-logging-1_1_1.jar;1
geronimo-atinject_1_0_spec-1_0.jar;1    myfaces-api-2_2_12.jar;1
myfaces-bundle-2_2_12.jar;1             myfaces-impl-2_2_12.jar;1
mysql-connector-j-8_0_33.jar;1

Total of 9 files.

Grand total of 5 directories, 28 files.

10's:

Laravel (PHP):

I think it should work, but I cannot get it to work with PHP 8.1 on VMS x86-64.

Grails (GSP + Groovy):

Grails is very much inspired by the popular RoR (Ruby on Rails) framework.

And Grails is not just a runtime environment but also a development environment.

Grails web applications runs fine on VMS, but I have not even tried getting the development environment up and running on VMS. I develop on Windows, build a war file and deploy war file to VMS.

Project creation:

grails create-app grails
cd grails
grails create-controller Manager

Build of war:

cd grails
grails war

(note that paths below are paths in dev enviroment not on VMS!)

grails-app/views/manager/show.gsp:

<html>
<head>
<title>Grails</title>
</head>
<body>
<h1>Grails</h1>
<h2>Show:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
<g:each in="${data}" var="o">
<tr>
<td>${o.f1}</td>
<td>${o.f2}</td>
<td><g:link action="del" params="[f1: o.f1]">Delete</g:link></td>
</tr>
</g:each>
</table>
<h2>Add:</h2>
<g:form method="post" action="add">
F1: <input type="text" name="f1">
<br>
F2: <input type="text" name="f2">
<br>
<input type="submit" value="Add"/>
</g:form>
</body>
</html>

grails-app/controllers/grails/ManagerController.groovy:

package grails

class ManagerController {
    def show() {
        Data data = new Data()
        return [data: data.selectAllT1()]
    }
    def add() {
        Data data = new Data();
        data.insertOneT1(new T1(f1: Integer.parseInt(params.f1), f2: params.f2)) 
        redirect(action: "show")
    }
    def del() {
        Data data = new Data();
        data.deleteOneT1(Integer.parseInt(params.f1)) 
        redirect(action: "show")
    }
}

grails-app/domain/grails/Data.groovy:

package grails

import java.sql.*

class T1 {
    int f1
    String f2
}

class Data {
    List<T1> selectAllT1() {
        List<T1> res = new ArrayList<T1>()
        Class.forName("com.mysql.cj.jdbc.Driver")
        def con = DriverManager.getConnection("jdbc:mysql://arnepc5:3306/test", "arne", "hemmeligt")
        def pstmt = con.prepareStatement("SELECT f1,f2 FROM t1")
        def rs = pstmt.executeQuery()
        while(rs.next()) {
            def f1 = rs.getInt(1)
            def f2 = rs.getString(2)
            def o = new T1(f1: f1, f2: f2)
            res.add(o)
        }
        rs.close()
        pstmt.close()
        con.close()
        return res
    }
    void insertOneT1(T1 o) {
        Class.forName("com.mysql.cj.jdbc.Driver")
        def con = DriverManager.getConnection("jdbc:mysql://arnepc5:3306/test", "arne", "hemmeligt")
        def pstmt = con.prepareStatement("INSERT INTO t1 VALUES(?,?)")
        pstmt.setInt(1, o.f1)
        pstmt.setString(2, o.f2)
        pstmt.executeUpdate()
        pstmt.close()
        con.close()
    }
    void deleteOneT1(int f1) {
        Class.forName("com.mysql.cj.jdbc.Driver")
        def con = DriverManager.getConnection("jdbc:mysql://arnepc5:3306/test", "arne", "hemmeligt")
        def pstmt = con.prepareStatement("DELETE FROM t1 WHERE f1 = ?")
        pstmt.setInt(1, f1)
        pstmt.executeUpdate()
        pstmt.close()
        con.close()
    }
}

Start URL: http://nodename:port/application/manager/show

A grails web application does not need to be deployed to Tomcat but can also be run standalone. Simply:

java -jar application.war

Note that this way to do database access is not the Grails way. But I have done it this way to keep the code more comparable to to the rest of the code.

Ktor with Thymeleaf view (Kotlin):

Ktor is one of the newest web frameworks.

It uses Kotlin as programming language and for web applications (as opposed to web services) it can use THymeleaf as view technology.

It can be built both as standalone application and as war for servlet engine deployment.

Ktor web applications can be made to on VMS, but I have not even tried getting the development environment up and running on VMS. I develop on Windows, build a fatjar and deploy fatjar to VMS.

Ktor need to be built for Java 8 to run on VMS.

To do that:

  1. do a normal Gradle build with Java 8
  2. unzip fatjar
  3. overwrite the tree with logback 1.3.14 class files
  4. zip new fatjar

src/main/resources/templates/thymeleaf/show.html:

<html xmlns:th="https://thymeleaf.org">
<head>
<title>Ktor with Thymeleaf</title>
</head>
<body>
<h1>Ktor with Thymeleaf</h1>
<h2>Show data:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
<th></th>
</tr>
<tr th:each="o: ${data}">
<td th:text="${o.f1}"/>
<td th:text="${o.f2}"/>
<td><a th:href="@{del?f1={v}(v=${o.f1})}">Delete</a></td>
</tr>
</table>
<h2>Add data:</h2>
<form method="post" action="add" th:object="${addForm}">
F1: <input type="text" name="f1" th:field="*{f1}"/>
<br>
F2: <input type="text" name="f2" th:field="*{f2}"/>
<br>
<input type="submit" value="Add"/>
</form>
</body>
</html>

src/main/kotlin/demo/plugins/Templating.kt:

package demo.plugins

import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.thymeleaf.Thymeleaf
import io.ktor.server.thymeleaf.ThymeleafContent
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver

import demo.model.*

fun Application.configureTemplating() {
    install(Thymeleaf) {
        setTemplateResolver(ClassLoaderTemplateResolver().apply {
            prefix = "templates/thymeleaf/"
            suffix = ".html"
            characterEncoding = "utf-8"
        })
    }
    routing {
        get("/show") {
            call.respond(ThymeleafContent("show", mapOf("data" to Data.selectAllT1(), "addForm" to AddForm("", ""))))
        }
        get("/showx") {
            call.respond(ThymeleafContent("showx", mapOf("data" to DataX.selectAllT1(), "addForm" to AddForm("", ""))))
        }
    }
}

data class AddForm(var f1: String, var f2: String)

src/main/kotlin/demo/plugins/Routing.kt:

package demo.plugins

import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*

import demo.model.*

fun Application.configureRouting() {
    routing {
        post("/add") {
            val param = call.receiveParameters()
            val f1 = param["f1"]!!.toInt()
            val f2 = param["f2"].toString()
            Data.insertOneT1(T1(f1, f2))         
            call.respondRedirect("/show")
        }
        get("/del") {
            val f1 = call.request.queryParameters["f1"]!!.toInt()
            Data.deleteOneT1(f1)
            call.respondRedirect("/show")
        }
        post("/addx") {
            val param = call.receiveParameters()
            val f1 = param["f1"]!!.toInt()
            val f2 = param["f2"].toString()
            DataX.insertOneT1(T1(f1, f2))         
            call.respondRedirect("/showx")
        }
        get("/delx") {
            val f1 = call.request.queryParameters["f1"]!!.toInt()
            DataX.deleteOneT1(f1)
            call.respondRedirect("/showx")
        }
    }
}

src/main/kotlin/demo/model/Data.kt:

package demo.model

import java.sql.*

data class T1(var f1: Int, var f2: String)

object Data {
    fun selectAllT1(): List<T1> {
        val con = DriverManager.getConnection("jdbc:mysql://arnepc5/test", "arne", "hemmeligt")
        val stmt = con.createStatement()
        val rs = stmt.executeQuery("SELECT f1,f2 FROM t1")
        val res = ArrayList<T1>()
        while(rs.next()) {
            val f1 = rs.getInt(1)
            val f2 = rs.getString(2)
            res.add(T1(f1, f2))
        }
        rs.close()
        stmt.close()
        con.close()
        return res
    }
    fun insertOneT1(o: T1) {
        val con = DriverManager.getConnection("jdbc:mysql://arnepc5/test", "arne", "hemmeligt")
        val pstmt = con.prepareStatement("INSERT INTO t1 VALUES(?,?)")
        pstmt.setInt(1, o.f1)
        pstmt.setString(2, o.f2)
        pstmt.executeUpdate()
        pstmt.close()
        con.close()
    }
    fun deleteOneT1(f1: Int) {
        val con = DriverManager.getConnection("jdbc:mysql://arnepc5/test", "arne", "hemmeligt")
        val pstmt = con.prepareStatement("DELETE FROM t1 WHERE f1 = ?")
        pstmt.setInt(1, f1)
        pstmt.executeUpdate()
        pstmt.close()
        con.close()
    }
}

Start URL: http://nodename:port/show

src/main/resources/templates/thymeleaf/showx.html:

<html xmlns:th="https://thymeleaf.org">
<head>
<title>Ktor with Thymeleaf (pool)</title>
</head>
<body>
<h1>Ktor with Thymeleaf (pool)</h1>
<h2>Show data:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
<th></th>
</tr>
<tr th:each="o: ${data}">
<td th:text="${o.f1}"/>
<td th:text="${o.f2}"/>
<td><a th:href="@{delx?f1={v}(v=${o.f1})}">Delete</a></td>
</tr>
</table>
<h2>Add data:</h2>
<form method="post" action="addx" th:object="${addForm}">
F1: <input type="text" name="f1" th:field="*{f1}"/>
<br>
F2: <input type="text" name="f2" th:field="*{f2}"/>
<br>
<input type="submit" value="Add"/>
</form>
</body>
</html>

src/main/kotlin/demo/plugins/Templating.kt:

package demo.plugins

import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.thymeleaf.Thymeleaf
import io.ktor.server.thymeleaf.ThymeleafContent
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver

import demo.model.*

fun Application.configureTemplating() {
    install(Thymeleaf) {
        setTemplateResolver(ClassLoaderTemplateResolver().apply {
            prefix = "templates/thymeleaf/"
            suffix = ".html"
            characterEncoding = "utf-8"
        })
    }
    routing {
        get("/show") {
            call.respond(ThymeleafContent("show", mapOf("data" to Data.selectAllT1(), "addForm" to AddForm("", ""))))
        }
        get("/showx") {
            call.respond(ThymeleafContent("showx", mapOf("data" to DataX.selectAllT1(), "addForm" to AddForm("", ""))))
        }
    }
}

data class AddForm(var f1: String, var f2: String)

src/main/kotlin/demo/plugins/Routing.kt:

package demo.plugins

import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*

import demo.model.*

fun Application.configureRouting() {
    routing {
        post("/add") {
            val param = call.receiveParameters()
            val f1 = param["f1"]!!.toInt()
            val f2 = param["f2"].toString()
            Data.insertOneT1(T1(f1, f2))         
            call.respondRedirect("/show")
        }
        get("/del") {
            val f1 = call.request.queryParameters["f1"]!!.toInt()
            Data.deleteOneT1(f1)
            call.respondRedirect("/show")
        }
        post("/addx") {
            val param = call.receiveParameters()
            val f1 = param["f1"]!!.toInt()
            val f2 = param["f2"].toString()
            DataX.insertOneT1(T1(f1, f2))         
            call.respondRedirect("/showx")
        }
        get("/delx") {
            val f1 = call.request.queryParameters["f1"]!!.toInt()
            DataX.deleteOneT1(f1)
            call.respondRedirect("/showx")
        }
    }
}

src/main/kotlin/demo/model/DataX.kt:

package demo.model

import java.sql.*

import org.apache.commons.dbcp2.*
import org.apache.commons.pool2.impl.*

data class T1X(var f1: Int, var f2: String)

object DataX {
    init {
        Class.forName("org.apache.commons.dbcp2.PoolingDriver")
        val driver = DriverManager.getDriver("jdbc:apache:commons:dbcp:") as PoolingDriver
        val pcf = PoolableConnectionFactory(DriverManagerConnectionFactory("jdbc:mysql://arnepc5/test", "arne", "hemmeligt"), null)
        val cp = GenericObjectPool<PoolableConnection>(pcf)
        cp.setMinIdle(10)
        cp.setMaxTotal(100)
        pcf.setPool(cp)
        driver.registerPool("test", cp)
    }
    fun selectAllT1(): List<T1> {
        val con = DriverManager.getConnection("jdbc:apache:commons:dbcp:test", "arne", "hemmeligt")
        val stmt = con.createStatement()
        val rs = stmt.executeQuery("SELECT f1,f2 FROM t1")
        val res = ArrayList<T1>()
        while(rs.next()) {
            val f1 = rs.getInt(1)
            val f2 = rs.getString(2)
            res.add(T1(f1, f2))
        }
        rs.close()
        stmt.close()
        con.close()
        return res
    }
    fun insertOneT1(o: T1) {
        val con = DriverManager.getConnection("jdbc:apache:commons:dbcp:test", "arne", "hemmeligt")
        val pstmt = con.prepareStatement("INSERT INTO t1 VALUES(?,?)")
        pstmt.setInt(1, o.f1)
        pstmt.setString(2, o.f2)
        pstmt.executeUpdate()
        pstmt.close()
        con.close()
    }
    fun deleteOneT1(f1: Int) {
        val con = DriverManager.getConnection("jdbc:apache:commons:dbcp:test", "arne", "hemmeligt")
        val pstmt = con.prepareStatement("DELETE FROM t1 WHERE f1 = ?")
        pstmt.setInt(1, f1)
        pstmt.executeUpdate()
        pstmt.close()
        con.close()
    }
}

Start URL: http://nodename:port/shows

Spring MVC with Thymeleaf view (Java):

JSP is a relative old technology and comes with a lot of baggage, so new view technologies was invented in the Java world.

Spring MVC introduced Thymeleaf as alternative to JSP.

display.html:

<html xmlns:th="https://thymeleaf.org">
<head>
<title>Spring with Thymeleaf view</title>
</head>
<body>
<h1>Spring with Thymeleaf view</h1>
<h2>Show:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
<tr th:each="o: ${data}">
<td th:text="${o.f1}"/>
<td th:text="${o.f2}"/>
<td><a th:href="@{del?f1={v}(v=${o.f1})}">Delete</a></td>
</tr>
</table>
<h2>Add:</h2>
<form method="post" th:action="@{add}" th:object="${addForm}">
F1: <input type="text" name="f1" th:field="*{f1}"/>
<br>
F2: <input type="text" name="f2" th:field="*{f2}"/>
<br>
<input type="submit" value="Add"/>
</form>
</body>
</html>

AddForm.java:

package springtl;

public class AddForm {
    private Integer f1;
    private String f2;
    public AddForm() {
        this(null, null);
    }
    public AddForm(Integer f1, String f2) {
        this.f1 = f1;
        this.f2 = f2;
    }
    public Integer getF1() {
        return f1;
    }
    public void setF1(Integer f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
}

Manager.java:

package springtl;

import org.springframework.stereotype.Controller;  
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.view.RedirectView;
import org.springframework.web.servlet.View;

@Controller
public class Manager {
    @RequestMapping(value="/show", method=RequestMethod.GET)
    public String show(Model m) {
        Data data = new Data();
        m.addAttribute("data", data.selectAllT1());
        m.addAttribute("addForm", new AddForm());
        return "display";
    }
    @RequestMapping(value="/add", method=RequestMethod.POST)
    public View add(@ModelAttribute("addForm") AddForm addfrm, Model m) {
        T1 o = new T1(addfrm.getF1(), addfrm.getF2());
        Data data = new Data();
        data.insertOneT1(o);
        return new RedirectView("show");
    }
    @RequestMapping(value="/del", method=RequestMethod.GET)
    public View del(@RequestParam("f1") int f1, Model m) {
        Data data = new Data();
        data.deleteOneT1(f1);
        return new RedirectView("show");
    }
}

T1.java:

package springtl;

import java.io.Serializable;

public class T1 implements Serializable {
    private int f1;
    private String f2;
    public T1() {
        this(0, "");
    }
    public T1(int f1, String f2) {
        this.f1 = f1;
        this.f2 = f2;
    }
    public int getF1() {
        return f1;
    }
    public void setF1(int f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
}

Data.java:

package springtl;

import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class Data {
    public List<T1> selectAllT1() {
        List<T1> res = new ArrayList<T1>();
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            Connection con = DriverManager.getConnection("jdbc:mysql://arnepc5:3306/test", "arne", "hemmeligt");
            PreparedStatement pstmt = con.prepareStatement("SELECT f1,f2 FROM t1");
            ResultSet rs = pstmt.executeQuery();
            while(rs.next()) {
                int f1 = rs.getInt(1);
                String f2 = rs.getString(2);
                T1 o = new T1(f1, f2);
                res.add(o);
            }
            rs.close();
            pstmt.close();
            con.close();
        } catch(ClassNotFoundException ex) {
            ex.printStackTrace();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
        return res;
    }
    public void insertOneT1(T1 o) {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            Connection con = DriverManager.getConnection("jdbc:mysql://arnepc5:3306/test", "arne", "hemmeligt");
            PreparedStatement pstmt = con.prepareStatement("INSERT INTO t1 VALUES(?,?)");
            pstmt.setInt(1, o.getF1());
            pstmt.setString(2, o.getF2());
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(ClassNotFoundException ex) {
            ex.printStackTrace();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
    public void deleteOneT1(int f1) {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            Connection con = DriverManager.getConnection("jdbc:mysql://arnepc5:3306/test", "arne", "hemmeligt");
            PreparedStatement pstmt = con.prepareStatement("DELETE FROM t1 WHERE f1 = ?");
            pstmt.setInt(1,  f1);
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(ClassNotFoundException ex) {
            ex.printStackTrace();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
}

Start URL: http://nodename:port/application/show

displayx.html:

<html xmlns:th="https://thymeleaf.org">
<head>
<title>Spring with Thymeleaf view (pool)</title>
</head>
<body>
<h1>Spring with Thymeleaf view (pool)</h1>
<h2>Show:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
<tr th:each="o: ${data}">
<td th:text="${o.f1}"/>
<td th:text="${o.f2}"/>
<td><a th:href="@{delx?f1={v}(v=${o.f1})}">Delete</a></td>
</tr>
</table>
<h2>Add:</h2>
<form method="post" th:action="@{addx}" th:object="${addForm}">
F1: <input type="text" name="f1" th:field="*{f1}"/>
<br>
F2: <input type="text" name="f2" th:field="*{f2}"/>
<br>
<input type="submit" value="Add"/>
</form>
</body>
</html>

AddForm.java:

package springtl;

public class AddForm {
    private Integer f1;
    private String f2;
    public AddForm() {
        this(null, null);
    }
    public AddForm(Integer f1, String f2) {
        this.f1 = f1;
        this.f2 = f2;
    }
    public Integer getF1() {
        return f1;
    }
    public void setF1(Integer f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
}

ManagerX.java:

package springtl;

import org.springframework.stereotype.Controller;  
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.view.RedirectView;
import org.springframework.web.servlet.View;

@Controller
public class ManagerX {
    @RequestMapping(value="/showx", method=RequestMethod.GET)
    public String showx(Model m) {
        DataX data = new DataX();
        m.addAttribute("data", data.selectAllT1());
        m.addAttribute("addForm", new AddForm());
        return "displayx";
    }
    @RequestMapping(value="/addx", method=RequestMethod.POST)
    public View addx(@ModelAttribute("addForm") AddForm addfrm, Model m) {
        T1 o = new T1(addfrm.getF1(), addfrm.getF2());
        DataX data = new DataX();
        data.insertOneT1(o);
        return new RedirectView("showx");
    }
    @RequestMapping(value="/delx", method=RequestMethod.GET)
    public View delx(@RequestParam("f1") int f1, Model m) {
        DataX data = new DataX();
        data.deleteOneT1(f1);
        return new RedirectView("showx");
    }
}

T1.java:

package springtl;

import java.io.Serializable;

public class T1 implements Serializable {
    private int f1;
    private String f2;
    public T1() {
        this(0, "");
    }
    public T1(int f1, String f2) {
        this.f1 = f1;
        this.f2 = f2;
    }
    public int getF1() {
        return f1;
    }
    public void setF1(int f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
}

DataX.java:

package springtl;

import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

public class DataX {
    private static DataSource ds;
    static {
        try {
            Context initctx = new InitialContext();
            Context envctx  = (Context)initctx.lookup("java:/comp/env");
            ds = (DataSource)envctx.lookup("jdbc/TestDB");
        } catch(NamingException ex) {
            ex.printStackTrace();
        }
    }
    public List<T1> selectAllT1() {
        List<T1> res = new ArrayList<T1>();
        try {
            Connection con = ds.getConnection();
            PreparedStatement pstmt = con.prepareStatement("SELECT f1,f2 FROM t1");
            ResultSet rs = pstmt.executeQuery();
            while(rs.next()) {
                int f1 = rs.getInt(1);
                String f2 = rs.getString(2);
                T1 o = new T1(f1, f2);
                res.add(o);
            }
            rs.close();
            pstmt.close();
            con.close();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
        return res;
    }
    public void insertOneT1(T1 o) {
        try {
            Connection con = ds.getConnection();
            PreparedStatement pstmt = con.prepareStatement("INSERT INTO t1 VALUES(?,?)");
            pstmt.setInt(1, o.getF1());
            pstmt.setString(2, o.getF2());
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
    public void deleteOneT1(int f1) {
        try {
            Connection con = ds.getConnection();
            PreparedStatement pstmt = con.prepareStatement("DELETE FROM t1 WHERE f1 = ?");
            pstmt.setInt(1,  f1);
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
}

Start URL: http://nodename:port/application/showx

[.WEB-INF]web.xml:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
    <servlet>
        <servlet-name>spring</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>spring</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

[.WEB-INF]spring-servlet.xml:

<beans xmlns = "http://www.springframework.org/schema/beans"
       xmlns:context = "http://www.springframework.org/schema/context"
       xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation = "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    <context:component-scan base-package="springtl" />
    <bean id="templateResolver" class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
        <property name="prefix" value="/WEB-INF/html/" />
        <property name="suffix" value=".html" />
    </bean>
    <bean id="templateEngine" class="org.thymeleaf.spring5.SpringTemplateEngine">
        <property name="templateResolver" ref="templateResolver" />
    </bean>
    <bean class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
        <property name="templateEngine" ref="templateEngine" />
    </bean>
</beans>

Directory structure:

Directory TOMCAT$ROOT:[webapps.springtl]

WEB-INF.DIR;1

Total of 1 file.

Directory TOMCAT$ROOT:[webapps.springtl.WEB-INF]

classes.DIR;1       html.DIR;1          lib.DIR;1           spring-servlet.xml;1
web.xml;1

Total of 5 files.

Directory TOMCAT$ROOT:[webapps.springtl.WEB-INF.classes]

springtl.DIR;1

Total of 1 file.

Directory TOMCAT$ROOT:[webapps.springtl.WEB-INF.classes.springtl]

AddForm.class;1     AddForm.java;1      Data.class;1        Data.java;1
DataX.class;1       DataX.java;1        gen.com;1           Manager.class;1
Manager.java;1      ManagerX.class;1    ManagerX.java;1     T1.class;1
T1.java;1

Total of 13 files.

Directory TOMCAT$ROOT:[webapps.springtl.WEB-INF.html]

display.html;1      displayx.html;1

Total of 2 files.

Directory TOMCAT$ROOT:[webapps.springtl.WEB-INF.lib]

attoparser-2_0_6_RELEASE.jar;1          commons-logging-1_1.jar;1
mysql-connector-j-8_0_33.jar;1          slf4j-api-2_0_3.jar;1
spring-aop-5_1_2_RELEASE.jar;1          spring-aspects-5_1_2_RELEASE.jar;1
spring-beans-5_1_2_RELEASE.jar;1        spring-context-5_1_2_RELEASE.jar;1
spring-core-5_1_2_RELEASE.jar;1         spring-expression-5_1_2_RELEASE.jar;1
spring-web-5_1_2_RELEASE.jar;1          spring-webmvc-5_1_2_RELEASE.jar;1
thymeleaf-3_1_0_RELEASE.jar;1           thymeleaf-extras-springsecurity5-3_1_0_RELEASE.jar;1
thymeleaf-spring5-3_1_0_RELEASE.jar;1   unbescape-1_1_6_RELEASE.jar;1

Total of 16 files.

Grand total of 6 directories, 38 files.

JSF with Facelet view (Java):

JSF from the beginning supported two view technologies: JSP and facelets.

In JSF 2.x then JSP became deprecated and facelets became the recommended view technology.

In JSF 2.x the XML declarations was replaced by annotations.

For more details about JSF see here.

show.xhtml:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<title>JSF with facelet view</title>
</h:head>
<h:body>
<f:view>
<h1>JSF with facelet view</h1>
<h2>Show (data table):</h2>
<h:dataTable value="#{manager.data}" var="o" border="1">
<h:column><f:facet name="header">F1</f:facet>#{o.f1}</h:column>
<h:column><f:facet name="header">F2</f:facet>#{o.f2}</h:column>
<h:column><h:form><h:commandButton value="Delete" action="#{manager.del(o.f1)}"/></h:form></h:column>
</h:dataTable>
<h2>Show (loop):</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
<ui:repeat value="#{manager.data}" var="o">
<tr>
<td><h:outputText value="#{o.f1}"/></td>
<td><h:outputText value="#{o.f2}"/></td>
<td><h:form><h:commandButton value="Delete" action="#{manager.del(o.f1)}"/></h:form></td>
</tr>
</ui:repeat>
</table>
<h2>Add:</h2>
<h:form>
F1: <h:inputText value="#{manager.f1}"/>
<br/>
F2: <h:inputText value="#{manager.f2}"/>
<br/>
<h:commandButton value="Add" action="#{manager.add}"/>
</h:form>
</f:view>
</h:body>
</html>

Manager.java:

package jsf2;

import java.io.Serializable;
import java.util.List;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;

@ManagedBean
@SessionScoped
public class Manager implements Serializable {
    // show
    public List<T1> getData() {
        Data data = new Data();
        return data.selectAllT1();
    }
    // add
    private Integer f1;
    private String f2;
    public Integer getF1() {
        return f1;
    }
    public void setF1(Integer f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
    public String add() {
        T1 o = new T1((int)f1, f2);
        Data data = new Data();
        data.insertOneT1(o);
        f1 = null;
        f2 = null;
        return "display";
    }
    // del
    public String del(int f1) {
        Data data = new Data();
        data.deleteOneT1(f1);
        return "display";
    }
}

T1.java:

package jsf2;

import java.io.Serializable;

public class T1 implements Serializable {
    private int f1;
    private String f2;
    public T1() {
        this(0, "");
    }
    public T1(int f1, String f2) {
        this.f1 = f1;
        this.f2 = f2;
    }
    public int getF1() {
        return f1;
    }
    public void setF1(int f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
}

Data.java:

package jsf2;

import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class Data {
    public List<T1> selectAllT1() {
        List<T1> res = new ArrayList<T1>();
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            Connection con = DriverManager.getConnection("jdbc:mysql://arnepc5:3306/test", "arne", "hemmeligt");
            PreparedStatement pstmt = con.prepareStatement("SELECT f1,f2 FROM t1");
            ResultSet rs = pstmt.executeQuery();
            while(rs.next()) {
                int f1 = rs.getInt(1);
                String f2 = rs.getString(2);
                T1 o = new T1(f1, f2);
                res.add(o);
            }
            rs.close();
            pstmt.close();
            con.close();
        } catch(ClassNotFoundException ex) {
            ex.printStackTrace();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
        return res;
    }
    public void insertOneT1(T1 o) {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            Connection con = DriverManager.getConnection("jdbc:mysql://arnepc5:3306/test", "arne", "hemmeligt");
            PreparedStatement pstmt = con.prepareStatement("INSERT INTO t1 VALUES(?,?)");
            pstmt.setInt(1, o.getF1());
            pstmt.setString(2, o.getF2());
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(ClassNotFoundException ex) {
            ex.printStackTrace();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
    public void deleteOneT1(int f1) {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            Connection con = DriverManager.getConnection("jdbc:mysql://arnepc5:3306/test", "arne", "hemmeligt");
            PreparedStatement pstmt = con.prepareStatement("DELETE FROM t1 WHERE f1 = ?");
            pstmt.setInt(1,  f1);
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(ClassNotFoundException ex) {
            ex.printStackTrace();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
}

Start URL: http://nodename:port/application/show.jsf

showx.xhtml:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<title>JSF with facelet view (pool)</title>
</h:head>
<h:body>
<f:view>
<h1>JSF with facelet view (pool)</h1>
<h2>Show (data table):</h2>
<h:dataTable value="#{managerX.data}" var="o" border="1">
<h:column><f:facet name="header">F1</f:facet>#{o.f1}</h:column>
<h:column><f:facet name="header">F2</f:facet>#{o.f2}</h:column>
<h:column><h:form><h:commandButton value="Delete" action="#{managerX.del(o.f1)}"/></h:form></h:column>
</h:dataTable>
<h2>Show (loop):</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
<ui:repeat value="#{managerX.data}" var="o">
<tr>
<td><h:outputText value="#{o.f1}"/></td>
<td><h:outputText value="#{o.f2}"/></td>
<td><h:form><h:commandButton value="Delete" action="#{managerX.del(o.f1)}"/></h:form></td>
</tr>
</ui:repeat>
</table>
<h2>Add:</h2>
<h:form>
F1: <h:inputText value="#{managerX.f1}"/>
<br/>
F2: <h:inputText value="#{managerX.f2}"/>
<br/>
<h:commandButton value="Add" action="#{managerX.add}"/>
</h:form>
</f:view>
</h:body>
</html>

ManagerX.java:

package jsf2;

import java.io.Serializable;
import java.util.List;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;

@ManagedBean
@SessionScoped
public class ManagerX implements Serializable {
    // show
    public List<T1> getData() {
        DataX data = new DataX();
        return data.selectAllT1();
    }
    // add
    private Integer f1;
    private String f2;
    public Integer getF1() {
        return f1;
    }
    public void setF1(Integer f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
    public String add() {
        T1 o = new T1((int)f1, f2);
        DataX data = new DataX();
        data.insertOneT1(o);
        f1 = null;
        f2 = null;
        return "display";
    }
    // del
    public String del(int f1) {
        DataX data = new DataX();
        data.deleteOneT1(f1);
        return "display";
    }
}

T1.java:

package jsf2;

import java.io.Serializable;

public class T1 implements Serializable {
    private int f1;
    private String f2;
    public T1() {
        this(0, "");
    }
    public T1(int f1, String f2) {
        this.f1 = f1;
        this.f2 = f2;
    }
    public int getF1() {
        return f1;
    }
    public void setF1(int f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
}

DataX.java:

package jsf2;

import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

public class DataX {
    private static DataSource ds;
    static {
        try {
            Context initctx = new InitialContext();
            Context envctx  = (Context)initctx.lookup("java:/comp/env");
            ds = (DataSource)envctx.lookup("jdbc/TestDB");
        } catch(NamingException ex) {
            ex.printStackTrace();
        }
    }
    public List<T1> selectAllT1() {
        List<T1> res = new ArrayList<T1>();
        try {
            Connection con = ds.getConnection();
            PreparedStatement pstmt = con.prepareStatement("SELECT f1,f2 FROM t1");
            ResultSet rs = pstmt.executeQuery();
            while(rs.next()) {
                int f1 = rs.getInt(1);
                String f2 = rs.getString(2);
                T1 o = new T1(f1, f2);
                res.add(o);
            }
            rs.close();
            pstmt.close();
            con.close();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
        return res;
    }
    public void insertOneT1(T1 o) {
        try {
            Connection con = ds.getConnection();
            PreparedStatement pstmt = con.prepareStatement("INSERT INTO t1 VALUES(?,?)");
            pstmt.setInt(1, o.getF1());
            pstmt.setString(2, o.getF2());
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
    public void deleteOneT1(int f1) {
        try {
            Connection con = ds.getConnection();
            PreparedStatement pstmt = con.prepareStatement("DELETE FROM t1 WHERE f1 = ?");
            pstmt.setInt(1,  f1);
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
}

Start URL: http://nodename:port/application/showx.jsf

[.WEB-INF]web.xml:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.jsf</url-pattern>
    </servlet-mapping>
    <context-param>
        <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
        <param-value>server</param-value>
    </context-param>
    <resource-env-ref>
        <resource-env-ref-name>BeanManager</resource-env-ref-name>
        <resource-env-ref-type>javax.enterprise.inject.spi.BeanManager</resource-env-ref-type>
    </resource-env-ref>
</web-app>

[.WEB-INF]faces-config.xml:

<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
              xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
              version="2.0">
</faces-config>

[.WEB-INF]beans.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
       bean-discovery-mode="annotated">
</beans>

[.META-INF]Context.xml:

<Context>
    <Resource name="BeanManager" 
              auth="Container"
              type="javax.enterprise.inject.spi.BeanManager"
              factory="org.jboss.weld.resources.ManagerObjectFactory" />
</Context>

The last two config files are required to get dependency injection working in Tomcat.

And fair warning: one need to be very careful with this stuff to get dependency injection
working in Tomcat - just one tiny mistake and the managed bean reference end up as null.

Directory structure:

Directory TOMCAT$ROOT:[webapps.jsf2]

META-INF.DIR;1      show.xhtml;1        showx.xhtml;1       WEB-INF.DIR;1

Total of 4 files.

Directory TOMCAT$ROOT:[webapps.jsf2.META-INF]

Context.xml;1

Total of 1 file.

Directory TOMCAT$ROOT:[webapps.jsf2.WEB-INF]

beans.xml;1         classes.DIR;1       faces-config.xml;1  lib.DIR;1
web.xml;1

Total of 5 files.

Directory TOMCAT$ROOT:[webapps.jsf2.WEB-INF.classes]

jsf2.DIR;1

Total of 1 file.

Directory TOMCAT$ROOT:[webapps.jsf2.WEB-INF.classes.jsf2]

Data.class;1        Data.java;1         DataX.class;1       DataX.java;1
gen.com;1           Manager.class;1     Manager.java;1      ManagerX.class;1
ManagerX.java;1     T1.class;1          T1.java;1

Total of 11 files.

Directory TOMCAT$ROOT:[webapps.jsf2.WEB-INF.lib]

commons-beanutils-1_9_2.jar;1           commons-collections-3_2_2.jar;1
commons-digester-1_8.jar;1              commons-logging-1_1_1.jar;1
geronimo-atinject_1_0_spec-1_0.jar;1    myfaces-api-2_2_12.jar;1
myfaces-bundle-2_2_12.jar;1             myfaces-impl-2_2_12.jar;1
mysql-connector-j-8_0_33.jar;1          weld-servlet-shaded.jar;1

Total of 10 files.

Grand total of 6 directories, 32 files.

Current:

The current trend in web applications are "HTML 5 web applications".

Which really is not much about HTML 4.x versus HTML 5.x.

Instead it covers an architecture with:

There are several wellknown JavaScript frameworks for doing the client side including Angular, Vue and React.

I don't have the skills to do Angular, Vue or React, so I will just do a demo client using plain jQuery and primitive DOM manipulation.

Slim (PHP):

Slim is a popular PHP framework for RESTful web services.

As I cannot get composer working on VMS, then I created the project on PC, zipped, uploaded to VMS and unzipped.

Project creation:

php composer.phar require slim/slim
php composer.phar require guzzlehttp/psr7 "^2"

display.html:

<html>
<head>
<title>HTML 5 + Slim</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script>
function fmtrow(f1, f2) {
    var res = '';
    res += '<tr id="row_' + f1 + '">';
    res += '<td>' + f1 + '</td>';
    res += '<td>' + f2 + '</td>';
    res += '<td><input type="button" value="Delete" onclick="del(' + f1 + ')"></td>';
    res += '</tr>';
    return  res;
}

function show() {
    $.ajax({
              method: 'GET',
              url: 'http://192.168.68.40/api.php/t1',
              headers: { accept:  'application/json' },
              dataType: 'json'
          }).done(function(data) {
              var rows = '';
              for(var i = 0; i < data.length; i++) {
                  var o = data[i];
                  rows += fmtrow(o.f1, o.f2);
              }
              $('#t1').append(rows);
          }).fail(function(header, status, error) {
              alert(status + ' ' + error);
          });
}

function add() {
    f1 = $('#f1').val();
    f2 = $('#f2').val();
    $.ajax({
              method: 'POST',
              url: 'http://192.168.68.40/api.php/t1',
              headers: { accept:  'application/json',  "content-type":  'application/json' },
              dataType: 'json',
              data: '{"f1":' + f1 + ',"f2":"' + f2 + '"}'
          }).done(function(data) {
              $('#t1').append(fmtrow(f1, f2));
              $('#f1').val('');
              $('#f2').val('');
          }).fail(function(header, status, error) {
              alert(status + ' ' + error);
          });
}

function del(f1) {
    $.ajax({
              method: 'DELETE',
              url: 'http://192.168.68.40/api.php/t1/' + f1
          }).done(function(data) {
              $('#row_' + f1).remove();
          }).fail(function(header, status, error) {
              alert(status + ' ' + error);
          });
}

$(document).ready(show());
</script>
</head>
<body>
<h1>HTML 5 + Slim</h1>
<h2>Show:</h2>
<table border="1" id="t1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
</table>
<h2>Add:</h2>
<form>
F1: <input type="text" name="f1" id="f1">
<br>
F2: <input type="text" name="f2" id="f2">
<br>
<input type="button" value="Add" onclick="add()">
</form>
</body>
</html>

api.php:

<?php

require '../vendor/autoload.php';

require 'T1.php';
require 'Data.php';

use Slim\Factory\AppFactory;

$app = AppFactory::create();

$app->get('/api.php/t1', function($request, $response, $args) {
    $data = new Data();
    $response->getBody()->write(json_encode($data->selectAllT1()));
    return $response->withHeader('Content-Type', 'application/json');
});

$app->post('/api.php/t1', function($request, $response, $args) {
    $o = json_decode($request->getBody());
    $data = new Data();
    $data->insertOneT1($o);
    $response->getBody()->write(json_encode($o));
    return $response->withHeader('Content-Type', 'application/json');
});

$app->delete('/api.php/t1/{f1}', function($request, $response, $args) {
    $f1 = (int)$args['f1'];
    $data = new Data();
    $data->deleteOneT1($f1);
    return $response->withStatus(204);
});

$app->run();
?>

T1.php:

<?php

class T1 {
    public $f1;
    public $f2;
    public function __construct($f1, $f2) {
        $this->f1 = $f1;
        $this->f2 = $f2;
    }
}

?>

Data.php:

<?php

class Data {
    private function getConnection() {
        $con = mysqli_connect('arnepc5', 'arne', 'hemmeligt', 'test');
        return $con;
    }
    public function selectAllT1() {
        $con = $this->getConnection();
        $stmt = mysqli_prepare($con, 'SELECT f1,f2 FROM t1');
        mysqli_stmt_execute($stmt);
        $rs = mysqli_stmt_get_result($stmt);
        $res = array();
        while($row = mysqli_fetch_array($rs, MYSQLI_ASSOC)) {
            $f1 = $row['f1'];
            $f2 = $row['f2'];
            $res[] = new T1($f1, $f2);
        }
        mysqli_stmt_close($stmt);
        mysqli_close($con);
        return $res;
    }
    public function insertOneT1($o) {
        $con = $this->getConnection();
        $stmt = mysqli_prepare($con, 'INSERT INTO t1 VALUES(?,?)');
        mysqli_stmt_bind_param($stmt, 'is', $o->f1, $o->f2);
        mysqli_stmt_execute($stmt);
        mysqli_stmt_close($stmt);
        mysqli_close($con);
    }
    public function deleteOneT1($f1) {
        $con = $this->getConnection();
        $stmt = mysqli_prepare($con, 'DELETE FROM t1 WHERE f1 = ?');
        mysqli_stmt_bind_param($stmt, 'i', $f1);
        mysqli_stmt_execute($stmt);
        mysqli_stmt_close($stmt);
        mysqli_close($con);
    }
}

?>

Start URL: http://nodename:port/api.php/t1

displayx.html:

<html>
<head>
<title>HTML 5 + Slim (pool)</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script>
function fmtrow(f1, f2) {
    var res = '';
    res += '<tr id="row_' + f1 + '">';
    res += '<td>' + f1 + '</td>';
    res += '<td>' + f2 + '</td>';
    res += '<td><input type="button" value="Delete" onclick="del(' + f1 + ')"></td>';
    res += '</tr>';
    return  res;
}

function show() {
    $.ajax({
              method: 'GET',
              url: 'http://192.168.68.40/apix.php/t1x',
              headers: { accept:  'application/json' },
              dataType: 'json'
          }).done(function(data) {
              var rows = '';
              for(var i = 0; i < data.length; i++) {
                  var o = data[i];
                  rows += fmtrow(o.f1, o.f2);
              }
              $('#t1').append(rows);
          }).fail(function(header, status, error) {
              alert(status + ' ' + error);
          });
}

function add() {
    f1 = $('#f1').val();
    f2 = $('#f2').val();
    $.ajax({
              method: 'POST',
              url: 'http://192.168.68.40/apix.php/t1x',
              headers: { accept:  'application/json',  "content-type":  'application/json' },
              dataType: 'json',
              data: '{"f1":' + f1 + ',"f2":"' + f2 + '"}'
          }).done(function(data) {
              $('#t1').append(fmtrow(f1, f2));
              $('#f1').val('');
              $('#f2').val('');
          }).fail(function(header, status, error) {
              alert(status + ' ' + error);
          });
}

function del(f1) {
    $.ajax({
              method: 'DELETE',
              url: 'http://192.168.68.40/apix.php/t1x/' + f1
          }).done(function(data) {
              $('#row_' + f1).remove();
          }).fail(function(header, status, error) {
              alert(status + ' ' + error);
          });
}

$(document).ready(show());
</script>
</head>
<body>
<h1>HTML 5 + Slim (pool)</h1>
<h2>Show:</h2>
<table border="1" id="t1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
</table>
<h2>Add:</h2>
<form>
F1: <input type="text" name="f1" id="f1">
<br>
F2: <input type="text" name="f2" id="f2">
<br>
<input type="button" value="Add" onclick="add()">
</form>
</body>
</html>

apix.php:

<?php

require '../vendor/autoload.php';

require 'T1.php';
require 'DataX.php';

use Slim\Factory\AppFactory;

$app = AppFactory::create();

$app->get('/apix.php/t1x', function($request, $response, $args) {
    $data = new DataX();
    $response->getBody()->write(json_encode($data->selectAllT1()));
    return $response->withHeader('Content-Type', 'application/json');
});

$app->post('/apix.php/t1x', function($request, $response, $args) {
    $o = json_decode($request->getBody());
    $data = new DataX();
    $data->insertOneT1($o);
    $response->getBody()->write(json_encode($o));
    return $response->withHeader('Content-Type', 'application/json');
});

$app->delete('/apix.php/t1x/{f1}', function($request, $response, $args) {
    $f1 = (int)$args['f1'];
    $data = new DataX();
    $data->deleteOneT1($f1);
    return $response->withStatus(204);
});

$app->run();
?>

T1.php:

<?php

class T1 {
    public $f1;
    public $f2;
    public function __construct($f1, $f2) {
        $this->f1 = $f1;
        $this->f2 = $f2;
    }
}

?>

DataX.php:

<?php

class DataX {
    private function getConnection() {
        $con = mysqli_connect('p:arnepc5', 'arne', 'hemmeligt', 'test');
        return $con;
    }
    public function selectAllT1() {
        $con = $this->getConnection();
        $stmt = mysqli_prepare($con, 'SELECT f1,f2 FROM t1');
        mysqli_stmt_execute($stmt);
        $rs = mysqli_stmt_get_result($stmt);
        $res = array();
        while($row = mysqli_fetch_array($rs, MYSQLI_ASSOC)) {
            $f1 = $row['f1'];
            $f2 = $row['f2'];
            $res[] = new T1($f1, $f2);
        }
        mysqli_stmt_close($stmt);
        mysqli_close($con);
        return $res;
    }
    public function insertOneT1($o) {
        $con = $this->getConnection();
        $stmt = mysqli_prepare($con, 'INSERT INTO t1 VALUES(?,?)');
        mysqli_stmt_bind_param($stmt, 'is', $o->f1, $o->f2);
        mysqli_stmt_execute($stmt);
        mysqli_stmt_close($stmt);
        mysqli_close($con);
    }
    public function deleteOneT1($f1) {
        $con = $this->getConnection();
        $stmt = mysqli_prepare($con, 'DELETE FROM t1 WHERE f1 = ?');
        mysqli_stmt_bind_param($stmt, 'i', $f1);
        mysqli_stmt_execute($stmt);
        mysqli_stmt_close($stmt);
        mysqli_close($con);
    }
}

?>

Start URL: http://nodename:port/display.html

Flask (Python):

Flask is a very common framework for RESTful web services in Python.

display.html:

<html>
<head>
<title>HTML 5 + Flask</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script>
function fmtrow(f1, f2) {
    var res = '';
    res += '<tr id="row_' + f1 + '">';
    res += '<td>' + f1 + '</td>';
    res += '<td>' + f2 + '</td>';
    res += '<td><input type="button" value="Delete" onclick="del(' + f1 + ')"></td>';
    res += '</tr>';
    return  res;
}

function show() {
    $.ajax({
              method: 'GET',
              url: 'http://arne1.vajhoej.dk/flask/flaskapi/t1',
              headers: { accept:  'application/json' },
              dataType: 'json'
          }).done(function(data) {
              var rows = '';
              for(var i = 0; i < data.length; i++) {
                  var o = data[i];
                  rows += fmtrow(o[0], o[1]);
              }
              $('#t1').append(rows);
          }).fail(function(header, status, error) {
              alert(status + ' ' + error);
          });
}
function add() {
    f1 = $('#f1').val();
    f2 = $('#f2').val();
    $.ajax({
              method: 'POST',
              url: 'http://arne1.vajhoej.dk/flask/flaskapi/t1',
              headers: { accept:  'application/json',  "content-type":  'application/json' },
              dataType: 'json',
              data: '{"f1":' + f1 + ',"f2":"' + f2 + '"}'
          }).done(function(data) {
              $('#t1').append(fmtrow(f1, f2));
              $('#f1').val('');
              $('#f2').val('');
          }).fail(function(header, status, error) {
              alert(status + ' ' + error);
          });
}
function del(f1) {
    $.ajax({
              method: 'DELETE',
              url: 'http://arne1.vajhoej.dk/flask/flaskapi/t1/' + f1
          }).done(function(data) {
              $('#row_' + f1).remove();
          }).fail(function(header, status, error) {
              alert(status + ' ' + error);
          });
}
$(document).ready(show());
</script>
</head>
<body>
<h1>HTML 5 + Flask</h1>
<h2>Show:</h2>
<table border="1" id="t1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
</table>
<h2>Add:</h2>
<form>
F1: <input type="text" name="f1" id="f1">
<br>
F2: <input type="text" name="f2" id="f2">
<br>
<input type="button" value="Add" onclick="add()">
</form>
</body>
</html>

server.py:

from flask import Flask, request, Response
import json
import pymysql

app = Flask(__name__)

@app.route('/flaskapi/t1', methods=['GET'])
def get_all():
    con = pymysql.connect(host='localhost',user='root',password='',db='test')
    c = con.cursor()
    c.execute('SELECT f1,f2 FROM t1')
    res = c.fetchall()
    c.close()
    con.close()
    return Response(json.dumps(res), status=200, content_type='application/json')


@app.route('/flaskapi/t1', methods=['POST'])
def add():
    o = json.loads(request.data)
    con = pymysql.connect(host='localhost',user='root',password='',db='test')
    c = con.cursor()
    c.execute('INSERT INTO t1(f1,f2) VALUES(%s,%s)', (o['f1'], o['f2']))
    c.close()
    con.close()
    return Response(json.dumps(o), status=200, content_type='application/json')



@app.route('/flaskapi/t1/<int:f1>', methods=['DELETE'])
def xdel(f1):
    con = pymysql.connect(host='localhost',user='root',password='',db='test')
    c = con.cursor()
    c.execute('DELETE FROM t1 WHERE f1 = %s', (f1,))
    c.close()
    con.close()
    return Response(status=204)

if __name__ == '__main__':
    app.run(host='arne1.vajhoej.dk', port=8000)

Start URL: http://nodename:port/display.html

The setup I tested with is:

Apache httpd.conf fragment:

ProxyPass "/flask" "http://arne1.vajhoej.dk:8000"

So:

JAX-RS via Jersey (Java):

JAX-RS is the Java standard for RESTful web services. Jersey is an implementation of that standard.

display.html:

<html>
<head>
<title>HTML 5 + Jersey</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script>
function fmtrow(f1, f2) {
    var res = '';
    res += '<tr id="row_' + f1 + '">';
    res += '<td>' + f1 + '</td>';
    res += '<td>' + f2 + '</td>';
    res += '<td><input type="button" value="Delete" onclick="del(' + f1 + ')"></td>';
    res += '</tr>';
    return  res;
}

function show() {
    $.ajax({
              method: 'GET',
              url: 'http://192.168.68.40:8080/jersey/api/T1',
              headers: { accept:  'application/json' },
              dataType: 'json'
          }).done(function(data) {
              var rows = '';
              for(var i = 0; i < data.t1.length; i++) {
                  var o = data.t1[i];
                  rows += fmtrow(o.f1, o.f2);
              }
              $('#t1').append(rows);
          }).fail(function(header, status, error) {
              alert(status + ' ' + error);
          });
}

function add() {
    f1 = $('#f1').val();
    f2 = $('#f2').val();
    $.ajax({
              method: 'POST',
              url: 'http://192.168.68.40:8080/jersey/api/T1',
              headers: { accept:  'application/json',  "content-type":  'application/json' },
              dataType: 'json',
              data: '{"f1":' + f1 + ',"f2":"' + f2 + '"}'
          }).done(function(data) {
              $('#t1').append(fmtrow(f1, f2));
              $('#f1').val('');
              $('#f2').val('');
          }).fail(function(header, status, error) {
              alert(status + ' ' + error);
          });
}

function del(f1) {
    $.ajax({
              method: 'DELETE',
              url: 'http://192.168.68.40:8080/jersey/api/T1/' + f1
          }).done(function(data) {
              $('#row_' + f1).remove();
          }).fail(function(header, status, error) {
              alert(status + ' ' + error);
          });
}

$(document).ready(show());
</script>
</head>
<body>
<h1>HTML 5 + Jersey</h1>
<h2>Show:</h2>
<table border="1" id="t1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
</table>
<h2>Add:</h2>
<form>
F1: <input type="text" name="f1" id="f1">
<br>
F2: <input type="text" name="f2" id="f2">
<br>
<input type="button" value="Add" onclick="add()">
</form>
</body>
</html>

T1Service.java:

package jersey;

import java.util.List;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.POST;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

@Path("/T1")
public class T1Service {
    @GET
    @Produces({MediaType.APPLICATION_JSON})
    @Path("")
    public List<T1> show() {
        Data data = new Data();
        return data.selectAllT1();
    }
    @POST
    @Consumes({MediaType.APPLICATION_JSON})
    @Produces({MediaType.APPLICATION_JSON})
    @Path("")
    public T1 add(T1 o) {
        Data data = new Data();
        data.insertOneT1(o);
        return o;
    }
    @DELETE
    @Path("/{f1}")
    public void del(@PathParam("f1") int f1) {
        Data data = new Data();
        data.deleteOneT1(f1);
    }
}

T1.java:

package jersey;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class T1 {
    private int f1;
    private String f2;
    public T1() {
        this(0, "");
    }
    public T1(int f1, String f2) {
        this.f1 = f1;
        this.f2 = f2;
    }
    public int getF1() {
        return f1;
    }
    public void setF1(int f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
}

Data.java:

package jersey;

import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class Data {
    public List<T1> selectAllT1() {
        List<T1> res = new ArrayList<T1>();
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            Connection con = DriverManager.getConnection("jdbc:mysql://arnepc5:3306/test", "arne", "hemmeligt");
            PreparedStatement pstmt = con.prepareStatement("SELECT f1,f2 FROM t1");
            ResultSet rs = pstmt.executeQuery();
            while(rs.next()) {
                int f1 = rs.getInt(1);
                String f2 = rs.getString(2);
                T1 o = new T1(f1, f2);
                res.add(o);
            }
            rs.close();
            pstmt.close();
            con.close();
        } catch(ClassNotFoundException ex) {
            ex.printStackTrace();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
        return res;
    }
    public void insertOneT1(T1 o) {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            Connection con = DriverManager.getConnection("jdbc:mysql://arnepc5:3306/test", "arne", "hemmeligt");
            PreparedStatement pstmt = con.prepareStatement("INSERT INTO t1 VALUES(?,?)");
            pstmt.setInt(1, o.getF1());
            pstmt.setString(2, o.getF2());
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(ClassNotFoundException ex) {
            ex.printStackTrace();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
    public void deleteOneT1(int f1) {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            Connection con = DriverManager.getConnection("jdbc:mysql://arnepc5:3306/test", "arne", "hemmeligt");
            PreparedStatement pstmt = con.prepareStatement("DELETE FROM t1 WHERE f1 = ?");
            pstmt.setInt(1, f1);
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(ClassNotFoundException ex) {
            ex.printStackTrace();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
}

Start URL: http://nodename:port/application/display.html

displayx.html:

<html>
<head>
<title>HTML 5 + Jersey (pool)</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script>
function fmtrow(f1, f2) {
    var res = '';
    res += '<tr id="row_' + f1 + '">';
    res += '<td>' + f1 + '</td>';
    res += '<td>' + f2 + '</td>';
    res += '<td><input type="button" value="Delete" onclick="del(' + f1 + ')"></td>';
    res += '</tr>';
    return  res;
}

function show() {
    $.ajax({
              method: 'GET',
              url: 'http://192.168.68.40:8080/jersey/api/T1X',
              headers: { accept:  'application/json' },
              dataType: 'json'
          }).done(function(data) {
              var rows = '';
              for(var i = 0; i < data.t1.length; i++) {
                  var o = data.t1[i];
                  rows += fmtrow(o.f1, o.f2);
              }
              $('#t1').append(rows);
          }).fail(function(header, status, error) {
              alert(status + ' ' + error);
          });
}

function add() {
    f1 = $('#f1').val();
    f2 = $('#f2').val();
    $.ajax({
              method: 'POST',
              url: 'http://192.168.68.40:8080/jersey/api/T1X',
              headers: { accept:  'application/json',  "content-type":  'application/json' },
              dataType: 'json',
              data: '{"f1":' + f1 + ',"f2":"' + f2 + '"}'
          }).done(function(data) {
              $('#t1').append(fmtrow(f1, f2));
              $('#f1').val('');
              $('#f2').val('');
          }).fail(function(header, status, error) {
              alert(status + ' ' + error);
          });
}

function del(f1) {
    $.ajax({
              method: 'DELETE',
              url: 'http://192.168.68.40:8080/jersey/api/T1X/' + f1
          }).done(function(data) {
              $('#row_' + f1).remove();
          }).fail(function(header, status, error) {
              alert(status + ' ' + error);
          });
}

$(document).ready(show());
</script>
</head>
<body>
<h1>HTML 5 + Jersey (pool)</h1>
<h2>Show:</h2>
<table border="1" id="t1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
</table>
<h2>Add:</h2>
<form>
F1: <input type="text" name="f1" id="f1">
<br>
F2: <input type="text" name="f2" id="f2">
<br>
<input type="button" value="Add" onclick="add()">
</form>
</body>
</html>

T1XService.java:

package jersey;

import java.util.List;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.POST;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

@Path("/T1X")
public class T1XService {
    @GET
    @Produces({MediaType.APPLICATION_JSON})
    @Path("")
    public List<T1> show() {
        DataX data = new DataX();
        return data.selectAllT1();
    }
    @POST
    @Consumes({MediaType.APPLICATION_JSON})
    @Produces({MediaType.APPLICATION_JSON})
    @Path("")
    public T1 add(T1 o) {
        DataX data = new DataX();
        data.insertOneT1(o);
        return o;
    }
    @DELETE
    @Path("/{f1}")
    public void del(@PathParam("f1") int f1) {
        DataX data = new DataX();
        data.deleteOneT1(f1);
    }
}

T1.java:

package jersey;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class T1 {
    private int f1;
    private String f2;
    public T1() {
        this(0, "");
    }
    public T1(int f1, String f2) {
        this.f1 = f1;
        this.f2 = f2;
    }
    public int getF1() {
        return f1;
    }
    public void setF1(int f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
}

DataX.java:

package jersey;

import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

public class DataX {
    private static DataSource ds;
    static {
        try {
            Context initctx = new InitialContext();
            Context envctx  = (Context)initctx.lookup("java:/comp/env");
            ds = (DataSource)envctx.lookup("jdbc/TestDB");
        } catch(NamingException ex) {
            ex.printStackTrace();
        }
    }
    public List<T1> selectAllT1() {
        List<T1> res = new ArrayList<T1>();
        try {
            Connection con = ds.getConnection();
            PreparedStatement pstmt = con.prepareStatement("SELECT f1,f2 FROM t1");
            ResultSet rs = pstmt.executeQuery();
            while(rs.next()) {
                int f1 = rs.getInt(1);
                String f2 = rs.getString(2);
                T1 o = new T1(f1, f2);
                res.add(o);
            }
            rs.close();
            pstmt.close();
            con.close();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
        return res;
    }
    public void insertOneT1(T1 o) {
        try {
            Connection con = ds.getConnection();
            PreparedStatement pstmt = con.prepareStatement("INSERT INTO t1 VALUES(?,?)");
            pstmt.setInt(1, o.getF1());
            pstmt.setString(2, o.getF2());
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
    public void deleteOneT1(int f1) {
        try {
            Connection con = ds.getConnection();
            PreparedStatement pstmt = con.prepareStatement("DELETE FROM t1 WHERE f1 = ?");
            pstmt.setInt(1, f1);
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
}

Start URL: http://nodename:port/application/displayx.html

LoadServices.java:

package jersey;

import java.util.HashSet;
import java.util.Set;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/api")
public class LoadServices extends Application {
    @Override
    public Set<Class<?>> getClasses() {
        Set<Class<?>> res = new HashSet<Class<?>>();
        res.add(T1Service.class);
        res.add(T1XService.class);
        return res;
    }
}

web.xml:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
</web-app>

Directory structure:

Directory TOMCAT$ROOT:[webapps.jersey]

display.html;1      displayx.html;1     WEB-INF.DIR;1

Total of 3 files.

Directory TOMCAT$ROOT:[webapps.jersey.WEB-INF]

classes.DIR;1       lib.DIR;1           web.xml;1

Total of 3 files.

Directory TOMCAT$ROOT:[webapps.jersey.WEB-INF.classes]

jersey.DIR;1

Total of 1 file.

Directory TOMCAT$ROOT:[webapps.jersey.WEB-INF.classes.jersey]

Data.class;1        Data.java;1         DataX.class;1       DataX.java;1
gen.com;1           LoadServices.class;1                    LoadServices.java;1
T1.class;1          T1.java;1           T1Service.class;1   T1Service.java;1
T1XService.class;1  T1XService.java;1

Total of 13 files.

Directory TOMCAT$ROOT:[webapps.jersey.WEB-INF.lib]

activation-1_1_1.jar;1                  asm-3_1.jar;1       config-1_4_3.jar;1
jackson-core-asl-1_1_1.jar;1            jaxb-api-2_1.jar;1  jaxb-impl-2_1_12.jar;1
jersey-bundle-1_2.jar;1                 jersey-client-1_2.jar;1
jersey-core-1_2.jar;1                   jersey-json-1_2.jar;1
jersey-server-1_2.jar;1                 jettison-1_1.jar;1  jsr311-api-1_1_1.jar;1
mysql-connector-j-8_0_33.jar;1          stax-api-1_0-2.jar;1

Total of 15 files.

Grand total of 5 directories, 35 files.

Spring MVC (Java):

Spring MVC is not only for traditional web applicatiosn but also for RESTful web services.

display.html:

<html>
<head>
<title>HTML 5 + Spring</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script>
function fmtrow(f1, f2) {
    var res = '';
    res += '<tr id="row_' + f1 + '">';
    res += '<td>' + f1 + '</td>';
    res += '<td>' + f2 + '</td>';
    res += '<td><input type="button" value="Delete" onclick="del(' + f1 + ')"></td>';
    res += '</tr>';
    return  res;
}

function show() {
    $.ajax({
              method: 'GET',
              url: 'http://192.168.68.40:8080/spring/api/T1',
              headers: { accept:  'application/json' },
              dataType: 'json'
          }).done(function(data) {
              var rows = '';
              for(var i = 0; i < data.length; i++) {
                  var o = data[i];
                  rows += fmtrow(o.f1, o.f2);
              }
              $('#t1').append(rows);
          }).fail(function(header, status, error) {
              alert(status + ' ' + error);
          });
}

function add() {
    f1 = $('#f1').val();
    f2 = $('#f2').val();
    $.ajax({
              method: 'POST',
              url: 'http://192.168.68.40:8080/spring/api/T1',
              headers: { accept:  'application/json',  "content-type":  'application/json' },
              dataType: 'json',
              data: '{"f1":' + f1 + ',"f2":"' + f2 + '"}'
          }).done(function(data) {
              $('#t1').append(fmtrow(f1, f2));
              $('#f1').val('');
              $('#f2').val('');
          }).fail(function(header, status, error) {
              alert(status + ' ' + error);
          });
}

function del(f1) {
    $.ajax({
              method: 'DELETE',
              url: 'http://192.168.68.40:8080/spring/api/T1/' + f1
          }).done(function(data) {
              $('#row_' + f1).remove();
          }).fail(function(header, status, error) {
              alert(status + ' ' + error);
          });
}

$(document).ready(show());
</script>
</head>
<body>
<h1>HTML 5 + Spring</h1>
<h2>Show:</h2>
<table border="1" id="t1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
</table>
<h2>Add:</h2>
<form>
F1: <input type="text" name="f1" id="f1">
<br>
F2: <input type="text" name="f2" id="f2">
<br>
<input type="button" value="Add" onclick="add()">
</form>
</body>
</html>

T1Service.java:

package spring;

import java.util.List;

import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value="/T1")
public class T1Service {
    @RequestMapping(value="",method=RequestMethod.GET,produces={MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<List<T1>> show() {
        Data data = new Data();
        return new ResponseEntity<List<T1>>(data.selectAllT1(), HttpStatus.OK);
    }
    @RequestMapping(value="",method=RequestMethod.POST,produces={MediaType.APPLICATION_JSON_VALUE},consumes={MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<T1> add(@RequestBody T1 o) {
        Data data = new Data();
        data.insertOneT1(o);
        return new ResponseEntity<T1>(o, HttpStatus.OK);
    }
    @RequestMapping(value="/{f1}",method=RequestMethod.DELETE)
    public ResponseEntity<Void> delete(@PathVariable("f1") int f1) {
        Data data = new Data();
        data.deleteOneT1(f1);
        return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
    }
}

T1.java:

package spring;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class T1 {
    private int f1;
    private String f2;
    public T1() {
        this(0, "");
    }
    public T1(int f1, String f2) {
        this.f1 = f1;
        this.f2 = f2;
    }
    public int getF1() {
        return f1;
    }
    public void setF1(int f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
}

Data.java:

package spring;

import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class Data {
    public List<T1> selectAllT1() {
        List<T1> res = new ArrayList<T1>();
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            Connection con = DriverManager.getConnection("jdbc:mysql://arnepc5:3306/test", "arne", "hemmeligt");
            PreparedStatement pstmt = con.prepareStatement("SELECT f1,f2 FROM t1");
            ResultSet rs = pstmt.executeQuery();
            while(rs.next()) {
                int f1 = rs.getInt(1);
                String f2 = rs.getString(2);
                T1 o = new T1(f1, f2);
                res.add(o);
            }
            rs.close();
            pstmt.close();
            con.close();
        } catch(ClassNotFoundException ex) {
            ex.printStackTrace();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
        return res;
    }
    public void insertOneT1(T1 o) {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            Connection con = DriverManager.getConnection("jdbc:mysql://arnepc5:3306/test", "arne", "hemmeligt");
            PreparedStatement pstmt = con.prepareStatement("INSERT INTO t1 VALUES(?,?)");
            pstmt.setInt(1, o.getF1());
            pstmt.setString(2, o.getF2());
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(ClassNotFoundException ex) {
            ex.printStackTrace();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
    public void deleteOneT1(int f1) {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            Connection con = DriverManager.getConnection("jdbc:mysql://arnepc5:3306/test", "arne", "hemmeligt");
            PreparedStatement pstmt = con.prepareStatement("DELETE FROM t1 WHERE f1 = ?");
            pstmt.setInt(1, f1);
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(ClassNotFoundException ex) {
            ex.printStackTrace();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
}

Start URL: http://nodename:port/application/display.html

displayx.html:

<html>
<head>
<title>HTML 5 + Spring (pool)</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script>
function fmtrow(f1, f2) {
    var res = '';
    res += '<tr id="row_' + f1 + '">';
    res += '<td>' + f1 + '</td>';
    res += '<td>' + f2 + '</td>';
    res += '<td><input type="button" value="Delete" onclick="del(' + f1 + ')"></td>';
    res += '</tr>';
    return  res;
}

function show() {
    $.ajax({
              method: 'GET',
              url: 'http://192.168.68.40:8080/spring/api/T1X',
              headers: { accept:  'application/json' },
              dataType: 'json'
          }).done(function(data) {
              var rows = '';
              for(var i = 0; i < data.length; i++) {
                  var o = data[i];
                  rows += fmtrow(o.f1, o.f2);
              }
              $('#t1').append(rows);
          }).fail(function(header, status, error) {
              alert(status + ' ' + error);
          });
}

function add() {
    f1 = $('#f1').val();
    f2 = $('#f2').val();
    $.ajax({
              method: 'POST',
              url: 'http://192.168.68.40:8080/spring/api/T1X',
              headers: { accept:  'application/json',  "content-type":  'application/json' },
              dataType: 'json',
              data: '{"f1":' + f1 + ',"f2":"' + f2 + '"}'
          }).done(function(data) {
              $('#t1').append(fmtrow(f1, f2));
              $('#f1').val('');
              $('#f2').val('');
          }).fail(function(header, status, error) {
              alert(status + ' ' + error);
          });
}

function del(f1) {
    $.ajax({
              method: 'DELETE',
              url: 'http://192.168.68.40:8080/spring/api/T1X/' + f1
          }).done(function(data) {
              $('#row_' + f1).remove();
          }).fail(function(header, status, error) {
              alert(status + ' ' + error);
          });
}

$(document).ready(show());
</script>
</head>
<body>
<h1>HTML 5 + Spring (pool)</h1>
<h2>Show:</h2>
<table border="1" id="t1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
</table>
<h2>Add:</h2>
<form>
F1: <input type="text" name="f1" id="f1">
<br>
F2: <input type="text" name="f2" id="f2">
<br>
<input type="button" value="Add" onclick="add()">
</form>
</body>
</html>

T1XService.java:

package spring;

import java.util.List;

import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value="/T1X")
public class T1XService {
    @RequestMapping(value="",method=RequestMethod.GET,produces={MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<List<T1>> show() {
        DataX data = new DataX();
        return new ResponseEntity<List<T1>>(data.selectAllT1(), HttpStatus.OK);
    }
    @RequestMapping(value="",method=RequestMethod.POST,produces={MediaType.APPLICATION_JSON_VALUE},consumes={MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<T1> add(@RequestBody T1 o) {
        DataX data = new DataX();
        data.insertOneT1(o);
        return new ResponseEntity<T1>(o, HttpStatus.OK);
    }
    @RequestMapping(value="/{f1}",method=RequestMethod.DELETE)
    public ResponseEntity<Void> delete(@PathVariable("f1") int f1) {
        DataX data = new DataX();
        data.deleteOneT1(f1);
        return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
    }
}

T1.java:

package spring;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class T1 {
    private int f1;
    private String f2;
    public T1() {
        this(0, "");
    }
    public T1(int f1, String f2) {
        this.f1 = f1;
        this.f2 = f2;
    }
    public int getF1() {
        return f1;
    }
    public void setF1(int f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
}

DataX.java:

package spring;

import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

public class DataX {
    private static DataSource ds;
    static {
        try {
            Context initctx = new InitialContext();
            Context envctx  = (Context)initctx.lookup("java:/comp/env");
            ds = (DataSource)envctx.lookup("jdbc/TestDB");
        } catch(NamingException ex) {
            ex.printStackTrace();
        }
    }
    public List<T1> selectAllT1() {
        List<T1> res = new ArrayList<T1>();
        try {
            Connection con = ds.getConnection();
            PreparedStatement pstmt = con.prepareStatement("SELECT f1,f2 FROM t1");
            ResultSet rs = pstmt.executeQuery();
            while(rs.next()) {
                int f1 = rs.getInt(1);
                String f2 = rs.getString(2);
                T1 o = new T1(f1, f2);
                res.add(o);
            }
            rs.close();
            pstmt.close();
            con.close();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
        return res;
    }
    public void insertOneT1(T1 o) {
        try {
            Connection con = ds.getConnection();
            PreparedStatement pstmt = con.prepareStatement("INSERT INTO t1 VALUES(?,?)");
            pstmt.setInt(1, o.getF1());
            pstmt.setString(2, o.getF2());
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
    public void deleteOneT1(int f1) {
        try {
            Connection con = ds.getConnection();
            PreparedStatement pstmt = con.prepareStatement("DELETE FROM t1 WHERE f1 = ?");
            pstmt.setInt(1, f1);
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
}

Start URL: http://nodename:port/application/displayx.html

Application.java:

package spring;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class Application extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class[] getRootConfigClasses() {
        return new Class[] { Config.class };
    }
    @Override
    protected Class[] getServletConfigClasses() {
        return null;
    }
    @Override
    protected String[] getServletMappings() {
        return new String[] { "/api/*" };
    }
}

Config.java:

package spring;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "spring")
public class Config {
}

web.xml:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
</web-app>

Directory structure:

Directory TOMCAT$ROOT:[webapps.spring]

display.html;1      displayx.html;1     WEB-INF.DIR;1

Total of 3 files.

Directory TOMCAT$ROOT:[webapps.spring.WEB-INF]

classes.DIR;1       lib.DIR;1           web.xml;1

Total of 3 files.

Directory TOMCAT$ROOT:[webapps.spring.WEB-INF.classes]

spring.DIR;1

Total of 1 file.

Directory TOMCAT$ROOT:[webapps.spring.WEB-INF.classes.spring]

Application.class;1 Application.java;1  Config.class;1      Config.java;1
Data.class;1        Data.java;1         DataX.class;1       DataX.java;1
gen.com;1           T1.class;1          T1.java;1           T1Service.class;1
T1Service.java;1    T1XService.class;1  T1XService.java;1

Total of 15 files.

Directory TOMCAT$ROOT:[webapps.spring.WEB-INF.lib]

commons-logging-1_1.jar;1               jackson-annotations-2_9_0.jar;1
jackson-core-2_9_9.jar;1                jackson-databind-2_9_9.jar;1
jackson-dataformat-cbor-2_9_9.jar;1     jackson-dataformat-smile-2_9_9.jar;1
mysql-connector-j-8_0_33.jar;1          spring-aop-5_1_2_RELEASE.jar;1
spring-aspects-5_1_2_RELEASE.jar;1      spring-beans-5_1_2_RELEASE.jar;1
spring-context-5_1_2_RELEASE.jar;1      spring-core-5_1_2_RELEASE.jar;1
spring-expression-5_1_2_RELEASE.jar;1   spring-web-5_1_2_RELEASE.jar;1
spring-webmvc-5_1_2_RELEASE.jar;1

Total of 15 files.

Grand total of 5 directories, 37 files.

Performance:

The two key aspects of web application performance is:

Note that starting a new process to execute each request obviously does not work with an in memory database connection pool, so for a web application using database (and that is by far the most web applications) then we have 3 models:

  1. start new process to execute each request
  2. execute requests in thread within server process + connect and disconnect to database for every request
  3. execute requests in thread within server process + use an in memory database connection pool

Note that the database connection overhead increases significantly when switching to encryption between web application and database, which is increasingly becoming a security requirment.

Let us try measure.

Test configuration:

Performance test setup

Config:

Results:

Technology Model requests/second
CGI (C)
(Apache)
start new process to execute each request awaiting MySQL client libs on VMS x86-64)
CGI (DCL wrapper + PHP)
(Apache)
start new process to execute each request 4
CGI (DCL wrapper + Python)
(Apache)
start new process to execute each request 1
Spagetti PHP - no pool
(Apache mod_php)
execute requests in thread within server process
connect and disconnect to database for every request
11 (default config)
133 (modified config)
Spagetti PHP - pool
(Apache mod_php)
execute requests in thread within server process
use an in memory database connection pool
11 (default config)
151 (modified config)
PHP DIY MVC - no pool
(Apache mod_php)
execute requests in thread within server process
connect and disconnect to database for every request
11 (default config)
94 (modified config)
PHP DIY MVC - pool
(Apache mod_php)
execute requests in thread within server process
use an in memory database connection pool
11 (default config)
103 (modified config)
PHP DIY MVC - no pool
(Tomcat + Quercus)
execute requests in thread within server process
connect and disconnect to database for every request
208
PHP DIY MVC - pool
(Tomcat + Quercus)
execute requests in thread within server process
use an in memory database connection pool
214
Spagetti JSP - no pool
(Tomcat)
execute requests in thread within server process
connect and disconnect to database for every request
105
Spagetti JSP - pool
(Tomcat)
execute requests in thread within server process
use an in memory database connection pool
691
JSP + Java DIY MVC - no pool
(Tomcat)
execute requests in thread within server process
connect and disconnect to database for every request
100
JSP + Java DIY MVC - pool
(Tomcat)
execute requests in thread within server process
use an in memory database connection pool
684
ZF - no pool
(Apache mod_php)
execute requests in thread within server process
connect and disconnect to database for every request
3 (default and modified config)
ZF - pool
(Apache mod_php)
execute requests in thread within server process
use an in memory database connection pool
3 (default and modified config)
Grails
(Tomcat)
execute requests in thread within server process
connect and disconnect to database for every request
87
Ktor with Thymeleaf view - no pool
standalone
execute requests in thread within server process
connect and disconnect to database for every request
95
Ktor with Thymeleaf view - pool
standalone (with commons dbcp2)
execute requests in thread within server process
use an in memory database connection pool
621
Spring with Thymeleaf view - no pool
(Tomcat)
execute requests in thread within server process
connect and disconnect to database for every request
87
Spring with Thymeleaf view - pool
(Tomcat)
execute requests in thread within server process
use an in memory database connection pool
618
JSF with Facelet view - non pool
(Tomcat)
execute requests in thread within server process
connect and disconnect to database for every request
50
JSF with Facelet view - pool
(Tomcat)
execute requests in thread within server process
use an in memory database connection pool
380
Technology Model requests/second
Slim - non pool
(Apache mod_php)
execute requests in thread within server process
connect and disconnect to database for every request
5 (default and modified config)
Slim - pool
(Apache mod_php)
execute requests in thread within server process
use an in memory database connection pool
5 (default and modified config)
Jersey - non pool
(Tomcat)
execute requests in thread within server process
connect and disconnect to database for every request
95
Jersey - pool
(Tomcat)
execute requests in thread within server process
use an in memory database connection pool
556
Spring MVC - non pool
(Tomcat)
execute requests in thread within server process
connect and disconnect to database for every request
96
Spring MVC - pool
(Tomcat)
execute requests in thread within server process
use an in memory database connection pool
621

We see that:

Changes to Apache config (difference between default and modified config):

More on the Java performance and resource requirements:

Regarding resources then this what I tested with:

$ mcr sysgen
SYSGEN>  SHOW CHANNELCNT
Parameter Name            Current    Default     Min.       Max.   Unit  Dynamic
--------------            -------    -------   -------    -------  ----  -------
CHANNELCNT                   8192        512        64      65535 Channels
$ mcr authorize
UAF> show system
...
Maxjobs:         0  Fillm:      4000  Bytlm:       8192000
Maxacctjobs:     0  Shrfillm:      0  Pbytlm:            0
Maxdetach:       0  BIOlm:      2000  JTquota:        4096
Prclm:          10  DIOlm:      2000  WSdef:          4096
Prio:            4  ASTlm:      5000  WSquo:          8192
Queprio:         0  TQElm:       100  WSextent:      16384
CPU:        (none)  Enqlm:     32767  Pgflquo:     4000000

Conclusion:

It is definitely possible to create web applications including modern web applications on VMS.

The available options to choose from may be smaller than on Linux/Windows, but there are multiple options.

What to choose will depend on ones skills in PHP, Java and JavaScript.

Personally I would choose like:

but that is because I know Java and Groovy.

Apache performance on VMS is below expectations. I suspect that the port of *nix execution model with multiple variable number processes each running multiple threads did not port well to VMS. I suspect that the Windows model with a single massive multi-threaded process would work better (it does for Tomcat).

Article history:

Version Date Description
1.0 March 29th 2024 Initial version
1.1 April 3rd 2024 Add some DCL and Python examples
1.2 September 27th 2024 New Apache + PHP performance numbers for modified config
1.3 October 27th 2024 Add Ktor examples

Other articles:

See list of all articles here

Comments:

Please send comments to Arne Vajhøj