Agrupar solicitudes en lotes

En este documento, se muestra cómo agrupar llamadas a la API a fin de reducir la cantidad de conexiones HTTP que debe hacer el cliente.

Este documento trata, en particular, sobre cómo hacer una solicitud por lotes mediante el envío de una solicitud HTTP. Sin embargo, si usas una biblioteca cliente de Google para realizar una solicitud por lotes, debes consultar la documentación sobre bibliotecas cliente.

Descripción general

Cada conexión HTTP que tu cliente realiza da como resultado una cierta cantidad de sobrecarga. La API de Google Classroom admite el procesamiento por lotes, a fin de permitir que tu cliente coloque varias llamadas a la API en una sola solicitud HTTP.

A continuación, se detallan algunos ejemplos de situaciones en las que te sería útil usar el procesamiento por lotes:

  • Recuperar listas de una gran cantidad de cursos
  • Crear o actualizar cursos de forma masiva
  • Agregar una gran cantidad de listas de cursos
  • Recuperación de listas de cursos para una gran cantidad de usuarios

En cada caso, en lugar de enviar cada llamada por separado, puedes agruparlas en una única solicitud HTTP. Todas las solicitudes internas deben ir en la misma API de Google.

Tienes un límite de 50 llamadas por cada solicitud por lotes. Si necesitas hacer más llamadas, usa varias solicitudes por lotes.

Nota: El sistema por lotes para la API de Google Classroom emplea la misma sintaxis que el sistema de procesamiento por lotes OData, pero difiere en la semántica.

Detalles del lote

Una solicitud por lotes consta de varias llamadas a la API combinadas en una solicitud HTTP, que pueden enviarse al batchPath especificado en el documento de descubrimiento de la API. La ruta de acceso predeterminada es /batch/api_name/api_version. En esta sección, se describe la sintaxis del lote en detalle; más adelante, se muestra un ejemplo.

Nota: Un conjunto de solicitudes n agrupadas se considera en tu límite de uso como solicitudes n, no como una sola. La solicitud por lotes se divide en un conjunto de solicitudes antes de procesarse.

Formato de una solicitud por lotes

Una solicitud por lotes es una solicitud HTTP estándar que contiene varias llamadas a la API de Google Classroom con el tipo de contenido multipart/mixed. Dentro de esa solicitud HTTP principal, cada una de las partes contiene una solicitud HTTP anidada.

Cada parte comienza con su propio encabezado HTTP Content-Type: application/http. También puede tener un encabezado opcional Content-ID. Sin embargo, los encabezados de partes solo están allí para marcar el comienzo de la parte; están separados de la solicitud anidada. Una vez que el servicio desenvuelve la solicitud por lotes en diferentes solicitudes, los encabezados de las partes se ignoran.

El cuerpo de cada parte es en sí mismo una solicitud HTTP completa, con su propio verbo, URL, encabezados y cuerpo. La solicitud HTTP debe contener solamente la parte de la ruta de la URL; no se admiten URL enteras en las solicitudes por lotes.

Los encabezados HTTP para la solicitud por lotes externa, excepto por los encabezados Content-, como Content-Type, se aplican a cada solicitud en el lote. Si especificas cierto encabezado HTTP tanto en la solicitud externa como en una llamada individual, el valor del encabezado de la llamada individual reemplaza el valor del encabezado de la solicitud por lotes externa. Los encabezados de una llamada individual solo se aplican a esa llamada.

Por ejemplo, si proporcionas un encabezado de autorización para una llamada específica, ese encabezado se aplica solamente a esa llamada. Si proporcionas un encabezado de autorización para la solicitud externa, se aplica a todas las llamadas individuales, a menos que la reemplacen con encabezados de autorización propios.

Cuando el servidor recibe la solicitud por lotes, aplica los encabezados y parámetros de consulta de la solicitud externa (según corresponda) a cada parte y, a continuación, trata cada parte como una solicitud HTTP separada.

Respuesta a una solicitud por lotes

La respuesta del servidor es una sola respuesta HTTP estándar con un tipo de contenido multipart/mixed; cada parte es la respuesta a una de las solicitudes de la solicitud por lotes, en el mismo orden que las solicitudes.

Como las partes de la solicitud, cada parte de respuesta contiene una respuesta HTTP completa, que incluye un código de estado, encabezados y un cuerpo. Además, cada una está precedida por un encabezado Content-Type que marca el comienzo de la parte.

Si cierta parte de la solicitud tenía un encabezado Content-ID, la parte correspondiente de la respuesta tendrá un encabezado Content-ID que coincida y un valor original precedido por la string response-, como se muestra en el siguiente ejemplo.

Nota: Es posible que el servidor realice llamadas en cualquier orden. No cuentes con que se ejecutarán en el orden en que las especificaste. Si quieres garantizar que dos llamadas sucedan en un orden determinado, no puedes enviarlas en una misma solicitud; en cambio, envía la primera sola, espera una respuesta y, luego, envía la segunda.

Ejemplo

En el siguiente ejemplo, se muestra el uso de la agrupación en lotes con la API de Google Classroom.

Ejemplo de solicitud por lotes

POST https://classroom.googleapis.com/batch HTTP/1.1
Authorization: Bearer your_auth_token
Content-Type: multipart/mixed; boundary=batch_foobarbaz
Content-Length: total_content_length

--batch_foobarbaz
Content-Type: application/http
Content-Transfer-Encoding: binary
MIME-Version: 1.0
Content-ID: <item1:12930812@classroom.example.com>

PATCH /v1/courses/134529639?updateMask=name HTTP/1.1
Content-Type: application/json; charset=UTF-8
Authorization: Bearer your_auth_token

{
  "name": "Course 1"
}
--batch_foobarbaz
Content-Type: application/http
Content-Transfer-Encoding: binary
MIME-Version: 1.0
Content-ID: <item2:12930812@classroom.example.com>

PATCH /v1/courses/134529901?updateMask=section HTTP/1.1
Content-Type: application/json; charset=UTF-8
Authorization: Bearer your_auth_token
{
  "section": "Section 2"
}
--batch_foobarbaz--

Ejemplo de respuesta por lotes

Esta es la respuesta a la solicitud de ejemplo de la sección anterior.

HTTP/1.1 200
Content-Length: response_total_content_length
Content-Type: multipart/mixed; boundary=batch_foobarbaz

--batch_foobarbaz
Content-Type: application/http
Content-ID: <response-item1:12930812@classroom.example.com>

HTTP/1.1 200 OK
Content-Type application/json
Content-Length: response_part_1_content_length

{
  "id": "134529639",
  "name": "Course 1",
  "section": "Section 1",
  "ownerId": "116269102540619633451",
  "creationTime": "2015-06-25T14:23:56.535Z",
  "updateTime": "2015-06-25T14:33:06.583Z",
  "enrollmentCode": "6paeflo",
  "courseState": "PROVISIONED",
  "alternateLink": "http://classroom.google.com/c/MTM0NTI5NjM5"
}
--batch_foobarbaz
Content-Type: application/http
Content-ID: <response-item2:12930812@classroom.example.com>

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: response_part_2_content_length

{
  "id": "134529901",
  "name": "Course 1",
  "section": "Section 2",
  "ownerId": "116269102540619633451",
  "creationTime": "2015-06-25T14:23:08.761Z",
  "updateTime": "2015-06-25T14:33:06.490Z",
  "enrollmentCode": "so75ha5",
  "courseState": "PROVISIONED",
  "alternateLink": "http://classroom.google.com/c/MTM0NTI5OTAx"
}
--batch_foobarbaz--

Uso de las bibliotecas cliente

En las siguientes muestras de código, se muestra cómo realizar solicitudes por lotes con las bibliotecas cliente de las APIs de Google. Consulta las guías de inicio rápido correspondientes para obtener más información sobre cómo instalar las bibliotecas y configurarlas.

.NET

classroom/snippets/ClassroomSnippets/BatchAddStudents.cs
using Google;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Classroom.v1;
using Google.Apis.Classroom.v1.Data;
using Google.Apis.Requests;
using Google.Apis.Services;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace ClassroomSnippets
{
    // Class to demonstrate the use of Classroom Batch Add Students API
    public class BatchAddStudents
    {
        /// <summary>
        /// Add multiple students in a specified course.
        /// </summary>
        /// <param name="courseId">Id of the course to add students.</param>
        /// <param name="studentEmails">Email address of the students.</param>
        public static void ClassroomBatchAddStudents(string courseId,
            List<string> studentEmails)
        {
            try
            {
                /* Load pre-authorized user credentials from the environment.
                 TODO(developer) - See https://developers.google.com/identity for 
                 guides on implementing OAuth2 for your application. */
                GoogleCredential credential = GoogleCredential.GetApplicationDefault()
                    .CreateScoped(ClassroomService.Scope.ClassroomRosters);

                // Create Classroom API service.
                var service = new ClassroomService(new BaseClientService.Initializer
                {
                    HttpClientInitializer = credential,
                    ApplicationName = "Classroom Snippets"
                });

                var batch = new BatchRequest(service, "https://classroom.googleapis.com/batch");
                BatchRequest.OnResponse<Student> callback = (student, error, i, message) =>
                {
                    if (error != null)
                    {
                        Console.WriteLine("Error adding student to the course: {0}", error.Message);
                    }
                    else
                    {
                        Console.WriteLine("User '{0}' was added as a student to the course.",
                            student.Profile.Name.FullName);
                    }
                };
                foreach (var studentEmail in studentEmails)
                {
                    var student = new Student() {UserId = studentEmail};
                    var request = service.Courses.Students.Create(student, courseId);
                    batch.Queue<Student>(request, callback);
                }

                Task.WaitAll(batch.ExecuteAsync());
            }
            catch (Exception e)
            {
                // TODO(developer) - handle error appropriately
                if (e is AggregateException)
                {
                    Console.WriteLine("Credential Not found");
                }
                else if (e is GoogleApiException)
                {
                    Console.WriteLine("Course does not exist.");
                }
                else
                {
                    throw;
                }
            }
        }
    }
}

Java

classroom/snippets/src/main/java/BatchAddStudents.java
import com.google.api.client.googleapis.batch.BatchRequest;
import com.google.api.client.googleapis.batch.json.JsonBatchCallback;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.googleapis.json.GoogleJsonError;
import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.services.classroom.Classroom;
import com.google.api.services.classroom.ClassroomScopes;
import com.google.api.services.classroom.model.Student;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/* Class to demonstrate the use of Classroom Batch Add Students API */
public class BatchAddStudents {

  /* Scopes required by this API call. If modifying these scopes, delete your previously saved
  tokens/ folder. */
  static ArrayList<String> SCOPES =
      new ArrayList<>(Arrays.asList(ClassroomScopes.CLASSROOM_ROSTERS));

  /**
   * Add multiple students in a specified course.
   *
   * @param courseId - Id of the course to add students.
   * @param studentEmails - Email address of the students.
   * @throws IOException - if credentials file not found.
   * @throws GeneralSecurityException - if a new instance of NetHttpTransport was not created.
   */
  public static void batchAddStudents(String courseId, List<String> studentEmails)
      throws GeneralSecurityException, IOException {

    // Create the classroom API client.
    final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
    Classroom service =
        new Classroom.Builder(
                HTTP_TRANSPORT,
                GsonFactory.getDefaultInstance(),
                ClassroomCredentials.getCredentials(HTTP_TRANSPORT, SCOPES))
            .setApplicationName("Classroom samples")
            .build();

    BatchRequest batch = service.batch();
    JsonBatchCallback<Student> callback =
        new JsonBatchCallback<>() {
          public void onSuccess(Student student, HttpHeaders responseHeaders) {
            System.out.printf(
                "User '%s' was added as a student to the course.\n",
                student.getProfile().getName().getFullName());
          }

          public void onFailure(GoogleJsonError error, HttpHeaders responseHeaders) {
            System.out.printf("Error adding student to the course: %s\n", error.getMessage());
          }
        };
    for (String studentEmail : studentEmails) {
      Student student = new Student().setUserId(studentEmail);
      service.courses().students().create(courseId, student).queue(batch, callback);
    }
    batch.execute();
  }
}

PHP

classroom/snippets/src/ClassroomBatchAddStudents.php
use Google\Client;
use Google\Service\Classroom;
use Google\Service\Classroom\Student;
use Google\Service\Exception;

function batchAddStudents($courseId, $studentEmails)
{
    /* Load pre-authorized user credentials from the environment.
    TODO(developer) - See https://developers.google.com/identity for
     guides on implementing OAuth2 for your application. */
    $client = new Client();
    $client->useApplicationDefaultCredentials();
    $client->addScope("https://www.googleapis.com/auth/classroom.profile.emails");
    $service = new Classroom($client);
    $service->getClient()->setUseBatch(true);
    //create batch
    $batch = $service->createBatch();
    foreach ($studentEmails as $studentEmail) {
        $student = new Student([
            'userId' => $studentEmail
        ]);
        $request = $service->courses_students->create($courseId, $student);
        $requestId = $studentEmail;
        $batch->add($request, $requestId);
    }
    //executing request
    $results = $batch->execute();
    foreach ($results as $responseId => $student) {
        $studentEmail = substr($responseId, strlen('response-') + 1);
        if ($student instanceof Exception) {
            $e = $student;
            printf("Error adding user '%s' to the course: %s\n", $studentEmail,
                $e->getMessage());
        } else {
            printf("User '%s' was added as a student to the course.\n",
                $student->profile->name->fullName, $courseId);
        }
    }
    $service->getClient()->setUseBatch(false);
    return $results;
}

Python

course_id = '123456'
student_emails = ['alice@example.edu', 'bob@example.edu']
def callback(request_id, response, exception):
    if exception is not None:
        print 'Error adding user "{0}" to the course course: {1}'.format(
            request_id, exception)
    else:
        print 'User "{0}" added as a student to the course.'.format(
            response.get('profile').get('name').get('fullName'))
batch = service.new_batch_http_request(callback=callback)
for student_email in student_emails:
    student = {
        'userId': student_email
    }
    request = service.courses().students().create(courseId=course_id,
                                                  body=student)
    batch.add(request, request_id=student_email)
batch.execute(http=http)