כתיבת פונקציות HTTP

בפונקציות Cloud Run, משתמשים בפונקציות HTTP כשרוצים להפעיל פונקציה באמצעות בקשת HTTP(S). כדי לאפשר סמנטיקה של HTTP, חתימות של פונקציות HTTP מקבלות ארגומנטים ספציפיים ל-HTTP.

הטמעה

בדוגמה הבאה מוצג קובץ מקור של פונקציית HTTP בסיסית לכל סביבת זמן ריצה. במאמר בנושא מבנה ספריית קובצי המקור מוסבר איפה אפשר למצוא את קוד המקור.

Node.js

const functions = require('@google-cloud/functions-framework');

// Register an HTTP function with the Functions Framework
functions.http('myHttpFunction', (req, res) => {
  // Your code here

  // Send an HTTP response
  res.send('OK');
});

ב-Node.js, רושמים פונקציה לטיפול ב-HTTP באמצעות Functions Framework for Node.js. פונקציית ה-handler של HTTP חייבת להיות פונקציית middleware של Express שמקבלת את הארגומנטים request ו-response ושולחת תגובת HTTP.

פונקציות Cloud Run מנתחות אוטומטית את גוף הבקשה על סמך הכותרת Content-Type של הבקשה באמצעות body-parser, כך שאפשר לגשת לאובייקטים req.body ו-req.rawBody ב-handler של HTTP.

נקודת הכניסה של הפונקציה היא השם שבו רשום ה-handler ב-Functions Framework. בדוגמה הזו, נקודת הכניסה היא myHttpFunction.

Python

import functions_framework

# Register an HTTP function with the Functions Framework
@functions_framework.http
def my_http_function(request):
  # Your code here

  # Return an HTTP response
  return 'OK'

ב-Python, רושמים פונקציית handler של HTTP באמצעות Functions Framework for Python. פונקציית הטיפול ב-HTTP צריכה לקבל אובייקט בקשת Flask כארגומנט ולהחזיר ערך ש-Flask יכול להמיר לאובייקט תגובת HTTP.

ה-entry point של הפונקציה הוא השם של פונקציית ה-handler שרשומה ב-Functions Framework. בדוגמה הזו, נקודת הכניסה היא my_http_function.

Go

package myhttpfunction

import (
    "fmt"
    "net/http"

    "github.com/GoogleCloudPlatform/functions-framework-go/functions"
)

func init() {
    // Register an HTTP function with the Functions Framework
    functions.HTTP("MyHTTPFunction", myHTTPFunction)
}

// Function myHTTPFunction is an HTTP handler
func myHTTPFunction(w http.ResponseWriter, r *http.Request) {
    // Your code here

    // Send an HTTP response
    fmt.Fprintln(w, "OK")
}

ב-Go, רושמים פונקציה לטיפול ב-HTTP באמצעות Functions Framework for Go בפונקציה init(). פונקציית ה-handler של HTTP צריכה להשתמש בממשק http.HandlerFunc הרגיל כדי לשלוח תגובת HTTP.

נקודת הכניסה של הפונקציה היא השם שבו רשום ה-handler ב-Functions Framework. בדוגמה הזו, נקודת הכניסה היא MyHTTPFunction.

פונקציית ה-handler של HTTP חייבת להטמיע את הממשק הרגיל של http.HandlerFunc. היא מקבלת ממשק http.ResponseWriter שהפונקציה משתמשת בו כדי ליצור תשובה לבקשה, ומצביע למבנה http.Request שמכיל את הפרטים של בקשת ה-HTTP הנכנסת.

Java

package myhttpfunction;

import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;

// Define a class that implements the HttpFunction interface
public class MyHttpFunction implements HttpFunction {
  // Implement the service() method to handle HTTP requests
  @Override
  public void service(HttpRequest request, HttpResponse response) throws Exception {
    // Your code here

    // Send an HTTP response
    response.getWriter().write("OK");
  }
}

ב-Java, משתמשים ב-Functions Framework Java API כדי להטמיע מחלקה של handler ל-HTTP עם הממשק HttpFunction. השיטה service() חייבת לשלוח תגובת HTTP.

נקודת הכניסה של הפונקציה היא השם המלא של מחלקת ה-handler של ה-HTTP, כולל שם החבילה. בדוגמה הזו, נקודת הכניסה היא myhttpfunction.MyHttpFunction.

השיטה service מקבלת אובייקט HttpRequest שמתאר את בקשת ה-HTTP הנכנסת, ואובייקט HttpResponse שהפונקציה מאכלסת בהודעת תגובה.

C#‎

using Google.Cloud.Functions.Framework;
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;

namespace MyProject
{
    // Define a class that implements the IHttpFunction interface
    public class MyHttpFunction : IHttpFunction
    {
        // Implement the HandleAsync() method to handle HTTP requests
        public async Task HandleAsync(HttpContext context)
        {
            // Your code here

            // Send an HTTP response
            await context.Response.WriteAsync("OK");
        }
    }
}

בזמני ריצה של .NET, משתמשים ב-Functions Framework for .NET כדי להטמיע מחלקה של handler ל-HTTP עם הממשק IHttpFunction. השיטה HandleAsync() מקבלת אובייקט HttpContext רגיל של ASP.NET כארגומנט, והיא חייבת לשלוח תגובת HTTP.

נקודת הכניסה של הפונקציה היא השם המלא של מחלקת ה-handler של ה-HTTP, כולל מרחב השמות. בדוגמה הזו, נקודת הכניסה היא MyProject.MyHttpFunction.

Ruby

require "functions_framework"

# Register an HTTP function with the Functions Framework
FunctionsFramework.http "my_http_function" do |request|
  # Your code here

  # Return an HTTP response
  "OK"
end

ב-Ruby, רושמים פונקציית handler של HTTP באמצעות Functions Framework for Ruby. פונקציית ה-handler של HTTP חייבת לקבל אובייקט Rack request כארגומנט ולהחזיר ערך שאפשר להשתמש בו כתגובת HTTP.

נקודת הכניסה של הפונקציה היא השם שבו רשום ה-handler ב-Functions Framework. בדוגמה הזו, נקודת הכניסה היא my_http_function.

PHP

<?php

use Google\CloudFunctions\FunctionsFramework;
use Psr\Http\Message\ServerRequestInterface;

// Register an HTTP function with the Functions Framework
FunctionsFramework::http('myHttpFunction', 'myHttpHandler');

// Define your HTTP handler
function myHttpHandler(ServerRequestInterface $request): string
{
    // Your code here

    // Return an HTTP response
    return 'OK';
}

ב-PHP, רושמים פונקציית handler של HTTP באמצעות Functions Framework for PHP. פונקציית הטיפול ב-HTTP צריכה לקבל ארגומנט שמיישם את הממשק PSR-7 ServerRequestInterface ולהחזיר תגובת HTTP כמחרוזת או כאובייקט שמיישם את הממשק PSR-7 ResponseInterface.

נקודת הכניסה של הפונקציה היא השם שבו רשום ה-handler ב-Functions Framework. בדוגמה הזו, נקודת הכניסה היא myHttpFunction.

בקשות ותגובות HTTP

פונקציות HTTP מקבלות את שיטות הבקשה של HTTP שמפורטות בדף טריגרים של HTTP. ה-handler של ה-HTTP יכול לבדוק את שיטת הבקשה ולבצע פעולות שונות על סמך השיטה.

הפונקציה צריכה לשלוח תגובת HTTP. אם הפונקציה יוצרת משימות ברקע (כמו עם שרשורים, עתידים, אובייקטים של JavaScript Promise, קריאות חוזרות או תהליכי מערכת), צריך לסיים את המשימות האלה או לפתור אותן בדרך אחרת לפני שליחת תגובת HTTP. יכול להיות שמשימות שלא הסתיימו לפני שליחת תגובת ה-HTTP לא יושלמו, ועלולות לגרום להתנהגות לא מוגדרת.

מידע נוסף על פונקציות HTTP ואפשרויות שקשורות אליהן זמין במאמר בנושא טריגרים של HTTP.

טיפול ב-CORS

שיתוף משאבים בין מקורות (CORS) הוא דרך לאפשר לאפליקציות שפועלות בדומיין אחד לגשת למשאבים מדומיין אחר. לדוגמה, יכול להיות שתצטרכו לאפשר לדומיין שלכם לשלוח בקשות לדומיין של פונקציות Cloud Run כדי לגשת לפונקציה.

אם CORS לא מוגדר כמו שצריך, יכול להיות שיוצגו שגיאות כמו השגיאות הבאות:

XMLHttpRequest cannot load https://YOUR_FUNCTION_URL.
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'https://YOUR_DOMAIN' is therefore not allowed access.

כדי לאפשר לבקשות ממקורות שונים לגשת לפונקציה, צריך להגדיר את הכותרת Access-Control-Allow-Origin בתגובת ה-HTTP. במקרה של בקשות קדם-הפעלה ממקורות שונים, צריך להשיב לבקשת קדם-ההפעלה OPTIONS עם קוד תגובה 204 וכותרות נוספות.

Node.js

const functions = require('@google-cloud/functions-framework');

/**
 * HTTP function that supports CORS requests.
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
functions.http('corsEnabledFunction', (req, res) => {
  // Set CORS headers for preflight requests
  // Allows GETs from any origin with the Content-Type header
  // and caches preflight response for 3600s

  res.set('Access-Control-Allow-Origin', '*');

  if (req.method === 'OPTIONS') {
    // Send response to OPTIONS requests
    res.set('Access-Control-Allow-Methods', 'GET');
    res.set('Access-Control-Allow-Headers', 'Content-Type');
    res.set('Access-Control-Max-Age', '3600');
    res.status(204).send('');
  } else {
    res.send('Hello World!');
  }
});

Python

import functions_framework

@functions_framework.http
def cors_enabled_function(request):
    # For more information about CORS and CORS preflight requests, see:
    # https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request

    # Set CORS headers for the preflight request
    if request.method == "OPTIONS":
        # Allows GET requests from any origin with the Content-Type
        # header and caches preflight response for an 3600s
        headers = {
            "Access-Control-Allow-Origin": "*",
            "Access-Control-Allow-Methods": "GET",
            "Access-Control-Allow-Headers": "Content-Type",
            "Access-Control-Max-Age": "3600",
        }

        return ("", 204, headers)

    # Set CORS headers for the main request
    headers = {"Access-Control-Allow-Origin": "*"}

    return ("Hello World!", 200, headers)

Go


// Package http provides a set of HTTP Cloud Functions samples.
package http

import (
	"fmt"
	"net/http"

	"github.com/GoogleCloudPlatform/functions-framework-go/functions"
)

// CORSEnabledFunction is an example of setting CORS headers.
// For more information about CORS and CORS preflight requests, see
// https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request.
func CORSEnabledFunction(w http.ResponseWriter, r *http.Request) {
	// Set CORS headers for the preflight request
	if r.Method == http.MethodOptions {
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "POST")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
		w.Header().Set("Access-Control-Max-Age", "3600")
		w.WriteHeader(http.StatusNoContent)
		return
	}
	// Set CORS headers for the main request.
	w.Header().Set("Access-Control-Allow-Origin", "*")
	fmt.Fprint(w, "Hello, World!")
}

func init() {
	functions.HTTP("CORSEnabledFunction", CORSEnabledFunction)
}

Java


import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import java.io.BufferedWriter;
import java.io.IOException;
import java.net.HttpURLConnection;

public class CorsEnabled implements HttpFunction {
  // corsEnabled is an example of setting CORS headers.
  // For more information about CORS and CORS preflight requests, see
  // https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request.
  @Override
  public void service(HttpRequest request, HttpResponse response)
      throws IOException {
    // Set CORS headers
    //   Allows GETs from any origin with the Content-Type
    //   header and caches preflight response for 3600s
    response.appendHeader("Access-Control-Allow-Origin", "*");

    if ("OPTIONS".equals(request.getMethod())) {
      response.appendHeader("Access-Control-Allow-Methods", "GET");
      response.appendHeader("Access-Control-Allow-Headers", "Content-Type");
      response.appendHeader("Access-Control-Max-Age", "3600");
      response.setStatusCode(HttpURLConnection.HTTP_NO_CONTENT);
      return;
    }

    // Handle the main request.
    BufferedWriter writer = response.getWriter();
    writer.write("CORS headers set successfully!");
  }
}

C#‎

using Google.Cloud.Functions.Framework;
using Microsoft.AspNetCore.Http;
using System.Net;
using System.Threading.Tasks;

namespace Cors;

// For more information about CORS and CORS preflight requests, see
// https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request.
public class Function : IHttpFunction
{
    public async Task HandleAsync(HttpContext context)
    {
        HttpRequest request = context.Request;
        HttpResponse response = context.Response;

        // Set CORS headers
        //   Allows GETs from any origin with the Content-Type
        //   header and caches preflight response for 3600s

        response.Headers.Append("Access-Control-Allow-Origin", "*");
        if (HttpMethods.IsOptions(request.Method))
        {
            response.Headers.Append("Access-Control-Allow-Methods", "GET");
            response.Headers.Append("Access-Control-Allow-Headers", "Content-Type");
            response.Headers.Append("Access-Control-Max-Age", "3600");
            response.StatusCode = (int) HttpStatusCode.NoContent;
            return;
        }

        await response.WriteAsync("CORS headers set successfully!", context.RequestAborted);
    }
}

Ruby

FunctionsFramework.http "cors_enabled_function" do |request|
  # For more information about CORS and CORS preflight requests, see
  # https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request
  # for more information.

  # Set CORS headers for the preflight request
  if request.options?
    # Allows GET requests from any origin with the Content-Type
    # header and caches preflight response for an 3600s
    headers = {
      "Access-Control-Allow-Origin"  => "*",
      "Access-Control-Allow-Methods" => "GET",
      "Access-Control-Allow-Headers" => "Content-Type",
      "Access-Control-Max-Age"       => "3600"
    }
    [204, headers, []]
  else
    # Set CORS headers for the main request
    headers = {
      "Access-Control-Allow-Origin" => "*"
    }

    [200, headers, ["Hello World!"]]
  end
end

PHP

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use GuzzleHttp\Psr7\Response;

function corsEnabledFunction(ServerRequestInterface $request): ResponseInterface
{
    // Set CORS headers for preflight requests
    // Allows GETs from any origin with the Content-Type header
    // and caches preflight response for 3600s
    $headers = ['Access-Control-Allow-Origin' => '*'];

    if ($request->getMethod() === 'OPTIONS') {
        // Send response to OPTIONS requests
        $headers = array_merge($headers, [
            'Access-Control-Allow-Methods' => 'GET',
            'Access-Control-Allow-Headers' => 'Content-Type',
            'Access-Control-Max-Age' => '3600'
        ]);
        return new Response(204, $headers, '');
    } else {
        return new Response(200, $headers, 'Hello World!');
    }
}

מגבלות CORS

בבקשות CORS שכוללות קדם-הפעלה, בקשות OPTIONS של קדם-הפעלה נשלחות ללא כותרת Authorization, ולכן הן יידחו בכל פונקציות ה-HTTP שדורשות אימות. הבקשות הראשיות ייכשלו גם הן כי בקשות ה-preflight נכשלות. כדי לעקוף את ההגבלה הזו, אפשר להשתמש באחת מהאפשרויות הבאות:

השלבים הבאים