Adam,
Let me try to answer your question fully today.
go -> job starts the web server. The webserver is started using this job command. $SYSTEM of 47 is assigned to GT.M, and YottaDB.
I $P($SY,",")=47 J start^%webreq(PORT,,$G(TLSCONFIG),$G(NOGBL),,$G(USERPASS),$G(NOGZIP)):(IN="/dev/null":OUT="/dev/null":ERR="webreq.mje"):5 ; no in and out files please.
After this, go -> job exits. The webserver is now running in the background.
On line 44, we open the TCP socket like this:
I %WOS="GT.M" O TCPIO:(LISTEN=TCPPORT_":TCP":delim=$C(13,10):attach="server"):15:"socket" E U 0 W !,"error cannot open port "_TCPPORT Q
Line 49 is the use command to use the socket. GT.M has two modes, and we need to ensure we are running in byte mode (aka M mode). We don't want to interpret data as UTF-8.
I %WOS="GT.M" U TCPIO:(CHSET="M")
Next, we listen for 5 concurrent connections max.
I %WOS="GT.M" W /LISTEN(5) ; Listen 5 deep - sets $KEY to "LISTENING|socket_handle|portnumber"
The hard part of the code is the next block.
Upon connection, we reach this block. First thing, we do for loop to wait for 10 seconds each loop around waiting for a message.
Each loop, we check to see if $KEY has the information we need. Different versions of GT.M had different possible values. The old code is commented out and is no longer in use.
If $KEY has "CONNECT", then we take the socket identifier, detach it from the parent job, and start a process using the job command with that socket.
. ; Wait until we have a connection (inifinte wait). |
. ; Stop if the listener asked us to stop. |
. FOR W /WAIT(10) Q:$KEY]"" Q:$G(NOGBL) Q:($E(^%webhttp(0,"listener"),1,4)="stop") |
. ; We have to stop! When we quit, we go to loop, and we exit at LOOP+1 |
. I '$G(NOGBL),$E(^%webhttp(0,"listener"),1,4)="stop" QUIT |
. ; At connection, job off the new child socket to be served away. |
. ; I $P($KEY,"|")="CONNECT" QUIT ; before 6.1 |
. I $P($KEY,"|")="CONNECT" D ; >=6.1 |
. . S CHILDSOCK=$P($KEY,"|",2) |
. . U TCPIO:(detach=CHILDSOCK) |
. . N ARG S ARG=Q_"SOCKET:"_CHILDSOCK_Q |
. . N J S J="CHILD($G(TLSCONFIG),$G(NOGBL),$G(TRACE),$G(USERPASS),$G(NOGZIP)):(input="_ARG_":output="_ARG_")" |
. ; Use the incoming socket; close the server, and restart it and goto CHILD |
. ; USE TCPIO:(SOCKET=$P($KEY,"|",2)) |
. ; CLOSE TCPIO:(SOCKET="server") |
. ; JOB START^%webreq(TCPPORT):(IN="/dev/null":OUT="/dev/null":ERR="/dev/null"):5 |
. ; SET GTMDONE=1 ; Will goto CHILD at the DO exist up above |
. ; ---- END GT.M CODE ---- |
QUIT
To summarize all of this: A parent job listens around for up to 5 connections at a time, and when it gets a connection, it starts off a new job with that connection.
You had another question after this: How are the jobs reused for other connections or the current connection for future requests?
The parent listening job loops around, and stays around forever until stopped. It's the same block above, which does
I %WOS="GT.M" D G LOOP
The child jobs handling each connection have a loop that is done using GOTO:
I %WOS="GT.M"&$G(HTTPLOG) ZGOTO 0:NEXT^%webreq ; unlink all routines; only for debug mode |
G NEXT
The ZGOTO 0 is a trick to make GT.M relink all the routines. This is hard to understand, so ask me about it later when you have more experience.
So what the goto is really doing is saying, listen for the next request FROM THE SAME CONNECTION.
A few lines down from the NEXT tag, we find these lines:
We try reading from TCPX for 10 seconds. If we don't get any data (IF '$TEST), we go to ETDC
If we somehow get data but it's empty (shouldn't happen), also go to ETDC.
Now what does ETDC do?
ETDC ; error trap for client disconnect ; not a true M trap |
K:'$G(NOGBL) ^TMP($J),^TMP("HTTPERR",$J) |
HALT ; Stop process
This just stops the process after doing some logging.
Summarizing this part:
1. The parent listener process runs forever until asked to stop.
2. The child job handling the connection will keep reading more from the same connection (pipelining) for 10 seconds, then dies.
Now, a few more items to talk about surrounding this code:
1. I didn't write the first draft of this code. Kevin Muldrum did. I adapted it to be a general purpose web server, added lots of Unit Tests, and made it work on GT.M/YottaDB.
2. GT.M kept adding features to sockets. One very nice recent feature is the ability to pass sockets between processes. If we implement this, our code will become much faster still than it is right now. Now, we have to start a new process for each new connection. It's now possible to start a children pool in advance and use these to service the processes. I am not interested in implementing this right now, but if you want to do it, please do, and I will take the pull request.
--Sam