Compare commits
7
Commits
ec2b78533d
..
v0.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a8c563b32 | ||
|
|
370a21760c | ||
|
|
f971de6821 | ||
|
|
0f7102a41d | ||
|
|
cd70157aea | ||
|
|
ac614be190 | ||
|
|
880c199ce4 |
@@ -1,53 +0,0 @@
|
|||||||
const https = require('https');
|
|
||||||
const base = 'test-test.zeabur.app';
|
|
||||||
|
|
||||||
function req(method, path, headers) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
const r = https.request({
|
|
||||||
hostname: base, port: 443, path, method, headers: headers || {},
|
|
||||||
}, (res) => {
|
|
||||||
const chunks = [];
|
|
||||||
res.on('data', (c) => chunks.push(c));
|
|
||||||
res.on('end', () => resolve({ status: res.statusCode, headers: res.headers, body: Buffer.concat(chunks) }));
|
|
||||||
});
|
|
||||||
r.on('error', (e) => resolve({ error: e.message }));
|
|
||||||
r.end();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
// 1. 预检 OPTIONS 请求
|
|
||||||
console.log('=== 预检: OPTIONS /v1/audio/speech (with Origin) ===');
|
|
||||||
const r1 = await req('OPTIONS', '/v1/audio/speech', {
|
|
||||||
'Origin': 'https://example.com',
|
|
||||||
'Access-Control-Request-Method': 'POST',
|
|
||||||
'Access-Control-Request-Headers': 'content-type, authorization',
|
|
||||||
});
|
|
||||||
console.log('Status:', r1.status);
|
|
||||||
console.log('Headers:');
|
|
||||||
for (const k of Object.keys(r1.headers)) {
|
|
||||||
if (k.startsWith('access-control') || k === 'vary') {
|
|
||||||
console.log(' ' + k + ': ' + r1.headers[k]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log('Body:', r1.body ? r1.body.toString('utf8') : '');
|
|
||||||
|
|
||||||
// 2. 预检但无 Origin(同源或非浏览器)
|
|
||||||
console.log('\n=== OPTIONS /v1/audio/speech (NO Origin) ===');
|
|
||||||
const r2 = await req('OPTIONS', '/v1/audio/speech', {});
|
|
||||||
console.log('Status:', r2.status);
|
|
||||||
|
|
||||||
// 3. 实际 POST 请求
|
|
||||||
console.log('\n=== POST /v1/audio/speech (with Origin) ===');
|
|
||||||
const body = JSON.stringify({ model: 'tts-1', input: 'test', voice: 'alloy' });
|
|
||||||
const r3 = await req('POST', '/v1/audio/speech', {
|
|
||||||
'Origin': 'https://example.com',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': 'Bearer sk-HRNfl15pp8e0KLewaydRCmvf2KfkOYd728yLJmuff5DkyaXd',
|
|
||||||
'Content-Length': Buffer.byteLength(body),
|
|
||||||
});
|
|
||||||
if (r3.body) r3.write = r3.write || (() => {});
|
|
||||||
console.log('Status:', r3.status);
|
|
||||||
console.log('Access-Control-Allow-Origin:', r3.headers['access-control-allow-origin']);
|
|
||||||
console.log('Vary:', r3.headers['vary']);
|
|
||||||
})();
|
|
||||||
@@ -642,6 +642,13 @@
|
|||||||
return `${m}m`;
|
return `${m}m`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatBytes = (b) => {
|
||||||
|
if (!b) return "0 B";
|
||||||
|
const k = 1024, sizes = ["B", "KB", "MB", "GB"];
|
||||||
|
const i = Math.floor(Math.log(b) / Math.log(k));
|
||||||
|
return (b / Math.pow(k, i)).toFixed(1) + " " + sizes[i];
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
refresh();
|
refresh();
|
||||||
setInterval(refresh, 10000);
|
setInterval(refresh, 10000);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ package middleware
|
|||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/volcano-tts/tts-api/metrics"
|
"github.com/volcano-tts/tts-api/metrics"
|
||||||
)
|
)
|
||||||
@@ -16,6 +17,11 @@ import (
|
|||||||
// RateLimitWithMetrics 是 middleware.RateLimit 的可埋点版本。
|
// RateLimitWithMetrics 是 middleware.RateLimit 的可埋点版本。
|
||||||
func RateLimitWithMetrics(next http.Handler) http.Handler {
|
func RateLimitWithMetrics(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// 仅对 /v1/ 下的业务请求限流,/health /metrics /dashboard 等监控路径不限流
|
||||||
|
if !strings.HasPrefix(r.URL.Path, "/v1/") {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
clientIP := GetClientIP(r)
|
clientIP := GetClientIP(r)
|
||||||
if !GlobalRateLimiter.Allow(clientIP) {
|
if !GlobalRateLimiter.Allow(clientIP) {
|
||||||
log.Printf("警告: 已超过IP速率限制,拒绝请求 - 客户端IP: %s", clientIP)
|
log.Printf("警告: 已超过IP速率限制,拒绝请求 - 客户端IP: %s", clientIP)
|
||||||
@@ -29,6 +35,11 @@ func RateLimitWithMetrics(next http.Handler) http.Handler {
|
|||||||
// ConcurrencyLimitWithMetrics 是 middleware.ConcurrencyLimit 的可埋点版本。
|
// ConcurrencyLimitWithMetrics 是 middleware.ConcurrencyLimit 的可埋点版本。
|
||||||
func ConcurrencyLimitWithMetrics(next http.Handler) http.Handler {
|
func ConcurrencyLimitWithMetrics(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// 仅对 /v1/ 下的业务请求统计并发和加锁,监控路径不占用并发槽位
|
||||||
|
if !strings.HasPrefix(r.URL.Path, "/v1/") {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
select {
|
select {
|
||||||
case ConcurrencySem <- struct{}{}:
|
case ConcurrencySem <- struct{}{}:
|
||||||
metrics.ConcurrencyActive.Inc(nil)
|
metrics.ConcurrencyActive.Inc(nil)
|
||||||
|
|||||||
Reference in New Issue
Block a user