express#Handler TypeScript Examples

The following examples show how to use express#Handler. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: artifacts.ts    From kfp-tekton-backend with Apache License 2.0 6 votes vote down vote up
export function getArtifactsProxyHandler({
  enabled,
  namespacedServiceGetter,
}: {
  enabled: boolean;
  namespacedServiceGetter: NamespacedServiceGetter;
}): Handler {
  if (!enabled) {
    return (req, res, next) => next();
  }
  return proxy(
    (_pathname, req) => {
      // only proxy requests with namespace query parameter
      return !!getNamespaceFromUrl(req.url || '');
    },
    {
      changeOrigin: true,
      onProxyReq: proxyReq => {
        console.log('Proxied artifact request: ', proxyReq.path);
      },
      pathRewrite: (pathStr, req) => {
        const url = new URL(pathStr || '', DUMMY_BASE_PATH);
        url.searchParams.delete(QUERIES.NAMESPACE);
        return url.pathname + url.search;
      },
      router: req => {
        const namespace = getNamespaceFromUrl(req.url || '');
        if (!namespace) {
          throw new Error(`namespace query param expected in ${req.url}.`);
        }
        return namespacedServiceGetter(namespace);
      },
      target: '/artifacts/get',
    },
  );
}
Example #2
Source File: gke-metadata.ts    From kfp-tekton-backend with Apache License 2.0 6 votes vote down vote up
clusterNameHandler: Handler = async (_, res) => {
  const response = await fetch(
    'http://metadata/computeMetadata/v1/instance/attributes/cluster-name',
    { headers: { 'Metadata-Flavor': 'Google' } },
  );
  if (!response.ok) {
    res.status(500).send('Failed fetching GKE cluster name');
    return;
  }
  res.send(await response.text());
}
Example #3
Source File: gke-metadata.ts    From kfp-tekton-backend with Apache License 2.0 6 votes vote down vote up
projectIdHandler: Handler = async (_, res) => {
  const response = await fetch('http://metadata/computeMetadata/v1/project/project-id', {
    headers: { 'Metadata-Flavor': 'Google' },
  });
  if (!response.ok) {
    res.status(500).send('Failed fetching GKE project id');
    return;
  }
  res.send(await response.text());
}
Example #4
Source File: healthz.ts    From kfp-tekton-backend with Apache License 2.0 6 votes vote down vote up
/**
 * Returns a handler which return the current state of the server.
 * @param options.healthzStats  partial health stats to be enriched with ml-pipeline metadata.
 * @param options.healthzEndpoint healthz endpoint for the ml-pipeline api server.
 */
export function getHealthzHandler(options: {
  healthzStats: Partial<HealthzStats>;
  healthzEndpoint: string;
}): Handler {
  const { healthzStats = {}, healthzEndpoint } = options;
  return async (_, res) => {
    try {
      const response = await fetch(healthzEndpoint, {
        timeout: 1000,
      });
      healthzStats.apiServerReady = true;
      const serverStatus = await response.json();
      healthzStats.apiServerCommitHash = serverStatus.commit_sha;
      healthzStats.apiServerTagName = serverStatus.tag_name;
    } catch (e) {
      healthzStats.apiServerReady = false;
    }
    res.json(healthzStats);
  };
}
Example #5
Source File: index-html.ts    From kfp-tekton-backend with Apache License 2.0 6 votes vote down vote up
/**
 * Returns a handler which retrieve and modify the index.html.
 * @param options.staticDir serve the static resources in this folder.
 * @param options.deployment whether this is a kubeflow deployment.
 */
export function getIndexHTMLHandler(options: {
  staticDir: string;
  deployment: Deployments;
}): Handler {
  const content = replaceRuntimeContent(loadIndexHtml(options.staticDir), options.deployment);

  return function handleIndexHtml(_, res) {
    if (content) {
      res.contentType('text/html');
      res.send(content);
    } else {
      res.sendStatus(404);
    }
  };
}
Example #6
Source File: pod-info.ts    From kfp-tekton-backend with Apache License 2.0 6 votes vote down vote up
podInfoHandler: Handler = async (req, res) => {
  const { podname, podnamespace } = req.query;
  if (!podname) {
    // 422 status code "Unprocessable entity", refer to https://stackoverflow.com/a/42171674
    res.status(422).send('podname argument is required');
    return;
  }
  if (!podnamespace) {
    res.status(422).send('podnamespace argument is required');
    return;
  }
  const podName = decodeURIComponent(podname);
  const podNamespace = decodeURIComponent(podnamespace);

  const [pod, err] = await k8sHelper.getPod(podName, podNamespace);
  if (err) {
    const { message, additionalInfo } = err;
    console.error(message, additionalInfo);
    res.status(500).send(message);
    return;
  }
  res.status(200).send(JSON.stringify(pod));
}
Example #7
Source File: pod-info.ts    From kfp-tekton-backend with Apache License 2.0 6 votes vote down vote up
podEventsHandler: Handler = async (req, res) => {
  const { podname, podnamespace } = req.query;
  if (!podname) {
    res.status(422).send('podname argument is required');
    return;
  }
  if (!podnamespace) {
    res.status(422).send('podnamespace argument is required');
    return;
  }
  const podName = decodeURIComponent(podname);
  const podNamespace = decodeURIComponent(podnamespace);

  const [eventList, err] = await k8sHelper.listPodEvents(podName, podNamespace);
  if (err) {
    const { message, additionalInfo } = err;
    console.error(message, additionalInfo);
    res.status(500).send(message);
    return;
  }
  res.status(200).send(JSON.stringify(eventList));
}
Example #8
Source File: vis.ts    From kfp-tekton-backend with Apache License 2.0 6 votes vote down vote up
/**
 * Return a handler which return whether custom visualization is allowed by the
 * ml-pipeline ui server.
 * @param allowed whether custom visualization is permitted.
 */
export function getAllowCustomVisualizationsHandler(allowed: boolean): Handler {
  return (_, res) => {
    res.send(allowed);
  };
}
Example #9
Source File: proxy.ts    From NanoRPCProxy with GNU General Public License v3.0 5 votes vote down vote up
expressHandlers: Handler[] = []
Example #10
Source File: artifacts.ts    From kfp-tekton-backend with Apache License 2.0 5 votes vote down vote up
/**
 * Returns an artifact handler which retrieve an artifact from the corresponding
 * backend (i.e. gcs, minio, s3, http/https).
 * @param artifactsConfigs configs to retrieve the artifacts from the various backend.
 */
export function getArtifactsHandler(artifactsConfigs: {
  aws: AWSConfigs;
  http: HttpConfigs;
  minio: MinioConfigs;
}): Handler {
  const { aws, http, minio } = artifactsConfigs;
  return async (req, res) => {
    const { source, bucket, key: encodedKey, peek = 0 } = req.query as Partial<
      ArtifactsQueryStrings
    >;
    if (!source) {
      res.status(500).send('Storage source is missing from artifact request');
      return;
    }
    if (!bucket) {
      res.status(500).send('Storage bucket is missing from artifact request');
      return;
    }
    if (!encodedKey) {
      res.status(500).send('Storage key is missing from artifact request');
      return;
    }
    const key = decodeURIComponent(encodedKey);
    console.log(`Getting storage artifact at: ${source}: ${bucket}/${key}`);
    switch (source) {
      case 'gcs':
        getGCSArtifactHandler({ bucket, key }, peek)(req, res);
        break;

      case 'minio':
        getMinioArtifactHandler(
          {
            bucket,
            client: new MinioClient(minio),
            key,
          },
          peek,
        )(req, res);
        break;

      case 's3':
        getMinioArtifactHandler(
          {
            bucket,
            client: await createMinioClient(aws),
            key,
          },
          peek,
        )(req, res);
        break;

      case 'http':
      case 'https':
        getHttpArtifactsHandler(
          getHttpUrl(source, http.baseUrl || '', bucket, key),
          http.auth,
          peek,
        )(req, res);
        break;

      default:
        res.status(500).send('Unknown storage source: ' + source);
        return;
    }
  };
}
Example #11
Source File: gke-metadata.ts    From kfp-tekton-backend with Apache License 2.0 5 votes vote down vote up
disabledHandler: Handler = async (_, res) => {
  res.status(500).send('GKE metadata endpoints are disabled.');
}
Example #12
Source File: pod-logs.ts    From kfp-tekton-backend with Apache License 2.0 5 votes vote down vote up
/**
 * Returns a handler which attempts to retrieve the logs for the specific pod,
 * in the following order:
 * - retrieve with k8s api
 * - retrieve log archive location from argo workflow status, and retrieve artifact directly
 * - retrieve log archive with the provided argo archive settings
 * @param argoOptions fallback options to retrieve log archive
 * @param artifactsOptions configs and credentials for the different artifact backend
 */
export function getPodLogsHandler(
  argoOptions: ArgoConfigs,
  artifactsOptions: {
    minio: MinioConfigs;
    aws: AWSConfigs;
  },
): Handler {
  const { archiveLogs, archiveArtifactory, archiveBucketName, archivePrefix = '' } = argoOptions;

  // get pod log from the provided bucket and prefix.
  const getPodLogsStreamFromArchive = toGetPodLogsStream(
    createPodLogsMinioRequestConfig(
      archiveArtifactory === 'minio' ? artifactsOptions.minio : artifactsOptions.aws,
      archiveBucketName,
      archivePrefix,
    ),
  );

  // get the pod log stream (with fallbacks).
  const getPodLogsStream = composePodLogsStreamHandler(
    getPodLogsStreamFromK8s,
    // if archive logs flag is set, then final attempt will try to retrieve the artifacts
    // from the bucket and prefix provided in the config. Otherwise, only attempts
    // to read from worflow status if the default handler fails.
    archiveLogs && archiveBucketName
      ? composePodLogsStreamHandler(getPodLogsStreamFromWorkflow, getPodLogsStreamFromArchive)
      : getPodLogsStreamFromWorkflow,
  );

  return async (req, res) => {
    if (!req.query.podname) {
      res.status(404).send('podname argument is required');
      return;
    }
    const podName = decodeURIComponent(req.query.podname);

    // This is optional.
    // Note decodeURIComponent(undefined) === 'undefined', so I cannot pass the argument directly.
    const podNamespace = decodeURIComponent(req.query.podnamespace || '') || undefined;

    try {
      const stream = await getPodLogsStream(podName, podNamespace);
      stream.on('error', err => res.status(500).send('Could not get main container logs: ' + err));
      stream.on('end', () => res.end());
      stream.pipe(res);
    } catch (err) {
      res.status(500).send('Could not get main container logs: ' + err);
    }
  };
}
Example #13
Source File: auth.ts    From monkeytype with GNU General Public License v3.0 5 votes vote down vote up
function authenticateRequest(authOptions = DEFAULT_OPTIONS): Handler {
  const options = {
    ...DEFAULT_OPTIONS,
    ...authOptions,
  };

  return async (
    req: MonkeyTypes.Request,
    _res: Response,
    next: NextFunction
  ): Promise<void> => {
    try {
      const { authorization: authHeader } = req.headers;
      let token: MonkeyTypes.DecodedToken;

      if (authHeader) {
        token = await authenticateWithAuthHeader(
          authHeader,
          req.ctx.configuration,
          options
        );
      } else if (options.isPublic) {
        token = {
          type: "None",
          uid: "",
          email: "",
        };
      } else if (process.env.MODE === "dev") {
        token = authenticateWithBody(req.body);
      } else {
        throw new MonkeyError(
          401,
          "Unauthorized",
          `endpoint: ${req.baseUrl} no authorization header found`
        );
      }

      incrementAuth(token.type);

      req.ctx = {
        ...req.ctx,
        decodedToken: token,
      };
    } catch (error) {
      return next(error);
    }

    next();
  };
}
Example #14
Source File: renderer.ts    From clearflask with Apache License 2.0 4 votes vote down vote up
export default function render(): Handler {
  return async (req, res, next) => {
    try {
      const staticRouterContext: StaticRouterContext = {};
      const storesState: StoresState = {};
      const port = req.app.settings.port;
      const requestedUrl = `${req.protocol}://${req.hostname}${(!port || port == 80 || port == 443) ? '' : (':' + port)}${req.path}`;
      const awaitPromises: Array<Promise<any>> = [];

      // From i18next-http-middleware
      const i18n = req.i18n as i18n;
      const lng = i18n.language;

      var renderResult: RenderResult | undefined;
      var isFinished = false;
      var renderCounter = 0;
      const renderPromise = new Promise<void>(async (resolve, reject) => {
        try {
          do {
            if (++renderCounter > 10) {
              console.warn(`Render give up after too many passes ${renderCounter} on ${requestedUrl}`);
              resolve();
              return;
            }
            // console.debug(`Rendering ${requestedUrl} pass #${renderCounter} with ${awaitPromises.length} promises`);
            const renderPassResult: RenderResult = {
              title: 'ClearFlask',
              extractor: renderResult?.extractor || new ChunkExtractor({
                statsFile: path.resolve(connectConfig.publicPath, 'loadable-stats.json'),
                entrypoints: ['main'],
                // SSR public path, for CSR see index.tsx
                outputPath: path.resolve(__dirname, '..', '..', 'build'),
                publicPath: (connectConfig.parentDomain !== 'clearflask.com')
                  ? '/' : undefined,
              }),
              muiSheets: new ServerStyleSheets(),
              renderedScreen: '',
            };
            try {
              await Promise.allSettled(awaitPromises);
            } catch (e) { }
            awaitPromises.length = 0;
            if (isFinished) return; // Request timed out

            resetServerContext(); // For react-beautiful-dnd library

            renderPassResult.renderedScreen = renderIndexSsr({
              i18n,
              url: req.url,
              staticRouterContext,
              storesState,
              awaitPromises,
              renderResult: renderPassResult,
              requestedUrl: requestedUrl,
            });

            if (isFinished) return; // Request timed out
            renderResult = renderPassResult;
          } while (awaitPromises.length > 0);
          console.info(`Rendered ${requestedUrl} in ${renderCounter} pass(es)`);
          resolve();
        } catch (e) {
          reject(e);
        }
      });
      const timeoutPromise = new Promise<void>(resolve => setTimeout(() => {
        !isFinished && console.warn(`Render timeout on ${requestedUrl} after ${renderCounter} pass(es)`);
        resolve();
      }, 10000));
      await Promise.race([timeoutPromise, renderPromise]);
      isFinished = true;

      if (!renderResult) {
        // Timeout with no render finished, fallback to client-side rendering
        res.status(500);
        res.sendFile(path.join(__dirname, '..', '..', "build", "index.html"));
        return;
      }

      var html = await indexHtmlPromise;

      if (process.env.ENV !== 'production') {
        html = html.replace(PH_ENV, `<script>window.ENV='${process.env.ENV}'</script>`);
      } else {
        html = html.replace(PH_ENV, '');
      }

      if (connectConfig.parentDomain !== 'clearflask.com') {
        html = html.replace(PH_PARENT_DOMAIN, `<script>window.parentDomain='${connectConfig.parentDomain}'</script>`);
      } else {
        html = html.replace(PH_PARENT_DOMAIN, '');
      }

      // Favicon
      html = html.replace(PH_FAVICON_URL, renderResult.faviconUrl || `${getParentDomainUrl()}/favicon.ico`);

      // Page title
      html = html.replace(PH_PAGE_TITLE, renderResult.title);

      // JS, CSS
      html = html.replace(PH_LINK_TAGS, renderResult.extractor.getLinkTags());
      html = html.replace(PH_STYLE_TAGS, renderResult.extractor.getStyleTags());
      html = html.replace(PH_SCRIPT_TAGS, renderResult.extractor.getScriptTags());
      html = html.replace(PH_MUI_STYLE_TAGS, renderResult.muiSheets.toString());

      // Add rendered html
      html = html.replace(PH_MAIN_SCREEN, renderResult.renderedScreen);

      // Add populated stores
      if (storesState.serverAdminStore !== undefined || storesState.serverStores !== undefined) {
        const storesStateSerializable: StoresStateSerializable = {
          serverAdminStore: storesState.serverAdminStore?.getState(),
          serverStores: {},
        };
        !!storesState.serverStores && Object.entries(storesState.serverStores)
          .forEach(([id, store]) => storesStateSerializable.serverStores![id] = store.getState());
        html = html.replace(PH_STORE_CONTENT, htmlDataCreate('__SSR_STORE_INITIAL_STATE__', storesStateSerializable));
      } else {
        html = html.replace(PH_STORE_CONTENT, '');
      }

      // I18N initial language and in-memory store
      html = html.replace(PH_I18N_INIT_LNG, htmlDataCreate('__SSR_I18N_INIT_LNG__', lng));
      const i18nStore: Resource = { [lng]: {} };
      (lng === 'en' ? ['en'] : ['en', lng]).forEach(l => {
        i18nStore[l] = {};
        i18n.reportNamespaces.getUsedNamespaces().forEach(ns => {
          i18nStore[l][ns] = i18n.services.resourceStore.data[l][ns];
        });
      });
      html = html.replace(PH_I18N_INIT_STORE, htmlDataCreate('__SSR_I18N_INIT_STORE__', i18nStore));

      res.writeHead(staticRouterContext.statusCode || 200, {
        'Content-Type': 'text/html',
        ...(renderResult.maxAge !== undefined && { 'Cache-Control': 'public, max-age=' + renderResult.maxAge }),
        ...(staticRouterContext.url && { Location: staticRouterContext.url }),
      });
      res.end(html);
    } catch (e) {
      console.error('Failed to get page', e);
      res.header('Cache-Control', 'public, max-age=10');
      res.status(500);
      if (process.env.ENV === 'production') {
        // fallback to client-side rendering (still throwing 500)
        // This is not exactly necessary for CloudFront as it is configured to get index.html on http 500
        // It is necessary for custom domains bypassing CloudFront
        res.sendFile(path.join(__dirname, 'public', 'index.html'));
      } else {
        res.end();
      }
    }
  };
}
Example #15
Source File: tensorboard.ts    From kfp-tekton-backend with Apache License 2.0 4 votes vote down vote up
getTensorboardHandlers = (
  tensorboardConfig: ViewerTensorboardConfig,
  authorizeFn: AuthorizeFn,
): { get: Handler; create: Handler; delete: Handler } => {
  /**
   * A handler which retrieve the endpoint for a tensorboard instance. The
   * handler expects a query string `logdir`.
   */
  const get: Handler = async (req, res) => {
    const { logdir, namespace } = req.query;
    if (!logdir) {
      res.status(400).send('logdir argument is required');
      return;
    }
    if (!namespace) {
      res.status(400).send('namespace argument is required');
      return;
    }

    try {
      const authError = await authorizeFn(
        {
          verb: AuthorizeRequestVerb.GET,
          resources: AuthorizeRequestResources.VIEWERS,
          namespace,
        },
        req,
      );
      if (authError) {
        res.status(401).send(authError.message);
        return;
      }
      res.send(await k8sHelper.getTensorboardInstance(logdir, namespace));
    } catch (err) {
      const details = await parseError(err);
      console.error(`Failed to list Tensorboard pods: ${details.message}`, details.additionalInfo);
      res.status(500).send(`Failed to list Tensorboard pods: ${details.message}`);
    }
  };

  /**
   * A handler which will create a tensorboard viewer CRD, waits for the
   * tensorboard instance to be ready, and return the endpoint to the instance.
   * The handler expects the following query strings in the request:
   * - `logdir`
   * - `tfversion`
   */
  const create: Handler = async (req, res) => {
    const { logdir, namespace, tfversion } = req.query;
    if (!logdir) {
      res.status(400).send('logdir argument is required');
      return;
    }
    if (!namespace) {
      res.status(400).send('namespace argument is required');
      return;
    }
    if (!tfversion) {
      res.status(400).send('tfversion (tensorflow version) argument is required');
      return;
    }

    try {
      const authError = await authorizeFn(
        {
          verb: AuthorizeRequestVerb.CREATE,
          resources: AuthorizeRequestResources.VIEWERS,
          namespace,
        },
        req,
      );
      if (authError) {
        res.status(401).send(authError.message);
        return;
      }
      await k8sHelper.newTensorboardInstance(
        logdir,
        namespace,
        tensorboardConfig.tfImageName,
        tfversion,
        tensorboardConfig.podTemplateSpec,
      );
      const tensorboardAddress = await k8sHelper.waitForTensorboardInstance(
        logdir,
        namespace,
        60 * 1000,
      );
      res.send(tensorboardAddress);
    } catch (err) {
      const details = await parseError(err);
      console.error(`Failed to start Tensorboard app: ${details.message}`, details.additionalInfo);
      res.status(500).send(`Failed to start Tensorboard app: ${details.message}`);
    }
  };

  /**
   * A handler that deletes a tensorboard viewer. The handler expects query string
   * `logdir` in the request.
   */
  const deleteHandler: Handler = async (req, res) => {
    const { logdir, namespace } = req.query;
    if (!logdir) {
      res.status(400).send('logdir argument is required');
      return;
    }
    if (!namespace) {
      res.status(400).send('namespace argument is required');
      return;
    }

    try {
      const authError = await authorizeFn(
        {
          verb: AuthorizeRequestVerb.DELETE,
          resources: AuthorizeRequestResources.VIEWERS,
          namespace,
        },
        req,
      );
      if (authError) {
        res.status(401).send(authError.message);
        return;
      }
      await k8sHelper.deleteTensorboardInstance(logdir, namespace);
      res.send('Tensorboard deleted.');
    } catch (err) {
      const details = await parseError(err);
      console.error(`Failed to delete Tensorboard app: ${details.message}`, details.additionalInfo);
      res.status(500).send(`Failed to delete Tensorboard app: ${details.message}`);
    }
  };

  return {
    get,
    create,
    delete: deleteHandler,
  };
}