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();
上面是我在读代码的一些补充注释,有些其实是废话,看代码显而易见,有些则是不那么明显的,有一些默认的设置需要查询参考才能获知,还有一些是不是很直白,需要绕一下才能理解的业务逻辑,分别做了解释。