-
-
Save xfournet/068592b3d1ddd488427b874b23f707bf to your computer and use it in GitHub Desktop.
import proxy from 'http2-proxy'; | |
import type { Plugin, ProxyOptions } from 'vite'; | |
export const pluginHttp2Proxy = (): Plugin => { | |
let routes: Record<string, string | ProxyOptions>; | |
return { | |
name: 'vite-plugin-http2-proxy', | |
config: (config) => { | |
const { server } = config; | |
routes = server?.proxy ?? {}; | |
if (server) { | |
server.proxy = undefined; | |
} | |
return config; | |
}, | |
configureServer: ({ config: { logger }, middlewares }) => { | |
Object.entries(routes).forEach(([route, target]) => { | |
if (typeof target !== 'string') { | |
throw new Error('ProxyOptions target are not supported yet, only string target are currently supported'); | |
} | |
const { protocol, hostname, port } = new URL(target); | |
const options = { | |
protocol: protocol as 'http' | 'https', | |
hostname, | |
port: Number(port), | |
proxyTimeout: 60000, | |
}; | |
middlewares.use(route, (req, res) => { | |
proxy.web(req, res, { ...options, path: req.originalUrl }, (err) => { | |
if (err) { | |
logger.error(`[http2-proxy] Error when proxying request on '${req.originalUrl}'`, { timestamp: true, error: err }); | |
} | |
}); | |
}); | |
}); | |
}, | |
}; | |
}; |
Thank you for this! โค๏ธ
Thank you for this! โค๏ธ
i get :
error when starting dev server:
Error: Not supported yet
/vite.config.js.timestamp-1712238134948-ef04cf446917a.mjs:114:17
at Array.forEach ()
with:
"http2-proxy": "^5.0.53",
"vite": "^5.2.0"
@A2E9 yes, ProxyOptions are not implemented. Can you share your proxy config ?
@A2E9 yes, ProxyOptions are not implemented. Can you share your proxy config ?
here is my proxy config:
'/123': { target: '/456', secure: false, rewrite: (e) => e.replace(/\/123/, '/'), configure: (proxy) => proxy.on('proxyReq', (proxyReq) => { proxyReq.setHeader('Cookie', proxyReq.getHeader('auth') || ''); }), },
Here is my version with rewrite
and configure
support.
Note that you should update your proxy config in configure()
sections
proxy.on('proxyRes', (proxyRes) => {...}
=> proxy.onRes = async (req, res, proxyRes) => {...}
import type { Http2ServerRequest } from 'http2';
import type { Plugin, ProxyOptions } from 'vite';
import http2Proxy, { type http2WebOptions as Http2WebOptions } from 'http2-proxy';
const getOptions = (proxyOptions: string | ProxyOptions) => {
if (typeof proxyOptions !== 'string') {
const { protocol, hostname, port } = new URL(proxyOptions.target);
const { proxyTimeout } = proxyOptions;
return {
protocol: protocol as 'https',
hostname,
port: Number(port),
proxyTimeout,
rejectUnauthorized: false,
};
}
const { protocol, hostname, port } = new URL(proxyOptions);
return {
protocol: protocol as 'https',
hostname,
port: Number(port),
proxyTimeout: 60000,
rejectUnauthorized: false,
};
};
export default (): Plugin => {
let routes: Record<string, string | ProxyOptions>;
return {
name: 'vite-plugin-http2-proxy',
config: (config) => {
const { server } = config;
routes = server?.proxy ?? {};
if (server) {
server.proxy = undefined;
}
return config;
},
configureServer: ({ config: { logger }, middlewares }) => {
Object.entries(routes).forEach(([route, proxyOptions]) => {
const options = getOptions(proxyOptions);
middlewares.use(route, (req, res) => {
const http2Options: Http2WebOptions = { ...options };
if (typeof proxyOptions !== 'string') {
if (proxyOptions.rewrite) {
http2Options.path = proxyOptions.rewrite(req.originalUrl);
} else {
http2Options.path = req.originalUrl;
}
if (proxyOptions.configure) {
proxyOptions.configure(http2Options, proxyOptions);
}
}
http2Proxy.web(req as Http2ServerRequest, res, http2Options, (err) => {
if (err) {
logger.error(`[http2-proxy] Error when proxying request on '${req.originalUrl}'`, {
timestamp: true,
error: err,
});
}
});
});
});
},
};
};
@c1aphas I didnโt quite understand what configure
should look like...
[vite] Internal server error: proxy.on is not a function
configure(proxy) {
// before
// proxy.on('proxyRes', (proxyRes) => {...}
// now
proxy.onRes = async (req, res, proxyRes) => {...}
}
Here is my version with
rewrite
andconfigure
support. Note that you should update your proxy config inconfigure()
sections
proxy.on('proxyRes', (proxyRes) => {...}
=>proxy.onRes = async (req, res, proxyRes) => {...}
import type { Http2ServerRequest } from 'http2'; import type { Plugin, ProxyOptions } from 'vite'; import http2Proxy, { type http2WebOptions as Http2WebOptions } from 'http2-proxy'; ....
This doesn't seem to work well with graphql subscriptions (stream types). Any insight as to why?
^ Are there any error messages or strange behaviours that you've noticed?
^ Are there any error messages or strange behaviours that you've noticed?
Hi Seb,
It says "proxy timeout". I tried to increase the timeout limit with no success. We are using server-side events, so its an http stream call that is open for a long time. It only does this on our http streams.
Appreciate the help!
@c1aphas I'm seeing a lack of 304 status on unchanged files (the thousands of them I have ๐ )
is there a secret to retaining browser-cache?
Edit: my network tab's "Disable browser cache" checkbox was checked again ๐
But now I get a bunch of net::ERR_HTTP2_PROTOCOL_ERROR
My code (includes mkcert)
import { $ } from 'execa';
import http2Proxy from 'http2-proxy';
import { mkdir, readFile, stat } from 'node:fs/promises';
import path from 'node:path';
const defaultCacheDir = 'node_modules/.vite';
const domains = ['localhost'];
async function createCert(keyPath, certPath) {
const dir = path.dirname(keyPath);
const keyFile = path.basename(keyPath);
const certFile = path.basename(certPath);
await mkdir(dir, { recursive: true });
await $({ cwd: dir })`mkcert --key-file ${keyFile} --cert-file ${certFile} ${domains.join(' ')}`;
}
/**
* Original implementation: https://github.com/vitejs/vite-plugin-basic-ssl/blob/main/src/index.ts
*
* Also: https://gist.github.com/xfournet/068592b3d1ddd488427b874b23f707bf
* and: https://gist.github.com/xfournet/068592b3d1ddd488427b874b23f707bf?permalink_comment_id=5050309#gistcomment-5050309
*
* Also: https://github.com/vitejs/vite/issues/2725
*/
export function ssl() {
let proxy;
return {
name: 'vite-ssl-http2-proxy',
async configResolved(config) {
if (config.mode === 'test') {
console.warn(
`WARNING: not using SSL for tests. This is due to how CI is currently missing mkcert. But also we fully build our tests for running in the CLI, so SSL isn't needed anyway.`,
);
return;
}
proxy = config.server?.proxy;
/**
* We don't want vite to handle the proxy, else it disables http2
*/
if (config.server) {
config.server.proxy = undefined;
}
const certPair = await getCertPair((config.cacheDir ?? defaultCacheDir) + '/auditboard-vite-ssl');
const https = () => ({
cert: certPair.cert,
key: certPair.key,
maxSessionMemory: 1000,
peerMaxConcurrentStreams: 1000,
});
if (config.server.https === undefined || !!config.server.https) {
config.server.https = Object.assign({}, config.server.https, https());
}
if (config.preview.https === undefined || !!config.preview.https) {
config.preview.https = Object.assign({}, config.preview.https, https());
}
},
configureServer({ config: { logger }, middlewares }) {
if (!proxy) return;
Object.entries(proxy).forEach(([route, proxyOptions]) => {
const options = getOptions(proxyOptions);
middlewares.use(route, (req, res) => {
const http2Options = {
...options,
};
if (typeof proxyOptions !== 'string') {
if (proxyOptions.rewrite) {
http2Options.path = proxyOptions.rewrite(req.originalUrl);
} else {
http2Options.path = req.originalUrl;
}
if (proxyOptions.configure) {
proxyOptions.configure(http2Options, proxyOptions);
}
}
http2Proxy.web(req, res, http2Options, (err) => {
if (err) {
logger.error(`[http2-proxy] Error when proxying request on '${req.originalUrl}'`, {
timestamp: true,
error: err,
});
logger.error(err);
}
});
});
});
},
};
}
const SECOND = 1000;
const HOUR = SECOND * 60 * 60;
const MONTH = HOUR * 24 * 30;
/**
* @returns {Promise<{ cert: string, key: string }>}
*/
export async function getCertPair(cacheDir) {
const keyPath = path.join(cacheDir, 'key.pem');
const certPath = path.join(cacheDir, 'cert.pem');
async function readFiles() {
const [key, cert] = await Promise.all([readFile(keyPath), readFile(certPath)]);
return { key, cert };
}
try {
const [stats, content] = await Promise.all([stat(keyPath), readFiles()]);
if (Date.now() - stats.ctime.valueOf() > MONTH) {
throw new Error('cache is outdated.');
}
return content;
} catch {
await createCert(keyPath, certPath);
return readFiles();
}
}
function getOptions(proxyOptions) {
if (typeof proxyOptions !== 'string') {
const { protocol, hostname, port } = new URL(proxyOptions.target);
const { proxyTimeout } = proxyOptions;
return {
protocol,
hostname,
port: Number(port),
proxyTimeout,
rejectUnauthorized: false,
};
}
const { protocol, hostname, port } = new URL(proxyOptions);
return {
protocol,
hostname,
port: Number(port),
proxyTimeout: 60000,
rejectUnauthorized: false,
};
}
Tested with Vite 5 (should probably work with Vite 4 too ?)
ProxyOptions not implemented.
Configure the Vite proxy as usual and just add the plugin in the vite configuration eg: