Author Topic: Multithreaded webserver  (Read 900 times)

0 Members and 1 Guest are viewing this topic.

Offline mdijkens

  • Newbie
  • Posts: 34
Multithreaded webserver
« on: March 30, 2022, 11:10:28 am »
For workshops I give, I sometimes need an easy mechanism to share (big) files with the audience or vice-versa on a local network.
I decided it would be a nice challenge to build something myself in QB64.
I've also added multithtreading (single CPU core) so people don't have to wait for eachother when downloading/uploading big files

Comments and advice always welcome ...

Code: QB64: [Select]
  1. Const VERSION = "v22.03.30.4"
  2. ' Start with root path of webserver as parameter and optional port e.g. mdserver2 d:\mysite 8088
  3. ' Place 'favicon.ico' file (+R+H) in root directory
  4. ' Place '.auth' file (+R+H) with authorized hashes in all protected directories
  5.  
  6. DefInt A-Z
  7. Const FALSE = 0, TRUE = Not FALSE
  8.  
  9. Const TIMEOUT = 10
  10. Const MAXURLLEN = 32000
  11. Const BLOCKSIZE = 2 ^ 21
  12. Const MAXFILESIZE = 2 ^ 39
  13. $If WIN Then
  14.   Const SEP0 = "/", SEP = "\"
  15.   Const SEP0 = "\", SEP = "/"
  16.  
  17. Type reqType
  18.   thread As Integer '                 thread#
  19.   client As Integer '                 http-client#
  20.   Typ As String '                     GET / POST / PUT
  21.   Path As String '                    Path in Url '/../../'
  22.   File As String '                    File in Url 'file.ext'
  23.   Param As String '                   param=123
  24.   Auth As String '                    Authorization hash
  25.   cLen As _Unsigned _Integer64 '      Centent-Length
  26.   cStart As _Unsigned _Integer64 '    Content start
  27.   cEnd As _Unsigned _Integer64 '      Content end
  28.   cBytes As _Unsigned _Integer64 '    Content bytes
  29.   cMime As String '                   Content mime type
  30.   cBoundary As String '               Content multi-part boundary string
  31.   cFileNameIn As String '             Post filename 'file.ext'
  32.   cFileNameOut As String '            '<SEP>...<SEP>file.ext'
  33.   cFileSize As _Unsigned _Integer64 ' Filesize in bytes
  34. Dim Shared CRLF As String * 2: CRLF = Chr$(13) + Chr$(10)
  35. Dim Shared As String APPLNAME, SITEPATH, PORT
  36.  
  37.  
  38. If init Then
  39.   main
  40.  
  41. Function init%
  42.   APPLNAME = appName$: SITEPATH = ".": PORT = "8088"
  43.   For c% = 1 To _CommandCount
  44.     cv~% = Val(Command$(c%))
  45.     If cv~% > 0 Then
  46.       PORT = _Trim$(Str$(cv~%))
  47.     Else
  48.       SITEPATH = replace$(_Trim$(Command$(c%)), SEP0, SEP)
  49.     End If
  50.   Next c%
  51.   If SITEPATH = SEP Or Right$(SITEPATH, 2) = ":\" Then SITEPATH = SITEPATH + "."
  52.   If Right$(SITEPATH, 1) = SEP Then SITEPATH = Left$(SITEPATH, Len(SITEPATH) - 1)
  53.   If SITEPATH = "" Or Not _DirExists(SITEPATH) Then SITEPATH = Left$(APPLNAME, _InStrRev(APPLNAME, SEP$) - 1)
  54.  
  55.   printLog "SERVER: '" + APPLNAME + "'"
  56.   printLog "VERSION: " + VERSION
  57.   printLog "PORT: " + PORT
  58.   If Right$(SITEPATH, 2) = "\." Then
  59.     printLog "SITEPATH: '" + Left$(SITEPATH, Len(SITEPATH) - 2) + SEP + "'"
  60.   Else
  61.     printLog "SITEPATH: '" + SITEPATH + SEP + "'"
  62.   End If
  63.   Dim thread(0 To 9) As Integer
  64.   For t% = 0 To 9: thread(t%) = _FreeTimer: Next t%
  65.   On Timer(thread(0), .01) Thread0
  66.   On Timer(thread(1), .01) Thread1
  67.   On Timer(thread(2), .01) Thread2
  68.   On Timer(thread(3), .01) Thread3
  69.   On Timer(thread(4), .01) Thread4
  70.   On Timer(thread(5), .01) Thread5
  71.   On Timer(thread(6), .01) Thread6
  72.   On Timer(thread(7), .01) Thread7
  73.   On Timer(thread(8), .01) Thread8
  74.   On Timer(thread(9), .01) Thread9
  75.   For t% = 0 To 9: Timer(thread(t%)) On: Next t%
  76.  
  77.   HOST = _OpenHost("TCP/IP:" + PORT)
  78.   init% = (HOST < 0)
  79.  
  80. Sub main
  81.   Do Until _KeyHit = 27
  82.     _Limit 10
  83.   Loop
  84.  
  85. Sub processRequest (thread%)
  86.   client% = _OpenConnection(HOST)
  87.   If client% < 0 Then
  88.     Dim req As reqType: req.thread = thread%: req.client = client%
  89.     Get req.client, , reqBytes$
  90.     If parseRequest(req, reqBytes$) Then
  91.       log$ = LTrim$(Str$(-req.client)) + ":" + req.Typ + " " + req.Path
  92.       If Not authorized(req) Then
  93.         log$ = log$ + "  Authenticate: "
  94.         If req.Auth <> "" Then log$ = log$ + req.Auth
  95.         res$ = httpHeader$(req, 401, ""): Put req.client, , res$
  96.       ElseIf req.Typ = "POST" Then
  97.         received~&& = handlePost(req, reqBytes$)
  98.         log$ = log$ + req.cFileNameIn + " (" + _Trim$(Str$(req.cFileSize)) + ")"
  99.         If received~&& <> req.cLen Then log$ = log$ + "  ERROR !!!"
  100.         'redirect
  101.         res$ = httpHeader$(req, 301, ""): Put req.client, , res$
  102.       ElseIf req.Typ = "GET" Then
  103.         If req.File <> "" Then 'download file
  104.           sent~&& = handleGetFile(req)
  105.           log$ = log$ + req.File
  106.           If req.Param <> "" Then log$ = log$ + "?" + req.Param
  107.           log$ = log$ + " (" + _Trim$(Str$(sent~&&)) + ")"
  108.           If sent~&& = 0 Then ' redirect
  109.             log$ = log$ + "  ERROR: File not found"
  110.             req.Path = "/": res$ = httpHeader$(req, 404, ""): Put req.client, , res$
  111.           End If
  112.         Else ' render page
  113.           html$ = renderDirectory$(req)
  114.           If html$ <> "" Then
  115.             res$ = httpHeader$(req, 200, html$)
  116.           Else
  117.             log$ = log$ + "  ERROR: Path not found"
  118.             res$ = httpHeader$(req, 404, "")
  119.           End If
  120.           Put req.client, , res$
  121.         End If
  122.       Else
  123.         log$ = "ERROR: NOT ALLOWED !!!"
  124.         'redirect
  125.         res$ = httpHeader$(req, 404, ""): Put req.client, , res$
  126.       End If
  127.       printLog log$
  128.     End If
  129.     Close req.client
  130.   End If
  131.  
  132. Function renderDirectory$ (req As reqType)
  133.   If Left$(req.Path, 1) <> "/" Or Right$(req.Path, 1) <> "/" Then Exit Function
  134.   fspec$ = replace$(SITEPATH + req.Path, SEP0, SEP)
  135.   px12$ = "style='font-family:" + quoted$("Courier New") + "; font-size:12px;'"
  136.   px14$ = "style='font-family:" + quoted$("Arial") + "; font-size:14px;'"
  137.   px20$ = "style='font-family:" + quoted$("Courier New") + "; font-size:20px;'"
  138.   html$ = "<html><body><form enctype='multipart/form-data' action='.' method='POST'>" + _
  139.           "<input type='file' name='filename' " + px14$ + ">" + _
  140.           "<input type='submit' " + px14$ + "></form><hr>" + _
  141.           "<p " + px20$ + ">"
  142.   If req.Path = "/" Then
  143.     html$ = html$ + "/"
  144.   Else
  145.     Dim p$(1000): parts% = split(req.Path, "/", p$())
  146.     href$ = ""
  147.     For i% = 0 To parts% - 2
  148.       href$ = href$ + p$(i%) + "/"
  149.       html$ = html$ + "<a href='" + href$ + "'>" + p$(i%) + "/</a> "
  150.     Next i%
  151.     html$ = html$ + p$(i%)
  152.   End If
  153.   html$ = html$ + "</p><table " + px12$ + ">"
  154.   tmp$ = APPLNAME + LTrim$(Str$(req.thread)) + ".tmp"
  155.   $If WIN Then
  156.     Shell _Hide "DIR /O:GN " + fspec$ + " >" + tmp$
  157.     Shell _Hide "ls -l --group-directories-first " + fspec$ + " >" + tmp$
  158.   $End If
  159.     ff% = FreeFile
  160.     Open tmp$ For Input As ff%
  161.     tdp$ = "<td style='padding-right: 20px;"
  162.     tda$ = tdp$ + " text-align:right;'>"
  163.     apName$ = Mid$(APPLNAME, _InStrRev(APPLNAME, SEP) + 1)
  164.     Do While Not EOF(ff%)
  165.       Line Input #ff%, l$
  166.       $If WIN Then
  167.         If Val(Left$(l$, 2)) > 0 And Right$(l$, 1) <> "." Then
  168.           dat$ = Left$(l$, 16): sz~&& = Val(replace$(Mid$(l$, 18), ",", "")): f$ = Mid$(l$, 36)
  169.           If InStr(l$, "<DIR>") > 0 And sz~&& = 0 Then
  170.     html$ = html$ + "<tr>" + tdp$ + "'><a href='" + req.Path + f$ + "/'>" + f$ + "</a></td>" + _
  171.     "<td/><td>" + dat$ + "</td></tr>"
  172.           ElseIf Left$(f$, 1) <> "." And Left$(f$, Len(apName$)) <> apName$ Then
  173.     html$ = html$ + "<tr>" + tdp$ + "'><a href='" + req.Path + f$ + "'>" + f$ + "</a></td>" + _
  174.     tda$ + Str$(sz~&&) + "</td><td>" + dat$ + "</td></tr>"
  175.           End If
  176.         End If
  177.       $Else
  178.         If InStr("-d", Left$(l$, 1)) > 0 Then
  179.         sp% = 0: For i% = 1 To 4: sp% = InStr(sp% + 1, l$, " "): Next i%: sp% = sp% + 1
  180.         Do While InStr("0123456789", Mid$(l$, sp%, 1)) = 0: sp% = sp% + 1: Loop
  181.         dp% = InStr(sp% + 1, l$, " ")
  182.         sz~&& = Val(Mid$(l$, sp%, dp% - sp%))
  183.         dat$ = _Trim$(Mid$(l$, dp%, 14))
  184.         f$ = _Trim$(Mid$(l$, dp% + 13))
  185.         If Left$(l$, 1) = "d" And sz~&& = 0 Then
  186.         html$ = html$ + "<tr>" + tdp$ + "'><a href='" + req.Path + f$ + "/'>" + f$ + "</a></td>" + _
  187.         "<td/><td>" + dat$ + "</td></tr>"
  188.         ElseIf Left$(f$, 1) <> "." And Left$(f$, Len(apName$)) <> apName$ Then
  189.         html$ = html$ + "<tr>" + tdp$ + "'><a href='" + req.Path + f$ + "'>" + f$ + "</a></td>" + _
  190.         tda$ + Str$(sz~&&) + "</td><td>" + dat$ + "</td></tr>"
  191.         End If
  192.         End If
  193.       $End If
  194.     Loop
  195.     Close ff%
  196.     Kill tmp$
  197.   End If
  198.   html$ = html$ + "</table></body></html>"
  199.   renderDirectory$ = html$
  200.  
  201. Function handleGetFile~&& (req As reqType)
  202.   If Left$(req.Path, 1) <> "/" Or Right$(req.Path, 1) <> "/" Or req.File = "" Then Exit Function
  203.   fspec$ = replace$(SITEPATH + req.Path + req.File, SEP0, SEP)
  204.   ff% = FreeFile: Open fspec$ For Binary Access Read As ff%
  205.   req.cFileSize = LOF(ff%)
  206.   res$ = httpHeader$(req, 200, "")
  207.   If req.cFileSize <= BLOCKSIZE Then
  208.     Put req.client, , res$
  209.     dat$ = String$(req.cFileSize, 0)
  210.     Get ff%, , dat$
  211.     req.cBytes = Len(dat$)
  212.     Put req.client, , dat$
  213.     _Delay .001 'for other threads
  214.   Else
  215.     blocks& = _Ceil((req.cFileSize / BLOCKSIZE))
  216.     dat$ = String$(BLOCKSIZE, 0)
  217.     Put req.client, , res$
  218.     req.cBytes = 0
  219.     For b& = 1 To blocks& - 1
  220.       Get ff%, , dat$
  221.       req.cBytes = req.cBytes + Len(dat$)
  222.       Put req.client, , dat$
  223.       _Delay .001 'for other threads
  224.     Next b&
  225.     dat$ = String$(req.cFileSize Mod BLOCKSIZE, 0)
  226.     Get ff%, , dat$
  227.     req.cBytes = req.cBytes + Len(dat$)
  228.     Put req.client, , dat$
  229.   End If
  230.   Close ff%
  231.   handleGetFile~&& = req.cBytes
  232.  
  233. Function handlePost~&& (req As reqType, reqBytes$)
  234.   If _FileExists(req.cFileNameOut) Then Exit Function
  235.   If req.cEnd = 0 Then
  236.     content$ = Mid$(reqBytes$, req.cStart)
  237.     req.cBytes = req.cBytes + Len(content$)
  238.   Else
  239.     content$ = Mid$(reqBytes$, req.cStart, req.cEnd - req.cStart - 4)
  240.     req.cBytes = req.cBytes + Len(content$) + Len(req.cBoundary) + 8
  241.   End If
  242.   ff% = FreeFile: Open req.cFileNameOut For Binary Access Write As ff%
  243.   Put ff%, , content$
  244.   req.cFileSize = Len(content$)
  245.   _Delay .001 'for other threads
  246.   If req.cEnd = 0 Then
  247.     Get req.client, , reqBytes$
  248.     tim! = Timer
  249.     Do While req.cBytes < req.cLen And ((86400 + Timer - tim!) Mod 86400) < TIMEOUT
  250.       If Len(reqBytes$) > 0 Then
  251.         req.cEnd = InStr(reqBytes$, req.cBoundary)
  252.         If req.cEnd = 0 Then
  253.           content$ = reqBytes$
  254.           req.cBytes = req.cBytes + Len(content$)
  255.         Else
  256.           content$ = Left$(reqBytes$, req.cEnd - 5)
  257.           req.cBytes = req.cBytes + Len(content$) + Len(req.cBoundary) + 8
  258.         End If
  259.         Put ff%, , content$
  260.         req.cFileSize = req.cFileSize + Len(content$)
  261.         _Delay .001 'for other threads
  262.         tim! = Timer
  263.       End If
  264.       Get req.client, , reqBytes$
  265.     Loop
  266.   End If
  267.   Close ff%
  268.   handlePost~&& = req.cBytes
  269.  
  270. Function parseRequest% (req As reqType, reqBytes$)
  271.   'printLog reqBytes$ '@@
  272.   parseRequest = FALSE
  273.   req.Typ = "": req.Path = "": req.Param = "": req.File = "": req.Auth = ""
  274.   req.cLen = 0: req.cStart = 0: req.cEnd = 0: req.cMime = "": req.cBoundary = ""
  275.   req.cFileNameIn = "": req.cFileNameOut = ""
  276.   req.cBytes = 0: req.cFileSize = 0
  277.   If Len(reqBytes$) < 36 Then Exit Function
  278.   If Left$(reqBytes$, 5) = "GET /" Then
  279.     req.Typ = "GET": s% = 5
  280.   ElseIf Left$(reqBytes$, 6) = "POST /" Then
  281.     req.Typ = "POST": s% = 6
  282.   Else
  283.   End If
  284.   h% = InStr(s%, reqBytes$, " HTTP/1.1" + CRLF): If h% = 0 Or h% > MAXURLLEN Then Exit Function
  285.   url$ = Mid$(reqBytes$, s%, h% - s%)
  286.   For i% = 1 To Len(url$)
  287.     uc$ = Mid$(url$, i%, 1)
  288.     If uc$ = "?" Then param% = TRUE
  289.     If Not param% Then
  290.       If InStr("./_-~ 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", uc$) > 0 Then req.Path = req.Path + uc$
  291.     Else
  292.       If InStr("=_-~ 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", uc$) > 0 Then req.Param = req.Param + uc$
  293.     End If
  294.   Next i%
  295.   If InStr(req.Path, "..") > 0 Or InStr(req.Path, "/.") > 0 Or InStr(req.Path, "./") > 0 Then Exit Function
  296.   If InStr(req.Path, "//") > 0 Or Left$(req.Path, 1) <> "/" Then Exit Function
  297.   bs% = _InStrRev(req.Path, "/")
  298.   req.File = Mid$(req.Path, bs% + 1)
  299.   req.Path = Left$(req.Path, bs%)
  300.   auth% = InStr(reqBytes$, "Authorization: ")
  301.   If auth% > 0 Then req.Auth = Mid$(reqBytes$, auth% + 15, InStr(auth%, reqBytes$, CRLF) - auth% - 15)
  302.   If req.Typ = "POST" Then
  303.     s% = InStr(h%, reqBytes$, "Content-Length: "): If s% = 0 Or s% > h% + 1000 Then Exit Function
  304.     req.cLen = Val(Mid$(reqBytes$, s% + 16)): If req.cLen < 1 Or req.cLen > MAXFILESIZE Then Exit Function
  305.     h% = InStr(s%, reqBytes$, "Content-Type: "): If h% = 0 Or h% > s% + 1000 Then Exit Function
  306.     s% = InStr(h%, reqBytes$, "; boundary="): If s% = 0 Or s% > h% + 100 Then Exit Function
  307.     h% = InStr(s%, reqBytes$, CRLF): If h% = 0 Or h% > s% + 100 Then Exit Function
  308.     req.cBoundary = Mid$(reqBytes$, s% + 11, h% - s% - 11)
  309.     req.cBytes = InStr(h%, reqBytes$, CRLF + CRLF) + 4: If req.cBytes = 0 Or req.cBytes > h% + 1000 Then Exit Function
  310.     s% = InStr(req.cBytes, reqBytes$, req.cBoundary): If s% = 0 Or s% > h% + 1000 Then Exit Function
  311.     h% = InStr(s%, reqBytes$, "; filename="): If h% = 0 Or h% > s% + 100 Then Exit Function
  312.     s% = InStr(h%, reqBytes$, CRLF): If s% = 0 Or s% > h% + 100 Then Exit Function
  313.     req.cFileNameIn = unquoted$(Mid$(reqBytes$, h% + 11, s% - h% - 11))
  314.     req.cFileNameOut = replace$(SITEPATH + req.Path + req.cFileNameIn, SEP0, SEP)
  315.     h% = InStr(s%, reqBytes$, "Content-Type: "): If h% = 0 Or h% > s% + 10 Then Exit Function
  316.     s% = InStr(h%, reqBytes$, CRLF): If s% = 0 Or s% > h% + 100 Then Exit Function
  317.     req.cMime = Mid$(reqBytes$, h% + 14, s% - h% - 14)
  318.     req.cStart = s% + 4
  319.     req.cBytes = req.cStart - req.cBytes
  320.     req.cEnd = InStr(req.cStart, reqBytes$, req.cBoundary)
  321.   End If
  322.   parseRequest = TRUE
  323.  
  324. Function authorized% (req As reqType)
  325.   fspec$ = replace$(SITEPATH + req.Path, SEP0, SEP) + ".auth"
  326.   If _FileExists(fspec$) Then
  327.     ff% = FreeFile
  328.     Open fspec$ For Input As ff%
  329.     Do While Not EOF(ff%)
  330.       Line Input #ff%, lin$
  331.       If req.Auth = lin$ Then authorized% = TRUE: Exit Do
  332.     Loop
  333.     Close ff%
  334.   Else
  335.     authorized% = TRUE
  336.   End If
  337.  
  338. Function httpHeader$ (req As reqType, code%, html$)
  339.   Select Case code%
  340.     Case 200 ' render page & favicon.ico & get file
  341.   res$ ="HTTP/1.1 200 OK" + CRLF + _
  342.   "Server: md" + CRLF + "Connection: close" + CRLF
  343.       If html$ <> "" Then ' render page
  344.         res$ = res$ + "Content-Type: text/html" + CRLF + "Content-Length:" + Str$(Len(html$)) + CRLF + CRLF + html$
  345.       ElseIf req.File = "favicon.ico" Then 'get favicon.ico
  346.   res$ = res$ + "Content-Type: image/png" + CRLF + _
  347.   "Content-Length:" + CRLF + Ltrim$(Str$(req.cFileSize)) + CRLF + CRLF
  348.       ElseIf req.cFileSize > 0 Then 'get file
  349.   res$ = res$ + "Content-Type: application/force-download" + CRLF + _
  350.   "Content-Length:" + CRLF + Ltrim$(Str$(req.cFileSize)) + CRLF + _
  351.   "Content-Transfer-Encoding: binary" + _
  352.   "Content-Disposition: attachment; filename=" + quoted$(req.File) + CRLF + CRLF
  353.       End If
  354.     Case 301 'after post & not allowed
  355.   res$ ="HTTP/1.1 301 Moved Permanently" + CRLF + _
  356.   "Location: " + req.Path + CRLF + _
  357.   "Server: md" + CRLF + "Connection: close" + CRLF + "Content-Type: text/html" + CRLF + CRLF
  358.     Case 401 'authenticate
  359.   res$ ="HTTP/1.1 401 Unauthorized" + CRLF + _
  360.   "WWW-Authenticate: Basic" + CRLF + _
  361.   "Server: md" + CRLF + "Connection: close" + CRLF + CRLF
  362.     Case Else '404
  363.   res$ ="HTTP/1.1 404 Not Found" + CRLF + _
  364.   "Server: md" + CRLF + "Connection: close" + CRLF + CRLF
  365.   httpHeader$ = res$
  366.  
  367. Sub printLog (l$)
  368.   ll$ = Date$ + " " + Time$ + " : " + l$
  369.   ff% = FreeFile: Open APPLNAME + ".log" For Append Access Write As ff%
  370.   Print #ff%, ll$
  371.   Close ff%
  372.   Print ll$
  373.  
  374. Function quoted$ (q$)
  375.   quoted$ = Chr$(34) + q$ + Chr$(34)
  376.  
  377. Function unquoted$ (qq$)
  378.   uq$ = _Trim$(qq$): l& = Len(uq$)
  379.   If l& > 0 Then
  380.     If Asc(Left$(uq$, 1)) = 34 And Asc(Mid$(uq$, l&, 1)) = 34 Then
  381.       unquoted$ = _Trim$(Mid$(uq$, 2, l& - 2))
  382.     Else
  383.       unquoted$ = uq$
  384.     End If
  385.   End If
  386.  
  387. Function replace$ (ir$, i0$, i1$)
  388.   or$ = ir$
  389.   i0len& = Len(i0$)
  390.   i& = InStr(or$, i0$)
  391.   If i0len& = Len(i1$) Then
  392.     Do While i& > 0
  393.       Mid$(or$, i&, i0len&) = i1$
  394.       i& = InStr(or$, i0$)
  395.     Loop
  396.   Else
  397.     Do While i& > 0
  398.       or$ = Left$(or$, i& - 1) + i1$ + Mid$(or$, i& + Len(i0$))
  399.       i& = InStr(or$, i0$)
  400.     Loop
  401.   End If
  402.   replace$ = or$
  403.  
  404. Function split& (inString As String, delim As String, outArray() As String)
  405.   Dim curpos As Long, arrpos As Long, LD As Long, dpos As Long
  406.   curpos = 1: arrpos = LBound(outArray): LD = Len(delim)
  407.   dpos = InStr(curpos, inString, delim)
  408.   Do Until dpos = 0
  409.     outArray(arrpos) = Mid$(inString, curpos, dpos - curpos)
  410.     arrpos = arrpos + 1
  411.     If arrpos > UBound(outArray) Then ReDim _Preserve outArray(LBound(outArray) To UBound(outArray) + 1000) As String
  412.     curpos = dpos + LD
  413.     dpos = InStr(curpos, inString, delim)
  414.   Loop
  415.   outArray(arrpos) = Mid$(inString, curpos)
  416.   ReDim _Preserve outArray(LBound(outArray) To arrpos) As String
  417.   split& = arrpos
  418.  
  419. Function appName$ ()
  420.   $If WIN Then
  421.     Declare Library 'Directory Information using KERNEL32
  422.       Function GetModuleFileNameA (ByVal hModule As Long, lpFileName As String, Byval nSize As Long)
  423.     End Declare
  424.     Static appNam$
  425.     If appNam$ = "" Then
  426.       FileName$ = Space$(512): res = GetModuleFileNameA(0, FileName$, Len(FileName$))
  427.       appNam$ = Left$(FileName$, Len(RTrim$(FileName$)) - 5)
  428.     End If
  429.     appName$ = appNam$
  430.     appName$ = Command$(0)
  431.   $End If
  432.  
  433. Sub Thread0 (): processRequest 0: End Sub
  434. Sub Thread1 (): processRequest 1: End Sub
  435. Sub Thread2 (): processRequest 2: End Sub
  436. Sub Thread3 (): processRequest 3: End Sub
  437. Sub Thread4 (): processRequest 4: End Sub
  438. Sub Thread5 (): processRequest 5: End Sub
  439. Sub Thread6 (): processRequest 6: End Sub
  440. Sub Thread7 (): processRequest 7: End Sub
  441. Sub Thread8 (): processRequest 8: End Sub
  442. Sub Thread9 (): processRequest 9: End Sub
  443.  

edit: cleaned it up a bit
« Last Edit: March 30, 2022, 05:22:08 pm by mdijkens »
~ 2 million lines of BASIC

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: Multithreaded webserver
« Reply #1 on: March 30, 2022, 03:44:44 pm »
Interesting!