1
0
Fork 0

Adding upstream version 2.52.6.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-17 06:50:16 +02:00
parent a960158181
commit 6d002e9543
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
441 changed files with 95392 additions and 0 deletions

View file

@ -0,0 +1,132 @@
package monitor
import (
"time"
"github.com/gofiber/fiber/v2"
)
// Config defines the config for middleware.
type Config struct {
// Metrics page title
//
// Optional. Default: "Fiber Monitor"
Title string
// Refresh period
//
// Optional. Default: 3 seconds
Refresh time.Duration
// Whether the service should expose only the monitoring API.
//
// Optional. Default: false
APIOnly bool
// Next defines a function to skip this middleware when returned true.
//
// Optional. Default: nil
Next func(c *fiber.Ctx) bool
// Custom HTML Code to Head Section(Before End)
//
// Optional. Default: empty
CustomHead string
// FontURL for specify font resource path or URL . also you can use relative path
//
// Optional. Default: https://fonts.googleapis.com/css2?family=Roboto:wght@400;900&display=swap
FontURL string
// ChartJsURL for specify ChartJS library path or URL . also you can use relative path
//
// Optional. Default: https://cdn.jsdelivr.net/npm/chart.js@2.9/dist/Chart.bundle.min.js
ChartJsURL string // TODO: Rename to "ChartJSURL" in v3
index string
}
var ConfigDefault = Config{
Title: defaultTitle,
Refresh: defaultRefresh,
FontURL: defaultFontURL,
ChartJsURL: defaultChartJSURL,
CustomHead: defaultCustomHead,
APIOnly: false,
Next: nil,
index: newIndex(viewBag{
defaultTitle,
defaultRefresh,
defaultFontURL,
defaultChartJSURL,
defaultCustomHead,
}),
}
func configDefault(config ...Config) Config {
// Users can change ConfigDefault.Title/Refresh which then
// become incompatible with ConfigDefault.index
if ConfigDefault.Title != defaultTitle ||
ConfigDefault.Refresh != defaultRefresh ||
ConfigDefault.FontURL != defaultFontURL ||
ConfigDefault.ChartJsURL != defaultChartJSURL ||
ConfigDefault.CustomHead != defaultCustomHead {
if ConfigDefault.Refresh < minRefresh {
ConfigDefault.Refresh = minRefresh
}
// update default index with new default title/refresh
ConfigDefault.index = newIndex(viewBag{
ConfigDefault.Title,
ConfigDefault.Refresh,
ConfigDefault.FontURL,
ConfigDefault.ChartJsURL,
ConfigDefault.CustomHead,
})
}
// Return default config if nothing provided
if len(config) < 1 {
return ConfigDefault
}
// Override default config
cfg := config[0]
// Set default values
if cfg.Title == "" {
cfg.Title = ConfigDefault.Title
}
if cfg.Refresh == 0 {
cfg.Refresh = ConfigDefault.Refresh
}
if cfg.FontURL == "" {
cfg.FontURL = defaultFontURL
}
if cfg.ChartJsURL == "" {
cfg.ChartJsURL = defaultChartJSURL
}
if cfg.Refresh < minRefresh {
cfg.Refresh = minRefresh
}
if cfg.Next == nil {
cfg.Next = ConfigDefault.Next
}
if !cfg.APIOnly {
cfg.APIOnly = ConfigDefault.APIOnly
}
// update cfg.index with custom title/refresh
cfg.index = newIndex(viewBag{
title: cfg.Title,
refresh: cfg.Refresh,
fontURL: cfg.FontURL,
chartJSURL: cfg.ChartJsURL,
customHead: cfg.CustomHead,
})
return cfg
}

View file

@ -0,0 +1,163 @@
package monitor
import (
"testing"
"time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/utils"
)
func Test_Config_Default(t *testing.T) {
t.Parallel()
t.Run("use default", func(t *testing.T) {
t.Parallel()
cfg := configDefault()
utils.AssertEqual(t, defaultTitle, cfg.Title)
utils.AssertEqual(t, defaultRefresh, cfg.Refresh)
utils.AssertEqual(t, defaultFontURL, cfg.FontURL)
utils.AssertEqual(t, defaultChartJSURL, cfg.ChartJsURL)
utils.AssertEqual(t, defaultCustomHead, cfg.CustomHead)
utils.AssertEqual(t, false, cfg.APIOnly)
utils.AssertEqual(t, (func(*fiber.Ctx) bool)(nil), cfg.Next)
utils.AssertEqual(t, newIndex(viewBag{defaultTitle, defaultRefresh, defaultFontURL, defaultChartJSURL, defaultCustomHead}), cfg.index)
})
t.Run("set title", func(t *testing.T) {
t.Parallel()
title := "title"
cfg := configDefault(Config{
Title: title,
})
utils.AssertEqual(t, title, cfg.Title)
utils.AssertEqual(t, defaultRefresh, cfg.Refresh)
utils.AssertEqual(t, defaultFontURL, cfg.FontURL)
utils.AssertEqual(t, defaultChartJSURL, cfg.ChartJsURL)
utils.AssertEqual(t, defaultCustomHead, cfg.CustomHead)
utils.AssertEqual(t, false, cfg.APIOnly)
utils.AssertEqual(t, (func(*fiber.Ctx) bool)(nil), cfg.Next)
utils.AssertEqual(t, newIndex(viewBag{title, defaultRefresh, defaultFontURL, defaultChartJSURL, defaultCustomHead}), cfg.index)
})
t.Run("set refresh less than default", func(t *testing.T) {
t.Parallel()
cfg := configDefault(Config{
Refresh: 100 * time.Millisecond,
})
utils.AssertEqual(t, defaultTitle, cfg.Title)
utils.AssertEqual(t, minRefresh, cfg.Refresh)
utils.AssertEqual(t, defaultFontURL, cfg.FontURL)
utils.AssertEqual(t, defaultChartJSURL, cfg.ChartJsURL)
utils.AssertEqual(t, defaultCustomHead, cfg.CustomHead)
utils.AssertEqual(t, false, cfg.APIOnly)
utils.AssertEqual(t, (func(*fiber.Ctx) bool)(nil), cfg.Next)
utils.AssertEqual(t, newIndex(viewBag{defaultTitle, minRefresh, defaultFontURL, defaultChartJSURL, defaultCustomHead}), cfg.index)
})
t.Run("set refresh", func(t *testing.T) {
t.Parallel()
refresh := time.Second
cfg := configDefault(Config{
Refresh: refresh,
})
utils.AssertEqual(t, defaultTitle, cfg.Title)
utils.AssertEqual(t, refresh, cfg.Refresh)
utils.AssertEqual(t, defaultFontURL, cfg.FontURL)
utils.AssertEqual(t, defaultChartJSURL, cfg.ChartJsURL)
utils.AssertEqual(t, defaultCustomHead, cfg.CustomHead)
utils.AssertEqual(t, false, cfg.APIOnly)
utils.AssertEqual(t, (func(*fiber.Ctx) bool)(nil), cfg.Next)
utils.AssertEqual(t, newIndex(viewBag{defaultTitle, refresh, defaultFontURL, defaultChartJSURL, defaultCustomHead}), cfg.index)
})
t.Run("set font url", func(t *testing.T) {
t.Parallel()
fontURL := "https://example.com"
cfg := configDefault(Config{
FontURL: fontURL,
})
utils.AssertEqual(t, defaultTitle, cfg.Title)
utils.AssertEqual(t, defaultRefresh, cfg.Refresh)
utils.AssertEqual(t, fontURL, cfg.FontURL)
utils.AssertEqual(t, defaultChartJSURL, cfg.ChartJsURL)
utils.AssertEqual(t, defaultCustomHead, cfg.CustomHead)
utils.AssertEqual(t, false, cfg.APIOnly)
utils.AssertEqual(t, (func(*fiber.Ctx) bool)(nil), cfg.Next)
utils.AssertEqual(t, newIndex(viewBag{defaultTitle, defaultRefresh, fontURL, defaultChartJSURL, defaultCustomHead}), cfg.index)
})
t.Run("set chart js url", func(t *testing.T) {
t.Parallel()
chartURL := "http://example.com"
cfg := configDefault(Config{
ChartJsURL: chartURL,
})
utils.AssertEqual(t, defaultTitle, cfg.Title)
utils.AssertEqual(t, defaultRefresh, cfg.Refresh)
utils.AssertEqual(t, defaultFontURL, cfg.FontURL)
utils.AssertEqual(t, chartURL, cfg.ChartJsURL)
utils.AssertEqual(t, defaultCustomHead, cfg.CustomHead)
utils.AssertEqual(t, false, cfg.APIOnly)
utils.AssertEqual(t, (func(*fiber.Ctx) bool)(nil), cfg.Next)
utils.AssertEqual(t, newIndex(viewBag{defaultTitle, defaultRefresh, defaultFontURL, chartURL, defaultCustomHead}), cfg.index)
})
t.Run("set custom head", func(t *testing.T) {
t.Parallel()
head := "head"
cfg := configDefault(Config{
CustomHead: head,
})
utils.AssertEqual(t, defaultTitle, cfg.Title)
utils.AssertEqual(t, defaultRefresh, cfg.Refresh)
utils.AssertEqual(t, defaultFontURL, cfg.FontURL)
utils.AssertEqual(t, defaultChartJSURL, cfg.ChartJsURL)
utils.AssertEqual(t, head, cfg.CustomHead)
utils.AssertEqual(t, false, cfg.APIOnly)
utils.AssertEqual(t, (func(*fiber.Ctx) bool)(nil), cfg.Next)
utils.AssertEqual(t, newIndex(viewBag{defaultTitle, defaultRefresh, defaultFontURL, defaultChartJSURL, head}), cfg.index)
})
t.Run("set api only", func(t *testing.T) {
t.Parallel()
cfg := configDefault(Config{
APIOnly: true,
})
utils.AssertEqual(t, defaultTitle, cfg.Title)
utils.AssertEqual(t, defaultRefresh, cfg.Refresh)
utils.AssertEqual(t, defaultFontURL, cfg.FontURL)
utils.AssertEqual(t, defaultChartJSURL, cfg.ChartJsURL)
utils.AssertEqual(t, defaultCustomHead, cfg.CustomHead)
utils.AssertEqual(t, true, cfg.APIOnly)
utils.AssertEqual(t, (func(*fiber.Ctx) bool)(nil), cfg.Next)
utils.AssertEqual(t, newIndex(viewBag{defaultTitle, defaultRefresh, defaultFontURL, defaultChartJSURL, defaultCustomHead}), cfg.index)
})
t.Run("set next", func(t *testing.T) {
t.Parallel()
f := func(c *fiber.Ctx) bool {
return true
}
cfg := configDefault(Config{
Next: f,
})
utils.AssertEqual(t, defaultTitle, cfg.Title)
utils.AssertEqual(t, defaultRefresh, cfg.Refresh)
utils.AssertEqual(t, defaultFontURL, cfg.FontURL)
utils.AssertEqual(t, defaultChartJSURL, cfg.ChartJsURL)
utils.AssertEqual(t, defaultCustomHead, cfg.CustomHead)
utils.AssertEqual(t, false, cfg.APIOnly)
utils.AssertEqual(t, f(nil), cfg.Next(nil))
utils.AssertEqual(t, newIndex(viewBag{defaultTitle, defaultRefresh, defaultFontURL, defaultChartJSURL, defaultCustomHead}), cfg.index)
})
}

271
middleware/monitor/index.go Normal file
View file

@ -0,0 +1,271 @@
package monitor
import (
"strconv"
"strings"
"time"
)
type viewBag struct {
title string
refresh time.Duration
fontURL string
chartJSURL string
customHead string
}
// returns index with new title/refresh
func newIndex(dat viewBag) string {
timeout := dat.refresh.Milliseconds() - timeoutDiff
if timeout < timeoutDiff {
timeout = timeoutDiff
}
ts := strconv.FormatInt(timeout, 10)
replacer := strings.NewReplacer("$TITLE", dat.title, "$TIMEOUT", ts,
"$FONT_URL", dat.fontURL, "$CHART_JS_URL", dat.chartJSURL, "$CUSTOM_HEAD", dat.customHead,
)
return replacer.Replace(indexHTML)
}
const (
defaultTitle = "Fiber Monitor"
defaultRefresh = 3 * time.Second
timeoutDiff = 200 // timeout will be Refresh (in milliseconds) - timeoutDiff
minRefresh = timeoutDiff * time.Millisecond
defaultFontURL = `https://fonts.googleapis.com/css2?family=Roboto:wght@400;900&display=swap`
defaultChartJSURL = `https://cdn.jsdelivr.net/npm/chart.js@2.9/dist/Chart.bundle.min.js`
defaultCustomHead = ``
// parametrized by $TITLE and $TIMEOUT
indexHTML = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="$FONT_URL" rel="stylesheet">
<script src="$CHART_JS_URL"></script>
<title>$TITLE</title>
<style>
body {
margin: 0;
font: 16px / 1.6 'Roboto', sans-serif;
}
.wrapper {
max-width: 900px;
margin: 0 auto;
padding: 30px 0;
}
.title {
text-align: center;
margin-bottom: 2em;
}
.title h1 {
font-size: 1.8em;
padding: 0;
margin: 0;
}
.row {
display: flex;
margin-bottom: 20px;
align-items: center;
}
.row .column:first-child { width: 35%; }
.row .column:last-child { width: 65%; }
.metric {
color: #777;
font-weight: 900;
}
h2 {
padding: 0;
margin: 0;
font-size: 2.2em;
}
h2 span {
font-size: 12px;
color: #777;
}
h2 span.ram_os { color: rgba(255, 150, 0, .8); }
h2 span.ram_total { color: rgba(0, 200, 0, .8); }
canvas {
width: 200px;
height: 180px;
}
$CUSTOM_HEAD
</style>
</head>
<body>
<section class="wrapper">
<div class="title"><h1>$TITLE</h1></div>
<section class="charts">
<div class="row">
<div class="column">
<div class="metric">CPU Usage</div>
<h2 id="cpuMetric">0.00%</h2>
</div>
<div class="column">
<canvas id="cpuChart"></canvas>
</div>
</div>
<div class="row">
<div class="column">
<div class="metric">Memory Usage</div>
<h2 id="ramMetric" title="PID used / OS used / OS total">0.00 MB</h2>
</div>
<div class="column">
<canvas id="ramChart"></canvas>
</div>
</div>
<div class="row">
<div class="column">
<div class="metric">Response Time</div>
<h2 id="rtimeMetric">0ms</h2>
</div>
<div class="column">
<canvas id="rtimeChart"></canvas>
</div>
</div>
<div class="row">
<div class="column">
<div class="metric">Open Connections</div>
<h2 id="connsMetric">0</h2>
</div>
<div class="column">
<canvas id="connsChart"></canvas>
</div>
</div>
</section>
</section>
<script>
function formatBytes(bytes, decimals = 1) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
Chart.defaults.global.legend.display = false;
Chart.defaults.global.defaultFontSize = 8;
Chart.defaults.global.animation.duration = 1000;
Chart.defaults.global.animation.easing = 'easeOutQuart';
Chart.defaults.global.elements.line.backgroundColor = 'rgba(0, 172, 215, 0.25)';
Chart.defaults.global.elements.line.borderColor = 'rgba(0, 172, 215, 1)';
Chart.defaults.global.elements.line.borderWidth = 2;
const options = {
scales: {
yAxes: [{ ticks: { beginAtZero: true }}],
xAxes: [{
type: 'time',
time: {
unitStepSize: 30,
unit: 'second'
},
gridlines: { display: false }
}]
},
tooltips: { enabled: false },
responsive: true,
maintainAspectRatio: false,
animation: false
};
const cpuMetric = document.querySelector('#cpuMetric');
const ramMetric = document.querySelector('#ramMetric');
const rtimeMetric = document.querySelector('#rtimeMetric');
const connsMetric = document.querySelector('#connsMetric');
const cpuChartCtx = document.querySelector('#cpuChart').getContext('2d');
const ramChartCtx = document.querySelector('#ramChart').getContext('2d');
const rtimeChartCtx = document.querySelector('#rtimeChart').getContext('2d');
const connsChartCtx = document.querySelector('#connsChart').getContext('2d');
const cpuChart = createChart(cpuChartCtx);
const ramChart = createChart(ramChartCtx);
const rtimeChart = createChart(rtimeChartCtx);
const connsChart = createChart(connsChartCtx);
const charts = [cpuChart, ramChart, rtimeChart, connsChart];
function createChart(ctx) {
return new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: '',
data: [],
lineTension: 0.2,
pointRadius: 0,
}]
},
options
});
}
ramChart.data.datasets.push({
data: [],
lineTension: 0.2,
pointRadius: 0,
backgroundColor: 'rgba(255, 200, 0, .6)',
borderColor: 'rgba(255, 150, 0, .8)',
})
ramChart.data.datasets.push({
data: [],
lineTension: 0.2,
pointRadius: 0,
backgroundColor: 'rgba(0, 255, 0, .4)',
borderColor: 'rgba(0, 200, 0, .8)',
})
function update(json, rtime) {
cpu = json.pid.cpu.toFixed(1);
cpuOS = json.os.cpu.toFixed(1);
cpuMetric.innerHTML = cpu + '% <span>' + cpuOS + '%</span>';
ramMetric.innerHTML = formatBytes(json.pid.ram) + '<span> / </span><span class="ram_os">' + formatBytes(json.os.ram) +
'<span><span> / </span><span class="ram_total">' + formatBytes(json.os.total_ram) + '</span>';
rtimeMetric.innerHTML = rtime + 'ms <span>client</span>';
connsMetric.innerHTML = json.pid.conns + ' <span>' + json.os.conns + '</span>';
cpuChart.data.datasets[0].data.push(cpu);
ramChart.data.datasets[2].data.push((json.os.total_ram / 1e6).toFixed(2));
ramChart.data.datasets[1].data.push((json.os.ram / 1e6).toFixed(2));
ramChart.data.datasets[0].data.push((json.pid.ram / 1e6).toFixed(2));
rtimeChart.data.datasets[0].data.push(rtime);
connsChart.data.datasets[0].data.push(json.pid.conns);
const timestamp = new Date().getTime();
charts.forEach(chart => {
if (chart.data.labels.length > 50) {
chart.data.datasets.forEach(function (dataset) { dataset.data.shift(); });
chart.data.labels.shift();
}
chart.data.labels.push(timestamp);
chart.update();
});
setTimeout(fetchJSON, $TIMEOUT)
}
function fetchJSON() {
var t1 = ''
var t0 = performance.now()
fetch(window.location.href, {
headers: { 'Accept': 'application/json' },
credentials: 'same-origin'
})
.then(res => {
t1 = performance.now()
return res.json()
})
.then(res => { update(res, Math.round(t1 - t0)) })
.catch(console.error);
}
fetchJSON()
</script>
</body>
</html>
`
)

View file

@ -0,0 +1,137 @@
package monitor
import (
"os"
"runtime"
"sync"
"sync/atomic"
"time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/internal/gopsutil/cpu"
"github.com/gofiber/fiber/v2/internal/gopsutil/load"
"github.com/gofiber/fiber/v2/internal/gopsutil/mem"
"github.com/gofiber/fiber/v2/internal/gopsutil/net"
"github.com/gofiber/fiber/v2/internal/gopsutil/process"
)
type stats struct {
PID statsPID `json:"pid"`
OS statsOS `json:"os"`
}
type statsPID struct {
CPU float64 `json:"cpu"`
RAM uint64 `json:"ram"`
Conns int `json:"conns"`
}
type statsOS struct {
CPU float64 `json:"cpu"`
RAM uint64 `json:"ram"`
TotalRAM uint64 `json:"total_ram"`
LoadAvg float64 `json:"load_avg"`
Conns int `json:"conns"`
}
var (
monitPIDCPU atomic.Value
monitPIDRAM atomic.Value
monitPIDConns atomic.Value
monitOSCPU atomic.Value
monitOSRAM atomic.Value
monitOSTotalRAM atomic.Value
monitOSLoadAvg atomic.Value
monitOSConns atomic.Value
)
var (
mutex sync.RWMutex
once sync.Once
data = &stats{}
)
// New creates a new middleware handler
func New(config ...Config) fiber.Handler {
// Set default config
cfg := configDefault(config...)
// Start routine to update statistics
once.Do(func() {
p, _ := process.NewProcess(int32(os.Getpid())) //nolint:errcheck // TODO: Handle error
numcpu := runtime.NumCPU()
updateStatistics(p, numcpu)
go func() {
for {
time.Sleep(cfg.Refresh)
updateStatistics(p, numcpu)
}
}()
})
// Return new handler
//nolint:errcheck // Ignore the type-assertion errors
return func(c *fiber.Ctx) error {
// Don't execute middleware if Next returns true
if cfg.Next != nil && cfg.Next(c) {
return c.Next()
}
if c.Method() != fiber.MethodGet {
return fiber.ErrMethodNotAllowed
}
if c.Get(fiber.HeaderAccept) == fiber.MIMEApplicationJSON || cfg.APIOnly {
mutex.Lock()
data.PID.CPU, _ = monitPIDCPU.Load().(float64)
data.PID.RAM, _ = monitPIDRAM.Load().(uint64)
data.PID.Conns, _ = monitPIDConns.Load().(int)
data.OS.CPU, _ = monitOSCPU.Load().(float64)
data.OS.RAM, _ = monitOSRAM.Load().(uint64)
data.OS.TotalRAM, _ = monitOSTotalRAM.Load().(uint64)
data.OS.LoadAvg, _ = monitOSLoadAvg.Load().(float64)
data.OS.Conns, _ = monitOSConns.Load().(int)
mutex.Unlock()
return c.Status(fiber.StatusOK).JSON(data)
}
c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8)
return c.Status(fiber.StatusOK).SendString(cfg.index)
}
}
func updateStatistics(p *process.Process, numcpu int) {
pidCPU, err := p.Percent(0)
if err == nil {
monitPIDCPU.Store(pidCPU / float64(numcpu))
}
if osCPU, err := cpu.Percent(0, false); err == nil && len(osCPU) > 0 {
monitOSCPU.Store(osCPU[0])
}
if pidRAM, err := p.MemoryInfo(); err == nil && pidRAM != nil {
monitPIDRAM.Store(pidRAM.RSS)
}
if osRAM, err := mem.VirtualMemory(); err == nil && osRAM != nil {
monitOSRAM.Store(osRAM.Used)
monitOSTotalRAM.Store(osRAM.Total)
}
if loadAvg, err := load.Avg(); err == nil && loadAvg != nil {
monitOSLoadAvg.Store(loadAvg.Load1)
}
pidConns, err := net.ConnectionsPid("tcp", p.Pid)
if err == nil {
monitPIDConns.Store(len(pidConns))
}
osConns, err := net.Connections("tcp")
if err == nil {
monitOSConns.Store(len(osConns))
}
}

View file

@ -0,0 +1,198 @@
package monitor
import (
"bytes"
"fmt"
"io"
"net/http/httptest"
"testing"
"time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/utils"
"github.com/valyala/fasthttp"
)
func Test_Monitor_405(t *testing.T) {
t.Parallel()
app := fiber.New()
app.Use("/", New())
resp, err := app.Test(httptest.NewRequest(fiber.MethodPost, "/", nil))
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, 405, resp.StatusCode)
}
func Test_Monitor_Html(t *testing.T) {
t.Parallel()
app := fiber.New()
// defaults
app.Get("/", New())
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, 200, resp.StatusCode)
utils.AssertEqual(t, fiber.MIMETextHTMLCharsetUTF8,
resp.Header.Get(fiber.HeaderContentType))
buf, err := io.ReadAll(resp.Body)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, true, bytes.Contains(buf, []byte("<title>"+defaultTitle+"</title>")))
timeoutLine := fmt.Sprintf("setTimeout(fetchJSON, %d)",
defaultRefresh.Milliseconds()-timeoutDiff)
utils.AssertEqual(t, true, bytes.Contains(buf, []byte(timeoutLine)))
// custom config
conf := Config{Title: "New " + defaultTitle, Refresh: defaultRefresh + time.Second}
app.Get("/custom", New(conf))
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/custom", nil))
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, 200, resp.StatusCode)
utils.AssertEqual(t, fiber.MIMETextHTMLCharsetUTF8,
resp.Header.Get(fiber.HeaderContentType))
buf, err = io.ReadAll(resp.Body)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, true, bytes.Contains(buf, []byte("<title>"+conf.Title+"</title>")))
timeoutLine = fmt.Sprintf("setTimeout(fetchJSON, %d)",
conf.Refresh.Milliseconds()-timeoutDiff)
utils.AssertEqual(t, true, bytes.Contains(buf, []byte(timeoutLine)))
}
func Test_Monitor_Html_CustomCodes(t *testing.T) {
t.Parallel()
app := fiber.New()
// defaults
app.Get("/", New())
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, 200, resp.StatusCode)
utils.AssertEqual(t, fiber.MIMETextHTMLCharsetUTF8,
resp.Header.Get(fiber.HeaderContentType))
buf, err := io.ReadAll(resp.Body)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, true, bytes.Contains(buf, []byte("<title>"+defaultTitle+"</title>")))
timeoutLine := fmt.Sprintf("setTimeout(fetchJSON, %d)",
defaultRefresh.Milliseconds()-timeoutDiff)
utils.AssertEqual(t, true, bytes.Contains(buf, []byte(timeoutLine)))
// custom config
conf := Config{
Title: "New " + defaultTitle,
Refresh: defaultRefresh + time.Second,
ChartJsURL: "https://cdnjs.com/libraries/Chart.js",
FontURL: "/public/my-font.css",
CustomHead: `<style>body{background:#fff}</style>`,
}
app.Get("/custom", New(conf))
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/custom", nil))
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, 200, resp.StatusCode)
utils.AssertEqual(t, fiber.MIMETextHTMLCharsetUTF8,
resp.Header.Get(fiber.HeaderContentType))
buf, err = io.ReadAll(resp.Body)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, true, bytes.Contains(buf, []byte("<title>"+conf.Title+"</title>")))
utils.AssertEqual(t, true, bytes.Contains(buf, []byte("https://cdnjs.com/libraries/Chart.js")))
utils.AssertEqual(t, true, bytes.Contains(buf, []byte("/public/my-font.css")))
utils.AssertEqual(t, true, bytes.Contains(buf, []byte(conf.CustomHead)))
timeoutLine = fmt.Sprintf("setTimeout(fetchJSON, %d)",
conf.Refresh.Milliseconds()-timeoutDiff)
utils.AssertEqual(t, true, bytes.Contains(buf, []byte(timeoutLine)))
}
// go test -run Test_Monitor_JSON -race
func Test_Monitor_JSON(t *testing.T) {
t.Parallel()
app := fiber.New()
app.Get("/", New())
req := httptest.NewRequest(fiber.MethodGet, "/", nil)
req.Header.Set(fiber.HeaderAccept, fiber.MIMEApplicationJSON)
resp, err := app.Test(req)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, 200, resp.StatusCode)
utils.AssertEqual(t, fiber.MIMEApplicationJSON, resp.Header.Get(fiber.HeaderContentType))
b, err := io.ReadAll(resp.Body)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, true, bytes.Contains(b, []byte("pid")))
utils.AssertEqual(t, true, bytes.Contains(b, []byte("os")))
}
// go test -v -run=^$ -bench=Benchmark_Monitor -benchmem -count=4
func Benchmark_Monitor(b *testing.B) {
app := fiber.New()
app.Get("/", New())
h := app.Handler()
fctx := &fasthttp.RequestCtx{}
fctx.Request.Header.SetMethod(fiber.MethodGet)
fctx.Request.SetRequestURI("/")
fctx.Request.Header.Set(fiber.HeaderAccept, fiber.MIMEApplicationJSON)
b.ReportAllocs()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
h(fctx)
}
})
utils.AssertEqual(b, 200, fctx.Response.Header.StatusCode())
utils.AssertEqual(b,
fiber.MIMEApplicationJSON,
string(fctx.Response.Header.Peek(fiber.HeaderContentType)))
}
// go test -run Test_Monitor_Next
func Test_Monitor_Next(t *testing.T) {
t.Parallel()
app := fiber.New()
app.Use("/", New(Config{
Next: func(_ *fiber.Ctx) bool {
return true
},
}))
resp, err := app.Test(httptest.NewRequest(fiber.MethodPost, "/", nil))
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, 404, resp.StatusCode)
}
// go test -run Test_Monitor_APIOnly -race
func Test_Monitor_APIOnly(t *testing.T) {
app := fiber.New()
app.Get("/", New(Config{
APIOnly: true,
}))
req := httptest.NewRequest(fiber.MethodGet, "/", nil)
req.Header.Set(fiber.HeaderAccept, fiber.MIMEApplicationJSON)
resp, err := app.Test(req)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, 200, resp.StatusCode)
utils.AssertEqual(t, fiber.MIMEApplicationJSON, resp.Header.Get(fiber.HeaderContentType))
b, err := io.ReadAll(resp.Body)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, true, bytes.Contains(b, []byte("pid")))
utils.AssertEqual(t, true, bytes.Contains(b, []byte("os")))
}