//simpleHttpServer 服务端 import inet.url; import wsock.tcp.server; import thread.works; namespace wsock.tcp; class simpleHttpServer{ ctor(ip,port,backlog){{ if(..request && ..response && !..response.closed){ error('在响应 HTTP 请求时不应启动 HTTP 服务端\n'); } var errMsg; this, errMsg = ..wsock.tcp.server(ip,port,backlog); if(!this ) return null,errMsg; this.getUrl = function(netIp){ var ip,port = this.getLocalIp(); if(ip=="0.0.0.0"){ ip = netIp? ..wsock.tcp.client.getLocalIp() : "localhost"; } return ..string.format("http://%s:%d",ip,port ); } this.defalutDocument = "main.aardio"; this.threadNum = 2; this.run = function( app ){ var works = ..thread.works( this.threadNum, function(acceptSocket){ var socketClient = ..wsock.tcp.client(,acceptSocket); var startRequestLine = socketClient.readTo('\r\n'); startRequestLine = startRequestLine ? ..string.splitEx(startRequestLine,"\s+",3); if( ( #startRequestLine != 3 ) ){ socketClient.close(); return; } if( ! ..string.startsWith(startRequestLine[3],"HTTP/") ){ socketClient.close(); return; } var environ = {}; with environ{ SERVER_NAME,SERVER_PORT = socketClient.getLocalIp(); REMOTE_ADDR,REMOTE_PORT = socketClient.getRemoteIp(); if(SERVER_PORT) SERVER_PORT = tostring(SERVER_PORT); if(REMOTE_PORT) REMOTE_PORT = tostring(REMOTE_PORT); REMOTE_HOST = REMOTE_ADDR; REQUEST_METHOD = startRequestLine[1]; REQUEST_URI = startRequestLine[2]; SERVER_PROTOCOL = startRequestLine[3]; SERVER_SOFTWARE = "aardio server/1.0"; GATEWAY_INTERFACE = 'CGI/1.1'; DOCUMENT_ROOT = _SIMPLEHTTPSRV_DOCUMENT_ROOT; HTTPS = "off"; REQUEST_SCHEME = "http"; if(REQUEST_URI[1]!='/'#) REQUEST_URI = "/" + REQUEST_URI; var uri = ..string.split(REQUEST_URI,"?",2); QUERY_STRING = uri[2]; PATH_INFO = uri[1]; } environ.PATH_INFO = ..inet.url.decode(environ.PATH_INFO); if(!environ.PATH_INFO){ socketClient.write(environ.SERVER_PROTOCOL + ' 400 Bad Request'); socketClient.close(); return; } environ.PATH_INFO = ..string.replace(environ.PATH_INFO,"\\","/"); environ.PATH_TRANSLATED = ..io.fullpath(..io.joinpath(_SIMPLEHTTPSRV_DOCUMENT_ROOT,_SIMPLEHTTPSRV_DOCUMENT_BASE,environ.PATH_INFO)); //检查默认文档 if(environ.PATH_INFO[#environ.PATH_INFO]=='/'#){ environ.PATH_INFO = ..io.joinpath(environ.PATH_INFO,_SIMPLEHTTPSRV_DEFAULT_DOCUMENT); environ.PATH_TRANSLATED = ..io.fullpath(..io.joinpath(_SIMPLEHTTPSRV_DOCUMENT_ROOT,_SIMPLEHTTPSRV_DOCUMENT_BASE,environ.PATH_INFO)); } elseif( ..io.exist(environ.PATH_TRANSLATED,0) && !..io.exist(environ.PATH_TRANSLATED,4) ){ socketClient.write(environ.SERVER_PROTOCOL + ' 301 Moved Permanently\r\nLocation: '+environ.PATH_INFO+'/\r\n\r\n'); socketClient.close(); return; } //设置 SCRIPT_NAME with environ{ PATH_INFO = ..string.replace(PATH_INFO,"\\","/"); PATH_TRANSLATED = ..string.replace(PATH_TRANSLATED,"/","\\"); if( ..string.endsWith(PATH_INFO,".aardio",true) ){ SCRIPT_NAME = PATH_INFO; SCRIPT_FILENAME = PATH_TRANSLATED; } else { SCRIPT_NAME = ..string.match(PATH_INFO,"(.*?\.aardio)/"); if(#SCRIPT_NAME) SCRIPT_FILENAME = ..io.fullpath(..io.joinpath(_SIMPLEHTTPSRV_DOCUMENT_ROOT,_SIMPLEHTTPSRV_DOCUMENT_BASE,SCRIPT_NAME)); } } var strHeaders = socketClient.readTo('\r\n\r\n'); if(!strHeaders){ socketClient.close(); return; } var httpHeaders = ..string.splitEx(strHeaders,"\r\n"); var lastHeaderKey; for(i=#httpHeaders;1;-1){ var line = httpHeaders[i]; if(line[1]==' '# || line[1]=='\t'#){ if(lastHeaderKey){ httpHeaders[ lastHeaderKey ] = httpHeaders[ lastHeaderKey ] ++ ..string.trimleft(line); } continue; }; var h = ..string.splitEx(line,"\:\s*",2); if( #h != 2){ ..io.stderr.write("http头错误:",line); socketClient.close(); return; } h[1] = ..string.trim(h[1]); h[2] = ..string.trim(h[2]); lastHeaderKey = ..string.lower(h[1]) httpHeaders[ lastHeaderKey ] = h[2]; environ[ "HTTP_" + ..string.replace(..string.upper(lastHeaderKey),"-","_") ] = h[2]; httpHeaders[i] = {lastHeaderKey;h[2]}; } httpHeaders@ = { _type="object" }; with environ{ URL = PATH_INFO; CONTENT_LENGTH = httpHeaders["content-length"]; CONTENT_TYPE = httpHeaders["content-type"]; } var cookies = environ.HTTP_COOKIE ? ..inet.url.splitParameters(environ.HTTP_COOKIE,"\;\s*") : {}; var queryString = ..inet.url.splitParameters(environ.QUERY_STRING:"","\&\s*"); var response,request; request = { headers = httpHeaders; environ = environ; cookies = cookies; get = queryString; files = {}; documentRoot = environ.DOCUMENT_ROOT; contentType = environ.CONTENT_TYPE; contentLength = tonumber(environ.CONTENT_LENGTH:"0",10); method = environ.REQUEST_METHOD; protocol = environ.SERVER_PROTOCOL; host = environ.HTTP_HOST; serverPort = tonumber(environ.SERVER_PORT:"80",10); remoteAddr = environ.REMOTE_ADDR; documentBase = _SIMPLEHTTPSRV_DOCUMENT_BASE; path = _SIMPLEHTTPSRV_DOCUMENT_BASE ? ..io.joinpath(_SIMPLEHTTPSRV_DOCUMENT_BASE,environ.PATH_INFO) : environ.PATH_INFO; pathInfo = environ.PATH_INFO; uri = environ.REQUEST_URI; scheme = environ.REQUEST_SCHEME; sessionId = cookies.aardiosessid; createSessionId = function(){ owner.sessionId = ..crypt.md5( ..string.format("%d|%.15s|%d|%d|%d|%0.8f",tonumber(..time()), environ.REMOTE_ADDR : environ.HTTP_USER_AGENT : "",::Kernel32.GetCurrentProcessId(),..time.tick(),1,..math.random() ) ); }; postData = function(){ if(owner._raw_stdin_data) return owner._raw_stdin_data; if( environ.CONTENT_LENGTH ){ owner._raw_stdin_data = socketClient.read( request.contentLength ); } return owner._raw_stdin_data; }; postJson = function(){ if ( environ.CONTENT_TYPE && ..string.find(environ.CONTENT_TYPE,"<@@text@>|<@@application@>/<@@json@>!\W") ) { var data = owner.postData(); if(data) return ..JSON.tryParse(data); } }; postFileData = function(){ if(! (environ.CONTENT_TYPE && environ.CONTENT_LENGTH ) ) return; if( owner._raw_stdin_data || owner._raw_file_data ) return owner._raw_file_data; if ( ..string.startsWith(environ.CONTENT_TYPE,"multipart/form-data",true) ) { owner._raw_stdin_data = ""; var boundary = ..string.match(environ.CONTENT_TYPE,"boundary\s*=\s*(\S+)"); if(!boundary){ response.status = "400 Bad Request"; response.write("Missing boundary in multipart/form-data!"); response.close(); } var tempDir = ..io.joinpath("/config/temp/",..string.match(request.host,"[^\:]+") ); if(!..io.createDir(tempDir)) error("Unable to create directory:"+tempDir,2); var pid = ::Kernel32.GetCurrentProcessId(); var tempPathId = ..time.tick(); var tempPath = ..io.joinpath(tempDir,tempPathId) while( ..io.exist(tempPath)){ tempPathId++; tempPath = ..io.joinpath(tempDir,pid ++ tempPathId); } var file,err = ..io.file(tempPath,"w+b"); if(!file) error(..string.concat("Upload failed: ",err) ,2); var len = request.contentLength; var bufferSize = socketClient.bufferSize; var outBuffer = ..raw.buffer(bufferSize); while( var readSize; readSize = len ? socketClient.readBuffer(outBuffer,..math.min(len,bufferSize )); readSize ) { file.writeBuffer(outBuffer,readSize); len = len - readSize; } file.close(); tempPath = ..io.fullpath( tempPath ); request.tempUploadPath = tempPath; if(tempPath) { import fsys.multipartFormData; owner._raw_file_data= ..fsys.multipartFormData(tempPath,boundary); if(owner._raw_file_data) return owner._raw_file_data; } response.status = "400 Bad Request"; response.write("Request format is invalid: multipart/form-data!",err); response.close(); } }; query = function(k){ return queryString[k] || request.post[[k]]; }; } if(!request.host){ request.host = environ.SERVER_NAME; if( request.serverPort != 80) request.host = request.host + ":" + request.serverPort; }; request.url = request.scheme + "://" + request.host ++ request.pathInfo; request.path = ..string.replace(request.path,"\\","/"); if( ! request.sessionId ){ request.createSessionId(); } var sesData,sesHandler,sesExpires; var session; session = { start = function(expires){ for(name,handler in ..sessionHandler){ if( name == "default" ) continue; if( handler[["startSession"]] ) { sesHandler = handler; break; } } if(!sesHandler) sesHandler = ..sessionHandler.default; sesData = sesHandler.startSession(expires); sesExpires = expires; }; clear = function(){ sesData = {}; if(sesHandler) sesHandler.clearSession(); else session.start(); }; save = function(){ if(sesHandler) sesHandler.saveSession(sesData,sesExpires); }; @{ _set = function(k,v){ if(!sesHandler) session.start(); sesData[[k]] = v; sesHandler.updateSession(sesData,k,v); }; _get = function(k){ if(!sesHandler) session.start(); return sesData[[k]]; }; } } response = { headers = {}; cookies = {}; contentType = "text/html"; ["close"] = function(continueRunning){ if(owner.closed){ if((!continueRunning) && (!response.continueRunning) )error(null); return; } response.open(); socketClient.close(); owner.closed = true; if( request.tempUploadPath ) { ..io.remove(request.tempUploadPath); request.tempUploadPath = null; } if((!continueRunning) && (!response.continueRunning) )error(null); } ["open"] = function(){ if( owner._headersWritten ) return; owner._headersWritten = true; var postData = request.postFileData() : request.postData(); owner.status = owner.status ? ..inet.httpStatusCode.format(owner.status) : "200 OK"; owner.write(environ.SERVER_PROTOCOL," ",owner.status, '\r\n'); if( owner.preheaders ){ var h = ..web.joinHeaders(owner.preheaders); owner.write(h); }; owner.write("Content-Type: ",owner.contentType:"text/html"); if(owner.charset!=""){owner.write("; charset=",owner.charset : "UTF-8");} owner.write('\r\n'); owner.headers.Connection = "close"; if(!owner.headers.Date) owner.headers.Date = tostring( ..time.gmt() ); for (k, v in owner.headers) { if( type(v) != 'table' ){ owner.write(k, ": ", v, '\r\n'); } else{ for(i=1;#v) owner.write(k, ": ", v[i], '\r\n'); } } if( owner.cookies ){ if(!owner.cookieDomain) owner.cookieDomain = ..string.match(request.host,"[^\:]+"); if(!owner.cookiePath) owner.cookiePath ="/"; var push = ..table.push; for(k,v in owner.cookies){ if ( type(v) == type.table ) { if( !v.value ){ owner.write("Set-Cookie:",..inet.url.encode(k) ,"=deleted; expires=" + tostring( ..time.gmt(1)) ); } var s = { ..inet.url.encode(tostring(v.value)) }; if(v.expires && ..time.istime(v.expires) ){ push(s,"expires=" + tostring( ..time.gmt(v.expires) ) ); } elseif(v.maxAge){ push(s,"max-age=" + v.maxAge ); }; push(s,"domain=" + (v.domain : owner.cookieDomain) ); push(s,"path=" + (v.path : owner.cookiePath) ); if(v.httpOnly || ((v.httpOnly === null ) && owner.cookieHttpOnly)) push(s,"HttpOnly"); if(v.secure || ((v.secure === null ) && owner.cookieSecure)) push(s,"Secure"); owner.write("Set-Cookie:",..inet.url.encode(k),"=",..string.join(s,"; ") ); } else{ if( v=="" ){ owner.write("Set-Cookie:",..inet.url.encode(k) ,"=deleted; expires=" + tostring( ..time.gmt(1)) ); } else{ owner.write("Set-Cookie:",..inet.url.encode(k),"=",..inet.url.encode(tostring(v)) ); owner.write('; domain=',owner.cookieDomain ,"; path=",owner.cookiePath); } } owner.write('\r\n'); } } if( request.sessionId ){ owner.write("Set-Cookie:AARDIOSESSID=" + request.sessionId + '; domain=' + owner.cookieDomain + '; path=/; HttpOnly\r\n'); } owner.write('\r\n'); }; flush = function(){}; writeBuffer = function(buffer,len){ if( buffer === null ) return; if( owner.closed ) return; if( ! owner._headersWritten ) owner.open(); socketClient.writeBuffer(buffer,len); }; write = function(s,...){ if( s === null ) return; if( response.closed ) return; if( ! response._headersWritten ) response.open(); var ts = type(s); if( ts == 'table' ){ if(s[["_struct"]]) { if( s@[["_tostring"]] ) s = tostring(s); } else s = ..JSON.stringify(s,owner.jsonPrettyPrint); } elseif( (ts != 'string') && (ts != 'buffer') ){ s = tostring(s); }; socketClient.write(s); if(...===null) return; return owner.write(...); }; eventStream = function(e){ response.contentType = "text/event-stream"; response.write(..string.eventStream(e)); }; redirect = function(url,status){ owner.status = status : "302 Moved Temporarily"; if(!..inet.url.is(url,0x0/*_URLIS_URL*/)){ if(!..io.exist(request.path,4)){ var pathDoc = ..io.joinpath(request.path,_SIMPLEHTTPSRV_DEFAULT_DOCUMENT); if( ..io.exist(request.path,0) && ( ..io.exist(pathDoc,4) ) ){ if(!..string.endsWith(request.path,"/") )request.path=request.path+"/"; } } url=..inet.url.joinpath(request.scheme + "://" + request.host ++request.path,url); } owner.headers["Location"] = url; owner.close(); }; error = function(s,...){ if( s === null ) return; if( owner.closed ) return; response.status = "500 Internal Server Error"; if( ! owner._headersWritten ) owner.open(); if( type(s) == type.table ) s = ..JSON.stringify(s,true); else s = tostring(s); socketClient.write(s); if(...===null)return owner.close(); return owner.error(...); }; customErrors = _SIMPLEHTTPSRV_CUSTOM_ERRORS : {}; errorStatus = function(status,msg,continueRunning){ var handler = response.customErrors[status]; if( handler && !response["errorStatusHandled"] ) { response["errorStatusHandled"] = true; if(type.isString(handler)) response.loadcode(handler); else handler(response,request,session); } else{ response.status = ..inet.httpStatusCode.format(status); response.write(msg : response.status); } response.close(continueRunning); }; loadcode = function(path,...){ if( request.method == "OPTIONS")return; if(!path) path = request.path; if( ! ..fsys.path.ischild( "/",path ) ){ return response.errorStatus(403); } if(!..io.exist(path,4)){ var pathDoc = ..io.joinpath(path,_SIMPLEHTTPSRV_DEFAULT_DOCUMENT); if( ..io.exist(path,0) && ( ..io.exist(pathDoc,4) ) ){ path = pathDoc; if(!..string.endsWith(request.path,"/") )request.path=request.path+"/"; } else { if( ! ..string.endsWith(path,".aardio",true) ){ if(!_exe_write_time){ var tmWrite = ..fsys.file.lastModified(..io._exepath); _exe_write_time = tmWrite ? tostring(tmWrite) } var etag = ..crypt.md5( path ++ tostring(_exe_write_time) ); if( request.headers["if-none-match"] == etag ){ return response.errorStatus(304); } response.headers["Etag"] = etag; var since = request.headers["if-modified-since"]; if( since ){ since = ..time.gmt(since); if(since>=_exe_write_time){ return response.errorStatus(304); } } } var strData = ..string.load(path); if(!strData){ strData = ..string.load(pathDoc); if( strData ){ path = pathDoc; if(!..string.endsWith(request.path,"/") )request.path=request.path+"/"; } } if(!strData){ response.headers["Etag"] = null; return response.errorStatus(404); } //资源文件 if( ! ..string.endsWith(path,".aardio",true) ){ response.headers["Last-Modified"] = tostring(_exe_write_time); response.headers["Accept-Ranges"] = "none"; response.contentType = ..fsys.mime.fromFile(path); if( request.method != "HEAD" ) response.write(strData); return; } var page,err = loadcode( strData ); if(!page){ response.error(err); return; } invoke(page,...,...) return; } } if( ! ..string.endsWith(path,".aardio",true) ){ if(..string.cmp(path,"/" + ..io._exefile)==0 ){ return response.errorStatus(403); } response.contentType = ..fsys.mime.fromFile(path); var file = ..fsys.file(path,"rb"); var tmWrite = file.lastModified(); var contentSize = file.size(1); var rangeBegin,rangeEnd = 0,contentSize-1; file.close(); var etag = ..crypt.md5( path ++ tostring(tmWrite) ) + "-" + ..string.hex( tostring(contentSize),""); if( request.headers["if-none-match"] == etag ){ return response.errorStatus(304); } response.headers["Etag"] = etag; var since = request.headers["if-modified-since"]; if( since ){ since = ..time.gmt(since); if(since>=tmWrite){ return response.errorStatus(304); } } response.headers["Last-Modified"] = tostring(tmWrite); response.headers["Accept-Ranges"] = "bytes"; var range = request.headers.range; if(range){ var rangeMatch,rangeSine;; var rangeTest = request.headers["if-range"]; if(rangeTest){ rangeMatch = ..string.match(rangeTest,'"(.+)"'); if(!rangeMatch) rangeMatch = rangeTest; } if(!rangeMatch) rangeMatch = request.headers["if-match"]; if(!rangeSine) rangeSine = request.headers["if-unmodified-since"] || request.headers["unless-modified-since"]; if(rangeSine) rangeSine = ..time.gmt(rangeSine); if( ( !rangeMatch || rangeMatch == etag ) && ( ! rangeSine || ( rangeSine >= tmWrite ) ) ){ var b,e = ..string.match(range,"bytes\s*=\s*(\d*)\-(\d*)"); if(e===null) e = rangeEnd; b = tonumber(b); e = tonumber(e); if( ( b && b> rangeEnd) || (e && e > rangeEnd) ){ return response.errorStatus(416); } if(b!==null) rangeBegin = b; if(e!=null) rangeEnd = e; response.status = 206; } } response.headers["Content-Length"] = (rangeEnd - rangeBegin) + 1; response.headers["Content-Range"] = ..string.format("bytes %.0f-%.0f/%.0f",rangeBegin,rangeEnd,contentSize ); var remainSize = rangeEnd-rangeBegin+1; if(!remainSize){return;} if(remainSize==contentSize)response.status = 200; if( request.method != "HEAD" ){ var file2,err = ..io.file(path,"rb"); if(!file2){ ..io.stderr.write(err,'\r\n'); response.errorStatus(404); return; } var buffer = ..raw.buffer(65535); file2.seek("set",rangeBegin) for(i=rangeBegin;rangeEnd;65535){ var readSize = file2.readBuffer(buffer,remainSize>65535?65535:remainSize); if(!readSize) break ; response.writeBuffer(buffer,readSize); remainSize = remainSize - readSize; } file2.close(); } return; } var page,err = loadcode( path ); if(!page){ response.error(err); return; } invoke(page,...,...) } } if (environ.REQUEST_METHOD == "POST") { if ( environ.CONTENT_TYPE && ..string.find(environ.CONTENT_TYPE,"@@application/x-www-form-urlencoded") ) { var postData = request.postData(); if(postData) request.post = ..inet.url.splitParameters(postData,'\&'); } } if( request.method == "OPTIONS"){ response.headers["Allow"] = "OPTIONS, GET, HEAD, POST, PUT, DELETE, PATCH"; response.headers["Public"] = "OPTIONS, GET, HEAD, POST, PUT, DELETE, PATCH"; } ..response = response; ..request = request; ..session = session; if( ..string.startsWith( request.path,"/config/",true ) ) { return response.errorStatus(403); } elseif( ..string.startsWith( request.path,"/lib/",true ) ) { return response.errorStatus(403); } else{ global.print = function(...){ response.write(...) }; var err; var f = ..fiber.create( function(response,request,session){ var r,e = callex(,_SIMPLEHTTPSRV_APP ,,response,request,session); if(!r) err = e; session.save(); },request.documentRoot ); ..fiber.resume (f,response,request,session); response.continueRunning = true; if(err) { if( environ.REMOTE_ADDR == "127.0.0.1"){ response.error('
',err,''); } else { response.error('
服务器错误/内部错误'); } ..io.stderr.write(err,'\r\n'); } } response.close(true); },100,function(app,documentRoot,documentBase,defalutDocument,customErrors,onThreadCreated,threadGlobal,urlRoot){ import wsock.tcp.client; import inet.url; import inet.httpStatusCode; import sessionHandler.default; import fsys.file; import fsys.path; import fsys.mime; import JSON; import web; import crypt; import string.eventStream; ..table.assign( global,threadGlobal ); _SIMPLEHTTPSRV_APP = app; _SIMPLEHTTPSRV_DOCUMENT_ROOT = ..io.fullpath( (documentRoot:"\") ); _SIMPLEHTTPSRV_DOCUMENT_BASE = documentBase; _SIMPLEHTTPSRV_DEFAULT_DOCUMENT = defalutDocument; _SIMPLEHTTPSRV_CUSTOM_ERRORS = customErrors; if(onThreadCreated) onThreadCreated(_SIMPLEHTTPSRV_DOCUMENT_ROOT,urlRoot); },app,this.documentRoot,this.documentBase,this.defalutDocument ,this.customErrors,this.onThreadCreated,this.threadGlobal,this.getUrl(), ); this._beforeClose = function(){ works.waitClose(); } this.forever( function(acceptSocket){ works.push(acceptSocket) } ) }} }; } namespace simpleHttpServer{ class mainThread{ ctor(app){ this.httpHandle = app; this.customErrors = {}; ..table.gc(this,"stop"); }; getLocalIp = function(){ return this.serverIp,this.serverPort; }; getUrl = function(path,localIp){ var ip,port = this.serverIp,this.serverPort; if(!(ip&&port)) return; if(ip=="0.0.0.0")ip = localIp? ..wsock.getLocalIp() : "localhost"; var url = ..string.format("http://%s:%d",ip,port ); if( path ) { path = ..string.replace(path,"\\","/"); if(path[1]=='/'#) path = ..string.right(path,-2); url = url + "/" + path; }; return url; }; stop = function(){ if(this.serverId){ ..wsock.tcp.server.closeById(this.serverId); this.serverId = null; sleep(10); } }; start = function(startIp,startPort,startBacklog){ if(..request && ..response ){ error('请求路径不能指向服务器启动代码\n'); } this.stop(); var serverInfo = ..thread.var(); ..thread.invoke( function(serverInfo,startIp,startPort,startBacklog,httpHandle,threadNum,documentRoot,documentBase,defalutDocument,customErrors,onThreadCreated,threadGlobal){ import wsock.tcp.simpleHttpServer; var server,errMsg = wsock.tcp.simpleHttpServer(startIp,startPort,startBacklog); if(!server){ serverInfo.set({ err = true; message = errMsg; }); return; } server.documentRoot = documentRoot; server.documentBase = documentBase; server.defalutDocument = defalutDocument; server.customErrors = customErrors; server.onThreadCreated = onThreadCreated; server.threadGlobal = threadGlobal; server.threadNum = threadNum : 2; if(!httpHandle){ httpHandle = function(response,request,session){ response.loadcode( request.path ); } }; var ip,port = server.getLocalIp(); serverInfo.set({ id = server.id; ip = ip; port = port; }); server.run(httpHandle); } ,serverInfo,startIp,startPort : this.startPort,startBacklog ,this.httpHandle ,this.threadNum ,this.documentRoot ,this.documentBase ,this.defalutDocument : "main.aardio" ,this.customErrors ,this.onThreadCreated ,this.threadGlobal , ) while(!serverInfo.get()){ sleep(10); } var info = serverInfo.get(); if(info.err){ return false,info.message; } serverInfo.set(null); this.serverId = info.id; this.serverIp = info.ip; this.serverPort = info.port; return true; } } var serverMain; stopUrl = function(){ if(serverMain) serverMain.stop(); } self.customErrors = {}; startSpaUrl = function(indexUrl,documentBase,app){ var indexPath,extraInfo = ..string.match(indexUrl,"([^\#\?]*)(.*)"); if(indexPath[1]!='/'# && indexPath[1]!='\\'#) indexPath = "/" + indexPath; self.customErrors[404] = indexPath; var f = ..string.match(indexPath,"[^\\/.]+\.[^\\/.]+$") if(f) self.defalutDocument = f; if(type(documentBase)=="string") self.documentBase = documentBase; return startUrl( ..string.concat( documentBase?"/":indexPath,extraInfo),,app); } startUrl = function(path,documentRoot,app){ if( type(path) == type.function){ app,path = path,null } if(documentRoot) self.documentRoot = documentRoot; if(!serverMain){ serverMain = mainThread(app); serverMain.documentRoot = self.documentRoot; serverMain.documentBase = self.documentBase; serverMain.defalutDocument = self.defalutDocument; serverMain.customErrors = self.customErrors; serverMain.onThreadCreated = self.onThreadCreated; serverMain.threadGlobal = self.threadGlobal; serverMain.threadNum = self.threadNum; serverMain.start(self.startIp : "127.0.0.1",self.startPort); } return serverMain.getUrl(path,false); } ..subscribe("beforeUnload",function(){ if(serverMain) serverMain.stop(); } ); getUrl = startUrl; } //@guide [HTTP 服务端开发指南](doc://guide/quickstart/web-server.md) /**intellisense() wsock.tcp.simpleHttpServer.documentRoot = 网站应用程序根目录,默认为"/",\n只能设置为硬盘上实际存在的目录,\n改变此目录,会同时改变\n服务端代码中的应用程序根目录以及用户库目录,\n如果只是相将所有请求路径转向某个目录,应当改用 documentBase 属性\n如果网站在嵌入资源目录中,应当改用 documentBase 属性 wsock.tcp.simpleHttpServer.documentBase = 网站根目录,\n不会修改应用程序根目录,支持硬盘目录与资源目录,\n这个属性应当设置为应用程序根目录下的相对路径,\n例如 "/res/web/"\n\n注意: request.path 前面包含 documentBase 目录。\n而 request.pathInfo 忽略 documentBase 目录 wsock.tcp.simpleHttpServer.defalutDocument = 默认文档,默认为"main.aardio",\n\n如果访问硬盘上存在的目录,request.path 尾部不加斜杠会自动跳转到以斜杠结束的路径\n\n如果访问嵌入资源目录,只有 request.path 以斜杠才会访问默认文档,\n对于访问嵌入资源文件,建议指定完整的文件路径,\n默认文档主要是用于硬盘上的网站 wsock.tcp.simpleHttpServer.mainThread(app) = @.mainThread( \n function(response,request,session){ \n response.loadcode( request.path __/*可省略,可增加多个模板参数,\n在被调用文件的函数外部可使用owner参数获取首个模板参数,\n也可以使用...获取多个模板参数*/ );\n } \n); wsock.tcp.simpleHttpServer.mainThread() = !stdHttpSimpleServerMain. wsock.tcp.simpleHttpServer.startUrl( = 查找可用端口创建 HTTP 服务器线程,返回返回完整 URL\n此服务端限制使用本机IP 127.0.0.1 访问,并随机分配端口不会出现端口冲突\n如果HTTP服务器已启动则直接返回URL而不是重复启动服务器,\n注意当前线程结束时,此服务器线程会自动退出 wsock.tcp.simpleHttpServer.startUrl(.(path,documentRoot,app) = 查找可用端口创建 HTTP 服务器线程,返回返回完整 URL\n如果 HTTP 服务器已启动则直接返回 URL 而不是重复启动服务器。\n省略参数返回首页URL,尾部不包含斜杠。\n\n可选用 @path 参数指定请求目标文件的相对路径。\n可选使用参数 @documentRoot 指定网站根目录,默认为"/"。\ndocumentRoot 只能设为硬盘实际存在的目录,并会改变用户库与应用根目录,\n如只想改变网页根目录请设置 wsock.tcp.simpleHttpServer.documentBase。\n可选用 @app 参数指定处理 HTTP 请求的线程函数,\n该线程函数有 response,request,session 等三个回调参数。\n也可以用参数@1 指定 @app 参数。 wsock.tcp.simpleHttpServer.startSpaUrl( = 返回 SPA 单页应用首页网址 wsock.tcp.simpleHttpServer.startSpaUrl(.(indexHtmlPath,documentBase,app) = 参数指定 SPA 单页应用首页路径,\n404错误页也会自动设置到该路径,\n返回首页网址\n\n可选用参数 @documentBase 指定根目录\n以避免网页不支持非根目录路径。\n\n可选用 @app 参数指定处理 HTTP 请求的线程函数,\n该线程函数有 response,request,session 等三个回调参数 wsock.tcp.simpleHttpServer.stopUrl(__) = 退出startUrl函数创建的HTTP服务器线程 wsock.tcp.simpleHttpServer = HTTP服务端\n支持文件上传,下载,下载支持断点续传\n支持多线程处理请求 wsock.tcp.simpleHttpServer(.(IP,端口,请求队列大小) = 创建HTTP服务端,所有参数可选,\n\n如果不写IP,则默认设为"0.0.0.0"也即监听本机所有IP,访问此服务端也不限制IP\n限制仅本机可以访问建议写127.0.0.1\n\n端口为0或省略则自动选择未用端口\n注意0-1023为系统通用服务保留端口,\n1024-49151为用户服务端口,其中大约%9已由IANA注册分配\n49152-65535为私有或临时端口 wsock.tcp.simpleHttpServer() = !stdHttpSimpleServer. !stdHttpSimpleServer.listen(.(请求队列大小) = 监听构造函数绑定的 IP 端口,成功返回 true 。\n已自动调用此函数。 !stdHttpSimpleServer.close() = 关闭 HTTP 服务端 !stdHttpSimpleServer.getLocalIp() = 返回当前绑定的IP,端口号 !stdHttpSimpleServer.getUrl() = 返回首页URL\n如果参数@1为true,IP "0.0.0.0"替换为上网卡IP而不是localhost !stdHttpSimpleServer._serverAddress = 服务端监听地址\n!sockaddr_in. !stdHttpSimpleServer.run( httpProc ) = @.run( \n function(response,request,session){ \n response.loadcode( request.path __/*可省略,可增加多个模板参数,\n在被调用文件的函数外部可使用owner参数获取首个模板参数,\n也可以使用...获取多个模板参数*/ );\n } \n); !stdHttpSimpleServer.documentRoot = 网站应用程序根目录,默认为"/",\n在启动服务端之前设置才会生效,\n\n只能设置为硬盘上实际存在的目录,\n改变此目录,会同时改变\n服务端代码中的应用程序根目录以及用户库目录,\n如果只是相将所有请求路径转向某个目录,应当改用 documentBase 属性\n如果网站在嵌入资源目录中,应当改用 documentBase 属性 !stdHttpSimpleServer.documentBase = 网站根目录,\n在启动服务端之前设置才会生效,\n不会修改应用程序根目录,支持硬盘目录与资源目录,\n这个属性应当设置为应用程序根目录下的相对路径,\n例如 "/res/web/"\n\n注意: request.path 前面包含 documentBase 目录。\n而 request.pathInfo 忽略 documentBase 目录 !stdHttpSimpleServer.defalutDocument = 默认文档,默认为"main.aardio",\n在启动服务端之前设置才会生效,\n\n如果访问硬盘上存在的目录,request.path 尾部不加斜杠会自动跳转到以斜杠结束的路径\n\n如果访问嵌入资源目录,只有 request.path 以斜杠才会访问默认文档,\n对于访问嵌入资源文件,建议指定完整的文件路径,\n默认文档主要是用于硬盘上的网站 !stdHttpSimpleServer.onThreadCreated() = @.onThreadCreated = function(documentRoot,urlRoot){ __/*一个HTTP服务监听线程准备就绪时触发此事件*/ } !stdHttpSimpleServer.beforeClose() = @.beforeClose = function(){ __/*服务器关闭前调用此函数*/ } !stdHttpSimpleServer.threadGlobal = @.threadGlobal = { __/*在onThreadCreated事件触发前\n添加HTTP服务监听线程的全局变量*/ } !stdHttpSimpleServer.threadNum = 设置服务器线程数,默认为2个线程 !stdHttpSimpleServer.customErrors[404] = @.customErrors[404] = function(response){ response.status = "404 Not Found"; response.write("404 Not Found"); __/*自定义错误页处理函数,\n注意这是线程函数,应遵守多线程规则,\n也可以直接指定错误页路径*/ } end intellisense**/ /**intellisense(!stdHttpSimpleServerMain) documentRoot = 网站应用程序根目录,默认为"/",\n只能设置为硬盘上实际存在的目录,\n改变此目录,会同时改变\n服务端代码中的应用程序根目录以及用户库目录,\n如果只是相将所有请求路径转向某个目录,应当改用 documentBase 属性\n如果网站在嵌入资源目录中,应当改用 documentBase 属性 documentBase = 网站根目录,\n不会修改应用程序根目录,支持硬盘目录与资源目录,\n这个属性应当设置为应用程序根目录下的相对路径,\n例如 "/res/web/",\n\n注意: request.path 前面包含 documentBase 目录。\n而 request.pathInfo 忽略 documentBase 目录 defalutDocument = 默认文档,默认为"main.aardio",\n\n如果访问硬盘上存在的目录,request.path 尾部不加斜杠会自动跳转到以斜杠结束的路径\n\n如果访问嵌入资源目录,只有 request.path 以斜杠才会访问默认文档,\n对于访问嵌入资源文件,建议指定完整的文件路径,\n默认文档主要是用于硬盘上的网站 onThreadCreated() = @.onThreadCreated = function(documentRoot,urlRoot){ __/*一个HTTP服务监听线程准备就绪时触发此事件*/ } threadGlobal = @.threadGlobal = { __/*在onThreadCreated事件触发前\n添加HTTP服务监听线程的全局变量*/ } threadNum = 设置服务器线程数,默认为2个线程 startPort = 设置服务器端口,默认自动分配空闲端口 customErrors = @.customErrors = {\n [404] = function(response){\n response.status = "404 Not Found";\n response.write("404 Not Found"); __/*自定义错误页*/\n } \n} getLocalIp() = 返回服务端IP,端口 getUrl(__) = 返回HTTP服务端访问网址,可选指定目录或文件路径\n注意参数第一个字符不需要指定斜杠\n如果参数@2为true,IP "0.0.0.0"替换为合适的内网IP而不是localhost \n如果服务器启动失败不返回任何值 startSpaUrl( = 返回 SPA 单页应用首页网址 startSpaUrl(.(indexHtmlPath,documentBase) = 参数指定 SPA 单页应用首页路径,\n404错误页也会自动设置到该路径,\n返回首页网址\n\n可选用参数 @documentBase 指定根目录以避免网页不支持非根目录路径 stop() = 停止HTTP服务端 start(.(IP,端口,请求队列大小) = 启动HTTP服务端 serverId = 当前服务端唯一ID,字符串,\n每次调用start函数都会改变这个属性的值 end intellisense**/