I remember compiling this some time ago but cant get it to compile with PBCC5
I'm looking to use PBCC instead of IIS as I have a PB CGI at the moment that takes a lot of memory for every instance. With PBCC I can just serve the pages from data held in memory (one copy)
I'm looking to use PBCC instead of IIS as I have a PB CGI at the moment that takes a lot of memory for every instance. With PBCC I can just serve the pages from data held in memory (one copy)
Code:
'MESSAGE http://www.powerbasic.com/support/forums/Archives/Archive-000002/HTML/20000801-7-000085.html 'FORUM: Source Code 'TOPIC: Archive: Web Server in PBCC 'NAME: Erik Olson, Member 'DATE: October 17, 1998 08:45 AM ' PowerBASIC WEB SERVER APPLICATION ' Author: Erik Olson [email protected] ' for more information refer to RFC 1945 ' Compiled with PBCC 1.0 ' Public Domain ' HOW TO RUN (must have TCP/IP installed) ' Create a web page. Run PBHTTP from that directory. Look at the page ' with your browser. There it is. If you are connected to the internet, ' then everyone else can see it too. Use this source code to build ' really fast custom web applications. Read the RFC's. ' ' HOW IT WORKS (because you really want to know) ' This program sets up a socket host and then launches a thread called ' LISTENTHREAD. This thread listens for connection requests on port 80 ' (the standard web server port) and upon receiving a connection will ' spawn a thread to handle it. The function spawned is SOCKETTHREAD ' which receives one line at a time from the web browser. This initial ' data is the header which looks something like this: ' ' GET /mypage.htm HTTP/1.0 ' Connection: keep -Alive ' User-Agent: Mozilla/4.01 [en] [WinNT; I] ' Host: 204.96.208.229 ' ' ' This is what the web browser actually sends to the web server. The initial ' hit will probably be an HTML page. If the page contains any references to ' other objects from the server (or any other server) such as .gif or .jpg ' images, the web browser will instantly (and simultaneously) attach concurrent ' connections to the server and ask for these items too. ' ' The server opens the requested file and loads it into a string. It then ' gets the length and type of file to create a header to send back that ' looks like this: ' ' HTTP/1.0 200 OK ' Server: PBHTTP 1.0 ' Content-Type: text/html ' Accept-Ranges: bytes ' Content-Length: 3423 ' ' This header is followed by an exact binary image of the file (be it a web ' page or gif image). Throw in a little error trapping, a few bells and ' whistles, and there you have yourself a web server! ' ' Finally, MONITORTHREAD wakes up every 10 seconds and checks to see if ' any threads are more than 20 seconds old (stuck). If they are, it issues a ' STOP command to the thread. During the next 10 second wake-up, it will ' see that the thread has responded to the stop command and will issue ' a KILL command to the thread to delete it from memory. A thread that ' has not been STOPped cannot be KILLed. (see the commands PS,STOP, and ' KILL) ' The main body of the program displays a command prompt and accepts ' commands. This command shell will be used to control the server. Right ' now the only commands it supports are as follows: ' QUIT - stops the server ' PS - (ProcesS) lists all active threads, run times, and modes ' ECHO ON, ECHO OFF, ECHO - turns on and off quiet mode, or shows mode ' STOP <thread> tells thread to stop ' KILL <thread> deletes a stopped thread from memory ' MON <thread> monitors the i/o on a thread's socket ' ' more commands can be added to kill threads, move files, etc. ' ' Web Server Features (existing, and the kind you might want): ' - defaults to default.htm if no page specified. ' - Supports html, gif, jpeg. all other types considered binary files. Browser will download them. ' - Error checking (some exists, and an error page is returned to the browser) ' - CGI (not implemented. Shell to DOS and execute an .exe, then echo back the output) ' - Display a directory if no default.htm exists. (not implemented) ' potential enhancements ' - Add your own html extensions for server side commands and functions, or database support ' (simplifies the creation of CGI scripts, speeds up server response time) ' - Recompile under pbdll for a graphical server interface ' - Use this code as the starting point for your own proxy server or BBS-internet interface ' (client asks server for page. this program connects to a remote server and gets the page, ' then echoes it back to the client.) ' ' ' LIMITATIONS ' supports up to 9999 concurrent threads ' A thread can survive for no more than 20 seconds. After that, it is ' terminated by the function MONITORTHREAD. 20 seconds is more than ' sufficient for retrieving a hypertext document or graphic. ' this server currently will not retrieve any objects larger than 62000 bytes. ' this limit is easily overcome by increasing the size of the string variable ' buf in SOCKETTHREAD ' WARNING: the logic used to calculate the age of a thread will not operate ' properly if the thread was created before midnight, and it exists into the ' new day. MONITORTHREAD will see it as a negative run time and will not ' terminate the thread. This will have the effect of eventually building up ' unterminated threads over time. These threads can be manually STOPped and ' KILLed using the PS, STOP, and KILL commands. ' All output is terminated with a Carriage-return/line feed pair. I set it up ' to check for what kind of line termination a client was using, and to use ' that kind in return, but http transmits a CRLF and wants a CRLF, so I just ' made it all CRLF. $INCLUDE "WIN32API.INC" $INCLUDE "WSOCK32.INC" %PORTNUM = 80 DECLARE FUNCTION SocketThread ( BYVAL hsocket AS LONG ) AS LONG DECLARE FUNCTION ListenThread( BYVAL hsocket AS LONG ) AS LONG DECLARE FUNCTION MonitorThread( BYVAL hsocket AS LONG ) AS LONG DECLARE FUNCTION TimeFile$ DECLARE FUNCTION CrLf$(hSocket as LONG) DECLARE SUB WriteErrorPage DECLARE FUNCTION Process$(HTML$) FUNCTION pbmain DIM result AS WORD DIM wDat AS WSAdata DIM Sockin AS Sockaddr_in DIM s AS LONG DIM ns AS LONG DIM pid AS LONG GLOBAL flags as long GLOBAL NumberOfThreads AS INTEGER GLOBAL BADCHARS AS STRING GLOBAL Quiet AS INTEGER ' if true shuts up threads from announcing status DIM Buf AS ASCIIZ * 512 DIM IdThread(9999) AS GLOBAL LONG 'array to store thread handles DIM TimeThread(9999) AS GLOBAL LONG ' array to store start time of thread DIM CRLFThread(9999) AS GLOBAL BYTE ' array to store thread output characteristics DIM Mon AS GLOBAL INTEGER DIM n& BADCHARS = CHR$( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26 ) result = WsaStartup( 257, wdat ) stdout "PBHTTP : Web Server application version 1.0" stdout "Written by Erik Olson, Clearwater, Florida" stdout "Press Control-Break or type QUIT to terminate server." DIM Title AS ASCIIZ * 80 Title = "PowerBASIC HTTP Server" SetConsoleTitle( Title ) Dummy& = 0 THREAD CREATE ListenThread( Dummy& ) to hLISTEN& THREAD CREATE MonitorThread( Dummy& ) to hMONITOR& sleep 2000 ' give threads a chance to start do stdout chr$(13,10)+"Command:"; ' display a prompt stdin line a$ ' ...............................get input from keyboard Mon = 0 '...................set monitoring to 0 always after any input stdout chr$(13,10); ' ....print a carriage return after keyboard input a$ = ucase$(ltrim$(rtrim$(a$))) ' ................upper case the input if a$ = "" then iterate do '.........................go to top of loop if no input end if if instr(a$," ") > 1 then '...................if line has a space then Cmd$ = left$(a$,instr(a$," ")-1) '.....separate out the first word Arg$ = mid$(a$,instr(a$," ")+1) '........from the rest of the line else Cmd$ = a$ '...........................otherwise it's just one word end if select case Cmd$'...................execute the command you just typed case "STOP" n& = val("&H" + Arg$) if TIMEThread(n&) = -1 then stdout chr$(13,10)+"Thread is stopped"+chr$(13,10) elseif TIMEThread(n&) = 99999 then stdout chr$(13,10)+"Thread is dying"+chr$(13,10) elseif TIMEThread(n&)=0 then stdout chr$(13,10)+"Thread does not exist"+chr$(13,10) else TIMEThread(n&)=-1 end if case "KILL" n& = val("&H" + Arg$) if TIMEThread(n&) = -1 then stdout chr$(13,10)+"Thread still running"+chr$(13,10) elseif TIMEThread(n&) = 99999 then stdout chr$(13,10)+"Killing thread, result ="; THREAD CLOSE idThread(n&) TO r& stdout str$(r&)+chr$(13,10) if r& = -1 then idThread(n&)=0 TIMEThread(n&)=0 CRLFThread(n&)=0 end if elseif TIMEThread(n&)=0 then stdout chr$(13,10)+"Thread does not exist"+chr$(13,10) else stdout chr$(13,10)+"Thread has not been told to stop"+chr$(13,10) end if case "MON" Mon = val("&H"+Arg$) '..........................monitor this thread case "QUIT" exit select case "ECHO" if Arg$ = "ON" then '.arg$ is what comes after the first space Quiet = 0 elseif Arg$ = "OFF" then Quiet = -1 else STDOUT "ECHO IS "; '.....if no ON or OFF then show status if Quiet then STDOUT "OFF" else STDOUT "ON" end if case "PS" sts& = 0 THREAD STATUS hListen& TO sts& STDOUT "Listen Thread status " + hex$(sts&) THREAD STATUS hTransmit& TO sts& STDOUT "Transmit Thread status " + hex$(sts&) THREAD STATUS hMonitor& TO sts& STDOUT "Monitor Thread status " + hex$(sts&) for y% = 1 to 9999 if idThread(y%) then sts& = 0 THREAD STATUS idThread(y%) TO sts& STDOUT "Thread " + hex$(y%) ; STDOUT " status " + hex$(sts&) ; if TIMEThread(y%)=-1 then STDOUT " STOPPED"; elseif TIMEThread(y%) = 99999 then STDOUT " DEAD "; else STDOUT " Runtime " + str$(timer - timethread(y%)) ; end if STDOUT " newline " + str$(CRLFThread(y%)) end if next y% case else '........................an unknown command was entered stdout "Unknown Command - " + Cmd$ + chr$(13,10) ' of course, we could shell to DOS (console) whatever command ' was typed (like DIR or COPY or MKDIR) and since this is a ' multithreaded app it would not interrupt the server. Cool. ' if this is NT ' SHELL environ$("SYSTEM_ROOT")+"\system32\cmd.exe /c " + a$ ' else ' SHELL \COMMAND.COM /c " + a$ ' endif end select if Cmd$ = "QUIT" then exit loop end if loop END FUNCTION ' terminates all threads FUNCTION SocketThread( BYVAL hsocket AS LONG ) AS LONG ' This is a distinct thread for each client ' this function receives data and writes it ' into the IN directory DIM Buf AS ASCIIZ * 62000 DIM pBuf AS ASCIIZ PTR DIM ThisThread AS INTEGER DIM Finished AS INTEGER DIM FF AS INTEGER if not quiet then stdout chr$(13,10) + "Thread " + hex$(hsocket) + " Created." + chr$(13,10) end if cmd& = %FIONBIO argp& = 1 n& = ioctlsocket( hSocket, cmd&, argp& ) ' if n& = - 1 then stdout "IOCTL error" closesocket hSocket exit function end if do until TIMEThread(hSocket) < 0 Sleep 1 'release CPU time i% = recv( hSocket, buf, sizeof( buf ), 0 ) if i% > 0 then a$ = left$( buf, i% ) if instr( a$, chr$( 4 )) then exit loop end if else a$ = "" end if if len( a$ ) > 0 then TIMEThread(hSocket) = timer ' Add input data to buffer a$ = Remove$( a$, ANY BADCHARS ) replace chr$(13,10) with chr$(13) in a$ ' filter out crlf pairs, make them cr InBuffer$ = InBuffer$ + a$ do while instr( InBuffer$, chr$( 13 )) Sleep 1 'release CPU time InBuffer$ = Remove$( InBuffer$, ANY BADCHARS ) Cmd$ = left$( InBuffer$, instr( InBuffer$, chr$( 13 )) - 1 ) InBuffer$ = mid$( InBuffer$, instr( InBuffer$, chr$( 13 )) + 1 ) if not Quiet then Stdout "["+Cmd$+"]" end if if left$(Cmd$,3)="GET" then '..................................GET an object by name fi$ = mid$(Cmd$,5) '...............GET /page.htm HTTP/1.x if instr(Cmd$," ") then fi$ = left$(fi$,instr(fi$," ")-1) end if fi$ = ltrim$(rtrim$(fi$)) if fi$ = "/" or fi$ = "\" or fi$ = "" then fi$ = "default.htm" replace "/" with "\" in fi$ ' ........................change unix style slashes if left$(fi$,1)="\" then fi$ = mid$(fi$,2) ' .............use current dir as root if right$(fi$,1)="\" then fi$ = fi$ + "default.htm" STDOUT "requested " + fi$ ' Should actually do real http error headers here, but this is good enough ' if page is not found then use "error.htm" as custom error msg. Create if not exist if instr(fi$,".")=0 then fi$ = fi$ + ".htm" end if '.........................default to .htm extension (how nice!) if dir$(fi$) = "" then fi$ = "error.htm" : if dir$("error.htm")="" then WriteErrorPage STDOUT "sending " + fi$ ' establish filename for data type. All files are binary files (even html files) ext$ = mid$(fi$,instr(fi$,".")+1) select case lcase$(ext$) case "htm", "html" Content$ = "text/html" case "jpg","jpeg" Content$ = "image/jpeg" case "gif" Content$ = "image/gif" case "exe", "com", "bat", "cgi", "cmd", "bin" Content$ = "text/html" ' CGI support (not done) ' need to shell fi$ with < stdin > stdout and echo back output shell fi$ + " > temp.htm" ' a kludge for testing fi$ = "temp.htm" ' show output of shell cmd case else Content$ = "application/octet-stream" end select STDOUT ext$ + " is " + Content$ end if if Cmd$="" then '.............CRLF on a line by itself indicates header is over ff = freefile open fi$ for binary shared as #ff '..........read the page from disk into HTML$ get$ #ff, LOF(FF), HTML$ close #ff 'HTML$ = Process$(HTML$) '............... Do any custom stuff to the web page ' ...................................... create the header for this page HEADER$ = "HTTP/1.0 200 OK" + CrLf$(hSocket) HEADER$ = HEADER$ + "Server: PBHTTP 1.0"+ CrLf$(hSocket) HEADER$ = HEADER$ + "Content-Type: " + Content$ + CrLf$(hSocket) HEADER$ = HEADER$ + "Accept-Ranges: bytes"+CrLf(hSocket) HEADER$ = HEADER$ + "Content-Length: " + ltrim$(str$(len(HTML$)))+ CrLf$(hSocket) HEADER$ = HEADER$ + CrLf$(hSocket) ' Show Time ! ! ! TIMEThread(hSocket) = Timer '...............update activity timer buf = HEADER$ + HTML$ if len( Buf ) > 0 then if send( hSocket, buf, len( HEADER$ ) + len( HTML$ ), flags ) = - 1 then stdout "Send Error on socket " + hex$(hSocket) + chr$(13,10) end if if Mon = hSocket Then '.............monitor activity on a thread stdout Buf stdout chr$(13,10) end if end if finished = -1 Exit Loop end if loop end if loop while not finished 'Sleep 2000 closesocket hSocket stdout chr$(13,10) + "Thread (" + ltrim$( str$( ThisThread )) + ") ended.===================" TIMEThread(hSocket)=99999 ' tell monitorthread to kill us END FUNCTION SUB Sprint ( hSocket AS LONG, Txt AS STRING ) DIM buf AS ASCIIZ * 512 Buf = Txt i% = send( hSocket, Buf, len( Buf ),flags) END SUB FUNCTION TimeFile$ ' create a unique file name ' number of seconds past midnight (to the tenth of a second) ' and then a random letter. ' 8 letters ' no extension RANDOMIZE TIMER FUNCTION = CHR$(int(rnd(1)*25)+65) + _ CHR$(int(rnd(1)*25)+65) + _ CHR$(int(rnd(1)*25)+65) + _ CHR$(int(rnd(1)*25)+65) + _ CHR$(int(rnd(1)*25)+65) + _ CHR$(int(rnd(1)*25)+65) + _ CHR$(int(rnd(1)*25)+65) + _ CHR$(int(rnd(1)*25)+65) END FUNCTION FUNCTION CrLf$(hSocket as LONG) ' can be used to select end-of-line character based on socket used FUNCTION = chr$(13,10) END FUNCTION FUNCTION ListenThread( BYVAL hsocket AS LONG ) AS LONG ' this is a single thread that operates independantly ' this thread listens for client connection requests ' and spawns SOCKETTHREAD for each client. ' DIM result AS WORD DIM wDat AS WSAdata DIM Sockin AS Sockaddr_in DIM s AS LONG DIM ns AS LONG DIM pid AS LONG DIM Buf AS ASCIIZ * 512 DIM FF AS INTEGER stdout "Listen Thread Active" + chr$(13,10) ' Create a socket s = socket( %PF_INET, %SOCK_STREAM, %IPPROTO_TCP ) if s = - 1 then stdout "create socket error" exit function end if sockin.sin_family = %AF_INET sockin.sin_port = htons( %PORTNUM ) sockin.sin_addr.s = chr$( 0, 0, 0, 0 ) '''''htonl(%INADDR_ANY) n& = bind( s, Sockin, SIZEOF( Sockin )) if n& = - 1 then stdout "BIND Error" closesocket S wsaCleanup exit function end if n& = listen( s, 5 ) if n& = - 1 then stdout "LISTEN Error" closesocket S wsaCleanup exit function end if do ns = accept( s, Sockin, sizeof( Sockin )) if ns = - 1 then stdout "ACCEPT Error" closesocket S wsaCleanup exit function end if THREAD CREATE SocketThread( ns ) to idthread( ns ) timethread(ns)=timer loop ' stop winsock closesocket S wsaCleanup END FUNCTION FUNCTION MonitorThread( BYVAL hsocket AS LONG ) AS LONG ' every 10 seconds, check all of the threads. if any are ' over 60 seconds old, tell them to die. If any threads ' have responded to the death instruction, close them. sleep 500 DIM R& DIM y% stdout "Monitor Thread Active" + chr$(13,10) do sleep 10000 for y% = 1 to 9999 if idThread(y%) then sts& = 0 THREAD STATUS idThread(y%) TO sts& if TIMEThread(y%)=-1 then ' thread has been told to die elseif TIMEThread(y%) = 99999 then ' close the dead thread sleep 1000 THREAD CLOSE idThread(y%) TO r& if not quiet then stdout "Closed thread " + hex$(y%)+" result = " + str$(r&) end if TIMEThread(y%)=0 CRLFThread(y%)=0 idThread(y%)=0 else if timer - timethread(y%) > 15 then TIMEThread(y%) = -1 end if end if end if next y% loop END FUNCTION SUB WriteErrorPage dim ff as integer ff = freefile open "Error.htm" for output as #ff print #ff, "<HTML><HEAD><TITLE>PBHTTP ERROR</TITLE></HEAD><BODY>URL NOT FOUND!</BODY></HTML>" close #ff END SUB FUNCTION Process$(HTML$) ' do anything to the web page that floats your boat before sending it. Invent your own ' server side script language, for example. FUNCTION = HTML$ END FUNCTION '############################ REPLY ############################ 'Steven Pringels, unregistered 'posted November 03, 1998 01:12 PM It runs perfectly. Compile it. Switch off any other Personal Web Services, place a file in the same directory and call the file with http://<computername>/<filename.htm> you can even do http://<computername>//Inetpub/wwwroot/<webname>/<filename.htm> Steven
Comment