스크린샷 마이크로서비스 구축

복잡성을 조금 더 높여 봅시다. 우리는 관계가 내가 당신에게 정보를 제공하고 당신이 나에게 응답을 제공하는 세 가지 매우 간단하고 직접적인 트랜잭션을 다루었습니다.
  • Online Screenshot demo
  • Source code powering repo
  • web component powering demo

  • 이 사용 사례/요구 사항은 웹 페이지를 원격으로 렌더링할 수 있는 서비스를 구축하는 다른 것보다 훨씬 까다로웠습니다(예상치 않게도 그렇습니다). 게시, 삭제 등의 변경 사항을 설명하기 위해 Twitter 및 다른 곳에서 스크린샷을 찍는 것이 뉴스 및 미디어 아울렛 사이에서 인기가 있기 때문에 이전에 이 개념을 실험한 적이 있습니다.

    많은 성공을 향한 길을 따라 생각하는 단계



    "스크린샷을 찍고 싶다"고 말했습니다. 그때 나는 생각했다..
  • NPM에서 이 작업을 수행할 수 있는 기능
  • "오, 인형극이 또 얼마나 커졌어?!"
  • "와우 로컬에서 환상적으로 작동합니다"
  • "와우 프로덕션에서는 전혀 작동하지 않습니다"
  • "왜 이것이 프로덕션 환경에서 작동하지 않는지"
  • 새 저장소 만들기
  • 아, 잘 됐네요. 프로덕션에서 훌륭하게 작동합니다!

  • 이제 이러한 문제와 해결책을 조금 풀어 보겠습니다.

    인형극이란 무엇이며 인형극이 되어야 하는 이유는 무엇입니까?



    Puppeteer은 사실상 헤드리스 브라우저 러너입니다. 그들은 "브라우저에서 수동으로 할 수 있는 대부분의 작업은 Puppeteer를 사용하여 수행할 수 있습니다!"라고 말합니다.

    브라우저를 열고, URL을 입력하고, 로드되기를 기다리고, 스크롤하고, 무언가를 찾는 일련의 작업을 수행했다면 인형 조종자는 동일한 작업을 수행하기 위해 어떤 순서로 무엇을 해야 하는지 지시받을 수 있습니다. 웹사이트에는 많은 일반적인 사용 사례가 있습니다. 그 중 테스트 환경과 끌어오기 요청에서 CSS/레이아웃 변경을 알아차리는 데 널리 사용됩니다.

    우리의 요구 사항은 미리 보기를 제공할 수 있도록 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을 처리할 수 있는 방법과 트위터에서 트윗을 격리하는 기능을 지원하여 복잡성이 증가하는 방법을 설명하는 동안 스크린샷 도구의 데모입니다 💪.

    좋은 웹페이지 즐겨찾기