proxy.ts 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. import { NextRequest, NextResponse } from "next/server";
  2. import { getServerSideConfig } from "@/app/config/server";
  3. export async function handle(
  4. req: NextRequest,
  5. { params }: { params: { path: string[] } },
  6. ) {
  7. console.log("[Proxy Route] params ", params);
  8. if (req.method === "OPTIONS") {
  9. return NextResponse.json({ body: "OK" }, { status: 200 });
  10. }
  11. const serverConfig = getServerSideConfig();
  12. // remove path params from searchParams
  13. req.nextUrl.searchParams.delete("path");
  14. req.nextUrl.searchParams.delete("provider");
  15. const subpath = params.path.join("/");
  16. const fetchUrl = `${req.headers.get(
  17. "x-base-url",
  18. )}/${subpath}?${req.nextUrl.searchParams.toString()}`;
  19. const skipHeaders = ["connection", "host", "origin", "referer", "cookie"];
  20. const headers = new Headers(
  21. Array.from(req.headers.entries()).filter((item) => {
  22. if (
  23. item[0].indexOf("x-") > -1 ||
  24. item[0].indexOf("sec-") > -1 ||
  25. skipHeaders.includes(item[0])
  26. ) {
  27. return false;
  28. }
  29. return true;
  30. }),
  31. );
  32. // if dalle3 use openai api key
  33. const baseUrl = req.headers.get("x-base-url");
  34. if (baseUrl?.includes("api.openai.com")) {
  35. if (!serverConfig.apiKey) {
  36. return NextResponse.json(
  37. { error: "OpenAI API key not configured" },
  38. { status: 500 },
  39. );
  40. }
  41. headers.set("Authorization", `Bearer ${serverConfig.apiKey}`);
  42. }
  43. const controller = new AbortController();
  44. const fetchOptions: RequestInit = {
  45. headers,
  46. method: req.method,
  47. body: req.body,
  48. // to fix #2485: https://stackoverflow.com/questions/55920957/cloudflare-worker-typeerror-one-time-use-body
  49. redirect: "manual",
  50. // @ts-ignore
  51. duplex: "half",
  52. signal: controller.signal,
  53. };
  54. const timeoutId = setTimeout(
  55. () => {
  56. controller.abort();
  57. },
  58. 10 * 60 * 1000,
  59. );
  60. try {
  61. const res = await fetch(fetchUrl, fetchOptions);
  62. // to prevent browser prompt for credentials
  63. const newHeaders = new Headers(res.headers);
  64. newHeaders.delete("www-authenticate");
  65. // to disable nginx buffering
  66. newHeaders.set("X-Accel-Buffering", "no");
  67. // The latest version of the OpenAI API forced the content-encoding to be "br" in json response
  68. // So if the streaming is disabled, we need to remove the content-encoding header
  69. // Because Vercel uses gzip to compress the response, if we don't remove the content-encoding header
  70. // The browser will try to decode the response with brotli and fail
  71. newHeaders.delete("content-encoding");
  72. return new Response(res.body, {
  73. status: res.status,
  74. statusText: res.statusText,
  75. headers: newHeaders,
  76. });
  77. } finally {
  78. clearTimeout(timeoutId);
  79. }
  80. }