Mở đầu: Câu chuyện thực tế từ dự án thương mại điện tử AI

Tôi vẫn nhớ rõ buổi tối tháng 3 năm 2025, khi đội ngũ của một trong những sàn thương mại điện tử lớn nhất Việt Nam gọi điện cầu cứu. Họ đang triển khai chatbot trả lời khách hàng 24/7 dựa trên AI, nhưng gặp vấn đề nghiêm trọng: mỗi khi lượng truy cập đỉnh điểm (21:00 - 23:00 hàng ngày), server bị quá tải vì mỗi request đều phải đợi response hoàn chỉnh mới trả về được. Khách hàng than phiền về độ trễ, đội ngũ kỹ thuật stress với những timeout liên tục. Sau 3 ngày debug và thử nghiệm với nhiều giải pháp, chúng tôi quyết định chuyển đổi sang SSE (Server-Sent Events) streaming qua HolySheep AI relay. Kết quả: giảm 73% độ trễ nhận thấy từ phía người dùng, xử lý được 10 lần lưu lượng truy cập đỉnh mà không cần scale infrastructure. Bài viết này sẽ chia sẻ toàn bộ kiến thức và kinh nghiệm thực chiến để bạn có thể triển khai thành công.

SSE Streaming là gì và tại sao nó quan trọng cho ứng dụng AI

SSE (Server-Sent Events) là một công nghệ HTTP protocol cho phép server push data đến client theo thời gian thực thông qua kết nối HTTP keep-alive đơn hướng. Khác với WebSocket (hai chiều), SSE chỉ server gửi data đến client, nhưng đổi lại đơn giản hơn nhiều về mặt implementation và hoạt động tốt qua proxy/firewall.

Trong bối cảnh ứng dụng AI, SSE streaming mang lại những lợi ích then chốt:

HolySheep AI Relay: Giải pháp streaming tối ưu chi phí

Trước khi đi vào chi tiết kỹ thuật, hãy tìm hiểu tại sao HolySheep AI là lựa chọn tối ưu cho việc triển khai SSE streaming trong production.

So sánh chi phí: HolySheep vs Direct API

Model Direct API ($/MTok) HolySheep ($/MTok) Tiết kiệm
GPT-4.1 $60.00 $8.00 86.7%
Claude Sonnet 4.5 $105.00 $15.00 85.7%
Gemini 2.5 Flash $17.50 $2.50 85.7%
DeepSeek V3.2 $3.00 $0.42 86.0%

Với tỷ giá cố định ¥1 = $1 và hỗ trợ thanh toán WeChat/Alipay cho thị trường châu Á, HolySheep đặc biệt phù hợp cho các doanh nghiệp Việt Nam muốn tối ưu chi phí API mà không phải lo lắng về tỷ giá ngoại hối.

Triển khai SSE Streaming với Authentication: Hướng dẫn từ A-Z

1. Cấu hình Authentication với HolySheep API Key

HolySheep AI relay sử dụng API key authentication theo chuẩn Bearer Token. Việc bảo mật API key đúng cách là then chốt để ngăn chặn unauthorized access và tránh bị trích xuất credential qua các lỗ hổng bảo mật thông thường.


server.py - Flask backend với SSE streaming

KHÔNG BAO GIỜ hardcode API key trong source code

import os import httpx from flask import Flask, Response, request, jsonify from flask_cors import CORS app = Flask(__name__) CORS(app)

Cách 1: Load từ environment variable (RECOMMENDED)

HOLYSHEEP_API_KEY = os.environ.get('HOLYSHEEP_API_KEY') BASE_URL = 'https://api.holysheep.ai/v1' if not HOLYSHEEP_API_KEY: raise ValueError("HOLYSHEEP_API_KEY environment variable is required")

Cách 2: Load từ file config riêng (staging/production)

def load_config():

with open('/etc/secrets/api_config.json') as f:

config = json.load(f)

return config['holysheep_key']

@app.route('/api/chat/stream', methods=['POST']) def chat_stream(): """ Endpoint proxy để streaming chat qua HolySheep relay. - Nhận request từ frontend - Forward đến HolySheep API với authentication - Stream response về client """ data = request.get_json() # Validate input if not data or 'messages' not in data: return jsonify({'error': 'Invalid request body'}), 400 messages = data['messages'] model = data.get('model', 'gpt-4.1') # Validate messages format for msg in messages: if 'role' not in msg or 'content' not in msg: return jsonify({'error': 'Invalid message format'}), 400 # Prepare headers cho HolySheep API headers = { 'Authorization': f'Bearer {HOLYSHEEP_API_KEY}', 'Content-Type': 'application/json', 'Accept': 'text/event-stream', # Yêu cầu SSE response 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' } # Request body theo OpenAI-compatible format payload = { 'model': model, 'messages': messages, 'stream': True # BẬT streaming mode } return Response( stream_with_holy_sheep(headers, payload), mimetype='text/event-stream', headers={ 'X-Accel-Buffering': 'no' # Disable nginx buffering } ) async def stream_with_holy_sheep(headers, payload): """ Generator function để stream data từ HolySheep về client. Xử lý authentication và transform data đúng format. """ async with httpx.AsyncClient(timeout=60.0) as client: async with client.stream( 'POST', f'{BASE_URL}/chat/completions', headers=headers, json=payload ) as response: if response.status_code != 200: error_body = await response.aread() yield f"data: {json.dumps({'error': error_body.decode()})}\n\n" return async for line in response.aiter_lines(): if line.strip(): # Transform SSE format nếu cần if line.startswith('data: '): yield f"{line}\n\n" elif line == 'data: [DONE]': yield "data: [DONE]\n\n" if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=False)

2. Frontend Implementation với JavaScript/TypeScript


// chat-service.ts - TypeScript service cho SSE streaming

interface Message {
  role: 'system' | 'user' | 'assistant';
  content: string;
}

interface StreamCallbacks {
  onChunk: (text: string, fullContent: string) => void;
  onComplete: (fullContent: string) => void;
  onError: (error: Error) => void;
}

class HolySheepStreamingClient {
  private baseUrl: string;
  private apiKey: string;
  
  constructor(apiKey: string) {
    // API key phải được truyền từ server-side, KHÔNG expose client-side
    // Trong production, call backend proxy endpoint thay vì direct API
    this.apiKey = apiKey;
    this.baseUrl = '/api'; // Proxy backend
  }
  
  async *streamChat(
    messages: Message[],
    model: string = 'gpt-4.1',
    callbacks: StreamCallbacks
  ): AsyncGenerator {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), 120000);
    
    try {
      const response = await fetch(${this.baseUrl}/chat/stream, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          // Authentication được handle bởi backend proxy
          // Không cần gửi API key từ client
        },
        body: JSON.stringify({ messages, model }),
        signal: controller.signal
      });
      
      if (!response.ok) {
        const error = await response.json();
        throw new Error(error.message || HTTP ${response.status});
      }
      
      const reader = response.body?.getReader();
      const decoder = new TextDecoder();
      let buffer = '';
      let fullContent = '';
      
      if (!reader) {
        throw new Error('Response body is not readable');
      }
      
      while (true) {
        const { done, value } = await reader.read();
        
        if (done) break;
        
        buffer += decoder.decode(value, { stream: true });
        const lines = buffer.split('\n');
        buffer = lines.pop() || ''; // Keep incomplete line in buffer
        
        for (const line of lines) {
          if (line.startsWith('data: ')) {
            const data = line.slice(6).trim();
            
            if (data === '[DONE]') {
              callbacks.onComplete(fullContent);
              return;
            }
            
            try {
              const parsed = JSON.parse(data);
              const chunk = this.extractChunkContent(parsed);
              
              if (chunk) {
                fullContent += chunk;
                callbacks.onChunk(chunk, fullContent);
                yield chunk;
              }
            } catch (e) {
              // Skip malformed JSON lines
              console.warn('Skipping malformed SSE line:', line);
            }
          }
        }
      }
      
      callbacks.onComplete(fullContent);
      
    } catch (error) {
      if (error instanceof Error && error.name === 'AbortError') {
        callbacks.onError(new Error('Request timeout after 120 seconds'));
      } else {
        callbacks.onError(error as Error);
      }
    } finally {
      clearTimeout(timeoutId);
    }
  }
  
  private extractChunkContent(data: any): string {
    // HolySheep sử dụng OpenAI-compatible format
    if (data.choices?.[0]?.delta?.content) {
      return data.choices[0].delta.content;
    }
    
    // Handle custom formats nếu cần
    if (data.content) {
      return data.content;
    }
    
    return '';
  }
}

// React hook example
import { useState, useCallback } from 'react';

function useChatStream() {
  const [messages, setMessages] = useState<Message[]>[]);
  const [isStreaming, setIsStreaming] = useState(false);
  const [currentResponse, setCurrentResponse] = useState('');
  
  const sendMessage = useCallback(async (userInput: string) => {
    const newMessages: Message[] = [
      ...messages,
      { role: 'user', content: userInput }
    ];
    setMessages(newMessages);
    setIsStreaming(true);
    setCurrentResponse('');
    
    const client = new HolySheepStreamingClient('');
    
    try {
      const stream = client.streamChat(newMessages, 'gpt-4.1', {
        onChunk: (chunk, full) => setCurrentResponse(full),
        onComplete: (full) => {
          setMessages(prev => [...prev, { role: 'assistant', content: full }]);
          setIsStreaming(false);
        },
        onError: (error) => {
          console.error('Stream error:', error);
          setIsStreaming(false);
          setCurrentResponse(Lỗi: ${error.message});
        }
      });
      
      for await (const chunk of stream) {
        // Streaming handled by callbacks
      }
    } catch (error) {
      console.error('Send message error:', error);
      setIsStreaming(false);
    }
  }, [messages]);
  
  return { messages, sendMessage, isStreaming, currentResponse };
}

3. Backend Implementation với Node.js/Express


// streaming-server.js - Node.js Express server với SSE

const express = require('express');
const cors = require('cors');
const { createProxyMiddleware } = require('http-proxy-middleware');
const https = require('https');
const http = require('http');

const app = express();
const PORT = process.env.PORT || 3000;

// Middleware
app.use(cors({
  origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'],
  credentials: true
}));
app.use(express.json());

// Rate limiting per IP
const rateLimitStore = new Map();
const RATE_LIMIT = {
  windowMs: 60 * 1000, // 1 phút
  maxRequests: 30
};

function rateLimiter(req, res, next) {
  const ip = req.ip;
  const now = Date.now();
  const record = rateLimitStore.get(ip) || { count: 0, resetTime: now + RATE_LIMIT.windowMs };
  
  if (now > record.resetTime) {
    record.count = 0;
    record.resetTime = now + RATE_LIMIT.windowMs;
  }
  
  record.count++;
  rateLimitStore.set(ip, record);
  
  if (record.count > RATE_LIMIT.maxRequests) {
    return res.status(429).json({
      error: 'Too many requests',
      retryAfter: Math.ceil((record.resetTime - now) / 1000)
    });
  }
  
  next();
}

// SSE Streaming endpoint
app.post('/api/chat/stream', rateLimiter, async (req, res) => {
  const apiKey = process.env.HOLYSHEEP_API_KEY;
  const baseUrl = 'https://api.holysheep.ai/v1';
  
  if (!apiKey) {
    return res.status(500).json({ error: 'API key not configured' });
  }
  
  const { messages, model = 'gpt-4.1', temperature = 0.7, max_tokens = 2000 } = req.body;
  
  // Validation
  if (!Array.isArray(messages) || messages.length === 0) {
    return res.status(400).json({ error: 'messages array is required' });
  }
  
  // Set SSE headers
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');
  res.setHeader('X-Accel-Buffering', 'no'); // Nginx buffering disable
  res.flushHeaders();
  
  const postData = JSON.stringify({
    model,
    messages,
    stream: true,
    temperature,
    max_tokens
  });
  
  const options = {
    hostname: 'api.holysheep.ai',
    port: 443,
    path: '/v1/chat/completions',
    method: 'POST',
    headers: {
      'Authorization': Bearer ${apiKey},
      'Content-Type': 'application/json',
      'Content-Length': Buffer.byteLength(postData),
      'Accept': 'text/event-stream'
    }
  };
  
  const proxyReq = https.request(options, (proxyRes) => {
    proxyRes.on('data', (chunk) => {
      res.write(chunk);
    });
    
    proxyRes.on('end', () => {
      res.end();
    });
    
    proxyRes.on('error', (err) => {
      console.error('Proxy response error:', err);
      res.write(data: ${JSON.stringify({ error: err.message })}\n\n);
      res.end();
    });
  });
  
  proxyReq.on('error', (err) => {
    console.error('Proxy request error:', err);
    if (!res.headersSent) {
      res.status(500).json({ error: err.message });
    } else {
      res.write(data: ${JSON.stringify({ error: err.message })}\n\n);
      res.end();
    }
  });
  
  proxyReq.write(postData);
  proxyReq.end();
  
  // Keep-alive management
  req.on('close', () => {
    proxyReq.destroy();
  });
});

// Health check endpoint
app.get('/health', (req, res) => {
  res.json({ status: 'ok', timestamp: new Date().toISOString() });
});

// Streaming health check
app.get('/api/health/stream', (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.flushHeaders();
  res.write(data: ${JSON.stringify({ status: 'ok' })}\n\n);
  setTimeout(() => res.end(), 100);
});

app.listen(PORT, () => {
  console.log(HolySheep SSE Proxy running on port ${PORT});
  console.log(Streaming endpoint: POST /api/chat/stream);
});

4. Authentication Middleware nâng cao cho Production


auth_middleware.py - JWT + API Key authentication

from functools import wraps from flask import request, jsonify, g import jwt import hashlib import time from datetime import datetime, timedelta

Trong production, lưu trong Redis hoặc database

API_KEY_STORE = { 'key_live_abc123': {'user_id': 'user_1', 'plan': 'pro', 'rate_limit': 100}, 'key_live_def456': {'user_id': 'user_2', 'plan': 'basic', 'rate_limit': 30}, } JWT_SECRET = os.environ.get('JWT_SECRET', 'your-secret-key-change-in-production') def require_api_key(f): """ Decorator yêu cầu valid API key. Support cả API key trực tiếp và JWT token. """ @wraps(f) def decorated(*args, **kwargs): auth_header = request.headers.get('Authorization', '') # Case 1: Bearer JWT Token if auth_header.startswith('Bearer '): token = auth_header[7:] try: payload = jwt.decode(token, JWT_SECRET, algorithms=['HS256']) # Check token expiration if payload.get('exp', 0) < time.time(): return jsonify({'error': 'Token expired'}), 401 # Store user info in Flask g object g.user_id = payload.get('user_id') g.user_plan = payload.get('plan', 'basic') g.rate_limit = payload.get('rate_limit', 30) except jwt.ExpiredSignatureError: return jsonify({'error': 'Token expired'}), 401 except jwt.InvalidTokenError: return jsonify({'error': 'Invalid token'}), 401 # Case 2: Direct API Key elif auth_header.startswith('Bearer '): api_key = auth_header[7:] key_hash = hashlib.sha256(api_key.encode()).hexdigest() # Check in store (hoặc query database trong production) key_info = API_KEY_STORE.get(key_hash) if not key_info: return jsonify({'error': 'Invalid API key'}), 401 g.user_id = key_info['user_id'] g.user_plan = key_info['plan'] g.rate_limit = key_info['rate_limit'] # Case 3: API Key in custom header (cho API gateway integration) elif request.headers.get('X-API-Key'): api_key = request.headers.get('X-API-Key') # Validate và extract info... else: return jsonify({ 'error': 'Missing authentication', 'hint': 'Provide Authorization: Bearer header' }), 401 return f(*args, **kwargs) return decorated def generate_user_token(user_id: str, plan: str = 'basic') -> str: """Generate JWT token cho user (dùng trong auth service riêng).""" payload = { 'user_id': user_id, 'plan': plan, 'rate_limit': {'basic': 30, 'pro': 100, 'enterprise': 500}[plan], 'exp': datetime.utcnow() + timedelta(hours=24) } return jwt.encode(payload, JWT_SECRET, algorithm='HS256') def check_rate_limit(user_id: str, endpoint: str) -> tuple[bool, int]: """ Kiểm tra rate limit cho user. Returns: (is_allowed, remaining_requests) """ # Trong production, dùng Redis với sliding window key = f"rate:{user_id}:{endpoint}:{int(time.time() / 60)}" # Giả lập với in-memory store # Thay bằng Redis: rate = redis_client.incr(key); redis_client.expire(key, 60) current = rate_limit_store.get(key, 0) limit = g.rate_limit if hasattr(g, 'rate_limit') else 30 if current >= limit: return False, 0 rate_limit_store[key] = current + 1 return True, limit - current - 1

Apply vào endpoint

@app.route('/api/chat/stream', methods=['POST']) @require_api_key def chat_stream(): # ... rest of implementation pass

Xử lý Error Cases và Retry Logic

Trong production, SSE streaming cần handle nhiều error scenarios một cách graceful để đảm bảo user experience tốt nhất. Dưới đây là comprehensive error handling strategy.


// error-handling.ts - Comprehensive SSE error handling

enum StreamErrorType {
  NETWORK_ERROR = 'NETWORK_ERROR',
  AUTH_ERROR = 'AUTH_ERROR',
  RATE_LIMIT = 'RATE_LIMIT',
  SERVER_ERROR = 'SERVER_ERROR',
  TIMEOUT = 'TIMEOUT',
  PARSE_ERROR = 'PARSE_ERROR',
  ABORTED = 'ABORTED'
}

interface StreamError {
  type: StreamErrorType;
  message: string;
  retryable: boolean;
  retryAfter?: number; // seconds
  partialContent?: string; // Content received before error
}

interface RetryConfig {
  maxRetries: number;
  baseDelay: number; // ms
  maxDelay: number; // ms
  backoffMultiplier: number;
}

const DEFAULT_RETRY_CONFIG: RetryConfig = {
  maxRetries: 3,
  baseDelay: 1000,
  maxDelay: 30000,
  backoffMultiplier: 2
};

class ResilientStreamingClient {
  private config: RetryConfig;
  
  constructor(config: Partial<RetryConfig> = {}) {
    this.config = { ...DEFAULT_RETRY_CONFIG, ...config };
  }
  
  async streamWithRetry(
    messages: Message[],
    onChunk: (chunk: string) => void,
    onError: (error: StreamError) => void,
    signal?: AbortSignal
  ): Promise<string> {
    let lastError: StreamError | null = null;
    let fullContent = '';
    let retryCount = 0;
    
    while (retryCount <= this.config.maxRetries) {
      try {
        const content = await this.executeStream(messages, onChunk, signal);
        return content;
        
      } catch (error) {
        lastError = this.categorizeError(error);
        
        // Log error for monitoring
        console.error(Stream attempt ${retryCount + 1} failed:, lastError);
        
        // Notify about partial content
        if (lastError.partialContent) {
          onChunk(lastError.partialContent);
        }
        
        // Check if retryable
        if (!lastError.retryable || retryCount >= this.config.maxRetries) {
          onError(lastError);
          throw lastError;
        }
        
        // Calculate delay with exponential backoff + jitter
        const delay = this.calculateDelay(retryCount, lastError.retryAfter);
        console.log(Retrying in ${delay}ms...);
        
        await this.sleep(delay);
        retryCount++;
      }
    }
    
    onError(lastError!);
    throw lastError;
  }
  
  private categorizeError(error: any): StreamError {
    // Network errors
    if (error.name === 'TypeError' && error.message.includes('fetch')) {
      return {
        type: StreamErrorType.NETWORK_ERROR,
        message: 'Network connection failed. Please check your internet.',
        retryable: true
      };
    }
    
    // HTTP errors
    if (error.response) {
      const status = error.response.status;
      
      switch (status) {
        case 401:
        case 403:
          return {
            type: StreamErrorType.AUTH_ERROR,
            message: 'Authentication failed. Please check your API key.',
            retryable: false
          };
          
        case 429:
          return {
            type: StreamErrorType.RATE_LIMIT,
            message: 'Rate limit exceeded.',
            retryable: true,
            retryAfter: parseInt(error.response.headers['retry-after']) || 60
          };
          
        case 500:
        case 502:
        case 503:
          return {
            type: StreamErrorType.SERVER_ERROR,
            message: 'Server error. Please try again later.',
            retryable: true,
            retryAfter: 5
          };
          
        default:
          return {
            type: StreamErrorType.SERVER_ERROR,
            message: HTTP ${status}: ${error.message},
            retryable: status >= 500
          };
      }
    }
    
    // Timeout
    if (error.name === 'AbortError' || error.code === 'ETIMEDOUT') {
      return {
        type: StreamErrorType.TIMEOUT,
        message: 'Request timed out. The response is taking too long.',
        retryable: true,
        retryAfter: 10
      };
    }
    
    // Parse error
    if (error instanceof SyntaxError || error.type === 'PARSE_ERROR') {
      return {
        type: StreamErrorType.PARSE_ERROR,
        message: 'Failed to parse server response.',
        retryable: true,
        retryAfter: 5
      };
    }
    
    // Unknown error
    return {
      type: StreamErrorType.NETWORK_ERROR,
      message: error.message || 'Unknown error occurred',
      retryable: false
    };
  }
  
  private calculateDelay(retryCount: number, serverRetryAfter?: number): number {
    // Use server-suggested delay if available
    if (serverRetryAfter) {
      return serverRetryAfter * 1000;
    }
    
    // Exponential backoff
    const exponentialDelay = this.config.baseDelay * 
      Math.pow(this.config.backoffMultiplier, retryCount);
    
    // Cap at max delay
    const cappedDelay = Math.min(exponentialDelay, this.config.maxDelay);
    
    // Add jitter (±25%)
    const jitter = cappedDelay * 0.25 * (Math.random() * 2 - 1);
    
    return Math.floor(cappedDelay + jitter);
  }
  
  private sleep(ms: number): Promise {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
  
  private async executeStream(
    messages: Message[],
    onChunk: (chunk: string) => void,
    signal?: AbortSignal
  ): Promise<string> {
    // Implementation of actual streaming request
    // (similar to previous examples)
  }
}

// Usage example with React
function ChatComponent() {
  const [messages, setMessages] = useState<Array<{role: string, content: string}>>([]);
  const [currentResponse, setCurrentResponse] = useState('');
  const [error, setError] = useState<StreamError | null>(null);
  
  const client = new ResilientStreamingClient({
    maxRetries: 3,
    baseDelay: 1000,
    maxDelay: 30000
  });
  
  const handleSend = async (input: string) => {
    setError(null);
    setCurrentResponse('');
    
    const newMessages = [...messages, { role: 'user', content: input }];
    
    try {
      await client.streamWithRetry(
        newMessages,
        (chunk) => setCurrentResponse(prev => prev + chunk),
        (err) => setError(err)
      );
    } catch (err) {
      // All retries exhausted
      console.error('Stream failed after all retries:', err);
    }
  };
  
  return (
    <div>
      {error && (
        <div className="error-banner">
          {error.message}
          {error.retryable && (
            <button onClick={() => handleSend(messages[messages.length - 1].content)}>
              Thử lại
            </button>
          )}
        </div>
      )}
      {/* Rest of UI */}
    </div>
  );
}

Lỗi thường gặp và cách khắc phục

1. Lỗi CORS khi streaming cross-domain

Mô tả lỗi: Browser chặn request với thông báo "Access to fetch at 'https://api.holysheep.ai' from origin has been blocked by CORS policy"

Nguyên nhân: Direct API call từ browser bị chặn do CORS restrictions. HolySheep API không set Access-Control-Allow-Origin header cho browser requests.

Mã khắc phục:


server.py - Thêm CORS headers cho SSE responses

from flask import Flask, Response from flask_cors import CORS app = Flask(__name__)

Configure CORS cho SSE endpoints

CORS(app, resources={ r"/api/*": { "origins": [ "https://your-frontend-domain.com", "https://www.your-frontend-domain.com" ], "methods": ["GET", "POST", "OPTIONS"], "allow_headers": ["Content-Type", "Authorization"], "expose_headers": ["X-Request-ID"], "supports_credentials": True, "max_age": 3600 # Cache preflight for 1 hour } } ) @app.route('/api/chat/stream', methods=['POST', 'OPTIONS']) def chat_stream(): # Handle preflight OPTIONS request if request.method == 'OPTIONS': response = Response() response.headers['Access-Control-Allow-Origin'] = request.headers.get('Origin') response.headers['Access-Control-Allow-Methods'] = 'POST, OPTIONS' response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization' response.headers['Access-Control-Max