NullLogic

This is a basic single-threaded HTTP proxy server. Its original purpose was not to serve as a general use service, but as a tool to aid in the design and testing of other network services. Here, it serves as an example of how to create both network clients, and servers.

#!/usr/bin/nsp class connection { function connection() { this.iocount = { read = 0; write = 0; }; this.headers = {}; }; function setopts() { this.socket.setsockopt('SO_KEEPALIVE', false); this.socket.setsockopt('SO_RCVTIMEO', 10000); }; function swrite(text) { rc=this.socket.write(text); this.iocount.write+=rc; if (do_logging) file.append("tcplog.txt", ">> "+text); if (typeof(text)!='string' || sizeof(text)<1) print("bad data in call to swrite()\r\n"); return rc; }; function sread(i) { text=this.socket.read(); text=text.tostr(); this.iocount.read+=sizeof(text); if (do_logging) file.append("tcplog.txt", sprintf("i=%d, '%s'", i, text)); return text; }; function sgets() { text=this.socket.gets(); text=text.tostr(); this.iocount.read+=sizeof(text); if (do_logging) file.append("tcplog.txt", "<< "+text+"\r\n"); return text; }; function close() { if (typeof(this.socket.socket)!='sock4') return; this.socket.close(); }; } class clientconn { // handles io with the client function clientconn(asock) { this.inherit(connection); this.socket=asock; this.connection(); }; function getrequest() { if (do_logging) file.append("tcplog.txt", "\r\n"+('-'*40)+"\r\n"); if (typeof(this.socket.socket)!='sock4') throw "typeof(this.socket.socket)!='sock4'"; this.setopts(); var RequestLine=this.sgets(); if (RequestLine.gettype()!='string') throw "gets() failed"; if (RequestLine=="") throw "request is empty"; print("\r\ngetrequest(): ["+RequestLine.sub(0, 60)+"]\r\n"); r=RequestLine.split(" "); //printf("[%s]", serialize(r)); if (r.length()<3) throw sprintf("r.length()<3 (%d)", r.length()); this.headers['REQUEST_METHOD'] = r[0]; this.headers['REQUEST_URI'] = r[1]; this.headers['SERVER_PROTOCOL'] = r[2]; clen=0; clenset=false; while (true) { i=this.sgets(); //printf("56-[%s]\r\n", i); if (typeof(i)!='string') break; if (i=="") break; x=string.str(i, ":"); //printf("[%s] [%s]\r\n", x, i); if (x==null) break; p=string.sub(i, 0, sizeof(i)-sizeof(x)); c=string.sub(x, 2, sizeof(x)); //printf("[%s][%s]\r\n", p, c); this.headers[string.toupper(p)]=c; if (string.icmp(p, 'Content-Length')==0) { clen=tonumber(c); clenset=true; } } if (this.headers['REQUEST_METHOD']=="CONNECT") { this.sslrelay(this); this.socket.close(); throw "CONNECT relay stuff"; } else { //print("headers = ",serialize(this.headers),";\n"); this.headers['CONNECTION']="Close"; if (this.headers['REQUEST_METHOD']=='POST') { this.headers['POSTDATA']=""; while (true) { i=this.sread(1); if (typeof(i)!='string') break; if (i=="") break; this.headers['POSTDATA']+=i; if (clenset) { clen-=sizeof(i); if (clen<=0) break; } } } //print("headers = ",serialize(this.headers),";\n"); x=string.str(this.headers['REQUEST_URI'], this.headers['HOST']); //printf("[3][%s][%s][%s]\r\n", this.headers['REQUEST_URI'], this.headers['HOST'], x); if (x===null) { printf("pac?[3][%s][%s][%s]\r\n", this.headers['REQUEST_URI'], this.headers['HOST'], x); if (this.headers['REQUEST_URI']=="/pac") { this.sendresponse("HTTP/1.0 200 OK\r\n\r\n"); this.sendresponse("function FindProxyForURL(url, host) { return \"PROXY "+hostname+":8080\"; }\r\n"); } else { this.sendresponse("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n"); this.sendresponse("This is a proxy. The only thing interesting here is this: <a href='/pac'>PAC</a>.\r\n"); } throw "pac stuff"; } var suburi=string.sub(string.str(this.headers['REQUEST_URI'], this.headers['HOST']), sizeof(this.headers['HOST'])); if (sizeof(suburi)<1) suburi='/'; io.flush(); } }; /* * this method implements a tcp tunnel through which the client will do its own ssl requests */ function sslrelay(client) { x=client.headers['HOST'].split(':'); host=x[0]; port=tonumber(x[1]); printf("%s, %d\r\n", host, port); sslsocket=new net.tcp.socket(); sslsocket.connect(host, port, false); if (typeof(sslsocket.socket)!='sock4') { throw sprintf("can't connect to server '"+client.headers['HOST']+"'\r\n"); return; } client.sendresponse("HTTP/1.0 200 Connection established\r\nProxy-Connection: close\r\n\r\n"); // try to do a relay loop and then die client.socket.setsockopt('SO_KEEPALIVE', false); sslsocket.setsockopt('SO_KEEPALIVE', false); client.socket.setsockopt('SO_RCVTIMEO', 100); sslsocket.setsockopt('SO_RCVTIMEO', 100); c=0; while (true) { io.flush(); r=client.socket.read(); if (r===null) { printf("r===null\r\n"); break; } if (r!==null && r!="") { rc=sslsocket.write(r); //printf("r rc=%d\r\n", rc); if (rc!=r.length()) { printf("r=%d rc=%d!!!\r\n", r.length(), rc); } if (rc<0) { printf("client broke connection\r\n"); break; } } w=sslsocket.read(); if (w===null) { printf("w===null\r\n"); break; } if (w!==null && w!="") { rc=client.socket.write(w); //printf("w rc=%d\r\n", rc); if (rc!=w.length()) { printf("w=%d rc=%d!!!\r\n", w.length(), rc); } if (rc<0) { printf("server broke connection\r\n"); break; } } if (r.length()==0 && w.length()==0) { c++; } else { //printf("r=[%s][%d]\r\n", r.gettype(), r.length()); //printf("w=[%s][%d]\r\n", w.gettype(), w.length()); printf("."); c=0; } if (c>5) { printf("idle\r\n"); break; } } client.socket.close(); sslsocket.close(); }; function sendresponse(data) { this.socket.write(data); }; } class serverconn { // handles request to the server made by the client function serverconn(client) { this.inherit(connection); this.socket=new net.tcp.socket(); this.connection(); this.client=client; }; function sendrequest() { var port=80; if (string.str(client.headers['HOST'], ':')!=null) { x=string.split(client.headers['HOST'], ':'); if (x.length()==2) { client.headers['HOST']=x[0]; port=tonumber(x[1]); } } this.socket.connect(client.headers['HOST'], port, false); if (typeof(this.socket.socket)!='sock4') { throw sprintf("can't connect to server '"+client.headers['HOST']+"'\r\n"); return; } this.setopts(); this.swrite(client.headers['REQUEST_METHOD']+" "+client.headers['REQUEST_URI']+" HTTP/1.0\r\n"); foreach (n,v in client.headers) { if (typeof(v)!='string') break; if (n=='POSTDATA') continue; this.swrite(n+": "+v+"\r\n"); } this.swrite("\r\n"); io.flush(); if (client.headers['REQUEST_METHOD']=='POST') { this.swrite(client.headers['POSTDATA']); io.flush(); } }; function getresponse() { /* request is done. now handle response. */ clen=0; clenset=false; StatusLine=this.sgets(); print("getresponse(): ["+StatusLine+"]\r\n"); if (typeof(StatusLine)!='string' || sizeof(StatusLine)<1) print("bad data in response\r\n"); client.sendresponse(StatusLine+"\r\n"); x=string.str(StatusLine, " "); p=string.sub(StatusLine, 0, sizeof(StatusLine)-sizeof(x)); c=string.sub(x, 1, sizeof(x)); x=string.str(c, " "); c=string.sub(c, 0, sizeof(c)-sizeof(x)); x=string.sub(x, 1, sizeof(x)); status=tonumber(c); // if (status<300||status>=400) { io.flush(); /* start reading and relaying the response header */ while (true) { inp=this.sgets(); if (typeof(inp)!='string') break; client.sendresponse(inp+"\r\n"); if (string.len(inp)<1) break; x=string.str(inp, ":"); if (x==null) break; p=string.sub(inp, 0, sizeof(inp)-sizeof(x)); c=string.sub(x, 2, sizeof(x)); this.headers[string.toupper(p)]=c; if (string.icmp(p, 'Content-Length')==0) { clen=tonumber(c); clenset=true; } } /* done reading and relaying the response header */ this.headers['OUTPUT']=""; io.flush(); /* start reading and relaying the response body */ if (write_files) { logmode=do_logging; do_logging=false; fname=""; } while (true) { inp=this.sread(2); if (typeof(inp)!='string') break; if (string.len(inp)<1) break; this.headers['OUTPUT']+=inp; client.sendresponse(inp); if (write_files && file.stat('files')!=-1) { if (fname=="") { filecounter++; fname=sprintf("files/%d-%s.dat", time.now(), string.sub("00000000"+filecounter.tostring(), -8, 8)); if (logmode) file.append("tcplog.txt", sprintf("[%s]\r\n", fname)); file.writeall(fname, ""); } file.append(fname, inp); } if (clenset) { clen-=sizeof(inp); if (clen<=0) break; } } if (write_files) { do_logging=logmode; } io.flush(); // } if (typeof(this.socket.socket)=='sock4') this.socket.close(); //delete this.socket; }; } function main() { //dl.path = { ".\\DLL" }; //dl.path = { "." }; dl.load("net"); //global maxwrite=2500; //global maxwrite=false; if (!net.tcp.socket) throw "net.tcp.socket not found"; global do_logging = true; global write_files = true; global filecounter = 0; host="INADDR_ANY"; global hostname="192.168.2.5"; port=8080; printf("have_ssl=%s\r\n",net.have_ssl); if (net.have_ssl) { printf("ssl_type=%s\r\n",net.ssl_type); } print("binding to ",host,":",port,"\n"); bsock=new net.tcp.socket(); bsock.bind(host, port); if (typeof(bsock.socket)!='sock4') throw "bind failure\n"; while (true) { io.flush(); asock=bsock.accept(); if (typeof(asock.socket)!='sock4') continue; curtime=runtime(); try { client = new clientconn(asock); client.getrequest(); server = new serverconn(client); server.sendrequest(); server.getresponse(); } catch (e) { print("\tException: \"",e.description,"\"\n"); } pulltime=runtime()-curtime; //printf("time = %d seconds\r\n", pulltime); asock.close(); delete asock; } bsock.close(); } main();