Tutorial 14: Creando el Método para los Headers de la API
¡Hola y bienvenido de nuevo!
En esta lección vamos a crear un método que nos permita configurar los headers necesarios para comunicarnos con la API de OpenAI. Los headers son fundamentales porque incluyen la autenticación y el tipo de contenido que estamos enviando. ¡Vamos a ello!
📋 Contenido del Tutorial
¿Qué son los headers HTTP?
Headers necesarios para la API de OpenAI
Creando el método getHeader
Diferencias entre ASR y Traducción
Código completo
Explicación detallada
Próximos pasos
📨 1. ¿Qué son los headers HTTP?
Definición
Los headers HTTP son metadatos que se envían junto con una solicitud o respuesta HTTP. Contienen información adicional sobre la petición, como autenticación, tipo de contenido, formato de respuesta, etc.
Estructura de un header
Nombre: Valor
Ejemplos:
Authorization: Bearer sk-proj-ABC123 Content-Type: application/json Accept: application/json
¿Para qué sirven?
| Header | Propósito | Ejemplo |
|---|---|---|
| Authorization | Autenticación | Bearer sk-proj-ABC123 |
| Content-Type | Tipo de contenido | application/json |
| Accept | Formato de respuesta | application/json |
| User-Agent | Identifica el cliente | MyApp/1.0 |
Visualización de headers
Cuando envías una solicitud, los headers se ven así:
POST /v1/audio/transcriptions HTTP/1.1 Host: api.openai.com Authorization: Bearer sk-proj-ABC123 Content-Type: multipart/form-data Content-Length: 12345 [datos del archivo]
🔑 2. Headers necesarios para la API de OpenAI
Headers obligatorios
| Header | Valor | ¿Por qué? |
|---|---|---|
| Authorization | Bearer TU_API_KEY | Autentica la solicitud |
| Content-Type | Variable según el endpoint | Indica el formato de los datos |
Headers para ASR (Transcripción)
[ 'Authorization: Bearer ' . API_TOKEN, 'Content-Type: multipart/form-data' ]
¿Por qué multipart/form-data?
Porque estamos enviando un archivo
Es el formato estándar para subir archivos
Headers para Traducción (Chat Completions)
[ 'Authorization: Bearer ' . API_TOKEN, 'Content-Type: application/json' ]
¿Por qué application/json?
Porque enviamos datos en formato JSON
Es el formato estándar para APIs REST
🏗️ 3. Creando el método getHeader
Estructura básica
public function getHeader() { if ($this->dataType === "ASR") { return [ 'Authorization: Bearer ' . API_TOKEN, 'Content-Type: multipart/form-data' ]; } else { return [ 'Authorization: Bearer ' . API_TOKEN, 'Content-Type: application/json' ]; } }
¿Qué hace este método?
Verifica el tipo de operación (
dataType)Si es ASR: Retorna headers para subida de archivos
Si es otro: Retorna headers para solicitudes JSON
Componentes del header
// 1. Autenticación - SIEMPRE necesaria 'Authorization: Bearer ' . API_TOKEN // 2. Tipo de contenido - Depende de la operación 'Content-Type: multipart/form-data' // Para ASR 'Content-Type: application/json' // Para otros
🔄 4. Diferencias entre ASR y Traducción
Comparativa de headers
| Aspecto | ASR (Transcripción) | Traducción (Chat) |
|---|---|---|
| Content-Type | multipart/form-data | application/json |
| Formato de datos | Archivo binario | JSON |
| Método HTTP | POST | POST |
| Endpoint | /audio/transcriptions | /chat/completions |
Ejemplo de solicitud ASR
POST /v1/audio/transcriptions HTTP/1.1 Host: api.openai.com Authorization: Bearer sk-proj-ABC123 Content-Type: multipart/form-data; boundary=----WebKitFormBoundary ------WebKitFormBoundary Content-Disposition: form-data; name="file"; filename="audio.mp3" Content-Type: audio/mpeg [datos binarios del archivo] ------WebKitFormBoundary Content-Disposition: form-data; name="model" whisper-1 ------WebKitFormBoundary--
Ejemplo de solicitud Traducción
POST /v1/chat/completions HTTP/1.1 Host: api.openai.com Authorization: Bearer sk-proj-ABC123 Content-Type: application/json { "model": "gpt-3.5-turbo", "messages": [ { "role": "system", "content": "Traduce al español" }, { "role": "user", "content": "Hello world" } ] }
💻 5. Código completo
Archivo: backend/classes/Whisper.php
<?php /** * Clase Whisper * * Maneja la subida, transcripción y traducción de archivos * utilizando la API de OpenAI */ class Whisper { /** * @var string Mensajes de error */ public $error; /** * @var string Tipo de operación (ASR o TRANSLATE) */ public $dataType; /** * @var PDO Conexión a la base de datos */ private $DB; /** * Constructor - Establece conexión a la base de datos */ public function __construct() { $db = new DB(); $this->DB = $db->connect(); } /** * Obtiene el mensaje de error actual * * @return string El mensaje de error */ public function errors() { return $this->error; } /** * Obtiene la URL de la API según el tipo de operación * * @return string URL del endpoint de la API */ public function getApiUrl() { if ($this->dataType === "ASR") { return "https://api.openai.com/v1/audio/transcriptions"; } else { return "https://api.openai.com/v1/chat/completions"; } } /** * Obtiene los headers necesarios para la API * * @return array Headers para la solicitud HTTP */ public function getHeader() { // Verificar que la API Key esté definida if (!defined('API_TOKEN')) { $this->error = "API_TOKEN no está definido"; return false; } // Headers para ASR (subida de archivos) if ($this->dataType === "ASR") { return [ 'Authorization: Bearer ' . API_TOKEN, 'Content-Type: multipart/form-data' ]; } // Headers para otros (JSON) else { return [ 'Authorization: Bearer ' . API_TOKEN, 'Content-Type: application/json' ]; } } /** * Sube un archivo al servidor * * @param array $file Arreglo $_FILES * @return string|false Ruta del archivo o false en caso de error */ public function upload($file) { // 1. Extraer información del archivo $fileTmp = $file['tmp_name']; $filename = basename($file['name']); $fileSize = $file['size']; $errors = $file['error']; $mime = $file['type']; // 2. Obtener la extensión del archivo $ext = pathinfo($filename, PATHINFO_EXTENSION); $ext = strtolower($ext); // 3. Obtener el directorio raíz del proyecto $parentDirectory = dirname(dirname(dirname(__FILE__))); // 4. Definir los tipos de archivo permitidos $allowedMedia = [ 'video/mp4', 'video/mpeg', 'audio/mpeg', 'audio/mpeg3', 'audio/wav' ]; // 5. Validar el tipo de archivo if (!in_array($mime, $allowedMedia)) { $this->error = "Formato de archivo inválido"; return false; } // 6. Validar el tamaño del archivo (máximo 20 MB) if ($fileSize > 20000000) { $this->error = "El archivo es demasiado grande (máximo 20 MB)"; return false; } // 7. Crear nombre único y subir el archivo $folder = 'files/'; $uniqueName = md5(time() . mt_rand()); $file = $folder . $uniqueName . '.' . $ext; // 8. Mover el archivo if (move_uploaded_file($fileTmp, $parentDirectory . '/' . $file)) { return $file; } else { $this->error = "Error al mover el archivo"; return false; } } /** * Prepara la solicitud completa para la API * * @param string $filePath Ruta del archivo * @return array|false Datos de la solicitud o false en caso de error */ public function prepareRequest($filePath) { // 1. Verificar que el archivo existe if (!file_exists($filePath)) { $this->error = "El archivo no existe"; return false; } // 2. Obtener URL y headers $url = $this->getApiUrl(); $headers = $this->getHeader(); if (!$headers) { return false; } // 3. Preparar los datos según el tipo if ($this->dataType === "ASR") { // Para ASR: usar el archivo directamente $postFields = [ 'file' => curl_file_create($filePath), 'model' => 'whisper-1', 'language' => 'es', 'response_format' => 'json' ]; } else { // Para traducción: leer el archivo y preparar JSON $content = file_get_contents($filePath); $postFields = json_encode([ 'model' => 'gpt-3.5-turbo', 'messages' => [ [ 'role' => 'system', 'content' => 'Eres un traductor profesional. Traduce el siguiente texto al español de forma precisa y natural.' ], [ 'role' => 'user', 'content' => $content ] ], 'temperature' => 0.7 ]); } // 4. Retornar todos los datos de la solicitud return [ 'url' => $url, 'headers' => $headers, 'postFields' => $postFields, 'dataType' => $this->dataType ]; } /** * Envía la solicitud a la API de OpenAI * * @param array $requestData Datos preparados * @return string|false Respuesta de la API o false en caso de error */ public function sendRequest($requestData) { // Inicializar cURL $ch = curl_init(); // Configurar cURL curl_setopt($ch, CURLOPT_URL, $requestData['url']); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_TIMEOUT, 120); // 2 minutos máximo // Configurar headers curl_setopt($ch, CURLOPT_HTTPHEADER, $requestData['headers']); // Configurar datos POST curl_setopt($ch, CURLOPT_POSTFIELDS, $requestData['postFields']); // Ejecutar la solicitud $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $curlError = curl_error($ch); // Cerrar cURL curl_close($ch); // Verificar errores if ($curlError) { $this->error = "Error cURL: " . $curlError; return false; } if ($httpCode !== 200) { $this->error = "Error HTTP: " . $httpCode . " - " . $response; return false; } return $response; } /** * Procesa la transcripción de un archivo * * @param string $filePath Ruta del archivo * @return string|false Texto transcrito o false en caso de error */ public function transcribe($filePath) { // Configurar el tipo de operación $this->dataType = "ASR"; // Preparar la solicitud $request = $this->prepareRequest($filePath); if (!$request) { return false; } // Enviar la solicitud $response = $this->sendRequest($request); if (!$response) { return false; } // Procesar la respuesta $data = json_decode($response, true); if (isset($data['text'])) { return $data['text']; } else { $this->error = "Respuesta inesperada de la API"; return false; } } } ?>
Archivo: backend/init.php (actualizado)
<?php /** * Archivo de inicialización de la aplicación */ // ============================================= // 1. CONFIGURACIÓN DE LA API KEY // ============================================= // Cargar variables de entorno desde .env function loadEnv($path = __DIR__ . '/../.env') { if (!file_exists($path)) { return false; } $lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); foreach ($lines as $line) { if (strpos($line, '#') === 0 || strpos($line, '=') === false) { continue; } list($key, $value) = explode('=', $line, 2); $key = trim($key); $value = trim($value, '"\''); putenv("$key=$value"); $_ENV[$key] = $value; } return true; } // Cargar .env loadEnv(); // Definir constantes define('API_TOKEN', getenv('OPENAI_API_KEY') ?: ''); define('APP_NAME', 'My Whisper AI'); define('MAX_FILE_SIZE', 20 * 1024 * 1024); // ============================================= // 2. VERIFICACIONES // ============================================= // Verificar API Key if (API_TOKEN === '') { die('⚠️ Error: La API Key de OpenAI no está configurada en .env'); } // ============================================= // 3. CARGAR CLASES // ============================================= require 'classes/DB.php'; require 'classes/Whisper.php'; // ============================================= // 4. CREAR INSTANCIAS // ============================================= $whisperObj = new Whisper(); // Configurar el tipo de operación (por defecto: ASR) $whisperObj->dataType = "ASR"; ?>
Archivo: index.php (sección PHP actualizada)
<?php include 'backend/init.php'; $error = null; $success = null; if ($_SERVER['REQUEST_METHOD'] === "POST") { if (isset($_FILES['file']) && !empty($_FILES['file']['name'])) { // 1. Subir el archivo $filePath = $whisperObj->upload($_FILES['file']); if ($filePath) { // 2. Configurar para transcripción $whisperObj->dataType = "ASR"; // 3. Obtener la URL $url = $whisperObj->getApiUrl(); // 4. Obtener los headers $headers = $whisperObj->getHeader(); // 5. Mostrar información para debug echo "<h3>Información de la solicitud:</h3>"; echo "<pre>"; echo "URL: " . $url . "\n"; echo "Headers:\n"; print_r($headers); echo "</pre>"; // 6. Aquí llamaremos a la API en la próxima lección } else { $error = $whisperObj->errors(); } } else { $error = "Por favor selecciona un archivo"; } } ?>
📖 6. Explicación detallada
El método getHeader paso a paso
public function getHeader() { // 1. Verificar que la constante existe if (!defined('API_TOKEN')) { $this->error = "API_TOKEN no está definido"; return false; } // 2. Decidir según el tipo de operación if ($this->dataType === "ASR") { // 3. Headers para subida de archivos return [ 'Authorization: Bearer ' . API_TOKEN, 'Content-Type: multipart/form-data' ]; } else { // 4. Headers para JSON return [ 'Authorization: Bearer ' . API_TOKEN, 'Content-Type: application/json' ]; } }
¿Qué hace cada header?
Authorization
Authorization: Bearer sk-proj-ABC123
Propósito: Autenticar la solicitud
Formato:
Bearer+ espacio +API_KEYObligatorio: Siempre
Content-Type (ASR)
Content-Type: multipart/form-data
Propósito: Enviar archivos binarios
Cuándo: Al subir archivos a la API
Formato: Multipart con boundary
Content-Type (Traducción)
Content-Type: application/json
Propósito: Enviar datos estructurados
Cuándo: Al enviar solicitudes JSON
Formato: JSON válido
🧪 7. Probando la funcionalidad
Prueba 1: Headers para ASR
$whisperObj->dataType = "ASR"; $headers = $whisperObj->getHeader(); print_r($headers);
Resultado esperado:
Array
(
[0] => Authorization: Bearer sk-proj-ABC123
[1] => Content-Type: multipart/form-data
)Prueba 2: Headers para Traducción
$whisperObj->dataType = "TRANSLATE"; $headers = $whisperObj->getHeader(); print_r($headers);
Resultado esperado:
Array
(
[0] => Authorization: Bearer sk-proj-ABC123
[1] => Content-Type: application/json
)Prueba 3: Verificar errores
// Si API_TOKEN no está definido $headers = $whisperObj->getHeader(); if ($headers === false) { echo "Error: " . $whisperObj->errors(); }
📊 8. Resumen de la clase Whisper hasta ahora
Propiedades
| Propiedad | Tipo | Descripción |
|---|---|---|
$error | string | Mensajes de error |
$dataType | string | Tipo de operación (ASR/TRANSLATE) |
$DB | PDO | Conexión a la base de datos |
Métodos
| Método | Parámetros | Retorno | Descripción |
|---|---|---|---|
__construct() | - | void | Constructor, conecta a BD |
errors() | - | string | Devuelve el error actual |
getApiUrl() | - | string | Devuelve la URL de la API |
getHeader() | - | array/false | Devuelve los headers HTTP |
upload() | $file | string/false | Sube un archivo |
prepareRequest() | $filePath | array/false | Prepara la solicitud |
sendRequest() | $requestData | string/false | Envía solicitud a API |
transcribe() | $filePath | string/false | Transcribe un archivo |
Diagrama de flujo con headers
┌─────────────────────────────────────────────────────┐ │ SOLICITUD A LA API │ ├─────────────────────────────────────────────────────┤ │ │ │ 1. getApiUrl() → Endpoint correcto │ │ ↓ │ │ 2. getHeader() → Headers correctos │ │ ↓ │ │ 3. ¿ASR? │ │ ├── Sí → multipart/form-data │ │ └── No → application/json │ │ ↓ │ │ 4. curl_setopt() → Configurar headers │ │ ↓ │ │ 5. curl_exec() → Enviar solicitud │ │ ↓ │ │ 6. Procesar respuesta │ │ │ └─────────────────────────────────────────────────────┘
🎯 9. Próximos pasos
Lo que hemos logrado
✅ Método getHeader() implementado
✅ Headers para ASR configurados
✅ Headers para Traducción configurados
✅ Verificación de API_TOKEN
✅ Preparación para cURL
Lo que viene en la próxima lección
En la siguiente lección vamos a:
Implementar cURL para enviar solicitudes HTTP
Enviar archivos a la API de OpenAI
Recibir respuestas de la API
Procesar los resultados
Avance del código de la próxima lección
// En Whisper.php - Método completo de envío public function sendToAPI($filePath) { // 1. Obtener URL $url = $this->getApiUrl(); // 2. Obtener headers $headers = $this->getHeader(); // 3. Configurar cURL $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); // ... más configuraciones // 4. Enviar solicitud $response = curl_exec($ch); // 5. Procesar respuesta return json_decode($response, true); }
❓ Preguntas frecuentes
¿Qué es un header HTTP?
Es información adicional que se envía con una solicitud HTTP
Incluye autenticación, tipo de contenido, etc.
¿Por qué necesito Authorization?
Para que OpenAI sepa quién está haciendo la solicitud
Asocia el uso con tu cuenta
¿Qué significa multipart/form-data?
Es un formato para enviar archivos y datos binarios
Se usa cuando subimos archivos
¿Qué significa application/json?
Es un formato para enviar datos estructurados
Se usa cuando enviamos objetos JSON
¿Puedo usar otros headers?
Sí, puedes añadir
Accept,User-Agent, etc.Pero los dos mencionados son obligatorios
¿Qué pasa si el header es incorrecto?
La API responderá con un error
Generalmente un error HTTP 400 o 401
🎓 Ejercicio práctico
Ejercicio 1: Agregar más headers
Añade el header Accept para especificar el formato de respuesta:
public function getHeader() { $headers = [ 'Authorization: Bearer ' . API_TOKEN, 'Accept: application/json' ]; if ($this->dataType === "ASR") { $headers[] = 'Content-Type: multipart/form-data'; } else { $headers[] = 'Content-Type: application/json'; } return $headers; }
Ejercicio 2: Validar headers
Agrega validación para asegurar que los headers sean correctos:
public function validateHeaders($headers) { $required = ['Authorization', 'Content-Type']; foreach ($required as $header) { $found = false; foreach ($headers as $h) { if (strpos($h, $header . ':') === 0) { $found = true; break; } } if (!$found) { return false; } } return true; }
Ejercicio 3: Headers personalizados
Crea un método que permita añadir headers personalizados:
public $customHeaders = []; public function addHeader($header) { $this->customHeaders[] = $header; } public function getHeader() { $headers = [ 'Authorization: Bearer ' . API_TOKEN ]; // Añadir headers personalizados $headers = array_merge($headers, $this->customHeaders); // Añadir Content-Type según el tipo if ($this->dataType === "ASR") { $headers[] = 'Content-Type: multipart/form-data'; } else { $headers[] = 'Content-Type: application/json'; } return $headers; }
¡Excelente trabajo! Ahora tenemos los headers correctos para comunicarnos con la API de OpenAI. En la próxima lección, implementaremos la comunicación real con cURL.
Comentarios
Publicar un comentario