• aardio web服务器程序模版代码 补充注释






    import fonts.fontAwesome;
    import win.ui;
    /*DSG{{*/
    var winform = win.form(text="asynHttpServer - 异步HTTP服务器";right=807;bottom=465;bgcolor=16777215;border="none";max=false)
    winform.add(
    bk={cls="bk";left=-2;top=-5;right=810;bottom=29;bgcolor=12639424;z=9};
    bkplus={cls="bkplus";text="asynHttpServer - 扫码传文件";left=18;top=4;right=203;bottom=26;align="left";color=5921370;z=10};
    btnOpen={cls="plus";text='\uF115';left=444;top=50;right=479;bottom=75;dr=1;dt=1;font=LOGFONT(h=-16;name='FontAwesome');notify=1;z=5};
    btnOpenUpload={cls="plus";text="打开上传目录";left=568;top=429;right=709;bottom=458;dr=1;dt=1;font=LOGFONT(h=-15);iconStyle={align="left";font=LOGFONT(h=-15;name='FontAwesome');padding={left=8;top=2}};iconText='\uF115';notify=1;textPadding={left=20};z=11};
    btnStart={cls="plus";text="重启服务";left=655;top=47;right=755;bottom=76;bgcolor=14935259;dr=1;dt=1;font=LOGFONT(h=-15);iconStyle={align="left";font=LOGFONT(h=-15;name='FontAwesome');padding={left=8;top=2}};iconText='\uF233';notify=1;textPadding={left=20};z=4};
    editDocumentRoot={cls="plus";left=131;top=49;right=430;bottom=73;align="right";border={bottom=1;color=-8355712};dl=1;dr=1;dt=1;editable=1;font=LOGFONT(h=-16);z=7};
    editPassword={cls="plus";left=441;top=84;right=632;bottom=108;align="left";border={bottom=1;color=-8355712};dl=1;dr=1;dt=1;editable=1;font=LOGFONT(h=-16);z=13};
    editPort={cls="plus";left=550;top=49;right=628;bottom=73;align="left";border={bottom=1;color=-8355712};dl=1;dr=1;dt=1;editable=1;font=LOGFONT(h=-16);z=8};
    plus={cls="plus";text="访问密码:";left=332;top=90;right=433;bottom=114;align="right";dr=1;dt=1;font=LOGFONT(h=-15);transparent=1;z=12};
    qr={cls="plus";left=499;top=132;right=760;bottom=418;db=1;dr=1;dt=1;repeat="scale";z=6};
    static={cls="plus";text="端口:";left=484;top=52;right=547;bottom=76;align="right";dr=1;dt=1;font=LOGFONT(h=-15);transparent=1;z=2};
    static2={cls="plus";text="网站根目录:";left=15;top=52;right=129;bottom=76;align="right";dl=1;dt=1;font=LOGFONT(h=-15);transparent=1;z=3};
    txtMessage={cls="richedit";left=42;top=132;right=469;bottom=418;autohscroll=false;db=1;dl=1;dr=1;dt=1;link=1;multiline=1;vscroll=1;z=1}
    )
    /*}}*/
    
    winform.btnStart.skin( {
        background={
            default=0x668FB2B0;
            hover=0xFF928BB3;
            disabled=0xFFCCCCCC; 
        }
    })
    
    winform.btnOpen.skin( {
        background={
            default=0;
            hover=0xFF928BB3;
            disabled=0xFFCCCCCC; 
        }
    })
    
    winform.btnOpenUpload.skin( {
        background={
            default=0;
            hover=0xFF928BB3;
            disabled=0xFFCCCCCC; 
        }
    })
    
    //从配置文件中读取文本信息标题,如果有写入文本信息框,如果没有获取系统“我的文档”文件夹路径并写入文本信息框
    //在这里,作者没有定义一个专用的变量,而是直接使用文本信息框的属性进行临时的文本存储,这种方式在后面反复出现
    //这种方式可能是为了节省资源,但造成可读性差。并且,只有在顺序执行的过程中才可以用。
    import fsys.config;
    config = fsys.config("/config/"); 
    if( io.exist(config.winform.txtMessage) ){
        winform.txtMessage.text = config.winform.txtMessage;
    }
    else {
        //获取系统“我的文档”路径,通过ide智能提示,还可以获得诸如系统文件夹、桌面、appdata等特殊文件夹的路径
        winform.txtMessage.text = io.getSpecial(0x5/*_CSIDL_MYDOCUMENTS*/)
        
    }
    
    //请注意此处引入的直接是WebSocket server,不是winsocket,这是一个websocketserver实现,同时也实现了http服务,对于单线程的个人应用非常适用
    import web.socket.server;
    var wsrv = web.socket.server();
    //单独将http服务器提出来,方便操作
    var srvHttp = wsrv.httpServer;
    //将前面提取的系统“我的文档”路径设置为http根目录
    srvHttp.documentRoot = winform.txtMessage.text;
    //生成一个随机字符串作为用户访问令牌
    srvHttp.userToken = string.random(18);
    //将令牌字符串显示在界面的文本框中
    winform.editPassword.text = srvHttp.userToken;
    
    //此部分提取指定文件图标用于网页显示。
    import fsys.info; 
    import console;
    var cacheSysIcons = {}
    var getSysIconIndex = function(path){ 
        //获取指定文件的图标信息,如果不存在则返回。
        //文件的图标分为两种,一种是自身自带了图标文件,比如exe文件,另一种是系统针对扩展名指定的图标,如doc,所以参数给了两个
        //返回类型为fsys.info.shFileInfoObject 
        var sfi = fsys.info.get(path, 0x100/*_SHGFI_ICON*/ | 0x4000/*_SHGFI_SYSICONINDEX*/);
        if( !(sfi.returnValue ) ) {
            return; 
        }
        //如果图标属性不存在于缓存变量中,则将图标导出为png,保存到缓存变量中
        if(!cacheSysIcons[sfi.iIcon]){
            var dataUrl;
            var bmp = ..gdip.bitmap(sfi.hIcon,1/*_IMAGE_ICON*/);        
                if(bmp){
                cacheSysIcons[sfi.iIcon] = bmp.saveToBuffer(".png"); 
                bmp.dispose();
            }
        }
        //如果存在句柄,必须手动释放,具体参考fsys.info
        if(sfi.hIcon)::DestroyIcon(sfi.hIcon);    
        return sfi.iIcon;
    }
    //客户端ip地址缓存
    var cacheClientIps = {}
    //过程运行服务器,并指定http的路由响应过程
    srvHttp.run( 
        function(response,request,session){ 
             //判断是否有token,同时支持会话和get参数
            var token = request.get["t"] : session["token"];
            //存在token且不一致,则返回一个未授权的错误给http客户端
            if( #srvHttp.userToken && (token != srvHttp.userToken) ){
                winform.txtMessage.printf("客户端:%s 连接被拒绝",request.remoteAddr);    
                response.errorStatus(401)
                return;
            }
            //将token存入会话变量,由于会话本身具有时长限制,长时间没有交互的客户端,会话变量会失效,需要重新带get参数访问才能长期有效
            session["token"] = token;
            //如果是新客户端,即ip不在缓存里诶包中,则显示客户端连接信息,并将ip加入缓存列表
            if(!cacheClientIps[request.remoteAddr]){
                winform.txtMessage.printf("客户端:%s 已连接",request.remoteAddr);    
                cacheClientIps[request.remoteAddr] = true;
            }
            //设置访问头,不进行限制
            response.headers["Access-Control-Allow-Origin"] = "*";
            response.headers["Access-Control-Allow-Headers"] = "*"
            //响应文件图标请求,请求参数icon为图标列表索引,即前述函数getSysIconIndex的返回值,shFileInfoObject.iIcon 
            //由于在生成文件列表的时候,已经将文件中的图标缓存到cacheSysIcons表变量中,此时直接进行提取即可
            //如果不存在则返回404
            if(request.path=="/main.aardio" && request.get["icon"]){
                var iconIdx = tonumber(request.get["icon"]);
                if(iconIdx!==null){
                    if(cacheSysIcons[iconIdx]){
                        response.contentType = "image/png";
                        response.write(cacheSysIcons[iconIdx])
                        return;
                    }
                }
                response.errorStatus(404);
                return;
            }
            //响应对上传文件夹的操作
            if(request.path=="/upload/main.aardio"){
            //响应删除已经上传到upload文件夹中的文件的请求
            //这个请求是由客户端引用的filepond脚本库实现并发起的,采用post方法提交要删除的文件
                if(request.method=="DELETE"){
                    var path = request.postData();
                    if(path && string.startWith(path,"/upload/")){
                        path = ..io.joinpath(srvHttp.documentRoot,path)
                        
                        if(io.exist(path)){
                            io.remove(path);
                            winform.txtMessage.print("已删除:" + path);
                            response.close();
                            return;    
                        }
                    }
                    
                    response.errorStatus(404);
                    return;
                }
            //响应上传文件的操作,参见fastcig.client.request.postFileData() 
            //返回的结果是fsys.multipartFormData类型
                fileData = request.postFileData()
                //如果存在提交文件数据,则创建文件夹并获取文件名并保存到上传文件夹中
                if(fileData){
                    io.createDir(..io.joinpath(srvHttp.documentRoot,"upload"))
                    winform.txtMessage.print(..io.joinpath(srvHttp.documentRoot,"upload"))
                    
                    var fileName = ..io.joinpath(srvHttp.documentRoot,"upload",fileData.filepond.filename) 
                    var ok,err = fileData.filepond.save(fileName); 
                    if(!ok){ response.error(err); }
                    //向ui输出信息
                    winform.txtMessage.text = 'http服务端已启动: \n'; 
                    winform.txtMessage.print( srvHttp.getUrl(,true) + "/?t=" + srvHttp.userToken  );
        
                    winform.txtMessage.print( "" );     
                    winform.txtMessage.print( "上传成功:" + fileName );     
                    //向客户端返回上传的文件名            
                    response.contentType = "text/plain";
                    response.write("/upload/",fileData.filepond.filename)
                    return response.close() 
                }         
            }
            winform.txtMessage.print( request.path  );
            //如果请求文件不是文件夹则返回文件指定结果
            if(!fsys.isDir(request.path) ) {
                //此判断稍微复杂一些,要求请求的文件存在,且(不是ide环境 或者 请求的不是根目录的主程序)
                //也就是说:
                //1,首先文件要存在
                //2,不能请求的是主程序,这种情况只能存在于ide环境下
                //3,如果不是ide环境下,则没有请求当前主程序的情况,所以执行主目录下的main.aardio是可能的
                //默认情况下,arrdio的http服务器的默认文档是main.aardio,类似于其他web服务器的index.html
                //或者iis的default.html,查找顺序原因,需要做个判断
                //默认是可以执行aardio的cgi程序或者页面模板的,如果不是则直接返回文件本身。但对于直接下载大
                //文件来说,loadcode可能不是一个好的选择。
                
                if( ..io.exist(request.path) 
                    && (!_STUDIO_INVOKED || request.path!="/main.aardio") )
                    return response.loadcode(request.path)
                else {
                    request.path = fsys.getParentDir(request.path)
                }
            } 
            //如果请求的是文件夹的情况下,则返回下列网页模版,输出文件清单
            response.write(`
    <!doctype html>
    <html>
    <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <title>asynHttpServer - 扫码快传</title>
    <link href="https://lib.baomitu.com/filepond/4.28.2/filepond.min.css" rel="stylesheet">
    <script src="https://lib.baomitu.com/filepond/4.28.2/filepond.min.js"></script> 
    <script>
    (function (doc, win) {
        var docEl = doc.documentElement, 
            recalc = function () {
                var clientWidth = docEl.clientWidth;
                if (!clientWidth) return;
                
                clientWidth=(clientWidth>640)?640:clientWidth;
                docEl.style.fontSize = ( (docEl.clientWidth>docEl.clientHeight) ? 12 : 20) * (clientWidth / 320) + 'px';
            };
            
        if (!doc.addEventListener) return;
        //响应窗体尺寸代码
        win.addEventListener('orientationchange' in window ? 'orientationchange' : 'resize', recalc, false);
        doc.addEventListener('DOMContentLoaded', recalc, false);
    })(document, window);
    </script>
    <style>
    html {
        padding:20px 0 0;
    }
    li{ list-style-type:none; }
    </style>
    </head>
    <body> 
    
    <input type="file" class="filepond" name="filepond" multiple>
    <script crossorigin="anonymous"> 
    if(document.body.style.order === undefined){
        alert("浏览器版本过低,请使用Chrome或IE11以上版本浏览器打开此页面!")
    }
    //初始化filepond组件
    var inputElement = document.querySelector('input[type="file"]');
    FilePond.create(inputElement);
    FilePond.setOptions({
        server: '/upload/?t=` + srvHttp.userToken + `',
        labelIdle: '拖放需要上传的文件到这里或者 <span class="filepond--label-action"> 浏览文件 </span>',
        labelFileProcessing: '上传中...',
        labelFileProcessingComplete: '上传成功'
    });
    //连接websocket服务器
    websocket = new WebSocket("`+wsrv.getUrl("ws",true)+`");
    //响应websocket消息更新页面,当服务器根目录被修改或者重启服务器时,服务器会发送reload消息
    websocket.onmessage = function(evt) {
        if(evt.data=="reload"){
            window.location.pathname = "/";
            window.location.reload(true)
        }
    };   
    </script> 
    
    <h2>当前目录:`
                ,request.path,`</h2><hr><ul>`)
            //上传文件夹如果存在置于第一个,并且单独替换名字
            if(request.path=="/" && ..io.exist("/upload/")){
                response.write('<li><img src="/?icon='++getSysIconIndex("/upload/")+'"><a href="/upload?t=' + srvHttp.userToken + '">上传目录</a><br>\r\n');    
            }
            //获取服务器根目录下的所有文件和文件夹
            var file,dir = fsys.list(request.path,,"*.*");
            //遍历文件夹,并排除上传文件夹,输出文件夹图标、路径,形成链接
            for(i=1;#dir;1){
                if(dir[i]==="upload" && request.path=="/") continue;
                
                var iconIdex = getSysIconIndex(dir[dir[ i ]])
                response.write('<li><img src="/?icon='++(iconIdex)+'"><a href="'
                    ,inet.url.append(request.path,dir[ i ])
                    ,'?t=' + srvHttp.userToken + '">',dir[ i ],'</a><br>\r\n');
            }
            //遍历文件,输出文件夹图标、路径,形成链接
            for(i=1;#file;1){
                var iconIdex = getSysIconIndex(file[file[ i ]])
                response.write('<li><img src="/?icon='++(iconIdex)+'"><a href="'
                        ,inet.url.append(request.path,file[ i ])
                        ,'?t=' + srvHttp.userToken + '">',file[ i ],'</a><br>\r\n');
            }
            
            response.write("</ul></body></html>")
        }     
    );//http服务器响应结束
    //根据服务器返回的信息,生成二维码和访问链接信息
    import qrencode.bitmap;
    var serverInfo = function(){
        var ip,port = srvHttp.getLocalIp();
        winform.editPort.text = port;
        winform.editDocumentRoot.text = io.fullpath(srvHttp.documentRoot)
    
        var url = srvHttp.getUrl(,true);
        if(#srvHttp.userToken){
            url = url + "/?t=" + srvHttp.userToken;
        } 
        
        winform.txtMessage.text = 'http服务端已启动: \n'; 
        winform.txtMessage.print(  url );
        //生成二维码
        var qrBmp = qrencode.bitmap( url );
        winform.qr.setBackground(qrBmp.copyBitmap(winform.qr.width)); 
            
        winform.txtMessage.print( 
            "手机扫码可自动打开此网页,可以方便地上传下载文件。
    拖动文件或目录到窗口上客户端网页会自动刷新。
    
    asynHttpServer 体积很小可嵌入任何 aardio 程序,
    asynHttpServer 可以创建单线程异步模式的 HTTP 服务端,并可以同时创建 WebSocket 服务端(与HTTP服务端共享端口)。asynHttpServer 支持保持连接(Keep Alive),分块传输协议,支持断点续传,支持304缓存,支持文件表单上传,支持使用aardio编写的网站( 接口可兼容IIS/FastCGI下)。
    "
        );     
    }
    //调用上面的函数
    serverInfo();
    //重启服务器
    winform.btnStart.oncommand = function(id,event){
        winform.txtMessage.text = "";
        winform.btnStart.disabledText = {'\uF254';'\uF251';'\uF252';'\uF253';'\uF250'}
        win.delay(500);
     
        var port = tonumber(winform.editPort.text);
        srvHttp.documentRoot = fsys.isDir(winform.editDocumentRoot.text) ? winform.editDocumentRoot.text;
        srvHttp.userToken = winform.editPassword.text;
        srvHttp.start("0.0.0.0",port);
        serverInfo();
        
        winform.btnStart.disabledText = null;
    }
    //启用右键菜单、响应链接点击事件
    import win.ui.menu
    winform.txtMessage.enablePopMenu()
    winform.txtMessage.onlink=function(message,href){
        if( message = 0x202/*_WM_LBUTTONUP*/ ) {
            import process;
            process.openUrl(href);
        }
    }
    
    //处理文件拖拽事件,将拖拽文件或者文件夹作为服务器根目录并刷新页面
    winform.onDropFiles = function(files){
        var path = files[1]
        //如果不是文件夹则打开文件所在文件夹
        if(!fsys.isDir(path)){
            path = fsys.getParentDir(path)
        }
        winform.editDocumentRoot.text = path;
        srvHttp.documentRoot = path;
        config.winform.txtMessage = path;
        config.winform.save();
        
        wsrv.publish("reload");
    }
    
    import fsys.dlg.dir;
    //处理设置主目录控件,打开目录打开对话框,处理内容同上
    winform.btnOpen.oncommand = function(id,event){
        var dir = fsys.dlg.dir(winform.editDocumentRoot.text,winform)
        if(dir){
            winform.editDocumentRoot.text = dir;
            srvHttp.documentRoot = dir;
            
            config.winform.txtMessage = dir;
            config.winform.save();
            wsrv.publish("reload");
        }
    }
    
    import process;
    //处理打开上传文件夹控件事件,建立并使用默认文件管理程序打开主目录下的upload目录
    winform.btnOpenUpload.oncommand = function(id,event){
        var path = io.joinpath(winform.editDocumentRoot.text,"upload")
        if(io.createDir(path)){
            process.explore(path)
        }
    }
    //使用简单窗体
    import win.ui.simpleWindow2;
    win.ui.simpleWindow2(winform);
    winform.show(); 
    //设置配置文件夹为隐藏状态
    import fsys;
    fsys.attrib("/config/",,2/*_FILE_ATTRIBUTE_HIDDEN*/)
    
    win.loopMessage();
    
    
    
      1 import fonts.fontAwesome;
      2 import win.ui;
      3 /*DSG{{*/
      4 var winform = win.form(text="asynHttpServer - 异步HTTP服务器";right=807;bottom=465;bgcolor=16777215;border="none";max=false)
      5 winform.add(
      6 bk={cls="bk";left=-2;top=-5;right=810;bottom=29;bgcolor=12639424;z=9};
      7 bkplus={cls="bkplus";text="asynHttpServer - 扫码传文件";left=18;top=4;right=203;bottom=26;align="left";color=5921370;z=10};
      8 btnOpen={cls="plus";text='\uF115';left=444;top=50;right=479;bottom=75;dr=1;dt=1;font=LOGFONT(h=-16;name='FontAwesome');notify=1;z=5};
      9 btnOpenUpload={cls="plus";text="打开上传目录";left=568;top=429;right=709;bottom=458;dr=1;dt=1;font=LOGFONT(h=-15);iconStyle={align="left";font=LOGFONT(h=-15;name='FontAwesome');padding={left=8;top=2}};iconText='\uF115';notify=1;textPadding={left=20};z=11};
     10 btnStart={cls="plus";text="重启服务";left=655;top=47;right=755;bottom=76;bgcolor=14935259;dr=1;dt=1;font=LOGFONT(h=-15);iconStyle={align="left";font=LOGFONT(h=-15;name='FontAwesome');padding={left=8;top=2}};iconText='\uF233';notify=1;textPadding={left=20};z=4};
     11 editDocumentRoot={cls="plus";left=131;top=49;right=430;bottom=73;align="right";border={bottom=1;color=-8355712};dl=1;dr=1;dt=1;editable=1;font=LOGFONT(h=-16);z=7};
     12 editPassword={cls="plus";left=441;top=84;right=632;bottom=108;align="left";border={bottom=1;color=-8355712};dl=1;dr=1;dt=1;editable=1;font=LOGFONT(h=-16);z=13};
     13 editPort={cls="plus";left=550;top=49;right=628;bottom=73;align="left";border={bottom=1;color=-8355712};dl=1;dr=1;dt=1;editable=1;font=LOGFONT(h=-16);z=8};
     14 plus={cls="plus";text="访问密码:";left=332;top=90;right=433;bottom=114;align="right";dr=1;dt=1;font=LOGFONT(h=-15);transparent=1;z=12};
     15 qr={cls="plus";left=499;top=132;right=760;bottom=418;db=1;dr=1;dt=1;repeat="scale";z=6};
     16 static={cls="plus";text="端口:";left=484;top=52;right=547;bottom=76;align="right";dr=1;dt=1;font=LOGFONT(h=-15);transparent=1;z=2};
     17 static2={cls="plus";text="网站根目录:";left=15;top=52;right=129;bottom=76;align="right";dl=1;dt=1;font=LOGFONT(h=-15);transparent=1;z=3};
     18 txtMessage={cls="richedit";left=42;top=132;right=469;bottom=418;autohscroll=false;db=1;dl=1;dr=1;dt=1;link=1;multiline=1;vscroll=1;z=1}
     19 )
     20 /*}}*/
     21 
     22 winform.btnStart.skin( {
     23     background={
     24         default=0x668FB2B0;
     25         hover=0xFF928BB3;
     26         disabled=0xFFCCCCCC; 
     27     }
     28 })
     29 
     30 winform.btnOpen.skin( {
     31     background={
     32         default=0;
     33         hover=0xFF928BB3;
     34         disabled=0xFFCCCCCC; 
     35     }
     36 })
     37 
     38 winform.btnOpenUpload.skin( {
     39     background={
     40         default=0;
     41         hover=0xFF928BB3;
     42         disabled=0xFFCCCCCC; 
     43     }
     44 })
     45 
     46 //从配置文件中读取文本信息标题,如果有写入文本信息框,如果没有获取系统“我的文档”文件夹路径并写入文本信息框
     47 //在这里,作者没有定义一个专用的变量,而是直接使用文本信息框的属性进行临时的文本存储,这种方式在后面反复出现
     48 //这种方式可能是为了节省资源,但造成可读性差。并且,只有在顺序执行的过程中才可以用。
     49 import fsys.config;
     50 config = fsys.config("/config/"); 
     51 if( io.exist(config.winform.txtMessage) ){
     52     winform.txtMessage.text = config.winform.txtMessage;
     53 }
     54 else {
     55     //获取系统“我的文档”路径,通过ide智能提示,还可以获得诸如系统文件夹、桌面、appdata等特殊文件夹的路径
     56     winform.txtMessage.text = io.getSpecial(0x5/*_CSIDL_MYDOCUMENTS*/)
     57     
     58 }
     59 
     60 //请注意此处引入的直接是WebSocket server,不是winsocket,这是一个websocketserver实现,同时也实现了http服务,对于单线程的个人应用非常适用
     61 import web.socket.server;
     62 var wsrv = web.socket.server();
     63 //单独将http服务器提出来,方便操作
     64 var srvHttp = wsrv.httpServer;
     65 //将前面提取的系统“我的文档”路径设置为http根目录
     66 srvHttp.documentRoot = winform.txtMessage.text;
     67 //生成一个随机字符串作为用户访问令牌
     68 srvHttp.userToken = string.random(18);
     69 //将令牌字符串显示在界面的文本框中
     70 winform.editPassword.text = srvHttp.userToken;
     71 
     72 //此部分提取指定文件图标用于网页显示。
     73 import fsys.info; 
     74 import console;
     75 var cacheSysIcons = {}
     76 var getSysIconIndex = function(path){ 
     77     //获取指定文件的图标信息,如果不存在则返回。
     78     //文件的图标分为两种,一种是自身自带了图标文件,比如exe文件,另一种是系统针对扩展名指定的图标,如doc,所以参数给了两个
     79     //返回类型为fsys.info.shFileInfoObject 
     80     var sfi = fsys.info.get(path, 0x100/*_SHGFI_ICON*/ | 0x4000/*_SHGFI_SYSICONINDEX*/);
     81     if( !(sfi.returnValue ) ) {
     82         return; 
     83     }
     84     //如果图标属性不存在于缓存变量中,则将图标导出为png,保存到缓存变量中
     85     if(!cacheSysIcons[sfi.iIcon]){
     86         var dataUrl;
     87         var bmp = ..gdip.bitmap(sfi.hIcon,1/*_IMAGE_ICON*/);        
     88             if(bmp){
     89             cacheSysIcons[sfi.iIcon] = bmp.saveToBuffer(".png"); 
     90             bmp.dispose();
     91         }
     92     }
     93     //如果存在句柄,必须手动释放,具体参考fsys.info
     94     if(sfi.hIcon)::DestroyIcon(sfi.hIcon);    
     95     return sfi.iIcon;
     96 }
     97 //客户端ip地址缓存
     98 var cacheClientIps = {}
     99 //过程运行服务器,并指定http的路由响应过程
    100 srvHttp.run( 
    101     function(response,request,session){ 
    102          //判断是否有token,同时支持会话和get参数
    103         var token = request.get["t"] : session["token"];
    104         //存在token且不一致,则返回一个未授权的错误给http客户端
    105         if( #srvHttp.userToken && (token != srvHttp.userToken) ){
    106             winform.txtMessage.printf("客户端:%s 连接被拒绝",request.remoteAddr);    
    107             response.errorStatus(401)
    108             return;
    109         }
    110         //将token存入会话变量,由于会话本身具有时长限制,长时间没有交互的客户端,会话变量会失效,需要重新带get参数访问才能长期有效
    111         session["token"] = token;
    112         //如果是新客户端,即ip不在缓存里诶包中,则显示客户端连接信息,并将ip加入缓存列表
    113         if(!cacheClientIps[request.remoteAddr]){
    114             winform.txtMessage.printf("客户端:%s 已连接",request.remoteAddr);    
    115             cacheClientIps[request.remoteAddr] = true;
    116         }
    117         //设置访问头,不进行限制
    118         response.headers["Access-Control-Allow-Origin"] = "*";
    119         response.headers["Access-Control-Allow-Headers"] = "*"
    120         //响应文件图标请求,请求参数icon为图标列表索引,即前述函数getSysIconIndex的返回值,shFileInfoObject.iIcon 
    121         //由于在生成文件列表的时候,已经将文件中的图标缓存到cacheSysIcons表变量中,此时直接进行提取即可
    122         //如果不存在则返回404
    123         if(request.path=="/main.aardio" && request.get["icon"]){
    124             var iconIdx = tonumber(request.get["icon"]);
    125             if(iconIdx!==null){
    126                 if(cacheSysIcons[iconIdx]){
    127                     response.contentType = "image/png";
    128                     response.write(cacheSysIcons[iconIdx])
    129                     return;
    130                 }
    131             }
    132             response.errorStatus(404);
    133             return;
    134         }
    135         //响应对上传文件夹的操作
    136         if(request.path=="/upload/main.aardio"){
    137         //响应删除已经上传到upload文件夹中的文件的请求
    138         //这个请求是由客户端引用的filepond脚本库实现并发起的,采用post方法提交要删除的文件
    139             if(request.method=="DELETE"){
    140                 var path = request.postData();
    141                 if(path && string.startWith(path,"/upload/")){
    142                     path = ..io.joinpath(srvHttp.documentRoot,path)
    143                     
    144                     if(io.exist(path)){
    145                         io.remove(path);
    146                         winform.txtMessage.print("已删除:" + path);
    147                         response.close();
    148                         return;    
    149                     }
    150                 }
    151                 
    152                 response.errorStatus(404);
    153                 return;
    154             }
    155         //响应上传文件的操作,参见fastcig.client.request.postFileData() 
    156         //返回的结果是fsys.multipartFormData类型
    157             fileData = request.postFileData()
    158             //如果存在提交文件数据,则创建文件夹并获取文件名并保存到上传文件夹中
    159             if(fileData){
    160                 io.createDir(..io.joinpath(srvHttp.documentRoot,"upload"))
    161                 winform.txtMessage.print(..io.joinpath(srvHttp.documentRoot,"upload"))
    162                 
    163                 var fileName = ..io.joinpath(srvHttp.documentRoot,"upload",fileData.filepond.filename) 
    164                 var ok,err = fileData.filepond.save(fileName); 
    165                 if(!ok){ response.error(err); }
    166                 //向ui输出信息
    167                 winform.txtMessage.text = 'http服务端已启动: \n'; 
    168                 winform.txtMessage.print( srvHttp.getUrl(,true) + "/?t=" + srvHttp.userToken  );
    169     
    170                 winform.txtMessage.print( "" );     
    171                 winform.txtMessage.print( "上传成功:" + fileName );     
    172                 //向客户端返回上传的文件名            
    173                 response.contentType = "text/plain";
    174                 response.write("/upload/",fileData.filepond.filename)
    175                 return response.close() 
    176             }         
    177         }
    178         winform.txtMessage.print( request.path  );
    179         //如果请求文件不是文件夹则返回文件指定结果
    180         if(!fsys.isDir(request.path) ) {
    181             //此判断稍微复杂一些,要求请求的文件存在,且(不是ide环境 或者 请求的不是根目录的主程序)
    182             //也就是说:
    183             //1,首先文件要存在
    184             //2,不能请求的是主程序,这种情况只能存在于ide环境下
    185             //3,如果不是ide环境下,则没有请求当前主程序的情况,所以执行主目录下的main.aardio是可能的
    186             //默认情况下,arrdio的http服务器的默认文档是main.aardio,类似于其他web服务器的index.html
    187             //或者iis的default.html,查找顺序原因,需要做个判断
    188             //默认是可以执行aardio的cgi程序或者页面模板的,如果不是则直接返回文件本身。但对于直接下载大
    189             //文件来说,loadcode可能不是一个好的选择。
    190             
    191             if( ..io.exist(request.path) 
    192                 && (!_STUDIO_INVOKED || request.path!="/main.aardio") )
    193                 return response.loadcode(request.path)
    194             else {
    195                 request.path = fsys.getParentDir(request.path)
    196             }
    197         } 
    198         //如果请求的是文件夹的情况下,则返回下列网页模版,输出文件清单
    199         response.write(`
    200 <!doctype html>
    201 <html>
    202 <head>
    203 <meta charset="utf-8">
    204 <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    205 <title>asynHttpServer - 扫码快传</title>
    206 <link href="https://lib.baomitu.com/filepond/4.28.2/filepond.min.css" rel="stylesheet">
    207 <script src="https://lib.baomitu.com/filepond/4.28.2/filepond.min.js"></script> 
    208 <script>
    209 (function (doc, win) {
    210     var docEl = doc.documentElement, 
    211         recalc = function () {
    212             var clientWidth = docEl.clientWidth;
    213             if (!clientWidth) return;
    214             
    215             clientWidth=(clientWidth>640)?640:clientWidth;
    216             docEl.style.fontSize = ( (docEl.clientWidth>docEl.clientHeight) ? 12 : 20) * (clientWidth / 320) + 'px';
    217         };
    218         
    219     if (!doc.addEventListener) return;
    220     //响应窗体尺寸代码
    221     win.addEventListener('orientationchange' in window ? 'orientationchange' : 'resize', recalc, false);
    222     doc.addEventListener('DOMContentLoaded', recalc, false);
    223 })(document, window);
    224 </script>
    225 <style>
    226 html {
    227     padding:20px 0 0;
    228 }
    229 li{ list-style-type:none; }
    230 </style>
    231 </head>
    232 <body> 
    233 
    234 <input type="file" class="filepond" name="filepond" multiple>
    235 <script crossorigin="anonymous"> 
    236 if(document.body.style.order === undefined){
    237     alert("浏览器版本过低,请使用Chrome或IE11以上版本浏览器打开此页面!")
    238 }
    239 //初始化filepond组件
    240 var inputElement = document.querySelector('input[type="file"]');
    241 FilePond.create(inputElement);
    242 FilePond.setOptions({
    243     server: '/upload/?t=` + srvHttp.userToken + `',
    244     labelIdle: '拖放需要上传的文件到这里或者 <span class="filepond--label-action"> 浏览文件 </span>',
    245     labelFileProcessing: '上传中...',
    246     labelFileProcessingComplete: '上传成功'
    247 });
    248 //连接websocket服务器
    249 websocket = new WebSocket("`+wsrv.getUrl("ws",true)+`");
    250 //响应websocket消息更新页面,当服务器根目录被修改或者重启服务器时,服务器会发送reload消息
    251 websocket.onmessage = function(evt) {
    252     if(evt.data=="reload"){
    253         window.location.pathname = "/";
    254         window.location.reload(true)
    255     }
    256 };   
    257 </script> 
    258 
    259 <h2>当前目录:`
    260             ,request.path,`</h2><hr><ul>`)
    261         //上传文件夹如果存在置于第一个,并且单独替换名字
    262         if(request.path=="/" && ..io.exist("/upload/")){
    263             response.write('<li><img src="/?icon='++getSysIconIndex("/upload/")+'"><a href="/upload?t=' + srvHttp.userToken + '">上传目录</a><br>\r\n');    
    264         }
    265         //获取服务器根目录下的所有文件和文件夹
    266         var file,dir = fsys.list(request.path,,"*.*");
    267         //遍历文件夹,并排除上传文件夹,输出文件夹图标、路径,形成链接
    268         for(i=1;#dir;1){
    269             if(dir[i]==="upload" && request.path=="/") continue;
    270             
    271             var iconIdex = getSysIconIndex(dir[dir[ i ]])
    272             response.write('<li><img src="/?icon='++(iconIdex)+'"><a href="'
    273                 ,inet.url.append(request.path,dir[ i ])
    274                 ,'?t=' + srvHttp.userToken + '">',dir[ i ],'</a><br>\r\n');
    275         }
    276         //遍历文件,输出文件夹图标、路径,形成链接
    277         for(i=1;#file;1){
    278             var iconIdex = getSysIconIndex(file[file[ i ]])
    279             response.write('<li><img src="/?icon='++(iconIdex)+'"><a href="'
    280                     ,inet.url.append(request.path,file[ i ])
    281                     ,'?t=' + srvHttp.userToken + '">',file[ i ],'</a><br>\r\n');
    282         }
    283         
    284         response.write("</ul></body></html>")
    285     }     
    286 );//http服务器响应结束
    287 //根据服务器返回的信息,生成二维码和访问链接信息
    288 import qrencode.bitmap;
    289 var serverInfo = function(){
    290     var ip,port = srvHttp.getLocalIp();
    291     winform.editPort.text = port;
    292     winform.editDocumentRoot.text = io.fullpath(srvHttp.documentRoot)
    293 
    294     var url = srvHttp.getUrl(,true);
    295     if(#srvHttp.userToken){
    296         url = url + "/?t=" + srvHttp.userToken;
    297     } 
    298     
    299     winform.txtMessage.text = 'http服务端已启动: \n'; 
    300     winform.txtMessage.print(  url );
    301     //生成二维码
    302     var qrBmp = qrencode.bitmap( url );
    303     winform.qr.setBackground(qrBmp.copyBitmap(winform.qr.width)); 
    304         
    305     winform.txtMessage.print( 
    306         "手机扫码可自动打开此网页,可以方便地上传下载文件。
    307 拖动文件或目录到窗口上客户端网页会自动刷新。
    308 
    309 asynHttpServer 体积很小可嵌入任何 aardio 程序,
    310 asynHttpServer 可以创建单线程异步模式的 HTTP 服务端,并可以同时创建 WebSocket 服务端(与HTTP服务端共享端口)。asynHttpServer 支持保持连接(Keep Alive),分块传输协议,支持断点续传,支持304缓存,支持文件表单上传,支持使用aardio编写的网站( 接口可兼容IIS/FastCGI下)。
    311 "
    312     );     
    313 }
    314 //调用上面的函数
    315 serverInfo();
    316 //重启服务器
    317 winform.btnStart.oncommand = function(id,event){
    318     winform.txtMessage.text = "";
    319     winform.btnStart.disabledText = {'\uF254';'\uF251';'\uF252';'\uF253';'\uF250'}
    320     win.delay(500);
    321  
    322     var port = tonumber(winform.editPort.text);
    323     srvHttp.documentRoot = fsys.isDir(winform.editDocumentRoot.text) ? winform.editDocumentRoot.text;
    324     srvHttp.userToken = winform.editPassword.text;
    325     srvHttp.start("0.0.0.0",port);
    326     serverInfo();
    327     
    328     winform.btnStart.disabledText = null;
    329 }
    330 //启用右键菜单、响应链接点击事件
    331 import win.ui.menu
    332 winform.txtMessage.enablePopMenu()
    333 winform.txtMessage.onlink=function(message,href){
    334     if( message = 0x202/*_WM_LBUTTONUP*/ ) {
    335         import process;
    336         process.openUrl(href);
    337     }
    338 }
    339 
    340 //处理文件拖拽事件,将拖拽文件或者文件夹作为服务器根目录并刷新页面
    341 winform.onDropFiles = function(files){
    342     var path = files[1]
    343     //如果不是文件夹则打开文件所在文件夹
    344     if(!fsys.isDir(path)){
    345         path = fsys.getParentDir(path)
    346     }
    347     winform.editDocumentRoot.text = path;
    348     srvHttp.documentRoot = path;
    349     config.winform.txtMessage = path;
    350     config.winform.save();
    351     
    352     wsrv.publish("reload");
    353 }
    354 
    355 import fsys.dlg.dir;
    356 //处理设置主目录控件,打开目录打开对话框,处理内容同上
    357 winform.btnOpen.oncommand = function(id,event){
    358     var dir = fsys.dlg.dir(winform.editDocumentRoot.text,winform)
    359     if(dir){
    360         winform.editDocumentRoot.text = dir;
    361         srvHttp.documentRoot = dir;
    362         
    363         config.winform.txtMessage = dir;
    364         config.winform.save();
    365         wsrv.publish("reload");
    366     }
    367 }
    368 
    369 import process;
    370 //处理打开上传文件夹控件事件,建立并使用默认文件管理程序打开主目录下的upload目录
    371 winform.btnOpenUpload.oncommand = function(id,event){
    372     var path = io.joinpath(winform.editDocumentRoot.text,"upload")
    373     if(io.createDir(path)){
    374         process.explore(path)
    375     }
    376 }
    377 //使用简单窗体
    378 import win.ui.simpleWindow2;
    379 win.ui.simpleWindow2(winform);
    380 winform.show(); 
    381 //设置配置文件夹为隐藏状态
    382 import fsys;
    383 fsys.attrib("/config/",,2/*_FILE_ATTRIBUTE_HIDDEN*/)
    384 
    385 win.loopMessage();
    上面是我在读代码的一些补充注释,有些其实是废话,看代码显而易见,有些则是不那么明显的,有一些默认的设置需要查询参考才能获知,还有一些是不是很直白,需要绕一下才能理解的业务逻辑,分别做了解释。
  • 相关阅读:
    java获取本机IP和主机名
    SSH框架总结(框架分析+环境搭建+实例源代码下载)
    Centos7安装mysql8教程
    jquery 操作HTML data全局属性缓存的坑
    mysql协议分析2---认证包
    mysql协议分析1---报文的格式和基本类型
    TCP三次握手抓包理解
    java读写文件小心缓存数组
    spring 事务隔离级别导致的bug
    mysql 不同版本下 group by 组内排序的差异
  • 原文地址:https://www.cnblogs.com/qinshoublog/p/16343719.html
Copyright © 2020-2023  润新知