1010 * 4. Respects domain whitelisting enforced by Squid
1111 */
1212
13- const express = require ( 'express' ) ;
14- const { createProxyMiddleware } = require ( 'http-proxy-middleware' ) ;
13+ const http = require ( 'http' ) ;
14+ const https = require ( 'https' ) ;
15+ const { URL } = require ( 'url' ) ;
16+ const { HttpsProxyAgent } = require ( 'https-proxy-agent' ) ;
1517
1618// Read API keys from environment (set by docker-compose)
1719const OPENAI_API_KEY = process . env . OPENAI_API_KEY ;
1820const ANTHROPIC_API_KEY = process . env . ANTHROPIC_API_KEY ;
1921
2022// Squid proxy configuration (set via HTTP_PROXY/HTTPS_PROXY in docker-compose)
21- const HTTP_PROXY = process . env . HTTP_PROXY ;
22- const HTTPS_PROXY = process . env . HTTPS_PROXY ;
23+ const HTTPS_PROXY = process . env . HTTPS_PROXY || process . env . HTTP_PROXY ;
2324
2425console . log ( '[API Proxy] Starting AWF API proxy sidecar...' ) ;
25- console . log ( `[API Proxy] HTTP_PROXY: ${ HTTP_PROXY } ` ) ;
2626console . log ( `[API Proxy] HTTPS_PROXY: ${ HTTPS_PROXY } ` ) ;
2727if ( OPENAI_API_KEY ) {
2828 console . log ( '[API Proxy] OpenAI API key configured' ) ;
@@ -31,72 +31,126 @@ if (ANTHROPIC_API_KEY) {
3131 console . log ( '[API Proxy] Anthropic API key configured' ) ;
3232}
3333
34- // Create Express app
35- const app = express ( ) ;
36-
37- // Health check endpoint
38- app . get ( '/health' , ( req , res ) => {
39- res . status ( 200 ) . json ( {
40- status : 'healthy' ,
41- service : 'awf-api-proxy' ,
42- squid_proxy : HTTP_PROXY || 'not configured' ,
43- providers : {
44- openai : ! ! OPENAI_API_KEY ,
45- anthropic : ! ! ANTHROPIC_API_KEY
34+ // Create proxy agent for routing through Squid
35+ const proxyAgent = HTTPS_PROXY ? new HttpsProxyAgent ( HTTPS_PROXY ) : undefined ;
36+ if ( ! proxyAgent ) {
37+ console . warn ( '[API Proxy] WARNING: No HTTPS_PROXY configured, requests will go direct' ) ;
38+ }
39+
40+ /**
41+ * Forward a request to the target API, injecting auth headers and routing through Squid.
42+ */
43+ function proxyRequest ( req , res , targetHost , injectHeaders ) {
44+ // Build target URL
45+ const targetUrl = new URL ( req . url , `https://${ targetHost } ` ) ;
46+
47+ // Read the request body
48+ const chunks = [ ] ;
49+ req . on ( 'data' , chunk => chunks . push ( chunk ) ) ;
50+ req . on ( 'end' , ( ) => {
51+ const body = Buffer . concat ( chunks ) ;
52+
53+ // Copy incoming headers, inject auth headers
54+ const headers = { ...req . headers } ;
55+ delete headers . host ; // Replace with target host
56+ Object . assign ( headers , injectHeaders ) ;
57+
58+ const options = {
59+ hostname : targetHost ,
60+ port : 443 ,
61+ path : targetUrl . pathname + targetUrl . search ,
62+ method : req . method ,
63+ headers,
64+ agent : proxyAgent , // Route through Squid
65+ } ;
66+
67+ const proxyReq = https . request ( options , ( proxyRes ) => {
68+ res . writeHead ( proxyRes . statusCode , proxyRes . headers ) ;
69+ proxyRes . pipe ( res ) ;
70+ } ) ;
71+
72+ proxyReq . on ( 'error' , ( err ) => {
73+ console . error ( `[API Proxy] Error proxying to ${ targetHost } : ${ err . message } ` ) ;
74+ if ( ! res . headersSent ) {
75+ res . writeHead ( 502 , { 'Content-Type' : 'application/json' } ) ;
76+ }
77+ res . end ( JSON . stringify ( { error : 'Proxy error' , message : err . message } ) ) ;
78+ } ) ;
79+
80+ if ( body . length > 0 ) {
81+ proxyReq . write ( body ) ;
4682 }
83+ proxyReq . end ( ) ;
4784 } ) ;
48- } ) ;
85+ }
86+
87+ // Health port is always 10000 — this is what Docker healthcheck hits
88+ const HEALTH_PORT = 10000 ;
4989
5090// OpenAI API proxy (port 10000)
5191if ( OPENAI_API_KEY ) {
52- app . use ( createProxyMiddleware ( {
53- target : 'https://api.openai.com' ,
54- changeOrigin : true ,
55- secure : true ,
56- onProxyReq : ( proxyReq , req , res ) => {
57- // Inject Authorization header
58- proxyReq . setHeader ( 'Authorization' , `Bearer ${ OPENAI_API_KEY } ` ) ;
59- console . log ( `[OpenAI Proxy] ${ req . method } ${ req . url } ` ) ;
60- } ,
61- onError : ( err , req , res ) => {
62- console . error ( `[OpenAI Proxy] Error: ${ err . message } ` ) ;
63- res . status ( 502 ) . json ( { error : 'Proxy error' , message : err . message } ) ;
92+ const server = http . createServer ( ( req , res ) => {
93+ if ( req . url === '/health' && req . method === 'GET' ) {
94+ res . writeHead ( 200 , { 'Content-Type' : 'application/json' } ) ;
95+ res . end ( JSON . stringify ( {
96+ status : 'healthy' ,
97+ service : 'awf-api-proxy' ,
98+ squid_proxy : HTTPS_PROXY || 'not configured' ,
99+ providers : { openai : true , anthropic : ! ! ANTHROPIC_API_KEY } ,
100+ } ) ) ;
101+ return ;
102+ }
103+
104+ console . log ( `[OpenAI Proxy] ${ req . method } ${ req . url } ` ) ;
105+ proxyRequest ( req , res , 'api.openai.com' , {
106+ 'Authorization' : `Bearer ${ OPENAI_API_KEY } ` ,
107+ } ) ;
108+ } ) ;
109+
110+ server . listen ( HEALTH_PORT , '0.0.0.0' , ( ) => {
111+ console . log ( `[API Proxy] OpenAI proxy listening on port ${ HEALTH_PORT } ` ) ;
112+ } ) ;
113+ } else {
114+ // No OpenAI key — still need a health endpoint on port 10000 for Docker healthcheck
115+ const server = http . createServer ( ( req , res ) => {
116+ if ( req . url === '/health' && req . method === 'GET' ) {
117+ res . writeHead ( 200 , { 'Content-Type' : 'application/json' } ) ;
118+ res . end ( JSON . stringify ( {
119+ status : 'healthy' ,
120+ service : 'awf-api-proxy' ,
121+ squid_proxy : HTTPS_PROXY || 'not configured' ,
122+ providers : { openai : false , anthropic : ! ! ANTHROPIC_API_KEY } ,
123+ } ) ) ;
124+ return ;
64125 }
65- } ) ) ;
66126
67- app . listen ( 10000 , '0.0.0.0' , ( ) => {
68- console . log ( '[API Proxy] OpenAI proxy listening on port 10000' ) ;
69- console . log ( '[API Proxy] Routing through Squid to api.openai.com' ) ;
127+ res . writeHead ( 404 , { 'Content-Type' : 'application/json' } ) ;
128+ res . end ( JSON . stringify ( { error : 'OpenAI proxy not configured (no OPENAI_API_KEY)' } ) ) ;
129+ } ) ;
130+
131+ server . listen ( HEALTH_PORT , '0.0.0.0' , ( ) => {
132+ console . log ( `[API Proxy] Health endpoint listening on port ${ HEALTH_PORT } (OpenAI not configured)` ) ;
70133 } ) ;
71134}
72135
73136// Anthropic API proxy (port 10001)
74137if ( ANTHROPIC_API_KEY ) {
75- const anthropicApp = express ( ) ;
138+ const server = http . createServer ( ( req , res ) => {
139+ if ( req . url === '/health' && req . method === 'GET' ) {
140+ res . writeHead ( 200 , { 'Content-Type' : 'application/json' } ) ;
141+ res . end ( JSON . stringify ( { status : 'healthy' , service : 'anthropic-proxy' } ) ) ;
142+ return ;
143+ }
76144
77- anthropicApp . get ( '/health' , ( req , res ) => {
78- res . status ( 200 ) . json ( { status : 'healthy' , service : 'anthropic-proxy' } ) ;
145+ console . log ( `[Anthropic Proxy] ${ req . method } ${ req . url } ` ) ;
146+ proxyRequest ( req , res , 'api.anthropic.com' , {
147+ 'x-api-key' : ANTHROPIC_API_KEY ,
148+ 'anthropic-version' : '2023-06-01' ,
149+ } ) ;
79150 } ) ;
80151
81- anthropicApp . use ( createProxyMiddleware ( {
82- target : 'https://api.anthropic.com' ,
83- changeOrigin : true ,
84- secure : true ,
85- onProxyReq : ( proxyReq , req , res ) => {
86- // Inject Anthropic authentication headers
87- proxyReq . setHeader ( 'x-api-key' , ANTHROPIC_API_KEY ) ;
88- proxyReq . setHeader ( 'anthropic-version' , '2023-06-01' ) ;
89- console . log ( `[Anthropic Proxy] ${ req . method } ${ req . url } ` ) ;
90- } ,
91- onError : ( err , req , res ) => {
92- console . error ( `[Anthropic Proxy] Error: ${ err . message } ` ) ;
93- res . status ( 502 ) . json ( { error : 'Proxy error' , message : err . message } ) ;
94- }
95- } ) ) ;
96-
97- anthropicApp . listen ( 10001 , '0.0.0.0' , ( ) => {
152+ server . listen ( 10001 , '0.0.0.0' , ( ) => {
98153 console . log ( '[API Proxy] Anthropic proxy listening on port 10001' ) ;
99- console . log ( '[API Proxy] Routing through Squid to api.anthropic.com' ) ;
100154 } ) ;
101155}
102156
0 commit comments