JUANCHI
InicioCVBlogLabContacto
Saltar al contenido
JUANCHI
InicioCVBlogLabContacto
Harté de esperar que alguien maintuviera la extensión de HAProxy para VS Code — así que la hice yo
Volver al blog
TypeScripthaproxyvscodelspdevtoolshomelabinfraestructura

Harté de esperar que alguien maintuviera la extensión de HAProxy para VS Code — así que la hice yo

La única extensión de HAProxy para VS Code no la tocaban desde 2019. Todos los días la usaba en el trabajo y en mi homelab, y todos los días me tragaba errores de sintaxis sin ningún feedback. Un día dije basta y construí gmm-haproxy-vscode: LSP propio, autocompletado por sección, validación multi-versión y go-to-definition.

8 de abril de 202610 min de lectura16 visualizacionesVer en Dev.to

Contenido

Contenido

Hay un momento específico en el que te das cuenta de que esperaste demasiado. Para mí fue un martes a las 11 de la noche, con tres ventanas de VS Code abiertas, un HAProxy 2.8 corriendo en Docker, y un error de validación que no entendía por qué estaba fallando. Abro la extensión de syntax highlighting que tenía instalada — la única que existía en el marketplace — y veo que el último commit fue en 2019.

  1. Cinco años sin un solo cambio. HAProxy pasó de la versión 2.0 a la 3.1 en ese tiempo. Metieron log-format-sd, reescribieron el comportamiento de option http-server-close, deprecaron directivas enteras. Y la extensión ahí, congelada en el tiempo como una momia digital, sin saber nada de nada.

Ese martes dije: basta. Si nadie lo va a hacer, lo hago yo.

Por qué HAProxy merece una extensión decente (y por qué casi nadie habla de esto)

Antes de meterme en el código, necesito darte contexto porque sé que el 80% de los devs que leen esto trabajan con Nginx o Traefik y creen que HAProxy es "esa cosa vieja que usan los bancos". Y sí, tienen razón en la segunda parte — los bancos lo usan, las telcos lo usan, los exchanges de crypto lo usan. Pero no porque sea viejo. Sino porque es brutalmente eficiente y tiene el modelo de configuración más expresivo que existe para un proxy.

Yo lo uso todos los días. En el trabajo para balancear tráfico entre microservicios. En mi homelab tengo un stack con HAProxy al frente, tres backends de servicios internos, rate limiting por IP, ACLs que distinguen tráfico de la LAN del tráfico que viene por VPN, y health checks cada cinco segundos. Todo en un archivo .cfg que tiene más de 400 líneas.

El problema es que ese archivo .cfg es básicamente texto plano para cualquier editor. Sin schema, sin LSP, sin nada. Escribís frontend mi-frontend y el editor no sabe que adentro de ese bloque hay directivas específicas que no existen en ningún otro contexto. Escribís backend y no te sugiere balance roundrobin versus balance leastconn. Usás una directiva que fue deprecada en 2.6 y nadie te avisa.

Eso es exactamente lo que fui a arreglar.

La arquitectura: no era tan simple como "un JSON con keywords"

La primera semana pensé que iba a ser fácil. "Meto todas las keywords en un archivo de gramática TextMate, le doy colores, listo". Esa ingenuidad duró exactamente hasta que abrí el spec completo de configuración de HAProxy.

HAProxy tiene una arquitectura de secciones: global, defaults, frontend, backend, listen, peers, resolvers, userlist, cache, program. Y cada sección acepta un subconjunto diferente de directivas. bind solo existe en frontend y listen. server solo existe en backend y listen. mode existe en varias pero con diferentes valores permitidos dependiendo del contexto.

Eso no se puede resolver con TextMate grammars. Eso necesita un Language Server Protocol real.

// src/server/haproxy-language-server.ts
// El corazón del LSP — inicializamos las capacidades que vamos a soportar
import {
  createConnection,
  TextDocuments,
  ProposedFeatures,
  CompletionItem,
  CompletionItemKind,
  TextDocumentSyncKind,
} from 'vscode-languageserver/node';
import { TextDocument } from 'vscode-languageserver-textdocument';
import { HaproxyParser } from './parser/haproxy-parser';
import { CompletionProvider } from './providers/completion-provider';
import { DiagnosticsProvider } from './providers/diagnostics-provider';

const connection = createConnection(ProposedFeatures.all);
const documents = new TextDocuments(TextDocument);

// Cuando el cliente (VS Code) nos pide completions, necesitamos saber
// en qué sección estamos parados para dar sugerencias contextuales
connection.onInitialize(() => ({
  capabilities: {
    textDocumentSync: TextDocumentSyncKind.Incremental,
    completionProvider: {
      resolveProvider: true,         // habilitamos el detalle de cada ítem
      triggerCharacters: [' ', '\t'] // autocompletado al tipear espacio o tab
    },
    definitionProvider: true,        // go-to-definition para backends
    codeActionProvider: true,        // quickfix para directivas deprecadas
    diagnosticProvider: {
      interFileDependencies: false,
      workspaceDiagnostics: false
    }
  }
}));

El parser fue la parte más complicada y la que más tiempo me llevó. HAProxy no tiene un formato estricto tipo YAML o JSON — es un lenguaje de configuración propio con indentación opcional, comentarios con #, continuación de línea con \, y una semántica de contexto que depende enteramente de en qué sección estás.

// src/server/parser/haproxy-parser.ts
// Parser que entiende el contexto de sección — clave para todo lo demás
export interface ParsedSection {
  type: SectionType;      // 'global' | 'defaults' | 'frontend' | 'backend' | etc.
  name: string | null;    // nombre de la sección (null para global/defaults)
  startLine: number;
  endLine: number;
  directives: ParsedDirective[];
}

export class HaproxyParser {
  parse(text: string): ParsedSection[] {
    const lines = text.split('\n');
    const sections: ParsedSection[] = [];
    let currentSection: ParsedSection | null = null;

    lines.forEach((line, lineNumber) => {
      const trimmed = line.trim();

      // Ignoramos comentarios y líneas vacías
      if (trimmed.startsWith('#') || trimmed === '') return;

      // Detectamos el inicio de una nueva sección
      const sectionMatch = trimmed.match(
        /^(global|defaults|frontend|backend|listen|peers|resolvers|userlist|cache|program)\s*(\S*)$/
      );

      if (sectionMatch) {
        // Cerramos la sección anterior si existe
        if (currentSection) {
          currentSection.endLine = lineNumber - 1;
          sections.push(currentSection);
        }

        // Iniciamos la nueva sección con su tipo y nombre
        currentSection = {
          type: sectionMatch[1] as SectionType,
          name: sectionMatch[2] || null,
          startLine: lineNumber,
          endLine: -1, // se completa cuando encontramos la próxima sección
          directives: []
        };
        return;
      }

      // Si estamos dentro de una sección, parseamos la directiva
      if (currentSection) {
        currentSection.directives.push(
          this.parseDirective(trimmed, lineNumber)
        );
      }
    });

    // No olvidemos cerrar la última sección
    if (currentSection) {
      (currentSection as ParsedSection).endLine = lines.length - 1;
      sections.push(currentSection as ParsedSection);
    }

    return sections;
  }
}

El autocompletado contextual: la feature que cambió todo

Una vez que tenía el parser funcionando, el autocompletado contextual fue casi natural. La idea es simple: cuando VS Code te pide completions, le preguntás al parser "¿en qué sección está el cursor?" y filtrás las sugerencias en base a eso.

¿Estás en un frontend? Te ofrezco bind, mode, acl, use_backend, default_backend, option, timeout... pero NO te ofrezco server ni balance, que son de backend. ¿Estás en global? Te ofrezco maxconn, daemon, log, ssl-default-bind-options... y nada más.

Esto parece trivial pero la diferencia en la experiencia de uso es brutal. En una configuración de HAProxy compleja con 10 secciones, el autocompletado que no entiende contexto te tira 200 opciones mezcladas. El mío te tira exactamente las que aplican a donde estás parado.

Pero el feature que más me enorgullece es el go-to-definition para backends. Si en tu frontend tenés default_backend mi-api y presionás F12, te lleva directo a la sección backend mi-api. Suena simple. Pero cuando tu config tiene 400 líneas y 15 backends, ese F12 te ahorra literalmente minutos de scroll todos los días.

Validación multi-versión: el quilombo de HAProxy 2.4 a 3.1

Acá es donde me volví un poco loco. HAProxy cambió bastante entre versiones. Cosas que eran válidas en 2.4 quedaron deprecadas en 2.6, y otras directamente removidas en 3.0. Si la extensión no sabe qué versión estás usando, los diagnósticos van a estar llenos de falsos positivos o falsos negativos.

La solución fue agregar una setting en VS Code donde el usuario declara su versión de HAProxy:

// .vscode/settings.json — configuración por workspace
{
  "gmm-haproxy.version": "2.8",
  "gmm-haproxy.strictMode": true
}

Y del lado del servidor, mantengo un registro de qué directivas existen en qué versión, cuáles fueron deprecadas y cuándo, y cuáles fueron removidas:

// src/server/schema/version-registry.ts
// Registry de directivas por versión — acá está el conocimiento duro de HAProxy
export interface DirectiveInfo {
  name: string;
  sections: SectionType[];        // en qué secciones es válida
  since: string;                  // versión en que fue introducida
  deprecated?: string;            // versión en que fue deprecada
  removed?: string;               // versión en que fue removida
  replacement?: string;           // directiva recomendada si fue deprecada
  description: string;
}

// Ejemplo real de directivas con su historial de versiones
export const DIRECTIVE_REGISTRY: DirectiveInfo[] = [
  {
    name: 'option forwardfor',
    sections: ['frontend', 'backend', 'listen', 'defaults'],
    since: '1.3',
    description: 'Agrega el header X-Forwarded-For con la IP real del cliente'
  },
  {
    name: 'reqadd',
    sections: ['frontend', 'listen', 'backend'],
    since: '1.3',
    deprecated: '2.2',         // deprecada en 2.2
    removed: '3.0',            // removida en 3.0
    replacement: 'http-request set-header', // la alternativa moderna
    description: '[DEPRECADA] Agregaba headers a la request. Usá http-request set-header'
  },
  {
    name: 'http-request set-header',
    sections: ['frontend', 'backend', 'listen'],
    since: '2.2',
    description: 'Modifica o agrega headers HTTP en la request entrante'
  }
  // ... y así con ~400 directivas más
];

Cuando el DiagnosticsProvider detecta que usás reqadd en una config con version 3.0, te tira un error con quickfix incluido: "Reemplazar por http-request set-header". Un click y listo.

Los errores que cometí (y que vos vas a cometer si hacés algo parecido)

Error 1: Subestimar el tiempo de startup del LSP. El primer prototipo parseaba el documento entero en cada keystroke. En archivos grandes, el lag era notable. La solución fue parsing incremental — solo re-parseás las secciones que cambiaron.

Error 2: No manejar configs incompletas. Mientras escribís, tu config está rota la mayoría del tiempo. El parser tiene que ser tolerante a errores y producir un AST parcial útil en lugar de explotar. Tomó dos semanas extra hacer el error recovery decente.

Error 3: Creer que la API de VS Code es estable. Entre la versión que leí en la doc y la versión que tenía instalada, había diferencias sutiles en cómo funcionaba onDocumentDiagnostic. Aprendí a siempre testear contra la versión mínima declarada en el engines.vscode del package.json.

Error 4: No tener un corpus de configs reales para testear. Armé un directorio con configs reales anonimizadas de mi homelab y del trabajo. Eso solo encontró más bugs que cualquier test unitario que escribí.

El resultado: lo que uso todos los días

Hoy gmm-haproxy-vscode tiene:

  • Syntax highlighting contextual — diferencia visualmente entre nombres de sección, directivas, valores, ACL names y comentarios
  • LSP propio con autocompletado filtrado por sección
  • Validación en tiempo real contra el schema de la versión declarada (2.4, 2.6, 2.8, 3.0, 3.1)
  • Go-to-definition para backends referenciados en use_backend y default_backend
  • Quickfix automático para directivas deprecadas
  • Hover documentation — pasás el mouse por cualquier directiva y te explica qué hace
  • Snippets para estructuras comunes: frontend básico, backend con health check, ACL de rate limiting

La semana pasada la usé para refactorizar toda la config de mi homelab de HAProxy 2.8 a 3.1. Sin la extensión, ese proceso hubiera sido un domingo entero de revisar el changelog y buscar directivas obsoletas a mano. Con la extensión, fueron dos horas — la mayoría del tiempo la pasé aplicando quickfixes.

Por qué hice esto y no simplemente usé Nginx

Alguien me va a preguntar eso, así que lo respondo antes. HAProxy hace cosas que Nginx no hace igual de bien. El modelo de ACLs de HAProxy es extraordinariamente expresivo. Podés tomar decisiones de routing basadas en headers, paths, source IPs, tiempo del día, peso del backend, número de conexiones activas — todo en el archivo de config, sin scripting. El health checking es más granular. El modelo de estadísticas via socket es más completo.

¿Es la herramienta correcta para todo? No. Para un proyecto personal chico, Traefik o Caddy son más cómodos. Pero cuando tenés tráfico real y necesitás control fino, HAProxy sigue siendo el rey. Y el rey se merece una extensión que no sea un zombie del 2019.

Si querés probar gmm-haproxy-vscode, la vas a encontrar en el VS Code Marketplace. Si encontrás un bug o una directiva que no reconoce — y seguro vas a encontrar, el spec de HAProxy es enorme — abrí un issue. Lo maintaingo activamente porque lo uso todos los días. Esa es la mejor garantía que puedo darte.

Compartir:

Comentarios (0)

Deja un comentario

No hay comentarios aún. ¡Sé el primero en opinar!

Artículos Relacionados

Vibe-coding vs stress-coding: cómo trabajo yo realmente con IA en proyectos que importan
OpiniónTypeScriptnextjs

Vibe-coding vs stress-coding: cómo trabajo yo realmente con IA en proyectos que importan

El vibe-coding es fantástico hasta que el proyecto tiene usuarios reales. Acá la diferencia concreta entre cómo uso IA para experimentar y cómo la uso cuando hay producción de por medio.

9 min44
El stack tecnológico perfecto en 2025: lo que elegiría si arrancara un proyecto hoy
OpiniónTypeScriptdesarrollo web

El stack tecnológico perfecto en 2025: lo que elegiría si arrancara un proyecto hoy

Después de años rompiendo cosas en producción, acá está mi stack ideal para 2025. Sin hype, sin vendor lock-in innecesario, y con las cicatrices suficientes para justificar cada decisión.

9 min114
TypeScript: los patrones que realmente uso todos los días
TutorialesTypeScriptjavascript

TypeScript: los patrones que realmente uso todos los días

Discriminated unions, branded types, generics avanzados y cómo pienso en tipos cuando programo. No es un tutorial académico — es lo que realmente uso en producción después de años de batallar con TypeScript.

10 min127

Categorías

Experimentos4Historia3Opinión3Reflexiones1Tecnología2Tutoriales4

Etiquetas

#nextjs#TypeScript#React#Full Stack#javascript#server-components#devops#node.js#desarrollo web#Gemma#linux#AI#LLM#productividad#claude code#WebGPU#ia#historia programador argentino#Tutorial#infraestructura#Portfolio#Performance#pnpm#npm#yarn#bun#package manager#monorepo#frontend#tooling#docker#backend#docker-compose#produccion#app-router#repomix#sistemas#elf#dynamic-linking#bajo-nivel#strace#haproxy#vscode#lsp#devtools#homelab#web-development#tailwind#railway#postmortem#Patrones de diseño#Programación#stack tecnologico 2025#postgresql#drizzle orm#optimizacion#web-performance#lighthouse#autobiografía tech#railway deploy#nativo digital#ai tools#desarrollo#reflexión técnica#anthropic#workflow#WebAssembly#inferencia en browser#WebLLM#edge inferencia#IA local#coding-agents#freestyle#sandboxes#seguridad#quantum computing#criptografía#seguridad web#post-quantum cryptography#TLS#JWT#vibe-coding#desarrollo-software#Browser#Edge#Inferencia Local#codebase visualization#github#análisis de código#developer tools#code review#Web Vitals#Historia#Best Practices#Carrera#Stack#Next.js#Herramientas#Reflexiones#WebDev#Opinión#Experimentos

Más Leídos

  • 01

    TypeScript: los patrones que realmente uso todos los días

    127 views
  • 02

    pnpm vs npm vs yarn vs bun: la comparativa definitiva que nadie te va a dar en 2025

    126 views
  • 03

    De DOS a Cloud: mi viaje de 33 años con la tecnología — desde una Amiga en 1994 hasta deployar en Railway con Next.js

    125 views
  • 04

    Next.js App Router: la guía que me hubiera gustado tener cuando migré de Pages Router

    124 views
  • 05

    Docker para desarrolladores Node.js: de cero a producción sin morir en el intento

    120 views

Newsletter

Recibe los últimos artículos directamente en tu inbox.

Categorías

Experimentos4Historia3Opinión3Reflexiones1Tecnología2Tutoriales4

Etiquetas

#nextjs#TypeScript#React#Full Stack#javascript#server-components#devops#node.js#desarrollo web#Gemma#linux#AI#LLM#productividad#claude code#WebGPU#ia#historia programador argentino#Tutorial#infraestructura#Portfolio#Performance#pnpm#npm#yarn#bun#package manager#monorepo#frontend#tooling#docker#backend#docker-compose#produccion#app-router#repomix#sistemas#elf#dynamic-linking#bajo-nivel#strace#haproxy#vscode#lsp#devtools#homelab#web-development#tailwind#railway#postmortem#Patrones de diseño#Programación#stack tecnologico 2025#postgresql#drizzle orm#optimizacion#web-performance#lighthouse#autobiografía tech#railway deploy#nativo digital#ai tools#desarrollo#reflexión técnica#anthropic#workflow#WebAssembly#inferencia en browser#WebLLM#edge inferencia#IA local#coding-agents#freestyle#sandboxes#seguridad#quantum computing#criptografía#seguridad web#post-quantum cryptography#TLS#JWT#vibe-coding#desarrollo-software#Browser#Edge#Inferencia Local#codebase visualization#github#análisis de código#developer tools#code review#Web Vitals#Historia#Best Practices#Carrera#Stack#Next.js#Herramientas#Reflexiones#WebDev#Opinión#Experimentos

Más Leídos

  • 01

    TypeScript: los patrones que realmente uso todos los días

    127 views
  • 02

    pnpm vs npm vs yarn vs bun: la comparativa definitiva que nadie te va a dar en 2025

    126 views
  • 03

    De DOS a Cloud: mi viaje de 33 años con la tecnología — desde una Amiga en 1994 hasta deployar en Railway con Next.js

    125 views
  • 04

    Next.js App Router: la guía que me hubiera gustado tener cuando migré de Pages Router

    124 views
  • 05

    Docker para desarrolladores Node.js: de cero a producción sin morir en el intento

    120 views

Newsletter

Recibe los últimos artículos directamente en tu inbox.