一 HTTP/HTTPS 服务端口监测的简易实现方法
在当今快节奏的工作环境中,工作忙碌成为了许多职场人的常态。就拿我们团队最近经历的事情来说,工作任务一个接一个,大家都在各自的岗位上争分夺秒地忙碌着。然而,就在这样高强度的工作节奏下,一个严重的问题悄然发生了。
我们负责的一个重要项目,在服务器重启时,其中的 http 服务竟然关闭了长达 3 天之久,令人遗憾的是,这期间居然没有一个人发现这个异常情况。这个看似不起眼的疏忽,却引发了极其严重的后果。由于 http 服务的长时间中断,我们丢失了部分订单。这些订单的丢失,不仅仅意味着直接的经济损失,还对我们与客户之间的信任关系造成了冲击,可能会影响到未来的业务合作。
基于对过往故障的深入反思与分析,为有效规避类似情况的再度发生,有必要采取切实可行的应对策略。于是便开发一款简易的 HTTP 服务端口监测程序,该程序仅通过单个 HTML 文件实现。此举旨在提升监测的便捷性与高效性,以实现对 HTTP 服务端口状态的实时监控。一旦检测到异常情况,能够迅速做出响应并采取相应的解决措施,从而有效避免因服务中断而引发的一系列严重后果。这一基于 HTML 的简易监测工具,不仅承担着保障业务稳定运行的关键职责,同时也警示我们,在复杂的业务环境中,任何可能影响系统稳定性的细节都不容忽视。
二 HTTP/HTTPS 服务端口监测
HTML 的简易监测效果图如下:
https://i-blog.csdnimg.cn/direct/4e2915c9b2664f8b85c14c5a2fd52d0b.png" width="1544" />
只需一个html,放在本地电脑或服务器Nginx都可运行,以下是项目完整html代码:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>http服务状态监控</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
}
body {
background-color: #f5f7fa;
padding: 20px;
color: #333;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
h1 {
text-align: center;
margin-bottom: 20px;
color: #2c3e50;
}
.projects-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
}
.project {
background: white;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
height: 100%;
display: flex;
flex-direction: column;
}
.project-header {
background: #3498db;
color: white;
padding: 10px 15px;
font-size: 16px;
font-weight: 600;
}
.services-container {
padding: 10px;
flex-grow: 1;
}
.service-item {
display: grid;
grid-template-columns: minmax(100px, 1fr) minmax(150px, 2fr) auto;
gap: 8px;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #eee;
font-size: 14px;
}
.service-item:last-child {
border-bottom: none;
}
.service-name {
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.service-url {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.service-url a {
color: #3498db;
text-decoration: none;
}
.service-url a:hover {
text-decoration: underline;
}
.service-status {
display: flex;
align-items: center;
white-space: nowrap;
justify-content: flex-end;
}
.status-indicator {
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 6px;
display: inline-block;
}
.status-normal {
background-color: #2ecc71;
}
.status-closed {
background-color: #e74c3c;
}
.status-loading {
background-color: #f39c12;
animation: pulse 1.5s infinite;
}
.refresh-container {
text-align: center;
margin: 15px 0 20px;
}
.refresh-btn {
background: #3498db;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background 0.3s;
}
.refresh-btn:hover {
background: #2980b9;
}
.refresh-controls {
display: flex;
justify-content: center;
align-items: center;
gap: 15px;
margin-top: 10px;
}
.refresh-info {
font-size: 14px;
color: #666;
}
.loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
visibility: hidden;
opacity: 0;
transition: all 0.3s;
}
.loading-overlay.active {
visibility: visible;
opacity: 1;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid rgba(52, 152, 219, 0.3);
border-top-color: #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
}
.tooltip {
position: relative;
display: inline-block;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 250px;
background-color: #555;
color: #fff;
text-align: center;
border-radius: 4px;
padding: 5px;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
transform: translateX(-50%);
opacity: 0;
transition: opacity 0.3s;
font-size: 12px;
word-break: break-all;
}
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
@keyframes pulse {
0% { opacity: 0.6; }
50% { opacity: 1; }
100% { opacity: 0.6; }
}
@media (max-width: 1200px) {
.projects-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 768px) {
.projects-grid {
grid-template-columns: 1fr;
}
.service-item {
grid-template-columns: 1fr auto;
gap: 5px;
}
.service-url {
grid-column: 1 / 3;
grid-row: 2;
}
}
</style>
</head>
<body>
<div class="container">
<h1>http服务状态监控</h1>
<div class="refresh-container">
<button id="refreshBtn" class="refresh-btn">立即刷新</button>
<div class="refresh-controls">
<div class="refresh-info">上次刷新时间: <span id="lastRefreshTime">-</span></div>
<div class="refresh-info">自动刷新间隔: <select id="refreshInterval">
<option value="30">30秒</option>
<option value="60" selected>1分钟</option>
<option value="300">5分钟</option>
<option value="600">10分钟</option>
</select></div>
</div>
</div>
<div id="projectsGrid" class="projects-grid"></div>
</div>
<div class="loading-overlay" id="loadingOverlay">
<div class="spinner"></div>
</div>
<script>
// 示例数据 - 10个项目,每个项目5个服务,这里根据自己的项目修改即可
var project_list = [
{
name: '项目名称1',
serv_list: [
{name: '服务名1-1', url: 'https://www.baidu.com', status: '正常'},
{name: '服务名1-2', url: 'https://www.not-exist-example.com', status: '关闭'},
{name: '服务名1-3', url: 'https://www.bing.com', status: '正常'},
{name: '服务名1-4', url: 'https://www.qq.com', status: '正常'},
{name: '服务名1-5', url: 'https://www.taobao.com', status: '正常'}
]
},
{
name: '项目名称2',
serv_list: [
{name: '服务名2-1', url: 'https://www.bing.com', status: '正常'},
{name: '服务名2-2', url: 'https://www.google.com', status: '关闭'},
{name: '服务名2-3', url: 'https://www.baidu.com', status: '正常'},
{name: '服务名2-4', url: 'https://www.github.com', status: '正常'},
{name: '服务名2-5', url: 'https://www.douban.com', status: '正常'}
]
},
{
name: '项目名称3',
serv_list: [
{name: '服务名3-1', url: 'https://www.jd.com', status: '正常'},
{name: '服务名3-2', url: 'https://www.not-exist-example.com', status: '关闭'},
{name: '服务名3-3', url: 'https://www.zhihu.com', status: '正常'},
{name: '服务名3-4', url: 'https://www.weibo.com', status: '正常'},
{name: '服务名3-5', url: 'https://www.163.com', status: '正常'}
]
},
{
name: '项目名称4',
serv_list: [
{name: '服务名4-1', url: 'https://www.douyin.com', status: '正常'},
{name: '服务名4-2', url: 'https://www.not-exist-example.com', status: '关闭'},
{name: '服务名4-3', url: 'https://www.alipay.com', status: '正常'},
{name: '服务名4-4', url: 'https://www.sohu.com', status: '正常'},
{name: '服务名4-5', url: 'https://www.bilibili.com', status: '正常'}
]
},
{
name: '项目名称5',
serv_list: [
{name: '服务名5-1', url: 'https://www.tmall.com', status: '正常'},
{name: '服务名5-2', url: 'https://www.not-exist-example.com', status: '关闭'},
{name: '服务名5-3', url: 'https://www.csdn.net', status: '正常'},
{name: '服务名5-4', url: 'https://www.oschina.net', status: '正常'},
{name: '服务名5-5', url: 'https://www.cnblogs.com', status: '正常'}
]
},
{
name: '项目名称6',
serv_list: [
{name: '服务名6-1', url: 'https://www.tencent.com', status: '正常'},
{name: '服务名6-2', url: 'https://www.not-exist-example.com', status: '关闭'},
{name: '服务名6-3', url: 'https://www.huawei.com', status: '正常'},
{name: '服务名6-4', url: 'https://www.xiaomi.com', status: '正常'},
{name: '服务名6-5', url: 'https://www.oppo.com', status: '正常'}
]
},
{
name: '项目名称7',
serv_list: [
{name: '服务名7-1', url: 'https://www.vivo.com', status: '正常'},
{name: '服务名7-2', url: 'https://www.not-exist-example.com', status: '关闭'},
{name: '服务名7-3', url: 'https://www.apple.com', status: '正常'},
{name: '服务名7-4', url: 'https://www.samsung.com', status: '正常'},
{name: '服务名7-5', url: 'https://www.mi.com', status: '正常'}
]
},
{
name: '项目名称8',
serv_list: [
{name: '服务名8-1', url: 'https://www.sina.com.cn', status: '正常'},
{name: '服务名8-2', url: 'https://www.not-exist-example.com', status: '关闭'},
{name: '服务名8-3', url: 'https://www.163.com', status: '正常'},
{name: '服务名8-4', url: 'https://www.sogou.com', status: '正常'},
{name: '服务名8-5', url: 'https://www.360.cn', status: '正常'}
]
},
{
name: '项目名称9',
serv_list: [
{name: '服务名9-1', url: 'https://www.so.com', status: '正常'},
{name: '服务名9-2', url: 'https://www.not-exist-example.com', status: '关闭'},
{name: '服务名9-3', url: 'https://www.ifeng.com', status: '正常'},
{name: '服务名9-4', url: 'https://www.cctv.com', status: '正常'},
{name: '服务名9-5', url: 'https://www.people.com.cn', status: '正常'}
]
}
];
// 全局变量
let autoRefreshTimer;
let refreshIntervalSeconds = 60;
// DOM 元素
const projectsGrid = document.getElementById('projectsGrid');
const refreshBtn = document.getElementById('refreshBtn');
const lastRefreshTimeEl = document.getElementById('lastRefreshTime');
const refreshIntervalSelect = document.getElementById('refreshInterval');
const loadingOverlay = document.getElementById('loadingOverlay');
// 初始化页面
function initPage() {
// 设置刷新间隔
refreshIntervalSelect.addEventListener('change', function() {
refreshIntervalSeconds = parseInt(this.value);
resetAutoRefreshTimer();
});
// 刷新按钮事件
refreshBtn.addEventListener('click', function() {
checkAllServices();
});
// 初始渲染项目列表
renderProjects();
// 初次检查服务状态
checkAllServices();
// 设置自动刷新
resetAutoRefreshTimer();
}
// 渲染项目列表
function renderProjects() {
projectsGrid.innerHTML = '';
project_list.forEach(project => {
const projectEl = document.createElement('div');
projectEl.className = 'project';
const projectHeader = document.createElement('div');
projectHeader.className = 'project-header';
projectHeader.textContent = project.name;
const servicesContainer = document.createElement('div');
servicesContainer.className = 'services-container';
project.serv_list.forEach(service => {
const serviceItem = document.createElement('div');
serviceItem.className = 'service-item';
const serviceName = document.createElement('div');
serviceName.className = 'service-name';
serviceName.textContent = service.name;
const serviceUrl = document.createElement('div');
serviceUrl.className = 'service-url';
const urlLink = document.createElement('a');
urlLink.href = service.url;
urlLink.target = '_blank';
urlLink.textContent = shortenUrl(service.url);
urlLink.title = service.url;
serviceUrl.appendChild(urlLink);
const serviceStatus = document.createElement('div');
serviceStatus.className = 'service-status';
const statusIndicator = document.createElement('span');
statusIndicator.className = `status-indicator status-loading`;
const statusText = document.createElement('span');
statusText.textContent = '检测中...';
serviceStatus.appendChild(statusIndicator);
serviceStatus.appendChild(statusText);
serviceItem.appendChild(serviceName);
serviceItem.appendChild(serviceUrl);
serviceItem.appendChild(serviceStatus);
servicesContainer.appendChild(serviceItem);
});
projectEl.appendChild(projectHeader);
projectEl.appendChild(servicesContainer);
projectsGrid.appendChild(projectEl);
});
}
// 缩短URL显示
function shortenUrl(url) {
try {
const urlObj = new URL(url);
return urlObj.hostname;
} catch (e) {
// 如果解析失败,返回原始URL的一部分
return url.length > 20 ? url.substring(0, 20) + '...' : url;
}
}
// 检测所有服务状态
async function checkAllServices() {
showLoading();
// 更新上次刷新时间
lastRefreshTimeEl.textContent = new Date().toLocaleString();
const projectElements = document.querySelectorAll('.project');
for (let i = 0; i < project_list.length; i++) {
const project = project_list[i];
for (let j = 0; j < project.serv_list.length; j++) {
const service = project.serv_list[j];
// 获取对应的 DOM 元素
const serviceItems = projectElements[i].querySelectorAll('.service-item');
const serviceStatusEl = serviceItems[j].querySelector('.service-status');
const statusIndicator = serviceStatusEl.querySelector('.status-indicator');
const statusText = serviceStatusEl.querySelector('span:last-child');
// 将状态设置为检测中
statusIndicator.className = 'status-indicator status-loading';
statusText.textContent = '检测中...';
try {
// 使用 fetch 带超时进行检测
const status = await checkServiceStatus(service.url);
// 更新服务状态
service.status = status ? '正常' : '关闭';
// 更新 UI
if (status) {
statusIndicator.className = 'status-indicator status-normal';
statusText.textContent = '正常';
} else {
statusIndicator.className = 'status-indicator status-closed';
statusText.textContent = '关闭';
}
} catch (error) {
console.error(`检测服务 ${service.name} 失败:`, error);
// 出错时将状态设为关闭
service.status = '关闭';
statusIndicator.className = 'status-indicator status-closed';
statusText.textContent = '关闭';
}
}
}
hideLoading();
}
// 检测单个服务状态
async function checkServiceStatus(url) {
try {
// 设置超时
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
const response = await fetch(url, {
method: 'HEAD',
mode: 'no-cors',
signal: controller.signal
});
clearTimeout(timeoutId);
// 返回状态码小于 400 表示服务正常
return response.status < 400;
} catch (error) {
// 出现异常(如超时、网络错误)表示服务异常
return false;
}
}
// 重置自动刷新定时器
function resetAutoRefreshTimer() {
if (autoRefreshTimer) {
clearInterval(autoRefreshTimer);
}
autoRefreshTimer = setInterval(() => {
checkAllServices();
}, refreshIntervalSeconds * 1000);
}
// 显示加载中遮罩
function showLoading() {
loadingOverlay.classList.add('active');
}
// 隐藏加载中遮罩
function hideLoading() {
loadingOverlay.classList.remove('active');
}
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', initPage);
</script>
</body>
</html>
说明:使用时只需根据自己项目修改js部分项目数据即可。