test #1
+52
-23
@@ -71,12 +71,15 @@ type ByteDanceTTSConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RateLimiter struct {
|
type RateLimiter struct {
|
||||||
requests map[string][]time.Time
|
requests map[string][]time.Time
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
limit int
|
limit int
|
||||||
window time.Duration
|
window time.Duration
|
||||||
|
lastCleanup time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cleanupInterval = time.Hour
|
||||||
|
|
||||||
type Stats struct {
|
type Stats struct {
|
||||||
totalRequests int64
|
totalRequests int64
|
||||||
successfulRequests int64
|
successfulRequests int64
|
||||||
@@ -131,6 +134,11 @@ func (rl *RateLimiter) Allow(key string) bool {
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
cutoff := now.Add(-rl.window)
|
cutoff := now.Add(-rl.window)
|
||||||
|
|
||||||
|
if now.Sub(rl.lastCleanup) > cleanupInterval {
|
||||||
|
rl.cleanup()
|
||||||
|
rl.lastCleanup = now
|
||||||
|
}
|
||||||
|
|
||||||
timestamps := rl.requests[key]
|
timestamps := rl.requests[key]
|
||||||
valid := make([]time.Time, 0, len(timestamps))
|
valid := make([]time.Time, 0, len(timestamps))
|
||||||
for _, ts := range timestamps {
|
for _, ts := range timestamps {
|
||||||
@@ -149,6 +157,23 @@ func (rl *RateLimiter) Allow(key string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rl *RateLimiter) cleanup() {
|
||||||
|
cutoff := time.Now().Add(-rl.window)
|
||||||
|
for k, v := range rl.requests {
|
||||||
|
valid := make([]time.Time, 0, len(v))
|
||||||
|
for _, ts := range v {
|
||||||
|
if ts.After(cutoff) {
|
||||||
|
valid = append(valid, ts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(valid) == 0 {
|
||||||
|
delete(rl.requests, k)
|
||||||
|
} else {
|
||||||
|
rl.requests[k] = valid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func initTTSConfig() error {
|
func initTTSConfig() error {
|
||||||
apiKey := os.Getenv("BYTEDANCE_TTS_API_KEY")
|
apiKey := os.Getenv("BYTEDANCE_TTS_API_KEY")
|
||||||
resourceId := os.Getenv("BYTEDANCE_TTS_RESOURCE_ID")
|
resourceId := os.Getenv("BYTEDANCE_TTS_RESOURCE_ID")
|
||||||
@@ -242,20 +267,12 @@ func httpPostStream(url string, headers map[string]string, body []byte, timeout
|
|||||||
req.Header.Set(key, value)
|
req.Header.Set(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
client := globalHTTPClient
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
if timeout != 0 {
|
defer cancel()
|
||||||
client = &http.Client{
|
|
||||||
Timeout: timeout,
|
|
||||||
Transport: &http.Transport{
|
|
||||||
MaxIdleConns: 100,
|
|
||||||
MaxIdleConnsPerHost: 10,
|
|
||||||
IdleConnTimeout: 90 * time.Second,
|
|
||||||
TLSHandshakeTimeout: 10 * time.Second,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return client.Do(req)
|
req = req.WithContext(ctx)
|
||||||
|
|
||||||
|
return globalHTTPClient.Do(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertSpeedToSpeechRate(speed float64) int {
|
func convertSpeedToSpeechRate(speed float64) int {
|
||||||
@@ -268,7 +285,12 @@ func convertSpeedToSpeechRate(speed float64) int {
|
|||||||
return int((speed - 1.0) * 100)
|
return int((speed - 1.0) * 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
func synthesis(text string, speed float64) ([]byte, error) {
|
type SynthesisResult struct {
|
||||||
|
AudioData []byte
|
||||||
|
ReqID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func synthesis(text string, speed float64) (*SynthesisResult, error) {
|
||||||
reqID := uuid.NewString()
|
reqID := uuid.NewString()
|
||||||
|
|
||||||
speechRate := convertSpeedToSpeechRate(speed)
|
speechRate := convertSpeedToSpeechRate(speed)
|
||||||
@@ -336,6 +358,8 @@ func synthesis(text string, speed float64) ([]byte, error) {
|
|||||||
if v3Resp.Usage != nil {
|
if v3Resp.Usage != nil {
|
||||||
log.Printf("TTS synthesis completed, usage: %+v", v3Resp.Usage)
|
log.Printf("TTS synthesis completed, usage: %+v", v3Resp.Usage)
|
||||||
}
|
}
|
||||||
|
for scanner.Scan() {
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,7 +389,7 @@ func synthesis(text string, speed float64) ([]byte, error) {
|
|||||||
return nil, fmt.Errorf("no audio data received")
|
return nil, fmt.Errorf("no audio data received")
|
||||||
}
|
}
|
||||||
|
|
||||||
return audioData, nil
|
return &SynthesisResult{AudioData: audioData, ReqID: reqID}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateAPIKey(r *http.Request) bool {
|
func validateAPIKey(r *http.Request) bool {
|
||||||
@@ -478,7 +502,11 @@ func openaiTTSHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, MAX_REQUEST_BODY_SIZE))
|
body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, MAX_REQUEST_BODY_SIZE))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Request body too large", http.StatusRequestEntityTooLarge)
|
if strings.Contains(err.Error(), "request body too large") {
|
||||||
|
http.Error(w, "Request body too large", http.StatusRequestEntityTooLarge)
|
||||||
|
} else {
|
||||||
|
http.Error(w, "Failed to read request body", http.StatusBadRequest)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -510,7 +538,7 @@ func openaiTTSHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ttsStart := time.Now()
|
ttsStart := time.Now()
|
||||||
audioData, err := synthesis(req.Input, speed)
|
result, err := synthesis(req.Input, speed)
|
||||||
duration := time.Since(ttsStart)
|
duration := time.Since(ttsStart)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -522,9 +550,10 @@ func openaiTTSHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
addRequestStats(true, duration, "")
|
addRequestStats(true, duration, "")
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "audio/wav")
|
w.Header().Set("Content-Type", "audio/wav")
|
||||||
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(audioData)))
|
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(result.AudioData)))
|
||||||
|
w.Header().Set("X-Request-Id", result.ReqID)
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write(audioData)
|
w.Write(result.AudioData)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addRequestStats(success bool, responseTime time.Duration, errMsg string) {
|
func addRequestStats(success bool, responseTime time.Duration, errMsg string) {
|
||||||
|
|||||||
Reference in New Issue
Block a user