HTTP 関数は、HTTP(s) リクエストを介して関数を呼び出す必要のある場合に使用します。HTTP セマンティクスを実行するため、HTTP 関数シグネチャは HTTP 固有の引数を受け入れます。
使用例
以下の例は、name
パラメータを含む HTTP POST リクエストを処理する方法を示しています。
Node.js
const escapeHtml = require('escape-html');
/**
* HTTP Cloud Function.
*
* @param {Object} req Cloud Function request context.
* More info: https://expressjs.com/en/api.html#req
* @param {Object} res Cloud Function response context.
* More info: https://expressjs.com/en/api.html#res
*/
exports.helloHttp = (req, res) => {
res.send(`Hello ${escapeHtml(req.query.name || req.body.name || 'World')}!`);
};
Python
from flask import escape
def hello_http(request):
"""HTTP Cloud Function.
Args:
request (flask.Request): The request object.
<https://flask.palletsprojects.com/en/1.1.x/api/#incoming-request-data>
Returns:
The response text, or any set of values that can be turned into a
Response object using `make_response`
<https://flask.palletsprojects.com/en/1.1.x/api/#flask.make_response>.
"""
request_json = request.get_json(silent=True)
request_args = request.args
if request_json and 'name' in request_json:
name = request_json['name']
elif request_args and 'name' in request_args:
name = request_args['name']
else:
name = 'World'
return 'Hello {}!'.format(escape(name))
Go
// Package helloworld provides a set of Cloud Functions samples.
package helloworld
import (
"encoding/json"
"fmt"
"html"
"net/http"
)
// HelloHTTP is an HTTP Cloud Function with a request parameter.
func HelloHTTP(w http.ResponseWriter, r *http.Request) {
var d struct {
Name string `json:"name"`
}
if err := json.NewDecoder(r.Body).Decode(&d); err != nil {
fmt.Fprint(w, "Hello, World!")
return
}
if d.Name == "" {
fmt.Fprint(w, "Hello, World!")
return
}
fmt.Fprintf(w, "Hello, %s!", html.EscapeString(d.Name))
}
Java
import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.logging.Logger;
public class HelloHttp implements HttpFunction {
private static final Logger logger = Logger.getLogger(HelloHttp.class.getName());
private static final Gson gson = new Gson();
@Override
public void service(HttpRequest request, HttpResponse response)
throws IOException {
// Check URL parameters for "name" field
// "world" is the default value
String name = request.getFirstQueryParameter("name").orElse("world");
// Parse JSON request and check for "name" field
try {
JsonElement requestParsed = gson.fromJson(request.getReader(), JsonElement.class);
JsonObject requestJson = null;
if (requestParsed != null && requestParsed.isJsonObject()) {
requestJson = requestParsed.getAsJsonObject();
}
if (requestJson != null && requestJson.has("name")) {
name = requestJson.get("name").getAsString();
}
} catch (JsonParseException e) {
logger.severe("Error parsing JSON: " + e.getMessage());
}
var writer = new PrintWriter(response.getWriter());
writer.printf("Hello %s!", name);
}
}
C#
using Google.Cloud.Functions.Framework;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
namespace HelloHttp
{
public class Function : IHttpFunction
{
private readonly ILogger _logger;
public Function(ILogger<Function> logger) =>
_logger = logger;
public async Task HandleAsync(HttpContext context)
{
HttpRequest request = context.Request;
// Check URL parameters for "name" field
// "world" is the default value
string name = ((string) request.Query["name"]) ?? "world";
// If there's a body, parse it as JSON and check for "name" field.
using TextReader reader = new StreamReader(request.Body);
string text = await reader.ReadToEndAsync();
if (text.Length > 0)
{
try
{
JsonElement json = JsonSerializer.Deserialize<JsonElement>(text);
if (json.TryGetProperty("name", out JsonElement nameElement) &&
nameElement.ValueKind == JsonValueKind.String)
{
name = nameElement.GetString();
}
}
catch (JsonException parseException)
{
_logger.LogError(parseException, "Error parsing JSON request");
}
}
await context.Response.WriteAsync($"Hello {name}!");
}
}
}
次のコマンドは、curl
を使用して関数を呼び出し、パラメータに渡す方法を示しています。
curl -X POST HTTP_TRIGGER_ENDPOINT -H "Content-Type:application/json" -d '{"name":"Jane"}'
HTTP_TRIGGER_ENDPOINT
は、関数のデプロイ時に取得される関数の URL です。詳しくは、HTTP トリガーをご覧ください。
HTTP フレームワーク
HTTP を処理するために、Cloud Functions は各ランタイムで特定の HTTP フレームワークを使用します。
ランタイム | HTTP フレームワーク |
---|---|
Node.js(8、10、12) | Express 4.17.1 |
Python | Flask 1.0.2 |
Go | 標準 http.HandlerFunc インターフェース |
Java | Functions Framework Java API |
.NET | Functions Framework for .NET |
HTTP リクエストの解析
次の例では、さまざまな形式の HTTP リクエストを読み取ります。Node.js
Node.js では、リクエストの本文は content-type
ヘッダーに基づいて自動的に解析され、HTTP 関数の引数を介して使用できるようになります。
const escapeHtml = require('escape-html');
/**
* Responds to an HTTP request using data from the request body parsed according
* to the "content-type" header.
*
* @param {Object} req Cloud Function request context.
* @param {Object} res Cloud Function response context.
*/
exports.helloContent = (req, res) => {
let name;
switch (req.get('content-type')) {
// '{"name":"John"}'
case 'application/json':
({name} = req.body);
break;
// 'John', stored in a Buffer
case 'application/octet-stream':
name = req.body.toString(); // Convert buffer to a string
break;
// 'John'
case 'text/plain':
name = req.body;
break;
// 'name=John' in the body of a POST request (not the URL)
case 'application/x-www-form-urlencoded':
({name} = req.body);
break;
}
res.status(200).send(`Hello ${escapeHtml(name || 'World')}!`);
};
Python
from flask import escape
def hello_content(request):
""" Responds to an HTTP request using data from the request body parsed
according to the "content-type" header.
Args:
request (flask.Request): The request object.
<https://flask.palletsprojects.com/en/1.1.x/api/#incoming-request-data>
Returns:
The response text, or any set of values that can be turned into a
Response object using `make_response`
<https://flask.palletsprojects.com/en/1.1.x/api/#flask.make_response>.
"""
content_type = request.headers['content-type']
if content_type == 'application/json':
request_json = request.get_json(silent=True)
if request_json and 'name' in request_json:
name = request_json['name']
else:
raise ValueError("JSON is invalid, or missing a 'name' property")
elif content_type == 'application/octet-stream':
name = request.data
elif content_type == 'text/plain':
name = request.data
elif content_type == 'application/x-www-form-urlencoded':
name = request.form.get('name')
else:
raise ValueError("Unknown content type: {}".format(content_type))
return 'Hello {}!'.format(escape(name))
Go
// Package http provides a set of HTTP Cloud Functions samples.
package http
import (
"encoding/json"
"fmt"
"html"
"io/ioutil"
"log"
"net/http"
)
// HelloContentType is an HTTP Cloud function.
// It uses the Content-Type header to identify the request payload format.
func HelloContentType(w http.ResponseWriter, r *http.Request) {
var name string
switch r.Header.Get("Content-Type") {
case "application/json":
var d struct {
Name string `json:"name"`
}
err := json.NewDecoder(r.Body).Decode(&d)
if err != nil {
log.Printf("error parsing application/json: %v", err)
} else {
name = d.Name
}
case "application/octet-stream":
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("error parsing application/octet-stream: %v", err)
} else {
name = string(body)
}
case "text/plain":
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("error parsing text/plain: %v", err)
} else {
name = string(body)
}
case "application/x-www-form-urlencoded":
if err := r.ParseForm(); err != nil {
log.Printf("error parsing application/x-www-form-urlencoded: %v", err)
} else {
name = r.FormValue("name")
}
}
if name == "" {
name = "World"
}
fmt.Fprintf(w, "Hello, %s!", html.EscapeString(name))
}
Java
import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Optional;
public class ParseContentType implements HttpFunction {
// Use GSON (https://github.com/google/gson) to parse JSON content.
private static final Gson gson = new Gson();
// Responds to an HTTP request using data from the request body parsed according to the
// "content-type" header.
@Override
public void service(HttpRequest request, HttpResponse response)
throws IOException {
String name;
// Default values avoid null issues (with switch/case) and exceptions from get() (optionals)
String contentType = request.getContentType().orElse("");
switch (contentType) {
case "application/json":
// '{"name":"John"}'
JsonObject body = gson.fromJson(request.getReader(), JsonObject.class);
if (body.has("name")) {
name = body.get("name").getAsString();
break;
}
// else: No "name" parameter specified; fall through to default case
case "application/octet-stream":
// 'John', stored in a Buffer
name = new String(Base64.getDecoder().decode(request.getInputStream().readAllBytes()),
StandardCharsets.UTF_8);
break;
case "text/plain":
// 'John'
name = request.getReader().readLine();
break;
case "application/x-www-form-urlencoded":
// 'name=John' in the body of a POST request (not the URL)
Optional<String> nameParam = request.getFirstQueryParameter("name");
if (nameParam.isPresent()) {
name = nameParam.get();
break;
}
// else: No "name" parameter specified; fall through to default case
default:
// Invalid or missing header "Content-Type"
response.setStatusCode(HttpURLConnection.HTTP_BAD_REQUEST);
return;
}
// Respond with a name, if one was detected
if (name != null) {
var writer = new PrintWriter(response.getWriter());
writer.printf("Hello %s!", name);
}
}
}
C#
using Google.Cloud.Functions.Framework;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Mime;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace ParseContentType
{
public class Function : IHttpFunction
{
public async Task HandleAsync(HttpContext context)
{
HttpRequest request = context.Request;
HttpResponse response = context.Response;
string name = null;
ContentType contentType = new ContentType(request.ContentType);
switch (contentType.MediaType)
{
case "application/json":
{
// '{"name":"John"}'
using TextReader reader = new StreamReader(request.Body);
string json = await reader.ReadToEndAsync();
JsonElement body = JsonSerializer.Deserialize<JsonElement>(json);
if (body.TryGetProperty("name", out JsonElement property) && property.ValueKind == JsonValueKind.String)
{
name = property.GetString();
}
break;
}
case "application/octet-stream":
{
// 'John', encoded to bytes using UTF-8, then encoded as base64
using TextReader reader = new StreamReader(request.Body);
string base64 = await reader.ReadToEndAsync();
byte[] data = Convert.FromBase64String(base64);
name = Encoding.UTF8.GetString(data);
break;
}
case "text/plain":
{
// 'John'
using TextReader reader = new StreamReader(request.Body);
name = await reader.ReadLineAsync();
break;
}
case "application/x-www-form-urlencoded":
{
// 'name=John' in the body of a POST request (not the URL)
if (request.Form.TryGetValue("name", out StringValues value))
{
name = value;
}
break;
}
}
if (name is object)
{
await response.WriteAsync($"Hello {name}!");
}
else
{
// Unrecognized content type, or the name wasn't in the content
// (e.g. JSON without a "name" property)
response.StatusCode = (int) HttpStatusCode.BadRequest;
}
}
}
}
CORS リクエストの処理
クロスオリジン リソース シェアリング(CORS)を使用すると、あるドメインで実行されているアプリケーションから別のドメインのコンテンツにアクセスできるようになります。たとえば、yourdomain.com
から region-project.cloudfunctions.net/yourfunction
にリクエストを送信できるようになります。
CORS が正しく設定されていなければ、次のようなエラーが発生する可能性があります。
XMLHttpRequest cannot load https://region-project.cloudfunctions.net/function. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://yourdomain.com' is therefore not allowed access.
CORS は、次の 2 つのリクエストから構成されています。
- プリフライトの
OPTIONS
リクエスト OPTIONS
リクエストに続くメイン リクエスト
プリフライト リクエストには、メイン リクエストで送信されるメソッド(Access-Control-Request-Method
)と追加ヘッダー(Access-Control-Request-Headers
)を示すヘッダーと、メイン リクエストの送信元(Origin
)が含まれています。
プリフライト リクエストを処理するには、受け入れるリクエストに合わせて適切な Access-Control-Allow-*
ヘッダーを設定する必要があります。
Node.js
/**
* HTTP function that supports CORS requests.
*
* @param {Object} req Cloud Function request context.
* @param {Object} res Cloud Function response context.
*/
exports.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
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
# for more information.
# 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"
)
// 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!")
}
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!");
}
}
}
また、サードパーティのライブラリを使用して、CORS を処理することもできます。
認証と CORS
Authorization
ヘッダーを使用してリクエストを送信する予定の場合は、次を行う必要があります。
Authorization
ヘッダーをAccess-Control-Allow-Headers
に追加する。Access-Control-Allow-Credentials
ヘッダーをtrue
に設定する。- 特定の起点を
Access-Control-Allow-Origin
に設定する(ワイルドカードは使用できません)。
Node.js
/**
* HTTP function that supports CORS requests with credentials.
*
* @param {Object} req Cloud Function request context.
* @param {Object} res Cloud Function response context.
*/
exports.corsEnabledFunctionAuth = (req, res) => {
// Set CORS headers for preflight requests
// Allows GETs from origin https://mydomain.com with Authorization header
res.set('Access-Control-Allow-Origin', 'https://mydomain.com');
res.set('Access-Control-Allow-Credentials', 'true');
if (req.method === 'OPTIONS') {
// Send response to OPTIONS requests
res.set('Access-Control-Allow-Methods', 'GET');
res.set('Access-Control-Allow-Headers', 'Authorization');
res.set('Access-Control-Max-Age', '3600');
res.status(204).send('');
} else {
res.send('Hello World!');
}
};
Python
def cors_enabled_function_auth(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 preflight requests
if request.method == 'OPTIONS':
# Allows GET requests from origin https://mydomain.com with
# Authorization header
headers = {
'Access-Control-Allow-Origin': 'https://mydomain.com',
'Access-Control-Allow-Methods': 'GET',
'Access-Control-Allow-Headers': 'Authorization',
'Access-Control-Max-Age': '3600',
'Access-Control-Allow-Credentials': 'true'
}
return ('', 204, headers)
# Set CORS headers for main requests
headers = {
'Access-Control-Allow-Origin': 'https://mydomain.com',
'Access-Control-Allow-Credentials': 'true'
}
return ('Hello World!', 200, headers)
Go
// Package http provides a set of HTTP Cloud Functions samples.
package http
import (
"fmt"
"net/http"
)
// CORSEnabledFunctionAuth is an example of setting CORS headers with
// authentication enabled.
// For more information about CORS and CORS preflight requests, see
// https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request.
func CORSEnabledFunctionAuth(w http.ResponseWriter, r *http.Request) {
// Set CORS headers for the preflight request
if r.Method == http.MethodOptions {
w.Header().Set("Access-Control-Allow-Credentials", "true")
w.Header().Set("Access-Control-Allow-Headers", "Authorization")
w.Header().Set("Access-Control-Allow-Methods", "POST")
w.Header().Set("Access-Control-Allow-Origin", "https://example.com")
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-Credentials", "true")
w.Header().Set("Access-Control-Allow-Origin", "https://example.com")
fmt.Fprint(w, "Hello World!")
}
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 CorsEnabledAuth implements HttpFunction {
// corsEnabledAuth 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 origin https://mydomain.com
// with the Authorization header present
response.appendHeader("Access-Control-Allow-Origin", "https://mydomain.com");
response.appendHeader("Access-Control-Allow-Credentials", "true");
if ("OPTIONS".equals(request.getMethod())) {
response.appendHeader("Access-Control-Allow-Methods", "GET");
response.appendHeader("Access-Control-Allow-Headers", "Authorization");
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!");
}
}
同じドメインでのホスティング
CORS を実装する代わりに、ウェブサイトと関数を同じドメインにホストすることもできます。リクエストが同じ起点から送信されるため、CORS は適用されません。これにより、コードが大幅に簡素化されます。
これを実現するには、Firebase Hosting と Google Cloud Functions を統合するのが最も簡単な方法です。
Cloud Endpoints を使用した CORS の処理
Cloud Endpoints プロキシをデプロイして、CORS を有効化できます。
認証機能が必要な場合、Google ID トークン認証を有効化して、認証トークンを検証することもできます。
HTTP メソッドを処理する
HTTP 関数は、すべての HTTP メソッドを受け入れます。次のサンプルは、受信した HTTP メソッド(GET
や PUT
など)に基づいて、さまざまなアクションを実行する方法を示しています。
Node.js
/**
* Responds to a GET request with "Hello World!". Forbids a PUT request.
*
* @example
* gcloud functions call helloHttp
*
* @param {Object} req Cloud Function request context.
* @param {Object} res Cloud Function response context.
*/
exports.helloHttp = (req, res) => {
switch (req.method) {
case 'GET':
res.status(200).send('Hello World!');
break;
case 'PUT':
res.status(403).send('Forbidden!');
break;
default:
res.status(405).send({error: 'Something blew up!'});
break;
}
};
Python
def hello_method(request):
""" Responds to a GET request with "Hello world!". Forbids a PUT request.
Args:
request (flask.Request): The request object.
<https://flask.palletsprojects.com/en/1.1.x/api/#incoming-request-data>
Returns:
The response text, or any set of values that can be turned into a
Response object using `make_response`
<https://flask.palletsprojects.com/en/1.1.x/api/#flask.make_response>.
"""
from flask import abort
if request.method == 'GET':
return 'Hello World!'
elif request.method == 'PUT':
return abort(403)
else:
return abort(405)
Go
// Package http provides a set of HTTP Cloud Functions samples.
package http
import (
"fmt"
"net/http"
)
// HelloHTTPMethod is an HTTP Cloud function.
// It uses the request method to differentiate the response.
func HelloHTTPMethod(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
fmt.Fprint(w, "Hello World!")
case http.MethodPut:
http.Error(w, "403 - Forbidden", http.StatusForbidden)
default:
http.Error(w, "405 - Method Not Allowed", http.StatusMethodNotAllowed)
}
}
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 HttpMethod implements HttpFunction {
@Override
public void service(HttpRequest request, HttpResponse response)
throws IOException {
BufferedWriter writer = response.getWriter();
switch (request.getMethod()) {
case "GET":
response.setStatusCode(HttpURLConnection.HTTP_OK);
writer.write("Hello world!");
break;
case "PUT":
response.setStatusCode(HttpURLConnection.HTTP_FORBIDDEN);
writer.write("Forbidden!");
break;
default:
response.setStatusCode(HttpURLConnection.HTTP_BAD_METHOD);
writer.write("Something blew up!");
break;
}
}
}
C#
using Google.Cloud.Functions.Framework;
using Microsoft.AspNetCore.Http;
using System.Net;
using System.Threading.Tasks;
namespace HttpRequestMethod
{
public class Function : IHttpFunction
{
public async Task HandleAsync(HttpContext context)
{
HttpResponse response = context.Response;
switch (context.Request.Method)
{
case "GET":
response.StatusCode = (int) HttpStatusCode.OK;
await response.WriteAsync("Hello world!");
break;
case "PUT":
response.StatusCode = (int) HttpStatusCode.Forbidden;
await response.WriteAsync("Forbidden!");
break;
default:
response.StatusCode = (int) HttpStatusCode.MethodNotAllowed;
await response.WriteAsync("Something blew up!");
break;
}
}
}
}
コンテンツ タイプの処理
Node.js の場合、Cloud Functions では上記のように、コンテンツ タイプが application/json
と application/x-www-form-urlencoded
のリクエスト本文が解析されます。書式なしテキストのコンテンツ タイプ(text/plain
)は、デフォルトのエンコードの UTF-8(または、content-type
ヘッダーで指定されるカスタム エンコード)を使用して、文字列として渡されます。
他のコンテンツ タイプにアクセスするには、HTTP 関数の引数を検査します。この方法は言語によって異なります。
以下の例では、コンテンツ タイプ text/xml
のリクエストを処理します。
Node.js
rawBody
プロパティには、リクエスト本文の未解析バイト数が組み込まれています。
/**
* Parses a document of type 'text/xml'
*
* @param {Object} req Cloud Function request context.
* @param {Object} res Cloud Function response context.
*/
exports.parseXML = (req, res) => {
// Convert the request to a Buffer and a string
// Use whichever one is accepted by your XML parser
const data = req.rawBody;
const xmlData = data.toString();
const {parseString} = require('xml2js');
parseString(xmlData, (err, result) => {
if (err) {
console.error(err);
res.status(500).end();
return;
}
res.send(result);
});
};
Python
import json
import xmltodict
def parse_xml(request):
""" Parses a document of type 'text/xml'
Args:
request (flask.Request): The request object.
Returns:
The response text, or any set of values that can be turned into a
Response object using `make_response`
<http://flask.pocoo.org/docs/1.0/api/#flask.Flask.make_response>.
"""
data = xmltodict.parse(request.data)
return json.dumps(data, indent=2)
Go
// Package http provides a set of HTTP Cloud Functions samples.
package http
import (
"encoding/xml"
"fmt"
"html"
"io/ioutil"
"net/http"
)
// ParseXML is an example of parsing a text/xml request.
func ParseXML(w http.ResponseWriter, r *http.Request) {
var d struct {
Name string
}
b, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Could not read request", http.StatusBadRequest)
}
if err := xml.Unmarshal(b, &d); err != nil {
http.Error(w, "Could not parse request", http.StatusBadRequest)
}
if d.Name == "" {
d.Name = "World"
}
fmt.Fprintf(w, "Hello, %v!", html.EscapeString(d.Name))
}
マルチパート データ
次の例は、コンテンツ タイプ multipart/form-data
でデータを処理する方法を示しています。選択した言語によっては、解析ライブラリを使用する必要が生じる場合があります。
Node.js
/**
* Parses a 'multipart/form-data' upload request
*
* @param {Object} req Cloud Function request context.
* @param {Object} res Cloud Function response context.
*/
const path = require('path');
const os = require('os');
const fs = require('fs');
// Node.js doesn't have a built-in multipart/form-data parsing library.
// Instead, we can use the 'busboy' library from NPM to parse these requests.
const Busboy = require('busboy');
exports.uploadFile = (req, res) => {
if (req.method !== 'POST') {
// Return a "method not allowed" error
return res.status(405).end();
}
const busboy = new Busboy({headers: req.headers});
const tmpdir = os.tmpdir();
// This object will accumulate all the fields, keyed by their name
const fields = {};
// This object will accumulate all the uploaded files, keyed by their name.
const uploads = {};
// This code will process each non-file field in the form.
busboy.on('field', (fieldname, val) => {
/**
* TODO(developer): Process submitted field values here
*/
console.log(`Processed field ${fieldname}: ${val}.`);
fields[fieldname] = val;
});
const fileWrites = [];
// This code will process each file uploaded.
busboy.on('file', (fieldname, file, filename) => {
// Note: os.tmpdir() points to an in-memory file system on GCF
// Thus, any files in it must fit in the instance's memory.
console.log(`Processed file ${filename}`);
const filepath = path.join(tmpdir, filename);
uploads[fieldname] = filepath;
const writeStream = fs.createWriteStream(filepath);
file.pipe(writeStream);
// File was processed by Busboy; wait for it to be written.
// Note: GCF may not persist saved files across invocations.
// Persistent files must be kept in other locations
// (such as Cloud Storage buckets).
const promise = new Promise((resolve, reject) => {
file.on('end', () => {
writeStream.end();
});
writeStream.on('finish', resolve);
writeStream.on('error', reject);
});
fileWrites.push(promise);
});
// Triggered once all uploaded files are processed by Busboy.
// We still need to wait for the disk writes (saves) to complete.
busboy.on('finish', async () => {
await Promise.all(fileWrites);
/**
* TODO(developer): Process saved files here
*/
for (const file in uploads) {
fs.unlinkSync(uploads[file]);
}
res.send();
});
busboy.end(req.rawBody);
};
Python
import os
import tempfile
from werkzeug.utils import secure_filename
# Helper function that computes the filepath to save files to
def get_file_path(filename):
# Note: tempfile.gettempdir() points to an in-memory file system
# on GCF. Thus, any files in it must fit in the instance's memory.
file_name = secure_filename(filename)
return os.path.join(tempfile.gettempdir(), file_name)
def parse_multipart(request):
""" Parses a 'multipart/form-data' upload request
Args:
request (flask.Request): The request object.
Returns:
The response text, or any set of values that can be turned into a
Response object using `make_response`
<http://flask.pocoo.org/docs/1.0/api/#flask.Flask.make_response>.
"""
# This code will process each non-file field in the form
fields = {}
data = request.form.to_dict()
for field in data:
fields[field] = data[field]
print('Processed field: %s' % field)
# This code will process each file uploaded
files = request.files.to_dict()
for file_name, file in files.items():
# Note: GCF may not keep files saved locally between invocations.
# If you want to preserve the uploaded files, you should save them
# to another location (such as a Cloud Storage bucket).
file.save(get_file_path(file_name))
print('Processed file: %s' % file_name)
# Clear temporary directory
for file_name in files:
file_path = get_file_path(file_name)
os.remove(file_path)
return "Done!"
Go
// Package http provides a set of HTTP Cloud Functions samples.
package http
import (
"fmt"
"log"
"net/http"
)
// UploadFile processes a 'multipart/form-data' upload request.
func UploadFile(w http.ResponseWriter, r *http.Request) {
const maxMemory = 2 * 1024 * 1024 // 2 megabytes.
// ParseMultipartForm parses a request body as multipart/form-data.
// The whole request body is parsed and up to a total of maxMemory bytes of
// its file parts are stored in memory, with the remainder stored on
// disk in temporary files.
// Note that any files saved during a particular invocation may not
// persist after the current invocation completes; persistent files
// should be stored elsewhere, such as in a Cloud Storage bucket.
if err := r.ParseMultipartForm(maxMemory); err != nil {
http.Error(w, "Unable to parse form", http.StatusBadRequest)
log.Printf("Error parsing form: %v", err)
return
}
// Be sure to remove all temporary files after your function is finished.
defer func() {
if err := r.MultipartForm.RemoveAll(); err != nil {
http.Error(w, "Error cleaning up form files", http.StatusInternalServerError)
log.Printf("Error cleaning up form files: %v", err)
}
}()
// r.MultipartForm.File contains *multipart.FileHeader values for every
// file in the form. You can access the file contents using
// *multipart.FileHeader's Open method.
for _, headers := range r.MultipartForm.File {
for _, h := range headers {
fmt.Fprintf(w, "File uploaded: %q (%v bytes)", h.Filename, h.Size)
// Use h.Open() to read the contents of the file.
}
}
}
Java
import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.logging.Logger;
public class HttpFormData implements HttpFunction {
private static final Logger logger = Logger.getLogger(HttpFormData.class.getName());
@Override
public void service(HttpRequest request, HttpResponse response)
throws IOException {
if (!"POST".equals(request.getMethod())) {
response.setStatusCode(HttpURLConnection.HTTP_BAD_METHOD);
return;
}
// This code will process each file uploaded.
String tempDirectory = System.getProperty("java.io.tmpdir");
for (HttpRequest.HttpPart httpPart : request.getParts().values()) {
String filename = httpPart.getFileName().orElse(null);
if (filename == null) {
continue;
}
logger.info("Processed file: " + filename);
// Note: GCF's temp directory is an in-memory file system
// Thus, any files in it must fit in the instance's memory.
Path filePath = Paths.get(tempDirectory, filename).toAbsolutePath();
// Note: files saved to a GCF instance itself may not persist across executions.
// Persistent files should be stored elsewhere, e.g. a Cloud Storage bucket.
Files.copy(httpPart.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
// TODO(developer): process saved files here
Files.delete(filePath);
}
// This code will process other form fields.
request.getQueryParameters().forEach(
(fieldName, fieldValues) -> {
String firstFieldValue = fieldValues.get(0);
// TODO(developer): process field values here
logger.info(String.format(
"Processed field: %s (value: %s)", fieldName, firstFieldValue));
});
}
}
C#
using Google.Cloud.Functions.Framework;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading.Tasks;
namespace HttpFormData
{
public class Function : IHttpFunction
{
private readonly ILogger _logger;
public Function(ILogger<Function> logger) =>
_logger = logger;
public async Task HandleAsync(HttpContext context)
{
HttpResponse response = context.Response;
HttpRequest request = context.Request;
if (request.Method != "POST")
{
response.StatusCode = (int) HttpStatusCode.MethodNotAllowed;
return;
}
// This code will process each file uploaded.
string tempDirectory = Path.GetTempPath();
foreach (IFormFile file in request.Form.Files)
{
if (string.IsNullOrEmpty(file.FileName))
{
continue;
}
_logger.LogInformation("Processed file: {file}", file.FileName);
// Note: GCF's temp directory is an in-memory file system
// Thus, any files in it must fit in the instance's memory.
string outputPath = Path.Combine(tempDirectory, file.FileName);
// Note: files saved to a GCF instance itself may not persist across executions.
// Persistent files should be stored elsewhere, e.g. a Cloud Storage bucket.
using (FileStream output = File.Create(outputPath))
{
await file.CopyToAsync(output);
}
// TODO(developer): process saved files here
File.Delete(outputPath);
}
// This code will process other form fields.
foreach (KeyValuePair<string, StringValues> parameter in request.Form)
{
// TODO(developer): process field values here
_logger.LogInformation("Processed field '{key}' (value: '{value}')",
parameter.Key, (string) parameter.Value);
}
}
}
}
Cloud Storage 経由のファイルのアップロード
Cloud Functions の一般的なユースケースとして、ファイルを処理する場合があります。サイズの大きいファイルや 1 つのリクエストの範囲を超える永続ストレージが必要なファイルの場合は、Cloud Storage をファイル アップロードのエントリ ポイントとして使用できます。これを行うには、署名付き URL を生成する必要があります。これにより、Cloud Storage バケットに対する一時的な書き込みアクセス権が提供されます。
Cloud Functions を直接使用している場合は、適切な Cloud Storage クラウド ライブラリを使用して署名付き URL を生成します。Cloud Storage を使用してファイルを Cloud Functions の関数にアップロードするには、以下の 3 つのステップによるプロセスを実行します。
クライアントが Cloud Function を直接呼び出して、署名付き URL を取得します。
次に、クライアントは HTTP PUT リクエストによって署名付き URL にファイルデータを送信します。
ファイルをさらに処理するために、ストレージ バケット内の mutation によって、2 番目の Cloud Function がトリガーされます。
以下の例では、Cloud Storage クライアント ライブラリを使用して、署名付き URL を生成します。
Cloud Functions には、iam.serviceAccounts.signBlob
権限は通常含まれていない「デフォルト アプリケーションの認証情報」があります。これを可能にするには、関数のサービス アカウントに適切な役割があることを確認する必要があります。これには、Cloud Console か、gcloud
コマンドライン ツールのいずれかを使用できます。
Console
関数のサービス アカウントに適切な役割があることを確認するために、アカウントの IAM 役割を直接変更できます。
- Google Cloud Console に移動します。
- 適切なアカウントを選択し、[編集者] > [サービス アカウント] > [サービス アカウント トークン作成者] を選択します。
gcloud
関数のサービス アカウントに適切な役割があることを確認するために、次のコマンドを実行します。事前定義された serviceAccountTokenCreator
ロールには、必要な iam.serviceAccounts.signBlob
権限が付与されています。
gcloud projects add-iam-policy-binding YOUR_PROJECT \ --member serviceAccount:YOUR_SERVICE_ACCOUNT --role roles/iam.serviceAccountTokenCreator
Cloud Console または gcloud
コマンドライン ツールを使用して、関数で使用されるサービス アカウントを判別できます。
Console
Cloud Console を使用して、関数で使用されるサービス アカウントを判別するには:
Google Cloud Console に移動します。
リストから、検査する関数を選択します。
関数の詳細ページでサービス アカウントを確認できます。
gcloud
関数で使用されるサービス アカウントを判別するには、次のコマンドを実行して、serviceAccountEmail
プロパティを探します。
gcloud beta functions describe YOUR_FUNCTION_NAME
署名付き URL を生成する例を次に示します。
Node.js
/**
* TODO(developer): Uncomment the following lines before running the sample.
*/
// const bucketName = 'Name of a bucket, e.g. my-bucket';
// const filename = 'File to access, e.g. file.txt';
// Imports the Google Cloud client library
const {Storage} = require('@google-cloud/storage');
// Creates a client
const storage = new Storage();
async function generateV4UploadSignedUrl() {
// These options will allow temporary uploading of the file with outgoing
// Content-Type: application/octet-stream header.
const options = {
version: 'v4',
action: 'write',
expires: Date.now() + 15 * 60 * 1000, // 15 minutes
contentType: 'application/octet-stream',
};
// Get a v4 signed URL for uploading file
const [url] = await storage
.bucket(bucketName)
.file(filename)
.getSignedUrl(options);
console.log('Generated PUT signed URL:');
console.log(url);
console.log('You can use this URL with any user agent, for example:');
console.log(
"curl -X PUT -H 'Content-Type: application/octet-stream' " +
`--upload-file my-file '${url}'`
);
}
generateV4UploadSignedUrl().catch(console.error);
Python
import datetime
from google.cloud import storage
def generate_upload_signed_url_v4(bucket_name, blob_name):
"""Generates a v4 signed URL for uploading a blob using HTTP PUT.
Note that this method requires a service account key file. You can not use
this if you are using Application Default Credentials from Google Compute
Engine or from the Google Cloud SDK.
"""
# bucket_name = 'your-bucket-name'
# blob_name = 'your-object-name'
storage_client = storage.Client()
bucket = storage_client.bucket(bucket_name)
blob = bucket.blob(blob_name)
url = blob.generate_signed_url(
version="v4",
# This URL is valid for 15 minutes
expiration=datetime.timedelta(minutes=15),
# Allow PUT requests using this URL.
method="PUT",
content_type="application/octet-stream",
)
print("Generated PUT signed URL:")
print(url)
print("You can use this URL with any user agent, for example:")
print(
"curl -X PUT -H 'Content-Type: application/octet-stream' "
"--upload-file my-file '{}'".format(url)
)
return url
Go
import (
"fmt"
"io"
"io/ioutil"
"time"
"cloud.google.com/go/storage"
"golang.org/x/oauth2/google"
)
// generateV4PutObjectSignedURL generates object signed URL with PUT method.
func generateV4PutObjectSignedURL(w io.Writer, bucket, object, serviceAccount string) (string, error) {
// bucket := "bucket-name"
// object := "object-name"
// serviceAccount := "service_account.json"
jsonKey, err := ioutil.ReadFile(serviceAccount)
if err != nil {
return "", fmt.Errorf("ioutil.ReadFile: %v", err)
}
conf, err := google.JWTConfigFromJSON(jsonKey)
if err != nil {
return "", fmt.Errorf("google.JWTConfigFromJSON: %v", err)
}
opts := &storage.SignedURLOptions{
Scheme: storage.SigningSchemeV4,
Method: "PUT",
Headers: []string{
"Content-Type:application/octet-stream",
},
GoogleAccessID: conf.Email,
PrivateKey: conf.PrivateKey,
Expires: time.Now().Add(15 * time.Minute),
}
u, err := storage.SignedURL(bucket, object, opts)
if err != nil {
return "", fmt.Errorf("storage.SignedURL: %v", err)
}
fmt.Fprintln(w, "Generated PUT signed URL:")
fmt.Fprintf(w, "%q\n", u)
fmt.Fprintln(w, "You can use this URL with any user agent, for example:")
fmt.Fprintf(w, "curl -X PUT -H 'Content-Type: application/octet-stream' --upload-file my-file %q\n", u)
return u, nil
}
Java
import com.google.cloud.storage.BlobId;
import com.google.cloud.storage.BlobInfo;
import com.google.cloud.storage.HttpMethod;
import com.google.cloud.storage.Storage;
import com.google.cloud.storage.StorageException;
import com.google.cloud.storage.StorageOptions;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class GenerateV4PutObjectSignedUrl {
/**
* Signing a URL requires Credentials which implement ServiceAccountSigner. These can be set
* explicitly using the Storage.SignUrlOption.signWith(ServiceAccountSigner) option. If you don't,
* you could also pass a service account signer to StorageOptions, i.e.
* StorageOptions().newBuilder().setCredentials(ServiceAccountSignerCredentials). In this example,
* neither of these options are used, which means the following code only works when the
* credentials are defined via the environment variable GOOGLE_APPLICATION_CREDENTIALS, and those
* credentials are authorized to sign a URL. See the documentation for Storage.signUrl for more
* details.
*/
public static void generateV4GPutObjectSignedUrl(
String projectId, String bucketName, String objectName) throws StorageException {
// String projectId = "my-project-id";
// String bucketName = "my-bucket";
// String objectName = "my-object";
Storage storage = StorageOptions.newBuilder().setProjectId(projectId).build().getService();
// Define Resource
BlobInfo blobInfo = BlobInfo.newBuilder(BlobId.of(bucketName, objectName)).build();
// Generate Signed URL
Map<String, String> extensionHeaders = new HashMap<>();
extensionHeaders.put("Content-Type", "application/octet-stream");
URL url =
storage.signUrl(
blobInfo,
15,
TimeUnit.MINUTES,
Storage.SignUrlOption.httpMethod(HttpMethod.PUT),
Storage.SignUrlOption.withExtHeaders(extensionHeaders),
Storage.SignUrlOption.withV4Signature());
System.out.println("Generated PUT signed URL:");
System.out.println(url);
System.out.println("You can use this URL with any user agent, for example:");
System.out.println(
"curl -X PUT -H 'Content-Type: application/octet-stream' --upload-file my-file '"
+ url
+ "'");
}
}
C#
private void GenerateV4SignedPutUrl(string bucketName, string objectName)
{
UrlSigner urlSigner = UrlSigner
.FromServiceAccountPath(Environment.GetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS"));
var contentHeaders = new Dictionary<string, IEnumerable<string>>
{
{ "Content-Type", new[] { "text/plain" } }
};
UrlSigner.Options options = UrlSigner.Options
.FromDuration(TimeSpan.FromHours(1))
.WithSigningVersion(SigningVersion.V4);
UrlSigner.RequestTemplate template = UrlSigner.RequestTemplate
.FromBucket(bucketName)
.WithObjectName(objectName)
.WithHttpMethod(HttpMethod.Put)
.WithContentHeaders(contentHeaders);
string url = urlSigner.Sign(template, options);
Console.WriteLine("Generated PUT signed URL:");
Console.WriteLine(url);
Console.WriteLine("You can use this URL with any user agent, for example:");
Console.WriteLine($"curl -X PUT -H 'Content-Type: text/plain' --upload-file my-file '{url}'");
}
クライアントが署名付き URL にファイルをアップロードするとき、アップロードでさらにアクションを実行する場合は、このミューテーションから 2 番目の関数をトリガーできます。Cloud Storage バケットに変更が加えられたら Cloud 関数をトリガーする方法の詳細については、Cloud Storage のチュートリアルをご覧ください。