如上图所示,这篇文章基于此图进行实现。其功能就是,可以在WEB端对后台的一些服务进行控制。最初的想法来自于近期将要做的毕设项目。
因为是爬虫程序,所以有可能会长期跑一些脚本,又不能随时带着电脑(阿里云APP上倒是有一个ssh工具,很方便),所以做成一个web服务,这样直接用浏览器就可以简单的完成一些操作了。
项目结构
因为类似的服务,有点绕。所以简单的分下层,理解起来会更容易点。其实说白了,就是一个带有了WEB服务的IPC实现。具体的流程为:
客户端通过web请求,调用到服务脚本执行对应的信号处理,完成对目标脚本的控制。
下面展示下目录结构,方便理解:
developer@aliyun:~/control$ tree
.
├── api.py # flask 服务相关
├── control.sh # 信号处理相关
├── father.log # 日志文件
├── get-process-status.sh # 脚本运行状态相关
├── jobs.list # 可操作的服务列表
├── log # 日志文件,测试用
├── py.log # 日志文件,测试用
├── scripts # 存放服务脚本的目录
│ ├── testforbash.sh # 测试bash脚本
│ ├── testforphp.php # 测试PHP脚本
│ └── testforpy.py # 测试Python脚本
├── start-program.py # 被control.sh调用,根据拓展名找到对应解释器并开启服务
├── static # flask 默认静态文件目录
│ ├── css
│ │ ├── detail.css
│ │ └── index.css
│ ├── imgs
│ │ ├── background.jpg
│ │ ├── favicon.ico
│ │ └── header.jpg
│ └── js
│ ├── detail.js
│ ├── index.js
│ └── jquery-2.2.4.min.js
└── templates # flask 默认模板目录
└── index.html
代码段
下面按照上述目录结构,贴一下代码。代码都很简单,了解了流程之后也就不难理解了。这里不再做过多的赘述。
api.py
developer@aliyun:~/control$ cat api.py
#!/usr/bin python
# coding: utf8
import sys
reload(sys)
sys.setdefaultencoding("utf8")
import os
import commands
import json
from flask import Flask,request,render_template
# for easy usage, just all in. begin
# 获取可以控制的脚本列表
def get_programs():
with open("./jobs.list", "r") as file:
programs = file.readlines()
file.close()
return [line.strip("\n") for line in programs]
# 根据脚本名称获取当前脚本的执行状态
def get_status_by_name(program=""):
cmd = "bash get-process-status.sh {}".format(program)
status, output = commands.getstatusoutput(cmd)
return status, output
# 控制脚本执行,暂停,或者杀死
def control_program(program="", command=""):
cmd = "bash control.sh {} {} &".format(program, command)
return os.system(cmd)
# end
app = Flask(__name__)
@app.route("/")
def index():
return render_template("index.html")
@app.route("/program")
def program():
res = {"data":[], "code":-1}
try:
programs = get_programs()
res = {"data": programs, "code":0}
except exception as e:
res = {"data": e, "code":-1}
return json.dumps(res)
@app.route("/status", methods=["POST", "GET"])
def status():
params = {}
if request.method == "POST":
params = request.form.to_dict()
elif request.method == "GET":
params = request.args.to_dict()
else:
params = {}
program = params.get("program", "")
status = get_status_by_name(program)
return json.dumps({"data":status})
@app.route("/control", methods=["POST", "GET"])
def control():
params = {}
if request.method == "POST":
params = request.form.to_dict()
elif request.method == "GET":
params = request.args.to_dict()
else:
pass
program = params.get("program")
command = params.get("command")
print program, "==", command
res = control_program(program, command)
return render_template("index.html")
if __name__ == "__main__":
#print get_programs()
#program = "/home/developer/control/scripts/testforphp.php"
#print get_status_by_name(program)
#print control_program(program, "start")
#print get_status_by_name(program)
app.run(host="0.0.0.0", port=9999, debug=True)
control.sh
developer@aliyun:~/control$ cat control.sh
#!/usr/bin bash
# 获取$1 内容对应的进程id
echo "命令为" $1
PID=$(ps aux | grep -v grep | grep $1 | awk '{print $2}'| head -1)
echo "捕获的进程号为" $PID "具体的命令为" $2
# 根据$2参数来控制进程
if [[ $2 == "stop" ]];then
echo "暂停" $1
kill -SIGSTOP $PID
elif [[ $2 == "resume" ]];then
echo "继续" $1
kill -SIGCONT $PID
elif [[ $2 == "kill" ]];then
echo "终止" $PID
kill -9 $PID
elif [[ $2 == "start" ]];then
#echo "可以交给python脚本根据对应的拓展名来开启对应的服务"
echo "正在开启脚本..."
/usr/bin/python /home/developer/control/start-program.py $1 > /home/developer/control/father.log
else
echo "暂不支持的操作~"
fi
echo "OVER!"
get-process-status.sh
developer@aliyun:~/control$ cat get-process-status.sh
#!/usr/bin bash
PROGRAM=$1
STATUS=$(ps aux | grep -v grep| grep $PROGRAM | head -1 | awk '{print $8}')
echo $STATUS
exit
if [[ $STATUS == "T" ]];then
echo $PROGRAM is Stopping...
elif [[ $STATUS == "S" ]];then
echo $PROGRAM is S
elif [[ $STATUS == "R" ]];then
echo $PROGRAM is Running.
else
echo $PROGRAM is $STATUS
fi
jobs.list
developer@aliyun:~/control$ cat jobs.list
/home/developer/control/scripts/testforpy.py
/home/developer/control/script/testforbash.sh
/home/developer/control/scripts/testforphp.php
scripts
这个目录用于拓展服务的列表,需要添加新的可控脚本的话,只需要在这个文件夹和jobs.list下进行添加即可。
testforbash.sh
developer@aliyun:~/control$ cat scripts/testforbash.sh
#!/usr/bin bash
for ((i=1; i<10;i++))
do
echo $i "carried."
sleep 3
done
testforphp.php
developer@aliyun:~/control$ cat scripts/testforphp.php
<?php
while(true) {
$date = date("Y-m-d H:i:s");
echo "PHP 当前时间是[{$date}].\n";
sleep(10);
}
testforpy.py
developer@aliyun:~/control$ cat scripts/testforpy.py
#!/usr/bin python
#coding: utf8
import sys
import time
reload(sys)
sys.setdefaultencoding("utf8")
index = 0
while True:
with open("/home/developer/control/py.log", "a") as file:
content = str("current time is: "+str(time.ctime())+"!\n")
file.write(content)
time.sleep(10)
index+=1
if index==10:
break
file.close()
start-program.py
developer@aliyun:~/control$ cat start-program.py
#!/usr/bin python
#coding: utf8
import sys
reload(sys)
sys.setdefaultencoding("utf8")
import os
# 也可以使用subprocess库,这样效果貌似会更好
path = str(sys.argv[1])
print(path)
# 默认使用最后一个拓展名
extension = path.split(".")[-1]
# 根据拓展名找到对应的处理器
mapping = {
"php": "php",
"py": "python",
"sh": "bash"
}
exts = ['php', 'py', 'sh']
print("extension is: " + extension)
print("commander is:" + mapping.get(extension))
if extension in exts:
cmd = "{} {} >> log &".format(mapping.get(extension), path)
print(cmd)
os.system(cmd)
else:
print("不支持的控制脚本")
index.js
developer@aliyun:~/control$ cat static/js/index.js
$(document).ready(function(){
loading_data_for_selector($("#programs"));
get_program_status($("#programs"), $("#previewer"));
//commit_command($("#btn_click"), $("#programs"), $("#command"))
});
function commit_command(btn, programs, commander) {
btn.click(function(){
var program = "";
var command = "";
programs.change(function(){
program = $(this).children("option:selected").val();
});
commander.change(function(){
command = $(this).children("option:selected").val();
});
console.log(program+"<>"+command)
$.ajax({
url: "/control",
method: "POST",
dataType: "JSON",
data: {program: program, command: command},
success: function(res) {
console.log(res);
},
error: function(err) {
console.log(err);
}
});
});
}
function loading_data_for_selector(obj){
$.ajax({
url: "/program",
method: "GET",
dataType: "JSON",
success: function(res) {
console.log(res);
var data = res.data;
for(var index=0; index<data.length; index++) {
var child = "<option value='"+data[index]+"'>"+data[index]+"</value>";
obj.append(child);
}
},
error: function(err) {
console.log(err);
}
});
}
/**
* 当selector的选中值发生变化的时候,就会触发对应的请求,用于及时将
* 脚本的运行状态显示到页面上。
*/
function get_program_status(obj, previewer) {
obj.change(function(){
previewer.children().remove();
var value = $(this).children("option:selected").val();
$.ajax({
url: "/status",
method: "POST",
dataType: "JSON",
data: {program: value},
success: function(res) {
console.log(res);
var data = res.data[1];
var child = "<p><code>"+value+"</code>的运行状态为"+data+"</p>";
previewer.append(child);
},
error: function(err){
console.log(err);
}
});
});
index.html
developer@aliyun:~/control$ cat templates/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>web端作业控制系统</title>
<script type="text/javascript" src="/static/js/jquery-2.2.4.min.js"></script>
<script type="text/javascript" src="/static/js/index.js"></script>
<link rel="stylesheet" type="text/css" href="/static/css/index.css">
</head>
<body>
<div class="container">
<div class="operator">
<form action="/control" method="POST">
<fieldset>
<legend>填写相关操作</legend>
<select id="programs" name="program"></select>
<select id="command" name="command">
<option value="start">开启</option>
<option value="stop">暂停</option>
<option value="resume">继续</option>
<option value="kill">杀死</option>
</select>
<input type="submit" id="btn_click" name="" value="提交">
</fieldset>
</form>
</div>
<div id="previewer" ="preview">
</div>
</div>
</body>
</html>
到此,核心的文件都已经罗列完毕了。虽然功能上没什么问题了,但是原生的页面看起来确实不能让人有赏心悦目的感觉。有兴趣的话可以在此基础上进行优化,写一个更好看的页面。
最终效果
开启web服务
查看可控脚本以及可用操作
在web页面开启一个测试的服务脚本
在web页面暂停一个测试服务脚本
确认脚本真的是暂停了。
继续之前暂停了的服务
终止一个服务
下拉框选中后会出现当前脚本的服务状态
总结
到此,这个在web页面控制服务行为的需求,马马虎虎
算是做完了。但是不得不说,界面有点难看。在完成这个小工具的过程中,也算是遇到了各种各样的坑。比如:
python
文件写操作锁没释放的Bash
和sh
某些命令不兼容的flask
请求参数一致性保证的问题
等等吧。
虽然看起来这个小工具,用到的语言特别乱,也特别杂,但大部分代码都是很简单的。从最上面的那张图入手,相信理解起来会更容易一点吧。