import http, { IncomingMessage, ServerResponse } from "http"; import https from "https"; import { IDevOptions, IProxy } from "./interfaces"; import { defaultDevOptions } from "./defaultDevOptions"; import { defaultProxy } from "./defaultProxy"; import open from "open"; import path from "path"; import mime from "mime"; import fs from "fs"; export function proxy( req: IncomingMessage, res: ServerResponse, proxy: IProxy ) { const { connection, host, ...originHeaders } = req.headers; const proxyHeaders = typeof proxy.headers == "function" ? proxy.headers(req) : proxy.headers ?? {}; const options = { method: req.method, hostname: proxy.host, port: proxy.port, path: proxy.path ?? proxy.to + req.url.substring(proxy.from.length), headers: { ...originHeaders, ...proxyHeaders }, }; console.log("call proxy:", options); const proxyRequest = proxy.https ? https.request : http.request; // Forward each incoming proxy request to proxy server const proxyReq = proxyRequest(options, (proxyRes) => { res.on("close", (e) => { if (!proxyRes.destroyed && !proxyRes.closed) { proxyRes.destroy(); console.log("client closed", e, req.url); } }); res.writeHead(proxyRes.statusCode, proxyRes.headers); proxyRes.pipe(res, { end: true }); }); req.on("error", (e) => { console.log("client error"); }); proxyReq.on("error", (err) => { console.error("pipe proxy request error", err); res.writeHead(500, { "Content-Type": "text/plain" }); res.end("500 ~"); }); // Forward the body of the request req.pipe(proxyReq, { end: true }); } const watchingScript = ` if (typeof EventSource != undefined) { const sse = new EventSource("/--watching") sse.addEventListener("message", evt => { if(evt.data=="reload") window.location.reload() }) } `; const sseInjection = ``; export function dev( options: Partial, ...proxies: Partial[] ) { const { server, root, home, port, https: httpsOption, openBrowser, notFoundHandler, serverErrorHandler, fixPath, response, } = { ...defaultDevOptions, ...(options ?? {}), }; proxies = proxies.flat(); //覆盖一下默认设置 proxies.forEach((proxy) => { proxy = { ...defaultProxy, ...proxy }; }); const devServer = httpsOption === false ? http.createServer() : https.createServer({ key: typeof httpsOption.key === "string" ? fs.readFileSync(httpsOption.key) : httpsOption.key, cert: typeof httpsOption.cert === "string" ? fs.readFileSync(httpsOption.cert) : httpsOption.cert, }); devServer.listen(port); const serverURL = `http${ httpsOption === false ? "" : "s" }://${server}:${port}${home}`; console.info("local-dev-server start:", serverURL); if (openBrowser != false) { let app = openBrowser == true ? "chrome" : openBrowser; open(serverURL, { app: { name: open.apps[app], }, }); } const watches = []; devServer.on("request", (req, res) => { if (req.url == "/--listening") { res.writeHead(200, { "Content-Type": "application/javascript", "Access-Control-Allow-Origin": "*", }); res.end(watchingScript); return; } if (req.url == "/--watching") { res.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive", "Access-Control-Allow-Origin": "*", }); res.write("retry: 5000\n\n"); watches.push(res); return; } let dispatch = proxies.find(({ from }) => req.url.startsWith(from)); if (dispatch == null && typeof proxies[0]?.dispatch === "function") { dispatch = proxies[0]?.dispatch(req.url); } if (req.url.startsWith(dispatch?.from)) { proxy(req, res, dispatch as IProxy); } else { if (req.method == "GET") { console.log("get url:", req.url); const { fileName, extName, reqDir } = fixPath(req); const filePath = path.resolve(root, `.${reqDir}${fileName}${extName}`); try { if ( !response( filePath, res, { fileName, extName, reqDir, req, }, req ) ) { if ( fs.existsSync(filePath) && !fs.statSync(filePath).isDirectory() ) { try { res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader( "Content-Type", mime.getType(filePath) + ";charset=utf-8" ); if (path.extname(filePath) == ".html") { let contents = fs.readFileSync(filePath, "utf8"); res.end(contents.replace(/(<\/body>)/i, `${sseInjection}$1`)); } else { fs.createReadStream(filePath).pipe(res); } } catch (exc) { res.writeHead(500, { "Content-Type": "text/plain" }); res.end("500 ~"); console.error(exc); if (typeof serverErrorHandler == "function") { serverErrorHandler(req, res); } if (!res.closed) { res.end(); } } } else { if (typeof notFoundHandler == "function") { notFoundHandler(req, res); } if (!res.closed) { res.end(); } } } } catch (exc) { console.error("call function error ", exc); } } } }); return { reload(message = "") { console.log("reload page", message); watches .filter((res) => res && !res.closed) .forEach((res) => { res.write("data: reload\n\n"); }); }, }; }