Построение микросервиса для создания скриншотов

Давайте немного повысим сложность. Мы рассмотрели три очень простых, прямолинейных транзакции, где отношения сводятся к тому, что я даю вам информацию, вы даете мне ответ.

  • Онлайн-демонстрация скриншотов
  • Исходный код в репозитории
  • веб-компонент для демонстрации

Этот сценарий использования / требование было намного сложнее, чем другие (немного неожиданно) — это создание сервиса, способного удаленно рендерить веб-страницу. Я уже экспериментировал с этой концепцией раньше, поскольку среди новостных и медиа изданий популярно делать скриншоты из twitter и других источников, чтобы проиллюстрировать изменение сообщения, удаление и т.д.

Шаги в мышлении на пути к большому успеху

«Я хочу делать скриншоты», — сказал я. Затем я подумал…

  • Что в NPM позволяет мне это сделать?
  • «омг, насколько велик кукловод?!»
  • «Вау, это работает фантастически локально»
  • «вау, это совсем не работает в продакшене»
  • «WTF ПОЧЕМУ ЭТО НЕ РАБОТАЕТ В ПРОДАКШЕНЕ»
  • делает новое репо
  • Ааа, вот оно, отлично работает в продакшене!

Итак, давайте немного распакуем эти проблемы и решения.

Что такое puppeteer и почему я должен им стать?

Puppeteer — это, по сути, безголовый браузерный бегун. Как они заявляют: «Большинство вещей, которые вы можете сделать вручную в браузере, можно сделать с помощью Puppeteer!».

Подумайте, если вы выполняете ряд задач: открываете браузер, набираете URL, ждете, пока он загрузится, прокручиваете и находите что-то, то кукловод может получить инструкции, что и в каком порядке делать, чтобы добиться того же самого. У сайта есть много общих сценариев использования, среди которых популярность для тестирования среды и замечения изменений CSS / макета в Pull-запросах.

Наше требование заключалось в том, чтобы делать скриншоты URL-адресов, чтобы мы могли обеспечить предварительный просмотр, эффективно указывая на то, что веб-сайт находится в процессе создания. Для ясности, на момент написания статьи эта функция не была подключена к нашему производственному приложению, поскольку ее добавление не планируется в ближайшее время, но возможность решить эту проблему с помощью Vercel помогла продемонстрировать возможности команды.

Код для меня

Используя подходы, аналогичные предыдущим трем конечным точкам, служба скриншотов выглядит следующим образом:

import { getBrowserInstance } from '../getBrowserInstance.js';
import { stdResponse, invalidRequest, stdPostBody } from "../requestHelpers.js";

// this requires its own service instance and can't live with the monorepo
// due to the size of the dependencies involved
export default async function handler(req, res) {
  const body = stdPostBody(req);
  const urlToCapture = body.urlToCapture;
  // Perform URL validation
  if (!urlToCapture || !urlToCapture.trim()) {
    res = invalidRequest(res, 'enter a valid url');
  }
  else {
    if (!urlToCapture.includes("https://")) {
      // try to fake it
      urlToCapture = `https://${urlToCapture}`;
    }

    // capture options
    var browserGoToOptions = {
      timeout: 60000,
      waitUntil: 'networkidle2',
    };
    var screenshotOptions = {
      quality: body.quality ? parseInt(body.quality) : 75,
      type: 'jpeg',
      encoding: "base64"
    };
    var base64 = '';
    let browser = null
    try {
      browser = await getBrowserInstance();
      let page = await browser.newPage();
      await page.goto(urlToCapture, browserGoToOptions);
      // special support for isolating a tweet
      if (urlToCapture.includes('twitter.com')) {
        await page.waitForSelector("article[data-testid='tweet']");
        const element = await page.$("article[data-testid='tweet']");
        base64 = await element.screenshot(screenshotOptions);
      }
      else {
        screenshotOptions.fullPage = true;
        base64 = await page.screenshot(screenshotOptions);
      }
      res = stdResponse(res,
        {
          url: urlToCapture,
          image: base64
        }, {
          methods: "GET,OPTIONS,PATCH,DELETE,POST,PUT",
          cache: 1800
        }
      );
    } catch (error) {
        console.log(error)
        res = invalidRequest(res, 'something went wrong', 500);
    } finally {
        if (browser !== null) {
            await browser.close()
        }
    }
  }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Магия» здесь заключена в том, что называется getBrowserInstance, см. ниже:

import chromium from 'chrome-aws-lambda'

export async function getBrowserInstance() {
    const executablePath = await chromium.executablePath
    if (!executablePath) {
        // running locally
        const puppeteer = await import('puppeteer').then((m) => {
      return m.default;
    });
        return await puppeteer.launch({
            args: chromium.args,
            headless: true,
            defaultViewport: {
                width: 1280,
                height: 720
            },
            ignoreHTTPSErrors: true
        });
    }

    return await chromium.puppeteer.launch({
    args: chromium.args,
    defaultViewport: chromium.defaultViewport,
    executablePath: executablePath,
        headless: chromium.headless,
        ignoreHTTPSErrors: true
    });
}
Вход в полноэкранный режим Выход из полноэкранного режима

Эта простая функция помогает устранить разницу между vercel в продакшене (ака вызовы, управляемые Lambda) и локальными вызовами vercel dev (которые используют локальную копию Chromium для рендеринга).

Возникшие проблемы

В процессе производства Vercel позволяет скомпилировать и запустить только такое количество кода. Из-за размера пакетов, сервис скриншотов пришлось вырезать из нашего монорепо и запускать отдельно. Об этом говорилось в предыдущем сообщении, но в результате определение в нашем промежуточном ПО выглядело следующим образом:

// screenshot - kept by itself bc of size of getBrowserInstance
  MicroFrontendRegistry.add({
    endpoint: "https://screenshoturl.elmsln.vercel.app/api/screenshotUrl",
    name: "@core/screenshotUrl",
    title: "Screenshot page",
    description: "Takes screenshot of a URL and returns image",
    params: {
      urlToCapture: "full url with https",
      quality: "Optional image quality parameter"
    }
  });
Войти в полноэкранный режим Выйти из полноэкранного режима

Это привязывает вызов @core/screenshotUrl к очень специфическому адресу. Я не большой поклонник этого решения, так как мне хотелось бы, чтобы оно было более согласовано с остальной структурой URL, но это не конец света.

Видео

Здесь демонстрируется инструмент для создания скриншотов, а также рассказывается о том, как код способен обрабатывать удаленный URL и даже имеет поддержку для изоляции твитов в twitter, чтобы показать некоторую повышенную сложность 💪.

Оцените статью
devanswers.ru
Добавить комментарий