Skip to content

Commit c026d90

Browse files
committed
Add advanced CLI options and host header support
Refactored proxy to use yargs for argument parsing, enabling new options for host header customization and TLS security control. Updated documentation and Docker references to reflect broader API proxy capabilities. Renamed CLI binary to 'api-proxy' and bumped version to 2.1.0.
1 parent dc9e8d2 commit c026d90

File tree

4 files changed

+299
-73
lines changed

4 files changed

+299
-73
lines changed

README.md

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,27 @@
1-
# OpenAI Proxy Docker
1+
# API Proxy
22

3-
[![Docker Pulls](https://img.shields.io/docker/pulls/aiql/openai-proxy-docker.svg)](https://hub.docker.com/r/aiql/openai-proxy-docker)
4-
[![LICENSE](https://img.shields.io/github/license/AI-QL/openai-proxy-docker)](https://github.com/AI-QL/openai-proxy-docker/blob/main/LICENSE)
3+
[![Docker Pulls](https://img.shields.io/docker/pulls/aiql/api-proxy.svg)](https://hub.docker.com/r/aiql/api-proxy)
4+
[![LICENSE](https://img.shields.io/github/license/AI-QL/api-proxy)](https://github.com/AI-QL/api-proxy/blob/main/LICENSE)
55

6-
This repository provides a Dockerized proxy for accessing the OpenAI API, allowing for simplified and streamlined interaction with the model.
6+
> The old version (version <= 2.0.0) could be found at: [![Docker Pulls](https://img.shields.io/docker/pulls/aiql/openai-proxy-docker.svg)](https://hub.docker.com/r/aiql/openai-proxy-docker)
77
8-
With the [Docker image](https://hub.docker.com/r/aiql/openai-proxy-docker), you can easily deploy a proxy instance to serve as a gateway between your application and the OpenAI API, reducing the complexity of API interactions and enabling more efficient development.
8+
This repository offers both Dockerized and local proxy solutions for accessing any API, with specialized support for popular interfaces like the OpenAI API. It enables simplified and streamlined interactions with various LLMs.
99

10-
## Use case
10+
With the [Docker image](https://hub.docker.com/r/aiql/api-proxy), you can easily deploy a proxy instance to serve as a gateway between your application and the OpenAI API, reducing the complexity of API interactions and enabling more efficient development.
1111

12-
1. For users who are restricted from direct access to the OpenAI API, particularly those in countries where OpenAI will be blocking API access starting July 2024
13-
2. For users who need to access private APIs that lack Cross-Origin Resource Sharing (CORS) headers, this solution provides a proxy to bypass CORS restrictions and enable seamless API interactions.
12+
## Use cases
13+
14+
### 1. Geo-restricted API Access | 地域限制API访问
15+
For users who are restricted from direct access to the OpenAI API, particularly those in countries where OpenAI will be blocking API access starting July 2024.
16+
17+
### 2. CORS Bypass | 跨域限制突破
18+
For users who need to access private APIs that lack Cross-Origin Resource Sharing (CORS) headers, this solution provides a proxy to bypass CORS restrictions and enable seamless API interactions.
19+
20+
### 3. TLS Certificate Validation Bypass | TLS证书验证绕过
21+
Bypass client-side security checks, such as enterprise internal self-signed TLS certificates that cannot directly pass TLS certificate validation in many commonly used libraries.
22+
23+
### 4. Custom Host Header Routing | 自定义Host请求头
24+
Specify different Host headers than the URL itself. For some custom hosts, frontend projects cannot directly modify the Host header, requiring a proxy to separately define the URL and Host header parameters.
1425

1526
## Demo
1627

@@ -27,7 +38,7 @@ With the [Docker image](https://hub.docker.com/r/aiql/openai-proxy-docker), you
2738
Execute this command to start the proxy with default settings:
2839

2940
```shell
30-
sudo docker run -d -p 9017:9017 aiql/openai-proxy-docker:latest
41+
sudo docker run -d -p 9017:9017 aiql/api-proxy:latest
3142
```
3243

3344
Then, you can access it by ```YOURIP:9017```
@@ -38,11 +49,12 @@ Then, you can access it by ```YOURIP:9017```
3849
3950
You can change default port and default target by setting `-e` in docker, which means that you can use it for any backend followed by OpenAPI format:
4051

41-
| Parameter | Default Value |
42-
| --------- | ------------- |
43-
| PORT | 9017 |
44-
| TARGET | https://api.openai.com |
45-
52+
| Parameter | Env Var | Default Value | Description |
53+
| --------- | ------- | ------------- | ----------- |
54+
| port | PORT | 9017 | Server port number (valid range: 1-65535) |
55+
| target | TARGET | https://api.openai.com | Target URL or API endpoint to connect to |
56+
| host | HOST | N/A (Inherited from the target URL) | Host header specifying the domain name |
57+
| secure | SECURE | true | Enables security features, such as TLS certificate validation |
4658

4759
## Run locally via NPX
4860

@@ -63,7 +75,7 @@ npx -y @ai-ql/api-proxy --target="https://api.deepinfra.com/v1/openai" --port="9
6375

6476
Click below to use the GitHub Codespace:
6577

66-
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/aiql-community/openai-proxy-docker?quickstart=1)
78+
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/aiql-community/api-proxy?quickstart=1)
6779

6880
Or fork this repo and create a codespace manually:
6981
1. Wait for env ready in your browser
@@ -85,7 +97,7 @@ Fork this repo and set `DOCKERHUB_USERNAME` and `DOCKERHUB_TOKEN` in your secret
8597

8698
Normally, the step should be:
8799

88-
1. [Fork](https://github.com/aiql-community/openai-proxy-docker/fork) this repo
100+
1. [Fork](https://github.com/aiql-community/api-proxy/fork) this repo
89101
2. Settings → Secrets and variables → Actions → New repository secret
90102

91103
## Docker Compose
@@ -98,7 +110,7 @@ You can apply this approach to other APIs, such as Nvidia NIM:
98110
```DOCKERFILE
99111
services:
100112
nvidia-proxy:
101-
image: aiql/openai-proxy-docker:latest
113+
image: aiql/api-proxy:latest
102114
container_name: nvidia-proxy
103115
environment:
104116
PORT: "9101"
@@ -146,9 +158,9 @@ services:
146158
- /var/run/docker.sock:/var/run/docker.sock:ro
147159
network_mode: bridge
148160

149-
openai-proxy:
150-
image: aiql/openai-proxy-docker:latest
151-
container_name: openai-proxy
161+
api-proxy:
162+
image: aiql/api-proxy:latest
163+
container_name: api-proxy
152164
environment:
153165
LETSENCRYPT_HOST: api.example.com
154166
VIRTUAL_HOST: api.example.com

index.js

Lines changed: 99 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,53 +3,93 @@
33
import express from 'express';
44
import { createProxyMiddleware } from 'http-proxy-middleware';
55

6+
import yargs from 'yargs';
7+
import { hideBin } from 'yargs/helpers';
8+
69
// ========== Default Config ==========
710
const defaultPort = 9017;
811
const defaultTarget = 'https://api.openai.com';
912

1013
// ========== Core Proxy App ==========
11-
function createProxyApp(target) {
12-
const app = express();
13-
14-
app.use('/', createProxyMiddleware({
15-
target: target,
16-
changeOrigin: true,
17-
on: {
18-
proxyReq: (proxyReq, req, res) => {
19-
proxyReq.removeHeader('x-forwarded-for');
20-
proxyReq.removeHeader('x-real-ip');
21-
},
22-
proxyRes: (proxyRes, req, res) => {
23-
proxyRes.headers['Access-Control-Allow-Origin'] = '*';
24-
proxyRes.headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept, api_key, Authorization';
25-
},
26-
error: (err, req, res) => {
27-
console.error('Proxy error:', err);
28-
res.status(500).json({
29-
error: 'Proxy Error',
30-
message: err.message
31-
});
32-
},
14+
function createProxyApp(target, host) {
15+
const app = express();
16+
17+
app.use('/', createProxyMiddleware(
18+
{
19+
...target,
20+
changeOrigin: true,
21+
on: {
22+
proxyReq: (proxyReq, req, res) => {
23+
proxyReq.removeHeader('x-forwarded-for');
24+
proxyReq.removeHeader('x-real-ip');
25+
26+
if (host) {
27+
proxyReq.setHeader('Host', host);
28+
}
29+
30+
},
31+
proxyRes: (proxyRes, req, res) => {
32+
proxyRes.headers['Access-Control-Allow-Origin'] = '*';
33+
proxyRes.headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept, api_key, Authorization';
34+
},
35+
error: (err, req, res) => {
36+
console.error('Proxy error:', err);
37+
res.status(500).json({
38+
error: 'Proxy Error',
39+
message: err.message
40+
});
3341
},
42+
},
3443
}));
35-
36-
return app;
44+
45+
return app;
3746
}
3847

3948
// ========== Main App ==========
4049
const parseArguments = () => {
41-
const args = process.argv.slice(2);
42-
const result = {};
43-
44-
args.forEach(arg => {
45-
if (arg.startsWith('--port=')) {
46-
result.port = arg.split('=')[1];
47-
} else if (arg.startsWith('--target=')) {
48-
result.target = arg.split('=')[1];
49-
}
50-
});
51-
52-
return result;
50+
const argv = yargs(hideBin(process.argv))
51+
.options({
52+
port: {
53+
type: 'number',
54+
demandOption: false,
55+
describe: 'Server port number (valid range: 1-65535)',
56+
alias: 'P'
57+
},
58+
target: {
59+
type: 'string',
60+
demandOption: false,
61+
describe: 'Target URL or API endpoint to connect to',
62+
alias: 'T'
63+
},
64+
host: {
65+
type: 'string',
66+
demandOption: false,
67+
describe: 'Host header specifying the domain name',
68+
alias: 'H'
69+
},
70+
secure: {
71+
type: 'boolean',
72+
demandOption: false,
73+
describe: 'Enables security features, such as TLS certificate validation',
74+
alias: 'S'
75+
}
76+
})
77+
.check(argv => {
78+
if (argv.port !== undefined) {
79+
if (!Number.isInteger(argv.port) || argv.port <= 0 || argv.port > 65535) {
80+
throw new Error('Port must be an integer between 1 and 65535');
81+
}
82+
}
83+
if (argv.security !== undefined && argv.security !== false) {
84+
throw new Error('Do not set --security to true. If provided, --security must be false (use --security=false) because security features are enabled by default.');
85+
}
86+
return true;
87+
}).strict()
88+
.help().alias('help', 'h')
89+
.argv;
90+
91+
return argv;
92+
5393
};
5494

5595
const validatePort = (port) => {
@@ -73,19 +113,33 @@ const runProxy = () => {
73113
const args = parseArguments();
74114
const port = args.port || process.env.PORT || defaultPort;
75115
const target = args.target || process.env.TARGET || defaultTarget;
76-
116+
const host = args.host || process.env.HOST;
117+
const secure = args.secure ?? process.env.SECURE
118+
77119
const validatedPort = validatePort(port);
78120
const validatedTarget = validateTarget(target);
79-
80-
const app = createProxyApp(validatedTarget);
81-
121+
122+
const validatedProxyParams = {
123+
target: validatedTarget
124+
}
125+
126+
if (secure === 'false' || secure === false) {
127+
validatedProxyParams.secure = false
128+
}
129+
130+
console.log(validatedProxyParams)
131+
132+
const app = createProxyApp(validatedProxyParams, host);
133+
82134
const server = app.listen(validatedPort, () => {
83135
console.log('API Proxy running:');
84-
console.log(` Local: http://localhost:${validatedPort}`);
85-
console.log(` Target: ${validatedTarget}`);
136+
console.log(` Local : http://localhost:${validatedPort}`);
137+
console.log(` Target : ${validatedTarget}`);
138+
console.log(` Host : ${host ? host : 'Inherited from the target URL'}`);
139+
console.log(` Secure : ${validatedProxyParams.secure ?? 'Enabled by default'}`);
86140
console.log('\nPress Ctrl+C to stop');
87141
});
88-
142+
89143
server.on('error', (err) => {
90144
if (err.code === 'EADDRINUSE') {
91145
console.error(`❌ Port ${validatedPort} is already in use`);
@@ -94,12 +148,12 @@ const runProxy = () => {
94148
}
95149
process.exit(1);
96150
});
97-
151+
98152
process.on('SIGINT', () => {
99153
console.log('\nShutting down proxy...');
100154
server.close(() => process.exit(0));
101155
});
102-
156+
103157
} catch (error) {
104158
console.error(`❌ Error: ${error.message}`);
105159
process.exit(1);

0 commit comments

Comments
 (0)