HTML 서비스: 서버 함수와 통신

google.script.run는 HTML 서비스 페이지에서 서버 측 Apps Script 함수를 호출할 수 있도록 하는 비동기 클라이언트 측 JavaScript API입니다. 다음 예는 클라이언트 측 JavaScript에서 서버에서 함수 호출google.script.run의 가장 기본적인 기능을 보여줍니다.

Code.gs

function doGet() {
  return HtmlService.createHtmlOutputFromFile('Index');
}

function doSomething() {
  Logger.log('I was called!');
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
      google.script.run.doSomething();
    </script>
  </head>
</html>

이 스크립트를 웹 앱으로 배포하고 URL을 방문하면 아무것도 표시되지 않지만 로그를 확인하면 서버 함수 doSomething()가 호출된 것을 확인할 수 있습니다.

서버 측 함수에 대한 클라이언트 측 호출은 비동기식입니다. 브라우저가 서버에 doSomething() 함수를 실행하도록 요청한 후에는 브라우저가 응답을 기다리지 않고 즉시 다음 코드 줄로 계속 진행합니다. 즉, 서버 함수 호출이 예상한 순서대로 실행되지 않을 수 있습니다. 동시에 두 함수를 호출하는 경우 어떤 함수가 먼저 실행될지 알 수 없으며, 페이지를 로드할 때마다 결과가 달라질 수 있습니다. 이러한 상황에서 성공 핸들러실패 핸들러는 코드 흐름을 제어하는 데 도움이 됩니다.

google.script.run API를 사용하면 서버 함수를 동시에 10회 호출할 수 있습니다. 10개가 실행 중인 상태에서 11번째 호출을 하면 10개 스팟 중 하나가 해제될 때까지 서버 함수가 지연됩니다. 실제로 대부분의 브라우저는 동일한 서버에 대한 동시 요청 수를 10보다 낮은 수로 이미 제한하고 있으므로 이러한 제한사항에 대해서는 거의 신경 쓰지 않아도 됩니다. 예를 들어 Firefox에서는 6으로 제한됩니다. 마찬가지로 대부분의 브라우저는 기존 요청 중 하나가 완료될 때까지 초과 서버 요청을 지연시킵니다.

매개변수 및 반환 값

클라이언트의 매개변수를 사용하여 서버 함수를 호출할 수 있습니다. 마찬가지로 서버 함수는 성공 핸들러에 전달되는 매개변수로 클라이언트에 값을 반환할 수 있습니다.

유효한 매개변수와 반환 값은 Number, Boolean, String, null와 같은 자바스크립트 프리미티브와 프리미티브, 객체, 배열로 구성된 자바스크립트 객체 및 배열입니다. 페이지 내의 form 요소도 매개변수로 유효하지만 함수의 유일한 매개변수여야 하고 반환값으로 유효하지 않습니다. Date, Function, form 외의 DOM 요소 또는 객체나 배열 내의 금지된 유형 등 금지된 유형을 전달하려고 하면 요청이 실패합니다. 원형 참조를 만드는 객체도 실패하고 배열 내에 정의되지 않은 필드는 null가 됩니다.

서버로 전달된 객체는 원본의 사본이 됩니다. 서버 함수가 객체를 수신하고 속성을 변경해도 클라이언트의 속성은 영향을 받지 않습니다.

성공 핸들러

서버 호출이 완료될 때까지 기다리지 않고 클라이언트 측 코드가 계속해서 다음 줄로 이동하므로 withSuccessHandler(function)를 사용하면 서버가 응답할 때 실행할 클라이언트 측 콜백 함수를 지정할 수 있습니다. 서버 함수가 값을 반환하면 API는 값을 새 함수에 매개변수로 전달합니다.

다음 예에서는 서버가 응답할 때 브라우저 알림을 표시합니다. 서버 측 함수가 Gmail 계정에 액세스하기 때문에 이 코드 샘플에는 승인이 필요합니다. 스크립트를 승인하는 가장 간단한 방법은 페이지를 로드하기 전에 스크립트 편집기에서 수동으로 getUnreadEmails() 함수를 실행하는 것입니다. 또는 웹 앱을 배포할 때 '웹 앱에 액세스하는 사용자'로 실행할 수 있습니다. 이 경우 앱을 로드할 때 승인하라는 메시지가 표시됩니다.

Code.gs

function doGet() {
  return HtmlService.createHtmlOutputFromFile('Index');
}

function getUnreadEmails() {
  return GmailApp.getInboxUnreadCount();
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
      function onSuccess(numUnread) {
        var div = document.getElementById('output');
        div.innerHTML = 'You have ' + numUnread
            + ' unread messages in your Gmail inbox.';
      }

      google.script.run.withSuccessHandler(onSuccess)
          .getUnreadEmails();
    </script>
  </head>
  <body>
    <div id="output"></div>
  </body>
</html>

실패 핸들러

서버가 응답하지 않거나 오류가 발생하는 경우 withFailureHandler(function)를 사용하면 Error 객체 (있는 경우)를 인수로 전달하여 성공 핸들러 대신 실패 핸들러를 지정할 수 있습니다.

기본적으로 실패 핸들러를 지정하지 않으면 실패가 자바스크립트 콘솔에 로깅됩니다. 이를 재정의하려면 withFailureHandler(null)를 호출하거나 아무 작업도 하지 않는 실패 핸들러를 제공합니다.

이 예에 표시된 것처럼 실패 핸들러의 구문은 성공 핸들러와 거의 동일합니다.

Code.gs

function doGet() {
  return HtmlService.createHtmlOutputFromFile('Index');
}

function getUnreadEmails() {
  // 'got' instead of 'get' will throw an error.
  return GmailApp.gotInboxUnreadCount();
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
      function onFailure(error) {
        var div = document.getElementById('output');
        div.innerHTML = "ERROR: " + error.message;
      }

      google.script.run.withFailureHandler(onFailure)
          .getUnreadEmails();
    </script>
  </head>
  <body>
    <div id="output"></div>
  </body>
</html>

사용자 객체

withUserObject(object)를 호출하여 핸들러에 두 번째 매개변수로 전달될 객체를 지정하면 같은 서버 호출에 동일한 성공 또는 실패 핸들러를 재사용할 수 있습니다. User 클래스와 혼동해서는 안 되는 이 '사용자 객체'를 사용하면 클라이언트가 서버에 접속한 컨텍스트에 응답할 수 있습니다. 사용자 객체는 서버로 전송되지 않으므로 서버 호출을 위한 매개변수 및 반환 값에 대한 제한 없이 함수, DOM 요소 등을 포함한 거의 모든 대상이 될 수 있습니다. 그러나 사용자 객체는 new 연산자로 구성된 객체가 될 수 없습니다.

이 예에서 두 버튼 중 하나를 클릭하면 버튼이 서버의 값으로 업데이트되고 다른 버튼은 변경되지 않은 상태로 유지됩니다. 이는 하나의 성공 핸들러를 공유하더라도 마찬가지입니다. onclick 핸들러 내에서 this 키워드는 button 자체를 참조합니다.

Code.gs

function doGet() {
  return HtmlService.createHtmlOutputFromFile('Index');
}

function getEmail() {
  return Session.getActiveUser().getEmail();
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
      function updateButton(email, button) {
        button.value = 'Clicked by ' + email;
      }
    </script>
  </head>
  <body>
    <input type="button" value="Not Clicked"
      onclick="google.script.run
          .withSuccessHandler(updateButton)
          .withUserObject(this)
          .getEmail()" />
    <input type="button" value="Not Clicked"
      onclick="google.script.run
          .withSuccessHandler(updateButton)
          .withUserObject(this)
          .getEmail()" />
  </body>
</html>

양식

form 요소를 매개변수로 사용하여 서버 함수를 호출하면 양식은 필드 이름을 키로, 필드 값을 값으로 갖는 단일 객체가 됩니다. 값은 모두 문자열로 변환됩니다. 단, Blob 객체가 되는 파일 입력 필드의 콘텐츠는 예외입니다.

이 예에서는 페이지를 새로고침하지 않고 파일 입력란을 포함한 양식을 처리합니다. 파일을 Google Drive에 업로드한 다음 클라이언트 측 페이지에 파일의 URL을 출력합니다. onsubmit 핸들러 내에서 키워드 this는 양식 자체를 참조합니다. 페이지의 모든 양식을 로드하면 preventFormSubmit에 의해 기본 제출 작업이 사용 중지됩니다. 이렇게 하면 예외 발생 시 페이지가 부정확한 URL로 리디렉션되는 것을 방지할 수 있습니다.

Code.gs

function doGet() {
  return HtmlService.createHtmlOutputFromFile('Index');
}

function processForm(formObject) {
  var formBlob = formObject.myFile;
  var driveFile = DriveApp.createFile(formBlob);
  return driveFile.getUrl();
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
      // Prevent forms from submitting.
      function preventFormSubmit() {
        var forms = document.querySelectorAll('form');
        for (var i = 0; i < forms.length; i++) {
          forms[i].addEventListener('submit', function(event) {
            event.preventDefault();
          });
        }
      }
      window.addEventListener('load', preventFormSubmit);

      function handleFormSubmit(formObject) {
        google.script.run.withSuccessHandler(updateUrl).processForm(formObject);
      }
      function updateUrl(url) {
        var div = document.getElementById('output');
        div.innerHTML = '<a href="' + url + '">Got it!</a>';
      }
    </script>
  </head>
  <body>
    <form id="myForm" onsubmit="handleFormSubmit(this)">
      <input name="myFile" type="file" />
      <input type="submit" value="Submit" />
    </form>
    <div id="output"></div>
 </body>
</html>

스크립트 실행기

google.script.run을 '스크립트 실행기'의 빌더로 생각할 수 있습니다. 성공 핸들러, 실패 핸들러 또는 사용자 객체를 스크립트 실행기에 추가해도 기존 실행기는 변경되지 않습니다. 대신 새로운 동작으로 새로운 스크립트 실행기가 반환됩니다.

withSuccessHandler(), withFailureHandler(), withUserObject()의 조합과 순서와 관계없이 사용할 수 있습니다. 이미 값이 설정된 스크립트 실행기에서 수정 함수를 호출할 수도 있습니다. 새 값이 이전 값을 재정의합니다.

이 예에서는 세 개의 서버 호출 모두에 공통된 실패 핸들러를 설정하지만 두 개의 개별 성공 핸들러를 설정합니다.

var myRunner = google.script.run.withFailureHandler(onFailure);
var myRunner1 = myRunner.withSuccessHandler(onSuccess);
var myRunner2 = myRunner.withSuccessHandler(onDifferentSuccess);

myRunner1.doSomething();
myRunner1.doSomethingElse();
myRunner2.doSomething();

비공개 함수

이름이 밑줄로 끝나는 서버 함수는 비공개로 간주됩니다. 이러한 함수는 google.script에서 호출할 수 없으며 그 이름은 클라이언트로 전송되지 않습니다. 따라서 이를 사용하여 서버에서 비밀로 유지해야 하는 구현 세부정보를 숨길 수 있습니다. 또한 google.script라이브러리 내의 함수와 스크립트의 최상위 수준에서 선언되지 않은 함수도 볼 수 없습니다.

이 예에서는 클라이언트 코드에서 getBankBalance() 함수를 사용할 수 있습니다. 소스 코드를 검사하는 사용자는 개발자가 호출하지 않더라도 소스 코드를 검색할 수 있습니다. 그러나 deepSecret_()obj.objectMethod() 함수는 클라이언트에 완전히 표시되지 않습니다.

Code.gs

function doGet() {
  return HtmlService.createHtmlOutputFromFile('Index');
}

function getBankBalance() {
  var email = Session.getActiveUser().getEmail()
  return deepSecret_(email);
}

function deepSecret_(email) {
 // Do some secret calculations
 return email + ' has $1,000,000 in the bank.';
}

var obj = {
  objectMethod: function() {
    // More secret calculations
  }
};

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
      function onSuccess(balance) {
        var div = document.getElementById('output');
        div.innerHTML = balance;
      }

      google.script.run.withSuccessHandler(onSuccess)
          .getBankBalance();
    </script>
  </head>
  <body>
    <div id="output">No result yet...</div>
  </body>
</html>

애플리케이션에서 대화상자 Google Workspace 크기 조절

클라이언트 측 코드에서 google.script.host 메서드 setWidth(width) 또는 setHeight(height)를 호출하여 Google Docs, Sheets 또는 Forms의 맞춤 대화상자 크기를 조절할 수 있습니다. (대화상자의 초기 크기를 설정하려면 HtmlOutput 메서드 setWidth(width)setHeight(height)를 사용합니다.) 참고로 대화상자는 크기를 조절했을 때 상위 창의 중앙에 배치되지 않으며 사이드바의 크기를 조절할 수 없습니다.

Google Workspace의 대화상자 및 사이드바 닫기

Google Docs, Sheets, Forms에서 HTML 서비스를 사용하여 대화상자나 사이드바를 표시하는 경우 window.close()를 호출하여 인터페이스를 닫을 수 없습니다. 대신 google.script.host.close()를 호출해야 합니다. 예시를 보려면 HTML을 Google Workspace 사용자 인터페이스로 제공 섹션을 참고하세요.

브라우저 포커스를 Google Workspace안쪽으로 이동 중

대화상자나 사이드바에서 사용자 브라우저의 포커스를 다시 Google Docs, Sheets 또는 Forms 편집기로 전환하려면 google.script.host.editor.focus() 메서드를 호출하기만 하면 됩니다. 이 메서드는 문서 서비스 메서드 Document.setCursor(position)Document.setSelection(range)와 함께 사용할 때 특히 유용합니다.